diff --git a/src/views/teacherEnd/exam/index.vue b/src/views/teacherEnd/exam/index.vue index 4518a7a..adc0c63 100644 --- a/src/views/teacherEnd/exam/index.vue +++ b/src/views/teacherEnd/exam/index.vue @@ -28,7 +28,8 @@ - + + @@ -58,7 +59,7 @@ - + @@ -78,6 +79,16 @@ +
+ +
@@ -130,6 +141,12 @@ const typeName = Object.fromEntries(typeOptions.map((item) => [item.value, item. const typeTone = { SINGLE: "", MULTIPLE: "success", TRUE_FALSE: "warning", SHORT_ANSWER: "info" }; const statusMeta = { DRAFT: { label: "未发布", tone: "info" }, NOT_STARTED: { label: "未开始", tone: "" }, IN_PROGRESS: { label: "进行中", tone: "success" }, ENDED: { label: "已结束", tone: "warning" } }; const questionQuery = reactive({ type: "", taskKey: "", keyword: "" }); +// 题库列表使用前端分页:后端一次返回全部,前端按页切片 +const questionPager = reactive({ page: 1, pageSize: 10 }); +const pagedQuestions = computed(() => { + const start = (questionPager.page - 1) * questionPager.pageSize; + return questions.value.slice(start, start + questionPager.pageSize); +}); const emptyQuestion = () => ({ id: null, taskKey: "", type: "SINGLE", stem: "", options: [{ label: "A", content: "" }, { label: "B", content: "" }, { label: "C", content: "" }, { label: "D", content: "" }], answer: [], analysis: "", attachmentName: "", attachmentUrl: "" }); const questionForm = reactive(emptyQuestion()); const questionRules = { type: [{ required: true, message: "请选择题型" }], stem: [{ required: true, message: "请输入题干" }], answer: [{ required: true, message: "请输入参考答案" }] }; @@ -140,7 +157,7 @@ onMounted(() => Promise.all([loadQuestions(), loadExams(), loadProjects()])); let examsMounted = false; // KeepAlive 缓存页面:再次进入时刷新考试列表(新建/发布后无需手动刷新),首次激活跳过避免与 onMounted 重复请求 onActivated(() => { if (examsMounted) loadExams(); examsMounted = true; }); async function loadProjects() { try { projectOptions.value = (await examApi.getQuestionProjects()).data || []; } catch (e) { projectOptions.value = []; } } -async function loadQuestions() { loading.value = true; try { questions.value = (await examApi.getQuestions(questionQuery)).data || []; } finally { loading.value = false; } } +async function loadQuestions() { loading.value = true; try { questionPager.page = 1; questions.value = (await examApi.getQuestions(questionQuery)).data || []; } finally { loading.value = false; } } async function loadExams() { loading.value = true; try { exams.value = (await examApi.getTeacherExams({ keyword: examKeyword.value })).data || []; } finally { loading.value = false; } } function openQuestion(row) { questionReadonly.value = row?.sourceType === 'PLATFORM'; Object.assign(questionForm, row ? JSON.parse(JSON.stringify(row)) : emptyQuestion()); if (!Array.isArray(questionForm.answer) && isChoice.value) questionForm.answer = []; questionDialog.value = true; } function openAi() { router.push('/exam/ai-generate'); } @@ -234,6 +251,13 @@ function durationBetween(row) { if (!row.startTime || !row.endTime) return 0; re display: none; } + /* ===== 题库分页(外观继承自 teacher-light.scss 全局样式,仅控制布局)===== */ + .question-pager { + display: flex; + justify-content: flex-end; + margin-top: var(--sp-4); + } + /* ===== 数据表格(纯白 + 灰线)===== */ .data-table { width: 100%; diff --git a/src/views/teacherEnd/trainingTask/index.vue b/src/views/teacherEnd/trainingTask/index.vue index 2f8ed7a..511f727 100644 --- a/src/views/teacherEnd/trainingTask/index.vue +++ b/src/views/teacherEnd/trainingTask/index.vue @@ -308,7 +308,7 @@ 03
@@ -327,6 +327,7 @@
@@ -426,11 +427,12 @@ const uploadingField = ref("") const choiceEditorVisible = ref(false) const bankPickerVisible = ref(false) // 四类资料槽位:key 对应 taskForm 的 `${key}Name` / `${key}Url` 字段 +// accept 为空表示不限制文件类型,否则按逗号分隔的扩展名校验 const materialSlots = [ - { key: "material", label: "案例资料" }, - { key: "trainingData", label: "案例视频" }, + { key: "material", label: "案例数据", accept: ".xls,.xlsx" }, + { key: "trainingData", label: "案例数据", accept: ".xls,.xlsx" }, { key: "guideManual", label: "实训指导手册" }, - { key: "aiManual", label: "AI排错指导手册" }, + { key: "aiManual", label: "AI辅助指导手册" }, ] const tasks = ref([]) const classList = ref([]) @@ -741,6 +743,19 @@ function removeObjective(index) { async function handleMaterialChange(slotKey, file) { const rawFile = file?.raw if (!rawFile) return + const slot = materialSlots.find((item) => item.key === slotKey) + if (slot?.accept) { + const allowedExts = slot.accept + .split(",") + .map((item) => item.trim().toLowerCase()) + .filter(Boolean) + const fileName = String(rawFile.name || file?.name || "").toLowerCase() + const isAllowed = allowedExts.some((ext) => fileName.endsWith(ext)) + if (!isAllowed) { + proxy?.$modal?.msgError?.(`${slot.label}仅支持 ${allowedExts.join(" / ")} 格式`) + return + } + } const data = new FormData() data.append("file", rawFile) uploadingField.value = slotKey