提交 325b2cc5 authored 作者: 王鹏飞's avatar 王鹏飞

chore: 修改实验成绩规则的自动评分

上级 d1490681
......@@ -86,7 +86,11 @@ export function getExperimentReportRule(params: { experiment_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-report/detail', { params })
}
// 更新实验报告规则
export function updateExperimentReportRule(data: { experiment_id: string; report_upload_way: number; detail_list: string }) {
export function updateExperimentReportRule(data: {
experiment_id: string
report_upload_way: number
detail_list: string
}) {
return httpRequest.post('/api/resource/v1/backend/experiment-report/save', data)
}
......@@ -98,7 +102,7 @@ export function getTripConfig(params: { experiment_id: string }) {
// 更新旅程配置
export function updateTripConfig(data: { experiment_id: string }) {
return httpRequest.post('/api/lab/v1/experiment/itinerary-config/save', data, {
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
})
}
......@@ -141,22 +145,50 @@ export function getQuestions(data: { experiment_id: string; types?: any }) {
return httpRequest.post('/api/resource/v1/backend/experiment-question/list', data)
}
// 获取老师创建的标签
// 试题获取老师创建的标签
export function getQuestionTags(params: { experiment_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-question/tags', { params })
return httpRequest.get('/api/resource/v1/backend/experiment-question/tags2', { params })
}
// 获取群组
// 试题获取群组
export function getQuestionGroup(params: { experiment_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-question/groups', { params })
}
// 试题获取营销资料
export function getQuestionMaterials(params: { experiment_id: string; type: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-question/materials', { params })
}
// 试题获取事件
export function getQuestionEvents(params: { experiment_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-question/events', { params })
}
// 获取Excel总条数
export function getExcelTotalLine(data: { file: File }) {
return httpRequest.post('/api/lab/v1/common/file/get-excel-total-line', data, {
headers: { 'Content-Type': 'multipart/form-data' },
})
}
// 判断是否维护了自动生成数据
export function checkAutoGenerateData(params: { experiment_id: string; event_id?: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment-question/check-auto-generate-data', { params })
}
// 获取实验下的所有理论考试
export function getAllExamList(params: { project: string; q?: string; name?: string; page?: number; 'per-page'?: number }) {
export function getAllExamList(params: {
project: string
q?: string
name?: string
page?: number
'per-page'?: number
}) {
return httpRequest.get('/api/resource/v1/backend/exam/search-all-exam', { params })
}
// 获取实验的理论考试列表
export function getExamList(params: { experiment_id: string, type: string }) {
export function getExamList(params: { experiment_id: string; type: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment/exam-list', { params })
}
// 更新实验的理论考试
......@@ -185,4 +217,4 @@ export function deleteExperiment(data: { experiment_id: string }) {
// 获取实验成绩规则
export function getLiveCommodity(params: { experiment_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity/all', { params })
}
\ No newline at end of file
}
......@@ -44,13 +44,14 @@ const form = reactive<ExperimentCreateItem>({
requirements: '',
content: '',
procedure: '',
exam_status: '0'
exam_status: '0',
can_repeat_commit: '0',
})
watchEffect(() => {
if (!props.data) return
const score = parseFloat(props.data.score)
const length = parseFloat(props.data.length)
const teachers_ids = props.data.teacher.map(item => item.id)
const teachers_ids = props.data.teacher.map((item) => item.id)
Object.assign(form, props.data, { score, length, teachers_ids })
})
......@@ -72,7 +73,7 @@ const rules = ref<FormRules>({
length: [{ required: true, message: '请输入实验学时' }],
type: [{ required: true, message: '请选择实验类型' }],
teachers_ids: [{ type: 'array', required: true, message: '请选择指导教师', trigger: 'change' }],
score: [{ required: true, message: '请输入实验总成绩' }]
score: [{ required: true, message: '请输入实验总成绩' }],
})
const isUpdate = $computed(() => {
return !!form.id
......@@ -90,7 +91,7 @@ function handleSubmit() {
formRef?.validate().then(() => {
const params = {
...form,
teachers_id: form.teachers_ids?.join(',') || ''
teachers_id: form.teachers_ids?.join(',') || '',
}
isUpdate ? handleUpdate(params) : handleCreate(params)
})
......@@ -118,8 +119,7 @@ function handleUpdate(params: ExperimentCreateItem) {
:title="title"
:close-on-click-modal="false"
width="600px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
@update:modelValue="(value) => $emit('update:modelValue', value)">
<el-form ref="formRef" :model="form" :rules="rules" label-width="145px">
<el-form-item label="实验所属部门/学校" prop="organ_id">
<el-select v-model="form.organ_id" style="width: 100%" :disabled="isUpdate" @change="handleOrgChange">
......@@ -164,8 +164,7 @@ function handleUpdate(params: ExperimentCreateItem) {
type="textarea"
maxlength="200"
show-word-limit
placeholder="请输入实验目的"
/>
placeholder="请输入实验目的" />
</el-form-item>
<el-form-item label="实验要求" prop="requirements">
<el-input
......@@ -174,8 +173,7 @@ function handleUpdate(params: ExperimentCreateItem) {
type="textarea"
maxlength="200"
show-word-limit
placeholder="请输入实验要求"
/>
placeholder="请输入实验要求" />
</el-form-item>
<el-form-item label="实验内容及原理" prop="content">
<el-input
......@@ -184,8 +182,7 @@ function handleUpdate(params: ExperimentCreateItem) {
type="textarea"
maxlength="200"
show-word-limit
placeholder="请输入实验内容及原理"
/>
placeholder="请输入实验内容及原理" />
</el-form-item>
<el-form-item label="实验步骤及过程" prop="procedure">
<el-input
......@@ -194,14 +191,19 @@ function handleUpdate(params: ExperimentCreateItem) {
type="textarea"
maxlength="200"
show-word-limit
placeholder="请输入实验步骤及过程"
/>
placeholder="请输入实验步骤及过程" />
</el-form-item>
<el-form-item label="有效状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否支持重复提交" prop="can_repeat_commit">
<el-radio-group v-model="form.can_repeat_commit">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
......
......@@ -144,7 +144,7 @@ function currentRuleNames(value: number) {
})
if (props.data.type === '4') {
// 数字营销实验
return tempList.filter((item) => [1, 5, 6, 7, 8, 9, 10].includes(item.value as number))
return tempList.filter((item) => [1, 5, 6, 7, 8, 9, 11, 12].includes(item.value as number))
} else {
return tempList.filter((item: any) => item.value <= 5)
}
......@@ -181,10 +181,12 @@ function rowScore(percent = 0) {
}
// 编辑题
const handleEdit = function (type: number) {
const handleEdit = function (row: any) {
handleSubmit(function () {
window.open(
`/admin/lab/experiment/questions?id=${props.data.id}&type=${type}&name=${props.data.name}&type_name=${props.data.type_name}&score=${props.data.score}`
`/admin/lab/experiment/questions?id=${props.data.id}&type=${row.type}&name=${props.data.name}&type_name=${
props.data.type_name
}&score=${props.data.score}&percent=${row.percent}&row_score=${rowScore(row.percent)}`
)
})
}
......@@ -283,7 +285,7 @@ onMounted(() => {
<el-divider></el-divider>
<el-form-item>
<el-row justify="space-between" style="width: 100%">
<p>理论考试</p>
<p>理论成绩规则</p>
<el-button type="primary" :icon="Plus" @click="handleAddExamRule" :disabled="form.exam_rules.length >= 1">
</el-button>
</el-row>
......@@ -309,7 +311,7 @@ onMounted(() => {
</template>
</el-table-column>
<el-table-column width="90">
<template #default="{ row }">满分:{{ 100 || rowScore(row.percent) }} </template>
<template #default="{ row }">满分:{{ rowScore(row.percent) }} </template>
</el-table-column>
<el-table-column align="right">
<template #default="{ $index, row }">
......@@ -327,19 +329,14 @@ onMounted(() => {
</el-form-item>
<el-form-item>
<el-row justify="space-between" style="width: 100%">
<p>实操考试</p>
<p>实操成绩规则</p>
<el-button type="primary" :icon="Plus" @click="handleAdd"></el-button>
</el-row>
<el-table :data="form.rule_list" row-key="id">
<el-table-column prop="name" width="170">
<template #default="{ row }">
<el-input v-model="row.name" :maxlength="20" style="width: 100%" v-if="row.type === 5" />
<el-select
v-model="row.type"
:disabled="row.type === 1"
style="width: 100%"
@change="handleTypeChange(row)"
v-else>
<el-select v-model="row.type" style="width: 100%" @change="handleTypeChange(row)" v-else>
<el-option v-for="item in currentRuleNames(row.type)" :key="item.value" v-bind="item"></el-option>
</el-select>
</template>
......@@ -354,24 +351,23 @@ onMounted(() => {
<el-radio-group v-model="row.rule_mode" size="small">
<el-radio :label="1">人工评分</el-radio>
<!-- <el-radio :label="2" v-if="[2, 3].includes(row.type)">自动评分</el-radio> -->
<el-radio :label="2" :disabled="row.type === 1 || row.type === 8">自动评分</el-radio>
<el-radio :label="2" :disabled="row.type === 1 || row.type === 5 || row.type === 8">自动评分</el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column width="90">
<template #default="{ row }">满分:{{ 100 || rowScore(row.percent) }} </template>
<template #default="{ row }">满分:{{ rowScore(row.percent) }} </template>
</el-table-column>
<el-table-column align="right">
<template #default="{ $index, row }">
<div class="btn-box">
<!-- || row.type === 8 -->
<el-button
:disabled="row.type === 1"
style="padding: 0"
text
type="primary"
@click="handleEdit(row.type)"
v-if="row.type !== 1"
@click="handleEdit(row)"
v-if="row.type !== 1 && row.type !== 5"
>编辑</el-button
>
<el-button style="padding: 0" text type="primary" @click="handleRemove($index)">删除</el-button>
......
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { Plus, CircleCloseFilled } from '@element-plus/icons-vue'
import { useFileDialog } from '@vueuse/core'
import { upload } from '@/utils/upload'
import { getExcelTotalLine, checkAutoGenerateData } from '../../api'
import { useAppConfig } from '@/composables/useAppConfig'
import { useEvent } from '../../composables/useQuestion'
const appConfig = useAppConfig()
defineEmits(['remove'])
const route = useRoute()
const modelValue: any = defineModel()
const formRef = ref<FormInstance>()
const form = reactive({
questions: modelValue,
})
const { eventList } = useEvent(route.query.id as string)
const { files, open, reset } = useFileDialog({
accept:
'.csv, .xls, .xlsx, text/csv, application/csv,text/comma-separated-values, application/csv, application/excel,application/vnd.msexcel, text/anytext, application/vnd. ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
multiple: false,
})
let currentItem: any = null
function handleUpload(item: any) {
currentItem = item
open()
}
watchEffect(async () => {
if (!files.value?.length) return
const [file] = files.value
const url = await upload(file)
const res = await getExcelTotalLine({ file })
const total = res.data.row_count || 0
if (currentItem) {
currentItem.answer.data.file = {
name: file.name,
size: file.size,
type: file.type,
is_download: false,
total,
url,
}
}
reset()
})
const href = computed(() => {
const dmlURL = appConfig.dmlURL || import.meta.env.VITE_DML_URL
return `${dmlURL}/connect?experiment_id=${route.query.id}`
})
async function handleEventChange(item: any) {
if (item.answer.data.type != 2) return
const res = await checkAutoGenerateData({ experiment_id: route.query.id as string, event_id: item.answer.choose })
item.answer.data.exists = res.data.exists
}
function handleDataTypeChange(item: any) {
item.answer.rule.type = item.answer.data.type == 2 ? '3' : '1'
}
defineExpose({ formRef })
</script>
<template>
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card shadow="hover" :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="$emit('remove', index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="0" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
<el-form-item
label="选择事件"
:prop="`questions.${index}.answer.choose`"
:rules="{ required: true, message: '请输入' }">
<el-select v-model="item.answer.choose" @change="handleEventChange(item)">
<el-option v-for="item in eventList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="事件数据集">
<div>
<el-radio-group v-model="item.answer.data.type" @change="handleDataTypeChange(item)">
<el-radio label="1">指定文件</el-radio>
<el-radio label="2">自动生成</el-radio>
</el-radio-group>
<div class="form-item-box">
<template v-if="item.answer.data.type == 1">
<el-button :icon="Plus" size="large" @click="handleUpload(item)"></el-button>
<template v-if="item.answer.data.file.url">
<div class="file-info">
<p>{{ item.answer.data.file.name }}</p>
<p>总数据量:{{ item.answer.data.file.total }}</p>
</div>
<el-button type="primary" plain @click="item.answer.data.file = {}">删除数据集</el-button>
</template>
</template>
<template v-else>
<a :href="href" target="_blank">
<el-button type="primary">查看自动生成规则</el-button>
</a>
<el-alert
title="该事件尚未维护自动生成数据规则,请维护!"
type="info"
style="margin-left: 40px"
v-if="!item.answer.data.exists" />
</template>
</div>
</div>
</el-form-item>
<el-form-item label="评分规则">
<el-radio-group v-model="item.answer.rule.type">
<template v-if="item.answer.data.type == 1">
<el-radio label="1">上传成功+数据量匹配</el-radio>
<el-radio label="2">仅上传成功</el-radio>
</template>
<el-radio label="3">仅数据量匹配</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="答案解析" v-if="false">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
position: absolute;
top: -10px;
right: -10px;
cursor: pointer;
}
.box-card {
padding-top: 20px;
position: relative;
margin-bottom: 30px;
overflow: visible;
}
.head-box {
display: flex;
margin-bottom: 30px;
.head-r {
margin-left: auto;
}
}
.form-item-box {
display: flex;
align-items: center;
p {
font-size: 13px;
margin: 0 20px 0 10px;
line-height: 1.4;
}
}
</style>
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { CircleCloseFilled } from '@element-plus/icons-vue'
import { getQuestionGroup } from '../../api'
import { useGroup } from '../../composables/useQuestion'
defineEmits(['remove'])
const route = useRoute()
const modelValue: any = defineModel()
const ruleFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const form = reactive({
questions: modelValue,
})
const { groupList } = useGroup(route.query.id as string)
const removeQuestion = (index: number) => {
modelValue.value.splice(index, 1)
function getGroupCount(id: string) {
return groupList.value.find((item) => item.id === id)?.count || '未计算'
}
let options = $ref<{ id: string; name: string }[]>()
onMounted(() => {
const dom: any = document.querySelectorAll('.app-main')[0]
dom.style.overflow = 'visible'
getQuestionGroup({ experiment_id: route.query.id as string }).then(res => {
options = res.data.items
})
})
defineExpose({ formRef })
</script>
<template>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="removeQuestion(index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-tabs v-model="item.type" type="card" class="demo-tabs">
<el-tab-pane label="静态群组" :name="301"></el-tab-pane>
<el-tab-pane label="动态群组" :name="302"></el-tab-pane>
</el-tabs>
<el-form-item label="本题分值" class="head-r">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form ref="ruleFormRef" label-width="120px" class="demo-ruleForm" status-icon>
<el-form-item label="题目标题" :required="true">
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card shadow="hover" :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="$emit('remove', index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40">
<CircleCloseFilled />
</el-icon>
<div class="head-box">
<el-tabs v-model="item.type" type="card">
<el-tab-pane label="静态群组" :name="301"></el-tab-pane>
<el-tab-pane label="动态群组" :name="302"></el-tab-pane>
</el-tabs>
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="0" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
<el-form-item label="正确答案" :required="true">
<span v-if="item.type === 301">创建静态群组成功</span>
<el-select v-else v-model="item.answer" class="m-2" placeholder="请选择" size="large">
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
<el-form-item label="评分规则">
<el-radio-group v-model="item.answer.rule.type">
<el-radio label="1">群组规则+计算结果</el-radio>
<el-radio label="2">仅群组规则</el-radio>
<el-radio label="3">仅计算结果</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="正确群组规则"
:prop="`questions.${index}.answer.choose`"
:rules="{ required: true, message: '请输入' }">
<el-select v-model="item.answer.choose" placeholder="请选择">
<el-option v-for="item in groupList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<p style="margin-left: 10px">满足该群组的用户数量:{{ getGroupCount(item.answer.choose) }}</p>
</el-form-item>
<el-form-item label="答案解析">
<el-form-item label="答案解析" v-if="false">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-form>
</el-card>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
......
......@@ -2,39 +2,41 @@
import type { FormInstance } from 'element-plus'
import { CircleCloseFilled } from '@element-plus/icons-vue'
const modelValue: any = defineModel()
const ruleFormRef = ref<FormInstance>()
defineEmits(['remove'])
const removeQuestion = (index: number) => {
modelValue.value.splice(index, 1)
}
const modelValue: any = defineModel()
onMounted(() => {
const dom: any = document.querySelectorAll('.app-main')[0]
dom.style.overflow = 'visible'
const formRef = ref<FormInstance>()
const form = reactive({
questions: modelValue,
})
defineExpose({ formRef })
</script>
<template>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="removeQuestion(index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-form-item label="本题分值" class="head-r">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form ref="ruleFormRef" label-width="120px" class="demo-ruleForm" status-icon>
<el-form-item label="题目标题" :required="true">
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="$emit('remove', index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40">
<CircleCloseFilled />
</el-icon>
<div class="head-box">
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-form>
</el-card>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
......
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { Document, CircleCheck, CircleClose, CircleCloseFilled } from '@element-plus/icons-vue'
import AppUpload from '@/components/base/AppUpload.vue'
import type { UploadFile } from 'element-plus'
import { CircleCloseFilled } from '@element-plus/icons-vue'
const modelValue: any = defineModel()
import { useMaterial } from '../../composables/useQuestion'
const ruleFormRef = ref<FormInstance>()
defineEmits(['remove'])
// 移除上传文件
const handleRemove = (file: UploadFile) => {
if (file) {
modelValue.value.forEach((item: any) => {
const index = item.files.findIndex((cItem: { url: string }) => cItem.url === file.url)
item.files.splice(index, 1)
})
}
}
const route = useRoute()
const handleDownload = (file: any) => {
if (file) {
modelValue.value.forEach((i: any) => {
const item: any = i.files.find((item: any) => item.url === file.url)
if (item) item.is_download = file.is_download
})
}
}
const modelValue: any = defineModel()
const removeQuestion = (index: number) => {
modelValue.value.splice(index, 1)
}
const formRef = ref<FormInstance>()
const form = reactive({ questions: modelValue })
const options = $ref<{ id: number; name: string }[]>([
{ id: 401, name: '文本资料' },
{ id: 402, name: '图片资料' },
{ id: 403, name: '语音资料' },
{ id: 404, name: '视频资料' },
{ id: 405, name: 'H5资料' },
{ id: 406, name: '二维码资料' },
{ id: 407, name: '小程序资料' },
{ id: 408, name: '卡券资料' },
])
onMounted(() => {
const dom: any = document.querySelectorAll('.app-main')[0]
dom.style.overflow = 'visible'
})
const options = [
{ id: 401, name: '文本', type: '1' },
{ id: 402, name: '图片', type: '2' },
{ id: 403, name: '语音', type: '3' },
{ id: 404, name: '视频', type: '4' },
{ id: 405, name: 'H5', type: '5' },
{ id: 406, name: '二维码', type: '6' },
{ id: 407, name: '小程序', type: '7' },
{ id: 408, name: '卡券', type: '8' },
]
const getTips = function (n: number) {
const tipText: any = {
402: '试题文件支持格式包含:png jpg jpeg ,大小不超过5M',
403: '试题文件支持格式包含:mp3 wav,大小不超过5M',
404: '试题文件支持格式包含:帧率为25fps/输出码率为4M/输出格式为mp4,建议采用格式工厂等工具处理后上传。',
405: '试题文件支持格式包含:png jpg jpeg ,大小不超过5M',
406: '试题文件支持格式包含:png jpg jpeg ,大小不超过5M',
407: '试题文件支持格式包含:png jpg jpeg ,大小不超过5M',
508: '试题文件支持格式包含:png jpg jpeg ,大小不超过5M',
}
return tipText[n]
const aiOptions = [
{ label: '文心一言', value: '1' },
{ label: 'DeepSeek', value: '2' },
{ label: '通义千问', value: '3' },
{ label: '天工', value: '4' },
]
const { materialList } = useMaterial(route.query.id as string)
function filterMateriaList(id: number) {
const option = options.find((item) => item.id === id)
return materialList.value.filter((item) => item.type === option?.type)
}
defineExpose({ formRef })
</script>
<template>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="removeQuestion(index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-select v-model="item.type" class="m-2" placeholder="请选择" size="large">
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-form-item label="本题分值" class="head-r">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form ref="ruleFormRef" label-width="120px" class="demo-ruleForm" status-icon>
<el-form-item label="题目标题" :required="true">
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="$emit('remove', index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40">
<CircleCloseFilled />
</el-icon>
<div class="head-box">
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="0" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
<el-form-item label="正确答案" :required="true">
<span>上传成功</span>
<el-form-item
label="营销物料类别"
:prop="`questions.${index}.type`"
:rules="{ required: true, message: '请选择' }">
<el-radio-group v-model="item.type" placeholder="请选择">
<el-radio v-for="item in options" :key="item.id" :label="item.id">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="答案解析">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
<el-form-item
label="参考答案"
:prop="`questions.${index}.answer.choose`"
:rules="{ required: true, message: '请选择' }">
<el-select v-model="item.answer.choose" placeholder="请选择" clearable>
<el-option
v-for="materia in filterMateriaList(item.type)"
:key="materia.id"
:label="materia.name"
:value="materia.id"></el-option>
</el-select>
</el-form-item>
<el-form-item
label="评分AI"
:prop="`questions.${index}.answer.ai`"
:rules="{ required: true, message: '请选择' }">
<el-select v-model="item.answer.ai" placeholder="请选择">
<el-option v-for="item in aiOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="试题文件" v-if="item.type !== 401">
<AppUpload v-model="item.files">
<template #tip>{{ getTips(item.type) }}</template>
<template #file="{ file }">
<div class="upload-box">
<div class="upload-loading">
<el-icon style="margin-right: 5px"><Document /></el-icon>
<p style="margin-right: 5px">{{ file.name }}</p>
<p v-if="file.status === 'uploading'">{{ file.percentage }}%</p>
<template v-if="file.status === 'success'">
<el-icon class="succ-icon1" color="#67c23a" size="18" style="margin-left: 30px"
><CircleCheck
/></el-icon>
<el-icon class="succ-icon2" size="18" style="margin-left: 30px" @click="handleRemove(file)"
><CircleClose
/></el-icon>
</template>
</div>
<el-switch
@change="handleDownload(file)"
v-model="file.is_download"
size="large"
active-text="能否下载" />
</div>
</template>
</AppUpload>
<el-form-item label="答案解析" v-if="false">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-form>
</el-card>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
......
<script setup lang="ts">
<script setup>
import { getQuestions } from '../../api'
const route: any = useRoute()
const route = useRoute()
onMounted(() => {
getCurrentQuestions()
})
// 获取题
let list: any = $ref()
const list = ref([])
const getCurrentQuestions = function () {
getQuestions({ experiment_id: route.query.id }).then((res) => {
list = res.data.items
list.value = res.data.items
})
}
</script>
<template>
<el-dialog
title="2023商业数据分析大赛决赛试题"
title="试题"
:close-on-click-modal="false"
width="50%"
@update:modelValue="(value) => $emit('update:modelValue', value)">
......@@ -46,10 +46,6 @@ const getCurrentQuestions = function () {
font-size: 14px;
}
.item {
// margin-bottom: 18px;
}
.box-card {
margin-bottom: 20px;
}
......
......@@ -37,11 +37,13 @@ window.onscroll = function () {
const typeName = computed(() => {
const name: any = {
'10': '用户/事件数据',
'6': '标签管理',
'7': '群组管理',
'8': '用户旅程',
'9': '营销资料管理',
'8': '用户旅程'
'10': '用户/事件数据',
'11': '用户数据导入/生成',
'12': '事件数据导入/生成',
}
return name[(route.query?.type as string) || '10']
})
......@@ -52,10 +54,10 @@ const typeName = computed(() => {
<div class="order-score">
<div class="order-score_flex">
<span class="el-tooltip question-total-number">{{ score }}</span>
<span class="el-tooltip paper-total-score">/100</span>
<span class="el-tooltip paper-total-score">/{{ route.query.row_score }}</span>
<em></em>
</div>
<div class="tip">注:每模块满分为100,请注意每题分值。</div>
<!-- <div class="tip">注:每模块满分为100,请注意每题分值。</div> -->
</div>
<div class="order-list">
<div class="title">{{ typeName }}</div>
......@@ -64,8 +66,7 @@ const typeName = computed(() => {
@click="handleOrder(index)"
:class="orderSite === index ? 'li active' : 'li'"
v-for="(item, index) in props.data"
:key="item"
>
:key="item">
{{ index + 1 }}
</div>
</div>
......
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { CircleCloseFilled } from '@element-plus/icons-vue'
import { getQuestionTags } from '../../api'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
import { useTag } from '../../composables/useQuestion'
defineEmits(['remove'])
const route = useRoute()
const modelValue: any = defineModel()
const ruleFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const form = reactive({
questions: modelValue,
})
const removeQuestion = (index: number) => {
modelValue.value.splice(index, 1)
}
const { tagList } = useTag(route.query.id as string)
let options = $ref<{ id: string; name: string }[]>()
onMounted(() => {
const dom: any = document.querySelectorAll('.app-main')[0]
dom.style.overflow = 'visible'
getQuestionTags({ experiment_id: route.query.id as string }).then(res => {
options = res.data.items
})
})
function getTagCount(id: string) {
return tagList.value.find((item) => item.id === id)?.count || '未计算'
}
const labelTitle = computed(() => {
return appConfig.system === 'dml' ? '标签目录' : '标签类型'
})
defineExpose({ formRef })
</script>
<template>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="removeQuestion(index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"><CircleCloseFilled /></el-icon>
<div class="head-box">
<el-tabs v-model="item.type" type="card" class="demo-tabs">
<el-tab-pane :label="labelTitle" :name="201"></el-tab-pane>
<el-tab-pane label="标签" :name="202"></el-tab-pane>
</el-tabs>
<el-form-item label="本题分值" class="head-r">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card
shadow="hover"
:id="`site-card${index}`"
class="box-card"
v-for="(item, index) in form.questions"
:key="item">
<el-icon @click="$emit('remove', index)" v-if="form.questions.length > 1" class="close" size="28" color="#c01c40">
<CircleCloseFilled />
</el-icon>
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="0" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form ref="ruleFormRef" label-width="120px" class="demo-ruleForm" status-icon>
<el-form-item label="题目标题" :required="true">
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
<el-form-item label="正确答案" :required="true">
<span v-if="item.type === 201">创建{{ labelTitle }}成功</span>
<el-select v-else v-model="item.answer" class="m-2" placeholder="请选择" size="large">
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
<el-form-item label="评分规则">
<el-radio-group v-model="item.answer.rule.type">
<el-radio label="1">标签规则+计算结果</el-radio>
<el-radio label="2">仅标签规则</el-radio>
<el-radio label="3">仅计算结果</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="正确标签规则"
:prop="`questions.${index}.answer.choose`"
:rules="{ required: true, message: '请输入' }">
<el-select v-model="item.answer.choose">
<el-option v-for="item in tagList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<p style="margin-left: 10px">满足该标签的用户数量:{{ getTagCount(item.answer.choose) }}</p>
</el-form-item>
<el-form-item label="答案解析">
<el-form-item label="答案解析" v-if="false">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-form>
</el-card>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
......
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { Document, CircleCheck, CircleClose, CircleCloseFilled } from '@element-plus/icons-vue'
import AppUpload from '@/components/base/AppUpload.vue'
import type { UploadFile } from 'element-plus'
import { Plus, CircleCloseFilled } from '@element-plus/icons-vue'
import { useFileDialog } from '@vueuse/core'
import { upload } from '@/utils/upload'
import { getExcelTotalLine, checkAutoGenerateData } from '../../api'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
defineEmits(['remove'])
const route = useRoute()
const modelValue: any = defineModel()
const ruleFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const form = reactive({
questions: modelValue,
})
// 移除上传文件
const handleRemove = (file: UploadFile) => {
if (file) {
modelValue.value.forEach((item: any) => {
const index = item.files.findIndex((cItem: { url: string }) => cItem.url === file.url)
item.files.splice(index, 1)
})
}
const { files, open, reset } = useFileDialog({
accept:
'.csv, .xls, .xlsx, text/csv, application/csv,text/comma-separated-values, application/csv, application/excel,application/vnd.msexcel, text/anytext, application/vnd. ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
multiple: false,
})
let currentItem: any = null
function handleUpload(item: any) {
currentItem = item
open()
}
const handleDownload = (file: any) => {
if (file) {
modelValue.value.forEach((i: any) => {
const item: any = i.files.find((item: any) => item.url === file.url)
if (item) item.is_download = file.is_download
})
watchEffect(async () => {
if (!files.value?.length) return
const [file] = files.value
const url = await upload(file)
const res = await getExcelTotalLine({ file })
const total = res.data.row_count || 0
if (currentItem) {
currentItem.answer.data.file = {
name: file.name,
size: file.size,
type: file.type,
is_download: false,
total,
url,
}
}
}
reset()
})
const removeQuestion = (index: number) => {
modelValue.value.splice(index, 1)
const href = computed(() => {
const dmlURL = appConfig.dmlURL || import.meta.env.VITE_DML_URL
return `${dmlURL}/connect?experiment_id=${route.query.id}`
})
async function handleDataTypeChange(item: any) {
item.answer.rule.type = item.answer.data.type == 2 ? '3' : '1'
if (item.answer.data.type != 2) return
const res = await checkAutoGenerateData({ experiment_id: route.query.id as string })
item.answer.data.exists = res.data.exists
}
onMounted(() => {
const dom: any = document.querySelectorAll('.app-main')[0]
dom.style.overflow = 'visible'
})
defineExpose({ formRef })
</script>
<template>
<el-card :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="removeQuestion(index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-tabs v-model="item.type" type="card" class="demo-tabs">
<el-tab-pane label="用户数据" :name="101"></el-tab-pane>
<el-tab-pane label="事件数据" :name="102"></el-tab-pane>
</el-tabs>
<el-form-item label="本题分值" class="head-r">
<el-input-number v-model="item.score" :min="1" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form ref="ruleFormRef" label-width="120px" class="demo-ruleForm" status-icon>
<el-form-item label="题目标题" :required="true">
<el-form ref="formRef" label-width="120px" :model="form" scroll-to-error>
<el-card shadow="hover" :id="`site-card${index}`" class="box-card" v-for="(item, index) in modelValue" :key="item">
<el-icon @click="$emit('remove', index)" v-if="modelValue.length > 1" class="close" size="28" color="#c01c40"
><CircleCloseFilled
/></el-icon>
<div class="head-box">
<el-form-item
label="本题分值"
class="head-r"
:prop="`questions.${index}.score`"
:rules="{ required: true, message: '请输入' }">
<el-input-number v-model="item.score" :min="0" :max="100" controls-position="right" />
</el-form-item>
</div>
<el-form-item label="题目标题" :prop="`questions.${index}.title`" :rules="{ required: true, message: '请输入' }">
<el-input v-model="item.title" placeholder="请输入" />
</el-form-item>
<el-form-item label="题干内容">
<el-input v-model="item.content" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
<el-form-item label="正确答案" :required="true"> 导入成功 </el-form-item>
<el-form-item label="答案解析">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
<el-form-item label="用户数据集">
<div>
<el-radio-group v-model="item.answer.data.type" @change="handleDataTypeChange(item)">
<el-radio label="1">指定文件</el-radio>
<el-radio label="2">自动生成</el-radio>
</el-radio-group>
<div class="form-item-box">
<template v-if="item.answer.data.type == 1">
<el-button :icon="Plus" size="large" @click="handleUpload(item)"></el-button>
<template v-if="item.answer.data.file.url">
<div class="file-info">
<p>{{ item.answer.data.file.name }}</p>
<p>总数据量:{{ item.answer.data.file.total }}</p>
</div>
<el-button type="primary" plain @click="item.answer.data.file = {}">删除数据集</el-button>
</template>
</template>
<template v-else>
<a :href="href" target="_blank">
<el-button type="primary">查看自动生成规则</el-button>
</a>
<el-alert
title="该实验尚未维护自动生成数据规则,请维护!"
type="info"
style="margin-left: 40px"
v-if="!item.answer.data.exists" />
</template>
</div>
</div>
</el-form-item>
<el-form-item label="试题文件">
<AppUpload v-model="item.files">
<template #tip>试题文件支持格式包含:png jpg doc docx xls xlsx pdf ppt pptx,大小不超过50M</template>
<template #file="{ file }">
<div class="upload-box">
<div class="upload-loading">
<el-icon style="margin-right: 5px"><Document /></el-icon>
<p style="margin-right: 5px">{{ file.name }}</p>
<p v-if="file.status === 'uploading'">{{ file.percentage }}%</p>
<template v-if="file.status === 'success'">
<el-icon class="succ-icon1" color="#67c23a" size="18" style="margin-left: 30px"
><CircleCheck
/></el-icon>
<el-icon class="succ-icon2" size="18" style="margin-left: 30px" @click="handleRemove(file)"
><CircleClose
/></el-icon>
</template>
</div>
<el-switch
@change="handleDownload(file)"
v-model="file.is_download"
size="large"
active-text="能否下载"
/>
</div>
<el-form-item label="评分规则">
<el-radio-group v-model="item.answer.rule.type">
<template v-if="item.answer.data.type == 1">
<el-radio label="1">上传成功+数据量匹配</el-radio>
<el-radio label="2">仅上传成功</el-radio>
</template>
</AppUpload>
<el-radio label="3">仅数据量匹配</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-card>
<el-form-item label="答案解析" v-if="false">
<el-input v-model="item.answer_analysis" :rows="5" type="textarea" placeholder="请输入" />
</el-form-item>
</el-card>
</el-form>
</template>
<style lang="scss" scoped>
.close {
......@@ -113,24 +150,14 @@ onMounted(() => {
margin-left: auto;
}
}
.upload-box {
.form-item-box {
display: flex;
.upload-loading {
width: 300px;
display: flex;
align-items: center;
&:hover {
.succ-icon2 {
display: block;
}
.succ-icon1 {
display: none;
}
}
.succ-icon2 {
display: none;
cursor: pointer;
}
align-items: center;
p {
font-size: 13px;
margin: 0 20px 0 10px;
line-height: 1.4;
}
}
</style>
import { getQuestionTags, getQuestionGroup, getQuestionMaterials, getQuestionEvents } from '../api'
// 标签类型
export interface TagType {
id: string
name: string
count: string
}
// 群组类型
export interface GroupType {
id: string
name: string
count: string
}
export interface MaterialType {
id: string
name: string
type: string
}
// 事件类型
interface EventType {
id: string
name: string
}
// 所有标签
const tagList = ref<TagType[]>([])
export function useTag(experiment_id: string) {
function fetchTagList() {
getQuestionTags({ experiment_id }).then((res: any) => {
tagList.value = res.data.items
})
}
onMounted(() => {
if (!tagList.value?.length) fetchTagList()
})
return { fetchTagList, tagList }
}
// 所有群组
const groupList = ref<GroupType[]>([])
export function useGroup(experiment_id: string) {
function fetchGroupList() {
getQuestionGroup({ experiment_id }).then((res: any) => {
groupList.value = res.data.items
})
}
onMounted(() => {
if (!groupList.value?.length) fetchGroupList()
})
return { fetchGroupList, groupList }
}
// 所有资料
const materialList = ref<MaterialType[]>([])
export function useMaterial(experiment_id: string, type = '') {
function fetchMaterialList() {
getQuestionMaterials({ experiment_id, type }).then((res: any) => {
materialList.value = res.data.items
})
}
onMounted(() => {
if (!materialList.value?.length) fetchMaterialList()
})
return { fetchMaterialList, materialList }
}
// 所有事件
const eventList = ref<EventType[]>([])
export function useEvent(experiment_id: string) {
function fetchMetaEventList() {
getQuestionEvents({ experiment_id }).then((res: any) => {
eventList.value = res.data.items
})
}
onMounted(() => {
if (!eventList.value?.length) fetchMetaEventList()
})
return { fetchMetaEventList, eventList }
}
......@@ -50,6 +50,7 @@ export interface ExperimentCreateItem {
content: string
procedure: string
exam_status: string
can_repeat_commit: '0' | '1'
}
export interface ClassItem {
......
......@@ -27,7 +27,7 @@ const params = reactive({
id: '',
course_id: '',
name: '',
status: ''
status: '',
})
watch(
() => params.course_id,
......@@ -50,12 +50,12 @@ const listOptions = $computed(() => {
},
callback(data: { total: number; list: ExperimentItem[] }) {
const { list, total } = data
const dataList = list.map(item => {
const teacher_names = item.teacher.map(teacher => teacher.name).join('、')
const dataList = list.map((item) => {
const teacher_names = item.teacher.map((teacher) => teacher.name).join('、')
return { ...item, teacher_names }
})
return { list: dataList, total }
}
},
},
filters: [
{
......@@ -65,7 +65,7 @@ const listOptions = $computed(() => {
placeholder: '请选择实验课程',
options: courses.value,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{
type: 'select',
......@@ -74,15 +74,15 @@ const listOptions = $computed(() => {
placeholder: '请选择实验',
options: courseExperiments.value,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{
type: 'select',
prop: 'status',
label: '生效状态',
placeholder: '请选择生效状态',
options: status
}
options: status,
},
],
columns: [
{ label: '序号', type: 'index', width: 60 },
......@@ -95,8 +95,8 @@ const listOptions = $computed(() => {
{ label: '生效状态', prop: 'status_name' },
{ label: '更新人', prop: 'updated_operator_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 250 }
]
{ label: '操作', slots: 'table-x', width: 250 },
],
}
})
......@@ -162,7 +162,7 @@ async function handleDelete(row: ExperimentItem) {
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'"
>编辑</el-button
>
<el-button type="primary" round :icon="Delete" @click="handleDelete(row)">删除</el-button>
<el-button type="primary" round @click="handleDelete(row)">删除</el-button>
<!-- 功能按钮移入详情里 s v-if="false" -->
<el-dropdown style="margin-left: 12px" v-if="false">
......@@ -226,14 +226,12 @@ async function handleDelete(row: ExperimentItem) {
v-model="gradeRulesDialogVisible"
:data="rowData"
@update="onUpdateSuccess"
v-if="gradeRulesDialogVisible && rowData"
></GradeRulesDialog>
v-if="gradeRulesDialogVisible && rowData"></GradeRulesDialog>
<!-- 配置数字营销实验 -->
<DMLFormDialog v-model="dmlDialogVisible" :data="rowData" v-if="dmlDialogVisible && rowData"></DMLFormDialog>
<CopyDialog
v-model="copyDialogVisible"
:data="rowData"
@update="onUpdateSuccess"
v-if="copyDialogVisible && rowData"
></CopyDialog>
v-if="copyDialogVisible && rowData"></CopyDialog>
</template>
<script setup lang="ts">
<script setup>
import { updateQuestions, getQuestions } from '../api'
import { ElMessage } from 'element-plus'
const PreviewDialog = defineAsyncComponent(() => import('../components/Questions/PreviewDialog.vue'))
const QuestionsOrder = defineAsyncComponent(() => import('../components/Questions/QuestionsOrder.vue'))
const UserQuestion = defineAsyncComponent(() => import('../components/Questions/UserQuestion.vue'))
const EventQuestion = defineAsyncComponent(() => import('../components/Questions/EventQuestion.vue'))
const TagQuestion = defineAsyncComponent(() => import('../components/Questions/TagQuestion.vue'))
const GroupQuestion = defineAsyncComponent(() => import('../components/Questions/GroupQuestion.vue'))
const MaterialQuestion = defineAsyncComponent(() => import('../components/Questions/MaterialQuestion.vue'))
const JourneyQuestion = defineAsyncComponent(() => import('../components/Questions/JourneyQuestion.vue'))
const route: any = useRoute()
const router = useRouter()
const route = useRoute()
const getQuestionTypeInitValue = function () {
const type: any = { 10: 101, 6: 201, 7: 301, 9: 401, 8: 501 }
return type[parseInt(route.query?.type || '10')]
}
let data = $ref([
{
type: getQuestionTypeInitValue(),
score: 1,
title: '',
content: '',
answer_analysis: '',
answer: '',
files: []
/*
101实验用户数据题 102实验事件数据题
201实验标签类型题 202实验标签题
301实验静态群组题 302动态群组题
401实验营销文本资料题 402验营销图片资料题 403验营销语音资料题 404验营销视频资料题 405验营销H5资料题 406验营销二维码资料题 407验营销小程序资料题 408验营销卡券资料题
501用户旅程题
601实验报告题
*/
const types = computed(() => {
const typesJson = {
6: [201, 202],
7: [301, 302],
8: [501],
9: [401, 402, 403, 404, 405, 406, 407, 408],
10: [101, 102],
11: [101],
12: [102],
}
])
return typesJson[route.query.type] || route.query.type
})
const addQuestion = () => {
data.push({
type: getQuestionTypeInitValue(),
score: 1,
const type = computed(() => {
const type = { 6: 202, 7: 301, 8: 501, 9: 401, 10: 101, 11: 101, 12: 102 }
return type[route.query.type] || 101
})
const hasAdd = computed(() => {
return route.query.type != 11
})
const createDefaultQuestion = () => {
return {
type: type.value,
score: 0,
title: '',
content: '',
answer_analysis: '',
answer: '',
files: []
})
answer: {
choose: '',
data: { type: '1', file: {} },
rule: { type: '1' },
ai: '1',
},
}
}
const previewDialogVisible = $ref(false)
const questionRef = ref()
const handleSubmit = function () {
const type: any = { 10: '1', 6: '2', 7: '3', 9: '4', 8: '5' }
console.log(data, 'data')
const params = {
experiment_id: route.query.id,
module: type[route.query.type],
questions: data
}
const questions = ref([{ ...createDefaultQuestion() }])
//判断必填不能为空
function findItem(value: string) {
return data.findIndex((item: any) => item[value] === '')
}
if (findItem('title') !== -1) {
ElMessage.error(`第${findItem('title') + 1}题,题目不能为空`)
return false
}
updateQuestions(params).then(res => {
if (res.data.status) {
ElMessage({ message: '保存成功', type: 'success' })
router.go(0)
}
const previewDialogVisible = ref(false)
async function getQuestion() {
const resp = await getQuestions({ experiment_id: route.query.id, types: types.value })
questions.value = resp.data.items.map((item) => {
return { ...item, answer: JSON.parse(item.answer), score: parseInt(item.score) || 0, type: parseInt(item.type) }
})
}
onMounted(() => {
getCurrentQuestions()
getQuestion()
})
// 获取题
const getCurrentQuestions = function () {
const typesJson: any = {
10: [101, 102],
6: [201, 202],
7: [301, 302],
9: [401, 402, 403, 404, 405, 406, 407, 408],
8: [501]
}
getQuestions({ experiment_id: route.query.id, types: typesJson[route.query.type] }).then(res => {
if (res.data?.items) {
if (res.data.items?.length) {
data = res.data.items.reduce((a: any, b: any) => {
b.type = parseInt(b.type)
b.files.map((item: any) => {
item.is_download === 'true' ? (item.is_download = true) : (item.is_download = false)
return item
})
a.push(b)
return a
}, [])
}
}
// 添加试题
const addQuestion = () => {
questions.value.push({ ...createDefaultQuestion() })
}
const removeQuestion = (index) => {
questions.value.splice(index, 1)
}
// 保存
const handleSubmit = async () => {
await unref(questionRef.value?.formRef).validate()
await updateQuestions({
experiment_id: route.query.id,
module: route.query.type,
questions: questions.value.map((item) => {
return { ...item, answer: JSON.stringify(item.answer) }
}),
})
ElMessage({ message: '保存成功', type: 'success' })
}
</script>
......@@ -108,31 +107,36 @@ const getCurrentQuestions = function () {
<p>实验类型: {{ route.query.type_name }}</p>
<p>实验总成绩:{{ route.query.score }}</p>
</div>
<el-button @click="previewDialogVisible = true">预览</el-button>
<el-button @click="previewDialogVisible = true" v-if="false">预览</el-button>
</div>
</AppCard>
<div class="content-bottom">
<AppCard class="l-card">
<UserQuestion v-model="data" v-if="route.query.type === '10'"></UserQuestion>
<TagQuestion v-model="data" v-if="route.query.type === '6'"></TagQuestion>
<GroupQuestion v-model="data" v-if="route.query.type === '7'"></GroupQuestion>
<MaterialQuestion v-model="data" v-if="route.query.type === '9'"></MaterialQuestion>
<JourneyQuestion v-model="data" v-if="route.query.type === '8'"></JourneyQuestion>
<TagQuestion v-model="questions" ref="questionRef" @remove="removeQuestion" v-if="route.query.type === '6'" />
<GroupQuestion v-model="questions" ref="questionRef" @remove="removeQuestion" v-if="route.query.type === '7'" />
<JourneyQuestion v-model="questions" ref="questionRef" @remove="removeQuestion" v-if="route.query.type === '8'" />
<MaterialQuestion
v-model="questions"
ref="questionRef"
@remove="removeQuestion"
v-if="route.query.type === '9'" />
<UserQuestion v-model="questions" ref="questionRef" @remove="removeQuestion" v-if="route.query.type === '11'" />
<EventQuestion v-model="questions" ref="questionRef" @remove="removeQuestion" v-if="route.query.type === '12'" />
<div class="btn-box">
<div class="btn-l">
<el-button type="primary" @click="addQuestion"> 添加试题 </el-button>
<el-button type="primary" @click="addQuestion" v-if="hasAdd">添加试题</el-button>
<!-- <el-button> 取消 </el-button> -->
</div>
<div class="btn-r">
<el-button type="primary" @click="handleSubmit"> 保存 </el-button>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</div>
</div>
</AppCard>
<AppCard class="r-card">
<QuestionsOrder :data="data"></QuestionsOrder>
<QuestionsOrder :data="questions"></QuestionsOrder>
</AppCard>
</div>
<PreviewDialog v-model="previewDialogVisible"></PreviewDialog>
<PreviewDialog v-model="previewDialogVisible" v-if="previewDialogVisible"></PreviewDialog>
</template>
<style lang="scss" scoped>
.btn-box {
......
<script setup lang="ts">
import type { ExperimentItem, ClassItem } from '../types'
import { CirclePlus, CopyDocument, Setting, Edit, EditPen } from '@element-plus/icons-vue'
import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
......@@ -28,7 +28,7 @@ provide('detail', $$(detail))
const teacherText = $computed(() => {
if (!detail) return ''
return detail.teacher.map(item => item.name).join('、')
return detail.teacher.map((item) => item.name).join('、')
})
const appList = $ref<InstanceType<typeof AppList> | null>(null)
......@@ -41,7 +41,7 @@ const listOptions = {
const { total, list, info } = data
detail = info
return { list, total }
}
},
},
columns: [
{ label: '序号', type: 'index', width: 60 },
......@@ -51,8 +51,8 @@ const listOptions = {
{ label: '已完成人数', prop: 'complete_nums' },
{ label: '未完成人数', prop: 'not_complete_nums' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 300 }
]
{ label: '操作', slots: 'table-x', width: 300 },
],
}
// 刷新
function handleRefetch() {
......@@ -120,16 +120,18 @@ function handleUpdateGradeRules() {
</el-button>
<el-button type="primary" @click="gradeRulesVisible = true">查看成绩规则</el-button>
<el-button type="primary" @click="reportRulesVisible = true">查看报告规则</el-button>
<el-button type="primary" :icon="CopyDocument" @click="handleCopy()">复制实验</el-button>
<el-button type="primary" :icon="Setting" @click="handleUpdateDML()" :disabled="detail?.type !== '4'"
>配置数字营销</el-button
>
<el-button type="primary" @click="handleCopy()">复制实验</el-button>
<el-button type="primary" @click="handleUpdateDML()" v-if="detail?.type === '4'">配置数字营销</el-button>
<el-button type="primary">
<router-link
:to="{ path: '/admin/lab/score', query: { course_id: detail?.course_id, experiment_id: detail?.id } }"
target="_blank"
>实验评分</router-link
>
</el-button>
<template v-if="!detail?.stu_commit_count">
<el-button type="primary" :icon="Edit" @click="handleUpdateGradeRules()">编辑成绩规则</el-button>
<!-- <el-dropdown-item :icon="EditPen">
<router-link :to="`/admin/lab/experiment/report/${row.id}`" target="_blank">编辑报告规则</router-link>
</el-dropdown-item> -->
<el-button type="primary" :icon="EditPen">
<el-button type="primary" @click="handleUpdateGradeRules()">编辑成绩规则</el-button>
<el-button type="primary">
<router-link :to="`/admin/lab/experiment/report/${detail?.id}`" target="_blank">编辑报告规则</router-link>
</el-button>
</template>
......@@ -197,15 +199,13 @@ function handleUpdateGradeRules() {
<StudentGroupDialog
v-model="studentGroupVisible"
:data="rowData"
v-if="studentGroupVisible && rowData"
></StudentGroupDialog>
v-if="studentGroupVisible && rowData"></StudentGroupDialog>
<!-- 查看学生 -->
<StudentListDialog
v-model="studentListVisible"
:data="rowData"
:experimentId="id"
v-if="studentListVisible && rowData"
></StudentListDialog>
v-if="studentListVisible && rowData"></StudentListDialog>
<ViewGradeRules v-model="gradeRulesVisible" :data="detail" v-if="gradeRulesVisible && detail"></ViewGradeRules>
<ViewReportRules v-model="reportRulesVisible" :experiment_id="id" v-if="reportRulesVisible"></ViewReportRules>
<CopyDialog v-model="copyDialogVisible" :data="detail" v-if="copyDialogVisible && detail"></CopyDialog>
......@@ -215,6 +215,5 @@ function handleUpdateGradeRules() {
<GradeRulesDialog
v-model="gradeRulesDialogVisible"
:data="detail"
v-if="gradeRulesDialogVisible && detail"
></GradeRulesDialog>
v-if="gradeRulesDialogVisible && detail"></GradeRulesDialog>
</template>
......@@ -23,15 +23,15 @@ const appList = $ref<InstanceType<typeof AppList> | null>(null)
const params = reactive({
student_name: '',
course_id: '',
course_id: (route.query.course_id as string) || '',
experiment_id: (route.query.experiment_id as string) || '',
specialty_id: '',
class_id: ''
class_id: '',
})
const classList = $computed(() => {
const specialty = specialties.value.find(item => item.id === params.specialty_id)
const specialty = specialties.value.find((item) => item.id === params.specialty_id)
if (specialty) {
return classes.value.filter(item => item.specialty_id === specialty.id)
return classes.value.filter((item) => item.specialty_id === specialty.id)
}
return classes.value
})
......@@ -48,7 +48,7 @@ const listOptions = $computed(() => {
}
params.specialty_id = requestParams.specialty_id || ''
return requestParams
}
},
},
filterForm: { labelWidth: 100 },
filters: [
......@@ -59,7 +59,7 @@ const listOptions = $computed(() => {
placeholder: '请选择实验课程名称',
options: courses.value,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{
type: 'select',
......@@ -68,7 +68,7 @@ const listOptions = $computed(() => {
placeholder: '请选择实验名称',
options: experiments.value,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{
type: 'select',
......@@ -77,7 +77,7 @@ const listOptions = $computed(() => {
placeholder: '请选择专业',
options: specialties.value,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{
type: 'select',
......@@ -86,9 +86,9 @@ const listOptions = $computed(() => {
placeholder: '请选择班级',
options: classList,
labelKey: 'name',
valueKey: 'id'
valueKey: 'id',
},
{ type: 'input', prop: 'student_name', label: '学生姓名', placeholder: '请输入学生姓名' }
{ type: 'input', prop: 'student_name', label: '学生姓名', placeholder: '请输入学生姓名' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
......@@ -100,8 +100,8 @@ const listOptions = $computed(() => {
{ label: '提交状态', prop: 'status_name', slots: 'table-status' },
{ label: '考试成绩', prop: 'score', slots: 'table-score' },
{ label: '更新时间', prop: 'commit_time' },
{ label: '操作', slots: 'table-x', width: 210 }
]
{ label: '操作', slots: 'table-x', width: 210 },
],
}
})
const importVisible = $ref(false)
......@@ -138,9 +138,28 @@ async function handleReset(row: RecordItem) {
<template #header-buttons>
<el-row justify="space-between">
<div>
<el-button type="primary" round :icon="Upload" @click="importVisible = true" v-permission="'v1-teacher-record-upload'">批量导入</el-button>
<el-button type="primary" round :icon="Download" @click="exportVisible = true" v-permission="'v1-teacher-record-download'">批量导出</el-button>
<el-button type="primary" round :icon="Refresh" @click="syncVisible = true" v-permission="'v1-teacher-record-sync-theory-score'"
<el-button
type="primary"
round
:icon="Upload"
@click="importVisible = true"
v-permission="'v1-teacher-record-upload'"
>批量导入</el-button
>
<el-button
type="primary"
round
:icon="Download"
@click="exportVisible = true"
v-permission="'v1-teacher-record-download'"
>批量导出</el-button
>
<el-button
type="primary"
round
:icon="Refresh"
@click="syncVisible = true"
v-permission="'v1-teacher-record-sync-theory-score'"
>同步理论成绩</el-button
>
</div>
......@@ -156,16 +175,34 @@ async function handleReset(row: RecordItem) {
<span :class="{ 'is-info': row.score !== '--' }">{{ row.score }}</span>
</template>
<template #table-x="{ row }">
<el-button text type="primary" v-if="row.status === 1 || row.status === 2" @click="handleScore(row)" v-permission="'v1-teacher-record-check'"
<el-button
text
type="primary"
v-if="row.status === 1 || row.status === 2"
@click="handleScore(row)"
v-permission="'v1-teacher-record-check'"
>打分</el-button
>
<el-button text type="primary" v-if="row.status === 1 || row.status === 2" @click="handleReset(row)">重置</el-button>
<el-button text type="primary" v-if="row.has_score_log" @click="handleScoreLog(row)" v-permission="'v1-teacher-record-check'">查看历史成绩</el-button>
<el-button text type="primary" v-if="row.status === 1 || row.status === 2" @click="handleReset(row)"
>重置</el-button
>
<el-button
text
type="primary"
v-if="row.has_score_log"
@click="handleScoreLog(row)"
v-permission="'v1-teacher-record-check'"
>查看历史成绩</el-button
>
</template>
</AppList>
</AppCard>
<!-- 评分 -->
<ScoreDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible && rowData"></ScoreDialog>
<ScoreDialog
v-model="dialogVisible"
:data="rowData"
@update="onUpdateSuccess"
v-if="dialogVisible && rowData"></ScoreDialog>
<!-- 历史成绩 -->
<ScoreLogDialog v-model="scoreLogVisible" :data="rowData" v-if="scoreLogVisible && rowData"></ScoreLogDialog>
<!-- 批量导入 -->
......
// json to array
export const json2Array = function (data: any, isValueToNumber = true) {
return Object.keys(data).map(value => ({ label: data[value], value: isValueToNumber ? parseInt(value) : value }))
return Object.keys(data).map((value) => ({ label: data[value], value: isValueToNumber ? parseInt(value) : value }))
}
// 参赛模式
export const contestMode: Record<string, any> = {
'1': '个人赛'
'1': '个人赛',
}
// 参赛模式列表
export const contestModeList = json2Array(contestMode, false)
......@@ -15,7 +15,7 @@ export const scoreRule: Record<number, any> = {
1: '平均法',
2: '加权平均法',
3: '综合得分法',
4: '最高分'
4: '最高分',
}
// 参赛模式列表
export const scoreRuleList = json2Array(scoreRule, false)
......@@ -31,7 +31,9 @@ export const gradeRule: Record<number, any> = {
7: '用户群组',
8: '用户旅程',
9: '营销资料',
10: '用户/事件数据'
10: '用户/事件数据',
11: '用户数据',
12: '事件数据',
}
// 参赛模式列表
export const gradeRuleList = json2Array(gradeRule)
......@@ -39,6 +41,6 @@ export const gradeRuleList = json2Array(gradeRule)
// 实验报告评分规则
export const reportScoreRule: Record<number, any> = {
1: '人工评分',
2: '自动评分'
2: '自动评分',
}
export const reportScoreRuleList = json2Array(reportScoreRule)
import axios from 'axios'
import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base'
export async function upload(blob: Blob) {
const key = 'upload/saas-lab/' + md5(new Date().getTime() + Math.random().toString(36).slice(-8)) + '.png'
export async function upload(blob: Blob | File) {
let fileType = 'png'
if (blob instanceof File && blob.name) {
const matches = blob.name.match(/\.(\w+)$/)
if (matches) {
fileType = matches[1]
}
} else if (blob.type) {
const mimeType = blob.type.split('/').pop()
if (mimeType) {
fileType = mimeType
}
}
const key = 'upload/saas-lab/' + md5(new Date().getTime() + Math.random().toString(36).slice(-8)) + '.' + fileType
const response: any = await getSignature()
const params = {
key,
......@@ -12,8 +25,13 @@ export async function upload(blob: Blob) {
signature: response.signature,
success_action_status: '200',
file: blob,
url: `${response.host}/${key}`
url: `${response.host}/${key}`,
}
await uploadFile(params)
return params.url
}
export async function uploadFileByUrl(url: string) {
const res = await axios.get(url, { responseType: 'blob' })
return upload(res.data)
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论