# 学生端考试模式实现计划(修订版)
## 一、需求回顾
根据后端字段:
- `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. 提交接口校验考试模式只能提交一次