# 学生端考试模式实现计划(修订版) ## 一、需求回顾 根据后端字段: - `Assignment.examMode`: 是否为考试模式 - `Assignment.examDuration`: 考试时长(分钟) - `EngagementVO.submitted`: 是否已交卷 功能要求: 1. 作业详情页显示考试模式标识 2. 写作页面实时显示考试倒计时 3. 结束前5分钟提示学生暂存内容 4. 倒计时结束时**自动暂存并提交** 5. 提交时提示"提交后不可再修改" 6. **考试模式已提交学生不可再进入写作页面** 7. 教师端无需隐藏批改区域 --- ## 二、涉及文件清单 | 文件 | 修改内容 | |------|----------| | `src/hooks/useExamCountdown.ts` | **新建** - 考试倒计时 hook | | `src/views/HomeworkPage/component/HomeworkDetail.vue` | 显示考试模式标识、已提交则禁用开始按钮 | | `src/views/EditPage/EditPage.vue` | 集成倒计时横幅、已提交则跳转离开 | | `src/views/Home/component/Edit/index.vue` | 考试模式提交确认提示 | --- ## 三、详细实现计划 ### 3.1 新建考试倒计时 Hook **文件**: `src/hooks/useExamCountdown.ts` ```typescript import { ref, computed, onUnmounted } from 'vue'; interface UseExamCountdownOptions { startTime: Date; duration: number; onAutoSubmit: () => void; onWarning: () => void; } export function useExamCountdown(options: UseExamCountdownOptions) { const { startTime, duration, onAutoSubmit, onWarning } = options; const endTime = new Date(startTime.getTime() + duration * 60 * 1000); const remainingSeconds = ref(0); const isWarning = ref(false); const isExpired = ref(false); let intervalId: number | null = null; let warningTriggered = false; const formattedTime = computed(() => { const hours = Math.floor(remainingSeconds.value / 3600); const minutes = Math.floor((remainingSeconds.value % 3600) / 60); const seconds = remainingSeconds.value % 60; return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; }); const tick = () => { const now = new Date(); const diff = Math.floor((endTime.getTime() - now.getTime()) / 1000); if (diff <= 0) { remainingSeconds.value = 0; isExpired.value = true; isWarning.value = false; stop(); onAutoSubmit(); return; } remainingSeconds.value = diff; if (diff <= 300 && !warningTriggered) { isWarning.value = true; warningTriggered = true; onWarning(); } }; const start = () => { tick(); intervalId = window.setInterval(tick, 1000); }; const stop = () => { if (intervalId !== null) { clearInterval(intervalId); intervalId = null; } }; onUnmounted(() => { stop(); }); return { remainingSeconds, isWarning, isExpired, formattedTime, start, stop }; } ``` --- ### 3.2 作业详情页修改 **文件**: `src/views/HomeworkPage/component/HomeworkDetail.vue` #### 3.2.1 数据模型修改 在 `IAssignment` 类型已包含 `examMode` 和 `examDuration`,无需修改接口 #### 3.2.2 模板添加考试模式标识 ```vue
截止日期: {{new Date(Date.parse(data.endTime)).toLocaleString()}}
作业模式: 考试模式 时长: {{ data.examDuration }}分钟
``` #### 3.2.3 开始按钮逻辑修改 ```typescript // 计算属性:判断作业是否应该禁用 const isAssignmentDisabled = computed(() => { const now = new Date() const startTime = new Date(data.startTime) const endTime = new Date(data.endTime) if (now < startTime) { return true } if (now > endTime) { if (engagementStatus.value) { return engagementStatus.value.status !== 'NOT_SUBMITTED' } return false } // 新增:考试模式且已提交,禁止再次进入 if (data.examMode && engagementStatus.value?.submitted) { return true } return false }) // 新增:按钮提示文本 const getButtonTooltip = computed(() => { // ... 现有逻辑 ... // 新增:考试模式已提交提示 if (data.examMode && engagementStatus.value?.submitted) { return '您已完成交卷,无法再次编辑' } return '' }) ``` #### 3.2.4 开始按钮模板修改 ```vue 开始{{ data.type }} ``` --- ### 3.3 写作页面修改 **文件**: `src/views/EditPage/EditPage.vue` #### 3.3.1 数据模型扩展 ```typescript const dataForm = reactive<{ messages: IDialogue[], dialogueId: string; engagement: engagementVO; assignment: IAssignment; // 新增 }>({ messages: [], dialogueId: '', engagement: {} as engagementVO, assignment: {} as IAssignment }) ``` #### 3.3.2 页面加载时校验 ```typescript import { ElMessage } from 'element-plus' onMounted(async () => { await fetchData() // 新增:考试模式已提交则不允许进入 if (isExamMode.value && dataForm.engagement.submitted) { ElMessage.warning('您已完成交卷,无法再次编辑') router.push(`/home/homeworkDetail/${assignmentId}`) return } if (isExamMode.value) { startCountdown() } registerAllEventListeners(indexedDB, store.user.id, assignmentId) }) // 计算属性 const isExamMode = computed(() => dataForm.assignment?.examMode ?? false) ``` #### 3.3.3 模板添加倒计时横幅 ```vue
返回
考试模式 {{ formattedTime }} 距离结束还有5分钟,请及时暂存内容!
``` #### 3.3.4 倒计时集成 ```typescript import { useExamCountdown } from '@/hooks/useExamCountdown' const handleStage = async () => { emitter.emit('get-html') emitter.emit('get-text') emitter.emit('get-quill-content') // 实际暂存逻辑... } const handleSubmit = async () => { // 实际提交逻辑... } const { remainingSeconds, isWarning, formattedTime, start: startCountdown, stop: stopCountdown } = useExamCountdown({ startTime: new Date(dataForm.assignment.startTime), duration: dataForm.assignment.examDuration || 60, onAutoSubmit: async () => { ElMessage.warning('考试时间已到,正在自动提交...') await handleStage() await handleSubmit() }, onWarning: () => { ElMessage.info('距离考试结束还有5分钟,请注意暂存您的内容!') } }) onUnmounted(() => { stopCountdown() removeAllEventListeners() emitter.off('save-ai-dialogue-before-submit') }) ``` #### 3.3.5 样式添加 **文件**: `src/views/EditPage/index.scss` ```scss .exam-countdown-banner { display: flex; align-items: center; gap: 20px; padding: 12px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 16px; justify-content: center; .countdown-time { font-size: 28px; font-weight: bold; font-family: 'Monaco', 'Menlo', monospace; letter-spacing: 2px; } } ``` --- ### 3.4 编辑组件提交确认 **文件**: `src/views/Home/component/Edit/index.vue` ```typescript interface IEditProps { engagement: engagementVO isExamMode?: boolean } defineProps() const props = defineProps<{ engagement: engagementVO isExamMode?: boolean }>() const handleSubmit = async () => { // 考试模式:强制确认 if (props.isExamMode) { try { await ElMessageBox.confirm( '考试模式下提交后将无法再次修改,您确定要提交吗?', '确认交卷', { confirmButtonText: '确认提交', cancelButtonText: '取消', type: 'warning', } ) } catch { return } } // 执行提交逻辑... } ``` --- ## 四、关键差异对照表 | 功能 | 普通作业模式 | 考试模式 | |------|------------|---------| | 暂存按钮 | ✅ 显示 | ✅ 显示 | | AI助手 | ✅ 显示 | ✅ 显示 | | 词典 | ✅ 显示 | ✅ 显示 | | 批改区域 | ✅ 显示 | ✅ 显示(教师端不隐藏) | | 倒计时 | ❌ 不显示 | ✅ 显示 | | 提交确认 | 无 | 有"不可修改"提示 | | 5分钟警告 | 无 | 有暂存提示 | | 自动交卷 | 无 | 倒计时结束自动暂存并提交 | | 已提交禁止再进入 | 无 | ✅ 有 | --- ## 五、实施顺序 | 顺序 | 文件 | 说明 | |------|------|------| | 1 | `src/hooks/useExamCountdown.ts` | 新建倒计时 hook | | 2 | `src/views/EditPage/index.scss` | 倒计时横幅样式 | | 3 | `src/views/HomeworkPage/component/HomeworkDetail.vue` | 模式标识 + 已提交禁用 | | 4 | `src/views/EditPage/EditPage.vue` | 集成倒计时 + 提交拦截 | | 5 | `src/views/Home/component/Edit/index.vue` | 提交确认提示 | --- ## 六、后端接口需求 **无需新增接口**,现有 API 已足够: | 接口 | 返回数据 | 用途 | |------|----------|------| | `GET /api/assignment/byAssignment/{id}` | `examMode`, `examDuration` | 获取作业考试配置 | | `GET /api/assignment/engage` | `submitted` | 获取学生交卷状态 | | `PUT /api/assignment/engage/stage` | - | 暂存作业内容 | | `PUT /api/assignment/engage/submit` | - | 提交作业 | **后端需确保**: 1. `Assignment` 返回中包含 `examMode` 和 `examDuration` 2. `EngagementVO` 包含 `submitted` 字段 3. 提交接口校验考试模式只能提交一次