|
|
|
@ -51,9 +51,9 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="!inProgress" class="fixed-step-actions" aria-label="考试编辑步骤">
|
|
|
|
<div v-if="!inProgress" class="fixed-step-actions" aria-label="考试编辑步骤">
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 1 }" @click="activeStep = 1">添加试题</button>
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 1 }" @click="activeStep = 1">添加试题</button>
|
|
|
|
<span class="fixed-step-arrow">>>></span>
|
|
|
|
<span class="fixed-step-arrow"><el-icon><ArrowRight /></el-icon></span>
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 2 }" @click="activeStep = 2">试卷概览</button>
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 2 }" @click="activeStep = 2">试卷概览</button>
|
|
|
|
<span class="fixed-step-arrow">>>></span>
|
|
|
|
<span class="fixed-step-arrow"><el-icon><ArrowRight /></el-icon></span>
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 0 }" @click="activeStep = 0">考试设置</button>
|
|
|
|
<button type="button" class="fixed-step-button" :class="{ 'is-active': activeStep === 0 }" @click="activeStep = 0">考试设置</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -233,7 +233,7 @@ const totalScore = computed(() => form.questions.reduce((sum, item) => sum + Num
|
|
|
|
const selectedTypes = computed(() => typeOptions.filter((item) => selectedByType(item.value)));
|
|
|
|
const selectedTypes = computed(() => typeOptions.filter((item) => selectedByType(item.value)));
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(load);
|
|
|
|
onMounted(load);
|
|
|
|
async function load() { loading.value = true; try { const [classRes, questionRes] = await Promise.all([examApi.getExamClasses(), examApi.getQuestions({})]); classes.value = classRes.data || []; bank.value = questionRes.data || []; if (!isNew.value) { const { data } = await examApi.getTeacherExam(id.value); status.value = data.status; const bankIds = new Set(bank.value.map((item) => item.id)); bank.value = [...bank.value, ...data.questions.filter((item) => !bankIds.has(item.id))]; Object.assign(form, { name: data.name, startTime: formatValue(data.startTime), endTime: formatValue(data.endTime), classIds: data.classes.map((item) => item.id), questionOrderMode: data.questionOrderMode || "FIXED", optionOrderMode: data.optionOrderMode || "FIXED", allowCopyQuestion: toBoolean(data.allowCopyQuestion, false), allowPasteAnswer: toBoolean(data.allowPasteAnswer, false), allowRollback: toBoolean(data.allowRollback, true), multipleScoreMode: data.multipleScoreMode || "ALL_OR_NOTHING", questions: data.questions.map((item) => ({ questionId: item.id, score: Number(item.score) })) }); for (const type of typeOptions.map((item) => item.value)) { const first = data.questions.find((item) => item.type === type); if (first) typeScores[type] = Number(first.score) || 1; } duration.value = data.durationMinutes; } else { const start = new Date(Date.now() + 3600000); form.startTime = formatValue(start); form.endTime = formatValue(new Date(start.getTime() + 3600000)); } } finally { loading.value = false; } }
|
|
|
|
async function load() { loading.value = true; try { const [classRes, questionRes] = await Promise.all([examApi.getExamClasses(), examApi.getQuestions({ scope: "school" })]); classes.value = classRes.data || []; bank.value = questionRes.data || []; if (!isNew.value) { const { data } = await examApi.getTeacherExam(id.value); status.value = data.status; const bankIds = new Set(bank.value.map((item) => item.id)); bank.value = [...bank.value, ...data.questions.filter((item) => !bankIds.has(item.id))]; Object.assign(form, { name: data.name, startTime: formatValue(data.startTime), endTime: formatValue(data.endTime), classIds: data.classes.map((item) => item.id), questionOrderMode: data.questionOrderMode || "FIXED", optionOrderMode: data.optionOrderMode || "FIXED", allowCopyQuestion: toBoolean(data.allowCopyQuestion, false), allowPasteAnswer: toBoolean(data.allowPasteAnswer, false), allowRollback: toBoolean(data.allowRollback, true), multipleScoreMode: data.multipleScoreMode || "ALL_OR_NOTHING", questions: data.questions.map((item) => ({ questionId: item.id, score: Number(item.score) })) }); for (const type of typeOptions.map((item) => item.value)) { const first = data.questions.find((item) => item.type === type); if (first) typeScores[type] = Number(first.score) || 1; } duration.value = data.durationMinutes; } else { const start = new Date(Date.now() + 3600000); form.startTime = formatValue(start); form.endTime = formatValue(new Date(start.getTime() + 3600000)); } } finally { loading.value = false; } }
|
|
|
|
function syncEndTime() { if (!form.startTime) return; form.endTime = formatValue(new Date(new Date(form.startTime.replace(" ", "T")).getTime() + duration.value * 60000)); }
|
|
|
|
function syncEndTime() { if (!form.startTime) return; form.endTime = formatValue(new Date(new Date(form.startTime.replace(" ", "T")).getTime() + duration.value * 60000)); }
|
|
|
|
function syncDurationFromEnd() { if (!form.startTime || !form.endTime) return; const start = new Date(form.startTime.replace(" ", "T")); const end = new Date(form.endTime.replace(" ", "T")); if (end > start) duration.value = Math.round((end - start) / 60000); }
|
|
|
|
function syncDurationFromEnd() { if (!form.startTime || !form.endTime) return; const start = new Date(form.startTime.replace(" ", "T")); const end = new Date(form.endTime.replace(" ", "T")); if (end > start) duration.value = Math.round((end - start) / 60000); }
|
|
|
|
function isSelected(questionId) { return form.questions.some((item) => item.questionId === questionId); }
|
|
|
|
function isSelected(questionId) { return form.questions.some((item) => item.questionId === questionId); }
|
|
|
|
@ -403,38 +403,48 @@ function toBoolean(value, fallback) { if (value === undefined || value === null
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
justify-content: center;
|
|
|
|
gap: var(--sp-4);
|
|
|
|
gap: var(--sp-3);
|
|
|
|
margin-top: var(--sp-5);
|
|
|
|
margin-top: var(--sp-5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.fixed-step-button {
|
|
|
|
.fixed-step-button {
|
|
|
|
min-width: 164px;
|
|
|
|
min-width: 148px;
|
|
|
|
height: 40px;
|
|
|
|
height: 38px;
|
|
|
|
padding: 0 var(--sp-5);
|
|
|
|
padding: 0 var(--sp-4);
|
|
|
|
border: 0;
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
border-radius: var(--r-control);
|
|
|
|
border-radius: var(--r-control);
|
|
|
|
background: var(--edu-primary);
|
|
|
|
background: #ffffff;
|
|
|
|
color: #ffffff;
|
|
|
|
color: var(--text-body);
|
|
|
|
|
|
|
|
font: inherit;
|
|
|
|
font-size: var(--fs-body);
|
|
|
|
font-size: var(--fs-body);
|
|
|
|
font-weight: 600;
|
|
|
|
font-weight: 500;
|
|
|
|
cursor: pointer;
|
|
|
|
cursor: pointer;
|
|
|
|
transition: background 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease;
|
|
|
|
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
|
|
border-color: var(--edu-primary);
|
|
|
|
|
|
|
|
color: var(--edu-primary);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover,
|
|
|
|
|
|
|
|
&.is-active {
|
|
|
|
&.is-active {
|
|
|
|
background: var(--edu-primary-deep);
|
|
|
|
border-color: var(--edu-primary);
|
|
|
|
|
|
|
|
background: var(--edu-primary);
|
|
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
box-shadow: 0 4px 10px rgba(25, 137, 204, 0.18);
|
|
|
|
box-shadow: 0 4px 10px rgba(25, 137, 204, 0.18);
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
|
|
|
|
|
|
&:hover { color: #ffffff; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:focus-visible { outline: 2px solid var(--edu-primary); outline-offset: 2px; }
|
|
|
|
&:focus-visible { outline: 2px solid var(--edu-primary); outline-offset: 2px; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.fixed-step-arrow {
|
|
|
|
.fixed-step-arrow {
|
|
|
|
color: var(--text-strong);
|
|
|
|
display: inline-flex;
|
|
|
|
font-size: 20px;
|
|
|
|
align-items: center;
|
|
|
|
font-weight: 700;
|
|
|
|
color: var(--text-muted);
|
|
|
|
letter-spacing: -3px;
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { font-size: 16px; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-row {
|
|
|
|
.form-row {
|
|
|
|
|