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

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

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