diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index 0de26a8..2d760b9 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -98,6 +98,13 @@ + + + + + + 修改密码 + @@ -112,6 +119,7 @@ + @@ -126,6 +134,7 @@ import SizeSelect from "@/components/SizeSelect"; import HeaderSearch from "@/components/HeaderSearch"; import RuoYiGit from "@/components/RuoYi/Git"; import RuoYiDoc from "@/components/RuoYi/Doc"; +import ChangePasswordDialog from "@/views/profile/ChangePasswordDialog.vue"; import useAppStore from "@/store/modules/app"; import useUserStore from "@/store/modules/user"; import useSettingsStore from "@/store/modules/settings"; @@ -145,6 +154,7 @@ const roleText = computed(() => { }); const studentTopTask = ref(null); +const changePasswordVisible = ref(false); const mentList = ref([ { name: "首页", path: "/index", icon: HomeFilled }, @@ -177,6 +187,9 @@ function toggleSideBar() { function handleCommand(command) { switch (command) { + case "changePassword": + changePasswordVisible.value = true; + break; case "logout": logout(); break; @@ -185,6 +198,27 @@ function handleCommand(command) { } } +function onPasswordChanged() { + // 密码修改成功后,引导用户重新登录,避免旧 token 仍持有访问权限 + setTimeout(() => { + ElMessageBox.confirm( + "密码已更新,建议立即重新登录。是否现在退出?", + "提示", + { + confirmButtonText: "立即退出", + cancelButtonText: "稍后再说", + type: "warning", + } + ) + .then(() => { + userStore.logOut().then(() => { + location.href = "/index"; + }); + }) + .catch(() => {}); + }, 200); +} + function logout() { ElMessageBox.confirm("确定注销并退出系统吗?", "提示", { confirmButtonText: "确定", diff --git a/src/views/profile/ChangePasswordDialog.vue b/src/views/profile/ChangePasswordDialog.vue new file mode 100644 index 0000000..18b49e2 --- /dev/null +++ b/src/views/profile/ChangePasswordDialog.vue @@ -0,0 +1,266 @@ + +
+ + + + 强度:{{ strength.label }} +
+ + + + + + + + + + + + + + diff --git a/src/views/teacherEnd/exam/editor.vue b/src/views/teacherEnd/exam/editor.vue index ef7c800..a0daf03 100644 --- a/src/views/teacherEnd/exam/editor.vue +++ b/src/views/teacherEnd/exam/editor.vue @@ -9,14 +9,14 @@ -
+

基础设置

填写考试名称、时间与班级,分值矩阵自动汇总

@@ -53,7 +53,7 @@
-
+

考试设置

设置试题展示、作答行为和多选题计分方式

@@ -103,7 +103,7 @@
-
+

选择试题

勾选题目并设置该题分值,选择结果实时同步到试卷概览

@@ -148,7 +148,7 @@
-
+

试卷概览

{{ form.questions.length }} 道题,共 {{ totalScore }} 分

导出 Word 试卷
@@ -214,7 +214,7 @@ import { saveAs } from "file-saver"; import * as examApi from "@/api/exam"; const route = useRoute(); const router = useRouter(); const id = computed(() => route.params.id); const isNew = computed(() => id.value === "new"); -const loading = ref(false); const activeStep = ref(1); const activeType = ref("SINGLE"); const status = ref("DRAFT"); const classes = ref([]); const bank = ref([]); const formRef = ref(); const duration = ref(60); +const loading = ref(false); const activeStep = ref(0); const activeType = ref("SINGLE"); const status = ref("DRAFT"); const classes = ref([]); const bank = ref([]); const formRef = ref(); const duration = ref(60); const typeOptions = [{ label: "单选题", value: "SINGLE" }, { label: "多选题", value: "MULTIPLE" }, { label: "判断题", value: "TRUE_FALSE" }, { label: "简答题", value: "SHORT_ANSWER" }]; const statusMeta = { DRAFT: { label: "未发布", tone: "info" }, NOT_STARTED: { label: "未开始", tone: "" }, IN_PROGRESS: { label: "进行中", tone: "success" }, ENDED: { label: "已结束", tone: "warning" } }; const form = reactive({ name: "", startTime: "", endTime: "", classIds: [], questionOrderMode: "FIXED", optionOrderMode: "FIXED", allowCopyQuestion: false, allowPasteAnswer: false, allowRollback: true, multipleScoreMode: "ALL_OR_NOTHING", questions: [] }); @@ -235,8 +235,8 @@ function selectedByType(type) { const ids = new Set(bank.value.filter((item) => function questionCount(type) { return bank.value.filter((item) => item.type === type).length; } function scoreByType(type) { return selectedQuestions(type).reduce((sum, item) => sum + Number(selectedScore(item.id)), 0); } function selectedQuestions(type) { const selected = new Set(form.questions.map((item) => item.questionId)); return bank.value.filter((item) => item.type === type && selected.has(item.id)); } -async function nextStep() { if (activeStep.value === 0) { try { await formRef.value.validate(); const start = new Date(form.startTime.replace(" ", "T")); const end = new Date(form.endTime.replace(" ", "T")); if (end <= start) return ElMessage.warning("结束时间必须晚于开始时间"); duration.value = Math.round((end - start) / 60000); } catch { return; } } if (activeStep.value === 1 && !form.questions.length) return ElMessage.warning("至少选择一道试题"); activeStep.value++; } -async function save(publish) { if (inProgress.value) { await examApi.updateExam(id.value, form); ElMessage.success("结束时间已更新"); return router.push("/exam/index"); } try { await formRef.value?.validate(); } catch { activeStep.value = 0; return; } if (!form.questions.length) { activeStep.value = 1; return ElMessage.warning("至少选择一道试题"); } const result = isNew.value ? await examApi.createExam(form) : await examApi.updateExam(id.value, form); const examId = result.data.id; if (publish) await examApi.publishExam(examId); ElMessage.success(publish ? "考试已保存并发布" : "考试已保存"); router.push("/exam/index"); } +async function nextStep() { if (activeStep.value === 0 && !form.questions.length) return ElMessage.warning("至少选择一道试题"); if (activeStep.value === 2) { try { await formRef.value.validate(); const start = new Date(form.startTime.replace(" ", "T")); const end = new Date(form.endTime.replace(" ", "T")); if (end <= start) return ElMessage.warning("结束时间必须晚于开始时间"); duration.value = Math.round((end - start) / 60000); } catch { return; } } activeStep.value++; } +async function save(publish) { if (inProgress.value) { await examApi.updateExam(id.value, form); ElMessage.success("结束时间已更新"); return router.push("/exam/index"); } try { await formRef.value?.validate(); } catch { activeStep.value = 2; return; } if (!form.questions.length) { activeStep.value = 0; return ElMessage.warning("至少选择一道试题"); } const result = isNew.value ? await examApi.createExam(form) : await examApi.updateExam(id.value, form); const examId = result.data.id; if (publish) await examApi.publishExam(examId); ElMessage.success(publish ? "考试已保存并发布" : "考试已保存"); router.push("/exam/index"); } async function importPaperQuestions({ file }) { const formData = new FormData(); formData.append("file", file); const { data } = await examApi.importQuestions(formData); bank.value = [...data, ...bank.value]; for (const q of data) if (!isSelected(q.id)) form.questions.push({ questionId: q.id, score: 1 }); ElMessage.success(`导入并选中 ${data.length} 道题`); } async function downloadTemplate() { saveAs(await examApi.downloadQuestionTemplate(), "考试题库导入模板.docx"); } async function exportPaper() { saveAs(await examApi.exportExamPaper(id.value), `${form.name}试卷.docx`); }