feat: 新增误操作结束学习交互
This commit is contained in:
@@ -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' } })
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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`);
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
<el-button v-if="timerIsOver" plain type="success" @click="restTimer" :loading="timerActionLoading">休息</el-button>
|
||||
</template>
|
||||
<el-button type="danger" plain @click="openSummaryDialog">结束会话</el-button>
|
||||
<el-button v-if="canAbortSession" text type="danger" @click="openAbortDialog">误操作结束</el-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -866,6 +914,7 @@ defineExpose({
|
||||
:rows="4"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button v-if="!expectationSaved" text type="danger" @click="closeExpectationAndOpenAbort">误操作结束</el-button>
|
||||
<el-button v-if="expectationSaved" @click="expectationDialogVisible = false">取消</el-button>
|
||||
<el-button type="success" :loading="savingExpectation" @click="saveExpectation">确定</el-button>
|
||||
</template>
|
||||
@@ -909,6 +958,32 @@ defineExpose({
|
||||
<el-button type="success" :loading="endingSession" @click="endTimer(summaryContent)">确认结束</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="abortDialogVisible"
|
||||
title="误操作结束"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<p class="dialog-hint">本次学习会话尚未产生学习残片,可以零数据关闭。关闭后会话与学习预期将被删除,不会留下任何学习记录。</p>
|
||||
<p class="dialog-hint">如果是误操作开启了本次学习,请输入以下内容以确认:</p>
|
||||
<div class="abort-phrase">{{ MISOPERATION_PHRASE }}</div>
|
||||
<el-input
|
||||
v-model="abortConfirmation"
|
||||
placeholder="请输入上方确认语"
|
||||
@keyup.enter="confirmAbort"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="abortDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:loading="abortingSession"
|
||||
:disabled="abortConfirmation.trim() !== MISOPERATION_PHRASE"
|
||||
@click="confirmAbort"
|
||||
>确认误操作结束</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user