|
@@ -0,0 +1,511 @@
|
|
|
+<template>
|
|
|
+ <div class="exam-edit-page">
|
|
|
+ <div class="page-header">
|
|
|
+ <h2>{{ isEdit ? '编辑考试' : '新增考试' }}</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="page-content">
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formModel"
|
|
|
+ :rules="rules"
|
|
|
+ label-width="280px"
|
|
|
+ class="exam-form"
|
|
|
+ >
|
|
|
+ <!-- 考试信息 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <h3>考试信息</h3>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="考试名称" prop="name">
|
|
|
+ <el-input
|
|
|
+ v-model="formModel.name"
|
|
|
+ placeholder="请输入考试名称"
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="类型" prop="type" label-width="200px">
|
|
|
+ <el-select
|
|
|
+ v-model="formModel.type"
|
|
|
+ placeholder="请选择类型"
|
|
|
+ style="width: 200px"
|
|
|
+ >
|
|
|
+ <el-option label="扫描图片" value="扫描图片" />
|
|
|
+ <el-option label="其他类型" value="其他类型" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="评卷开始日期">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formModel.markingStartDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择评卷开始日期"
|
|
|
+ style="width: 200px"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="评卷结束日期" label-width="200px">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="formModel.markingEndDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择评卷结束日期"
|
|
|
+ style="width: 200px"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row v-if="isEdit" :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select
|
|
|
+ v-model="formModel.status"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 200px"
|
|
|
+ >
|
|
|
+ <el-option label="A4" value="1" />
|
|
|
+ <el-option label="A3" value="2" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 阅卷设置 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>阅卷设置</h3>
|
|
|
+ <el-button type="primary" link @click="openAdvancedSettings">
|
|
|
+ 高级设置
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <h4 class="section-sub-title">评卷给分方式</h4>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="评卷模式" prop="markingMode">
|
|
|
+ <el-select
|
|
|
+ v-model="formModel.markingMode"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 200px"
|
|
|
+ >
|
|
|
+ <el-option label="不限" value="NONE" />
|
|
|
+ <el-option label="轨迹" value="TRACK" />
|
|
|
+ <el-option label="普通" value="COMMON" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="强制标记" label-width="200px">
|
|
|
+ <el-checkbox v-model="formModel.forceMark"></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <h4 class="section-sub-title">数据安全</h4>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="禁止他人查看考生信息">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.forbidViewStudentInfo"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="禁止科组长成绩查询" label-width="200px">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.forbidLeaderScoreQuery"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <h4 class="section-sub-title">全卷复核设置</h4>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="全卷多次复核不能同一账号">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.preventSameAccountRecheck"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item
|
|
|
+ label="全卷复核强制试卷拉到底部"
|
|
|
+ label-width="200px"
|
|
|
+ >
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.forceScrollToBottom"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="全卷复核进度100%时才能再次复核">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.requireFullProgressForRecheck"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <h4 class="section-sub-title">其他</h4>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="回评卷数" prop="recheckCount">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formModel.recheckCount"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ :step="1"
|
|
|
+ :precision="0"
|
|
|
+ :controls="false"
|
|
|
+ step-strictly
|
|
|
+ style="width: 100px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item
|
|
|
+ label="给分次数限制"
|
|
|
+ prop="scoringLimitType"
|
|
|
+ label-width="200px"
|
|
|
+ >
|
|
|
+ <el-select
|
|
|
+ v-model="formModel.scoringLimitType"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 200px"
|
|
|
+ >
|
|
|
+ <el-option label="小于等于" value="le" />
|
|
|
+ <el-option label="等于" value="eq" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="评卷端显示客观分">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.showObjectiveScore"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="原图遮盖" label-width="200px">
|
|
|
+ <el-button type="primary" link>设置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="打回后显示原分值及轨迹">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.showOriginalScoreAfterReturn"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="原卷显示" label-width="200px">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.showOriginalPaper"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="评卷提交自动定位">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.autoPositionAfterSubmit"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="自动对切题卡" label-width="200px">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="formModel.autoAlignAnswerSheet"
|
|
|
+ ></el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <h4 class="section-sub-title">数据分析设置</h4>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="及格分" prop="passingScore">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formModel.passingScore"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ :step="1"
|
|
|
+ :precision="0"
|
|
|
+ :controls="false"
|
|
|
+ step-strictly
|
|
|
+ style="width: 100px"
|
|
|
+ />
|
|
|
+ <span class="input-suffix">%</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item
|
|
|
+ label="优秀分"
|
|
|
+ prop="excellentScore"
|
|
|
+ label-width="200px"
|
|
|
+ >
|
|
|
+ <el-input-number
|
|
|
+ v-model="formModel.excellentScore"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ :step="1"
|
|
|
+ :precision="0"
|
|
|
+ :controls="false"
|
|
|
+ step-strictly
|
|
|
+ style="width: 100px"
|
|
|
+ />
|
|
|
+ <span class="input-suffix">%</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="20">
|
|
|
+ <el-form-item label="备注描述">
|
|
|
+ <el-input
|
|
|
+ v-model="formModel.description"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ maxlength="999"
|
|
|
+ placeholder="请输入备注描述"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ <div class="form-actions">
|
|
|
+ <el-button @click="goBack">返回</el-button>
|
|
|
+ <el-button type="primary" :loading="loading" @click="handleSave">
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 高级设置弹窗 -->
|
|
|
+ <ExamAdvancedDialog
|
|
|
+ ref="advancedDialogRef"
|
|
|
+ :advanced-config="advancedConfig"
|
|
|
+ @confirm="handleAdvancedConfirm"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, reactive, computed, onMounted } from 'vue';
|
|
|
+ import { useRoute, useRouter } from 'vue-router';
|
|
|
+ import type { FormInstance, FormRules } from 'element-plus';
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
+ import { getExamDetail, updateExam } from '@/api/exam';
|
|
|
+ import type { ExamUpdateParam, ExamAdvancedConfig } from '@/api/types/exam';
|
|
|
+ import useLoading from '@/hooks/loading';
|
|
|
+ import ExamAdvancedDialog from './ExamAdvancedDialog.vue';
|
|
|
+
|
|
|
+ defineOptions({
|
|
|
+ name: 'ExamEdit',
|
|
|
+ });
|
|
|
+
|
|
|
+ const route = useRoute();
|
|
|
+ const router = useRouter();
|
|
|
+ const { loading, setLoading } = useLoading();
|
|
|
+
|
|
|
+ const examId = computed(() => Number(route.query.id) || 0);
|
|
|
+ const isEdit = computed(() => !!examId.value);
|
|
|
+
|
|
|
+ const formRef = ref<FormInstance>();
|
|
|
+ const advancedDialogRef = ref();
|
|
|
+
|
|
|
+ // 表单数据
|
|
|
+ const initialFormState: Partial<ExamUpdateParam> = {
|
|
|
+ name: '',
|
|
|
+ type: '',
|
|
|
+ scanImageType: 1,
|
|
|
+ examDate: '',
|
|
|
+ markingStartDate: '',
|
|
|
+ markingEndDate: '',
|
|
|
+ status: '',
|
|
|
+ markingMode: 'TRACK',
|
|
|
+ forceMark: false,
|
|
|
+ forbidViewStudentInfo: false,
|
|
|
+ forbidLeaderScoreQuery: false,
|
|
|
+ preventSameAccountRecheck: false,
|
|
|
+ forceScrollToBottom: false,
|
|
|
+ requireFullProgressForRecheck: false,
|
|
|
+ showObjectiveScore: false,
|
|
|
+ showOriginalScoreAfterReturn: false,
|
|
|
+ recheckCount: undefined,
|
|
|
+ passingScore: 60,
|
|
|
+ excellentScore: 90,
|
|
|
+ showOriginalPaper: false,
|
|
|
+ autoPositionAfterSubmit: false,
|
|
|
+ autoAlignAnswerSheet: false,
|
|
|
+ description: '',
|
|
|
+ // 高级配置默认值
|
|
|
+ minMarkingDuration: undefined,
|
|
|
+ singlePaperRecheckCount: undefined,
|
|
|
+ scoringLimitType: null,
|
|
|
+ scoringLimitCount: undefined,
|
|
|
+ enableBarcodeAIDetection: false,
|
|
|
+ enableAnswerSheetAIDetection: false,
|
|
|
+ };
|
|
|
+
|
|
|
+ const formModel = reactive({ ...initialFormState });
|
|
|
+
|
|
|
+ // 高级配置数据
|
|
|
+ const advancedConfig = computed<ExamAdvancedConfig>(() => ({
|
|
|
+ forceScrollToBottom: formModel.forceScrollToBottom || false,
|
|
|
+ minMarkingDuration: formModel.minMarkingDuration,
|
|
|
+ singlePaperRecheckCount: formModel.singlePaperRecheckCount,
|
|
|
+ scoringLimitType: formModel.scoringLimitType,
|
|
|
+ scoringLimitCount: formModel.scoringLimitCount,
|
|
|
+ enableBarcodeAIDetection: formModel.enableBarcodeAIDetection || false,
|
|
|
+ enableAnswerSheetAIDetection:
|
|
|
+ formModel.enableAnswerSheetAIDetection || false,
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 表单验证规则
|
|
|
+ const rules: FormRules<keyof ExamUpdateParam> = {
|
|
|
+ name: [{ required: true, message: '请输入考试名称', trigger: 'change' }],
|
|
|
+ type: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
|
|
+ };
|
|
|
+
|
|
|
+ // 加载考试详情
|
|
|
+ async function loadExamDetail() {
|
|
|
+ if (!examId.value) return;
|
|
|
+
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const result = await getExamDetail(examId.value);
|
|
|
+ Object.assign(formModel, result);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取考试详情失败:', error);
|
|
|
+ ElMessage.error('获取考试详情失败');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开高级设置弹窗
|
|
|
+ function openAdvancedSettings() {
|
|
|
+ advancedDialogRef.value?.open();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 高级设置确认回调
|
|
|
+ function handleAdvancedConfirm(config: ExamAdvancedConfig) {
|
|
|
+ Object.assign(formModel, config);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存
|
|
|
+ async function handleSave() {
|
|
|
+ const valid = await formRef.value?.validate().catch(() => false);
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const data = { ...formModel } as ExamUpdateParam;
|
|
|
+
|
|
|
+ await updateExam(data);
|
|
|
+ ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功!`);
|
|
|
+ goBack();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回
|
|
|
+ function goBack() {
|
|
|
+ router.back();
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ loadExamDetail();
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .page-header {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-header h2 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .exam-form {
|
|
|
+ max-width: 1200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-section {
|
|
|
+ margin-bottom: 30px;
|
|
|
+ padding: 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-section h3 {
|
|
|
+ margin: 0 0 20px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-sub-title {
|
|
|
+ margin: 10px 0;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-header h3 {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-suffix {
|
|
|
+ margin-left: 8px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-actions {
|
|
|
+ margin-top: 30px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-actions .el-button {
|
|
|
+ margin: 0 10px;
|
|
|
+ min-width: 100px;
|
|
|
+ }
|
|
|
+</style>
|