+
{{ typeName[row.type] }}
{{ row.sourceType === 'PLATFORM' ? '平台题库' : '教师自建' }}
@@ -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
资料与步骤
-
上传案例资料、案例视频、实训指导手册与AI排错指导手册;步骤数量固定,仅可修改步骤名称
+
上传案例数据、实训指导手册与AI辅助指导手册;步骤数量固定,仅可修改步骤名称
@@ -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