diff --git a/src/__tests__/api/studySessions.spec.ts b/src/__tests__/api/studySessions.spec.ts index bef77d5..7a92e8e 100644 --- a/src/__tests__/api/studySessions.spec.ts +++ b/src/__tests__/api/studySessions.spec.ts @@ -14,6 +14,7 @@ import { continueSession, pauseSession, endSession, + abortSession, startOrContinueStudySession, getSessionDetail, } from '@/api/studySessions' @@ -69,6 +70,14 @@ describe('studySessions API', () => { { content: '自定义结束' } ) }) + it('abortSession sends POST with confirmation phrase', async () => { + mockedRequest.post.mockResolvedValue({ code: 200, data: null }) + await abortSession('S001', '我误操作导致开启了本次学习') + expect(mockedRequest.post).toHaveBeenCalledWith( + '/study-sessions/S001/study-sessions/abort', + { confirmation: '我误操作导致开启了本次学习' } + ) + }) it('startOrContinueStudySession sends GET to correct endpoint', async () => { mockedRequest.get.mockResolvedValue({ code: 200, data: { sessionNum: 'S002' } }) diff --git a/src/__tests__/components/StartTask.spec.ts b/src/__tests__/components/StartTask.spec.ts index 855da16..a78055a 100644 --- a/src/__tests__/components/StartTask.spec.ts +++ b/src/__tests__/components/StartTask.spec.ts @@ -6,6 +6,7 @@ import { createMemoryHistory, createRouter } from 'vue-router' import StartTask from '@/components/StartTask.vue' import router from '@/router' import { + abortSession, continueSession, endSession, getActiveSession, @@ -17,6 +18,7 @@ import { import { getFragmentsBySession } from '@/api/reportFragments' vi.mock('@/api/studySessions', () => ({ + abortSession: vi.fn(), continueSession: vi.fn(), endSession: vi.fn(), getActiveSession: vi.fn(), @@ -222,4 +224,44 @@ describe('StartTask.vue', () => { expect(pauseSession).toHaveBeenCalledTimes(2) wrapper.unmount() }) + + it('confirmAbort aborts session with correct phrase and navigates to study page', async () => { + vi.mocked(abortSession).mockResolvedValue({ code: 200, message: '请求成功' } as any) + const wrapper = await mountTask() + + wrapper.vm.abortConfirmation = '我误操作导致开启了本次学习' + await wrapper.vm.confirmAbort() + + expect(abortSession).toHaveBeenCalledWith('SESSION_001', '我误操作导致开启了本次学习') + expect(ElMessage.success).toHaveBeenCalledWith('本次学习已关闭,未产生任何数据') + expect(localStorage.getItem('activeSession')).toBeNull() + expect(router.currentRoute.value.path).toBe('/study') + wrapper.unmount() + }) + + it('confirmAbort refuses wrong phrase without calling API', async () => { + const wrapper = await mountTask() + + wrapper.vm.abortConfirmation = '不是确认语' + await wrapper.vm.confirmAbort() + + expect(ElMessage.warning).toHaveBeenCalledWith('确认语输入不正确,请重新输入') + expect(abortSession).not.toHaveBeenCalled() + wrapper.unmount() + }) + + it('confirmAbort shows backend message when session has fragments and stays on page', async () => { + vi.mocked(abortSession).mockRejectedValue({ + code: 400, + message: '这次学习已经产生了学习残片,不能按误操作结束了哦', + } as any) + const wrapper = await mountTask() + + wrapper.vm.abortConfirmation = '我误操作导致开启了本次学习' + await wrapper.vm.confirmAbort() + + expect(ElMessage.error).toHaveBeenCalledWith('这次学习已经产生了学习残片,不能按误操作结束了哦') + expect(router.currentRoute.value.path).not.toBe('/study') + wrapper.unmount() + }) }) diff --git a/src/api/studySessions.ts b/src/api/studySessions.ts index 981a3a8..dfddbb6 100644 --- a/src/api/studySessions.ts +++ b/src/api/studySessions.ts @@ -14,6 +14,11 @@ export const endSession = (sessionNum: string, content: string = "任务结束") return request.post(`/study-sessions/${sessionNum}/study-sessions/ended`, { content }); }; +/** 误操作结束学习会话:需输入确认语,严格零数据关闭(删除会话与预期) */ +export const abortSession = (sessionNum: string, confirmation: string) => { + return request.post(`/study-sessions/${sessionNum}/study-sessions/abort`, { confirmation }); +}; + export const startOrContinueStudySession = (taskNum: string) => { return request.get(`/tasks/${taskNum}/study-sessions/start-or-continue`); }; diff --git a/src/components/StartTask.vue b/src/components/StartTask.vue index 99985d1..e4ec0e7 100644 --- a/src/components/StartTask.vue +++ b/src/components/StartTask.vue @@ -5,6 +5,7 @@ import { ElMessage, ElMessageBox } from "element-plus"; import request from "@/utils/request"; import { useTimer } from "@/components/composables/useTimer"; import { + abortSession, continueSession, endSession, getActiveSession, @@ -31,6 +32,14 @@ const { const pageLoading = ref(true); const timerActionLoading = ref(false); const endingSession = ref(false); +// 误操作结束(零数据关闭) +const MISOPERATION_PHRASE = "我误操作导致开启了本次学习"; +const abortDialogVisible = ref(false); +const abortConfirmation = ref(""); +const abortingSession = ref(false); +const canAbortSession = computed( + () => taskInfo.value.sessionState !== "ENDED" && fragmentsList.value.length === 0, +); const summaryDialogVisible = ref(false); const summaryContent = ref(""); @@ -432,6 +441,41 @@ const closeSummaryDialog = () => { summaryDialogVisible.value = false; if (summaryLoadingTimer) { clearInterval(summaryLoadingTimer); summaryLoadingTimer = null; } }; +const openAbortDialog = () => { + abortConfirmation.value = ""; + abortDialogVisible.value = true; +}; + +const closeExpectationAndOpenAbort = () => { + expectationDialogVisible.value = false; + openAbortDialog(); +}; + +const confirmAbort = async () => { + if (abortConfirmation.value.trim() !== MISOPERATION_PHRASE) { + ElMessage.warning("确认语输入不正确,请重新输入"); + return; + } + if (abortingSession.value) return; + abortingSession.value = true; + try { + const res = await abortSession(taskInfo.value.sessionNum, abortConfirmation.value.trim()); + if (res.code === 200) { + ElMessage.success("本次学习已关闭,未产生任何数据"); + clear(); + clearBreakState(); + localStorage.removeItem("activeSession"); + await router.push("/study"); + } else { + ElMessage.error(res.message || "误操作结束失败"); + } + } catch (e: any) { + ElMessage.error(e?.message || "误操作结束失败,请重试"); + } + finally { + abortingSession.value = false; + } +}; const restTimer = async () => { await stopTimer(); @@ -649,6 +693,9 @@ defineExpose({ startTimer, stopTimer, endTimer, + openAbortDialog, + confirmAbort, + abortConfirmation, syncDisplay, clear, }); @@ -766,6 +813,7 @@ defineExpose({ 休息 结束会话 + 误操作结束 @@ -866,6 +914,7 @@ defineExpose({ :rows="4" /> @@ -909,6 +958,32 @@ defineExpose({ 确认结束 + + +

本次学习会话尚未产生学习残片,可以零数据关闭。关闭后会话与学习预期将被删除,不会留下任何学习记录。

+

如果是误操作开启了本次学习,请输入以下内容以确认:

+
{{ MISOPERATION_PHRASE }}
+ + +
@@ -1298,4 +1373,15 @@ defineExpose({ text-overflow: ellipsis; } +.abort-phrase { + margin-bottom: 12px; + padding: 8px 12px; + border: 1px dashed var(--el-color-danger); + border-radius: 6px; + color: var(--el-color-danger); + font-size: 14px; + text-align: center; + word-break: break-all; +} +