fix: 学习会话按钮补防抖加载

This commit is contained in:
2026-08-05 21:42:45 +08:00
parent 9ed53aa4bc
commit c95e593cf1
3 changed files with 78 additions and 32 deletions
@@ -88,4 +88,25 @@ describe('useStudyFragment', () => {
expect(result).toBe(false) expect(result).toBe(false)
expect(ElMessage.error).toHaveBeenCalledWith('网络超时') expect(ElMessage.error).toHaveBeenCalledWith('网络超时')
}) })
it('prevents duplicate submission while creating fragment', async () => {
let resolveCreate!: (value: any) => void
vi.mocked(createFragments).mockImplementation(
() => new Promise((resolve) => { resolveCreate = resolve }),
)
const { fragmentContent, creatingFragment, confirmGenerateFragment } = useStudyFragment()
fragmentContent.value = '学习内容'
const first = confirmGenerateFragment('S001')
const second = confirmGenerateFragment('S001')
await Promise.resolve()
expect(createFragments).toHaveBeenCalledTimes(1)
expect(creatingFragment.value).toBe(true)
resolveCreate({ code: 200 })
await Promise.all([first, second])
expect(creatingFragment.value).toBe(false)
})
}) })
+51 -32
View File
@@ -25,9 +25,12 @@ const {
openFragmentDialog, openFragmentDialog,
closeFragmentDialog, closeFragmentDialog,
confirmGenerateFragment, confirmGenerateFragment,
creatingFragment,
} = useStudyFragment(); } = useStudyFragment();
const pageLoading = ref(true); const pageLoading = ref(true);
const timerActionLoading = ref(false);
const endingSession = ref(false);
const summaryDialogVisible = ref(false); const summaryDialogVisible = ref(false);
const summaryContent = ref(""); const summaryContent = ref("");
@@ -309,39 +312,51 @@ const statusText = computed(() => {
}); });
const startTimer = async () => { const startTimer = async () => {
if (taskInfo.value.sessionState === "PAUSED") { if (timerActionLoading.value) return;
const res = await continueSession(taskInfo.value.sessionNum); timerActionLoading.value = true;
if (res.code === 200) ElMessage.success("任务继续"); try {
await loadTaskSession(); if (taskInfo.value.sessionState === "PAUSED") {
const res = await continueSession(taskInfo.value.sessionNum);
if (res.code === 200) ElMessage.success("任务继续");
await loadTaskSession();
}
// 手动开始 → 清除休息状态
localStorage.removeItem(BREAK_END_KEY.value);
isBreak.value = false;
const duration = taskInfo.value.pointerPosition || 25 * 60 * 1000;
runCountdown(duration);
} finally {
timerActionLoading.value = false;
} }
// 手动开始 → 清除休息状态
localStorage.removeItem(BREAK_END_KEY.value);
isBreak.value = false;
const duration = taskInfo.value.pointerPosition || 25 * 60 * 1000;
runCountdown(duration);
}; };
const stopTimer = async () => { const stopTimer = async () => {
const res = await pauseSession(taskInfo.value.sessionNum); if (timerActionLoading.value) return;
if (res.code === 200) { timerActionLoading.value = true;
ElMessage.success("任务暂停"); try {
// 重新获取会话数据,同步后端计算后的时间字段 const res = await pauseSession(taskInfo.value.sessionNum);
try { if (res.code === 200) {
const detail = await getSessionDetail(taskInfo.value.sessionNum); ElMessage.success("任务暂停");
if (detail?.data) { // 重新获取会话数据,同步后端计算后的时间字段
const d = detail.data; try {
taskInfo.value.sessionState = d.sessionState ?? "PAUSED"; const detail = await getSessionDetail(taskInfo.value.sessionNum);
taskInfo.value.actualTime = d.actualTime ?? 0; if (detail?.data) {
taskInfo.value.effectiveTime = d.effectiveTime ?? 0; const d = detail.data;
taskInfo.value.effectivenessRatio = d.effectivenessRatio ?? 0; taskInfo.value.sessionState = d.sessionState ?? "PAUSED";
taskInfo.value.startTime = d.startTime ?? taskInfo.value.startTime; taskInfo.value.actualTime = d.actualTime ?? 0;
taskInfo.value.endTime = d.endTime ?? ""; taskInfo.value.effectiveTime = d.effectiveTime ?? 0;
taskInfo.value.lastStartTime = d.lastStartTime ?? ""; taskInfo.value.effectivenessRatio = d.effectivenessRatio ?? 0;
taskInfo.value.pointerPosition = d.pointerPosition ?? 0; taskInfo.value.startTime = d.startTime ?? taskInfo.value.startTime;
taskInfo.value.endTime = d.endTime ?? "";
taskInfo.value.lastStartTime = d.lastStartTime ?? "";
taskInfo.value.pointerPosition = d.pointerPosition ?? 0;
}
} catch {
taskInfo.value.sessionState = "PAUSED";
} }
} catch {
taskInfo.value.sessionState = "PAUSED";
} }
} finally {
timerActionLoading.value = false;
} }
clear(); clear();
clearBreakState(); clearBreakState();
@@ -367,6 +382,8 @@ const endTimer = async (content: string) => {
// 用户取消结束会话,不做任何操作 // 用户取消结束会话,不做任何操作
return; return;
} }
if (endingSession.value) return;
endingSession.value = true;
try { try {
const res = await endSession(taskInfo.value.sessionNum, content); const res = await endSession(taskInfo.value.sessionNum, content);
@@ -385,6 +402,8 @@ const endTimer = async (content: string) => {
await router.push("/study"); await router.push("/study");
} catch { } catch {
ElMessage.error("结束会话失败,请重试"); ElMessage.error("结束会话失败,请重试");
} finally {
endingSession.value = false;
} }
}; };
@@ -741,10 +760,10 @@ defineExpose({
<el-button @click="openFragmentDialog">生成残片</el-button> <el-button @click="openFragmentDialog">生成残片</el-button>
</template> </template>
<template v-else> <template v-else>
<el-button type="success" @click="startTimer" :disabled="timerRunning">开始</el-button> <el-button type="success" @click="startTimer" :disabled="timerRunning" :loading="timerActionLoading">开始</el-button>
<el-button @click="stopTimer" :disabled="!timerRunning" v-if="!timerIsOver">暂停</el-button> <el-button @click="stopTimer" :disabled="!timerRunning" :loading="timerActionLoading" v-if="!timerIsOver">暂停</el-button>
<el-button @click="openFragmentDialog">生成残片</el-button> <el-button @click="openFragmentDialog">生成残片</el-button>
<el-button v-if="timerIsOver" plain type="success" @click="restTimer">休息</el-button> <el-button v-if="timerIsOver" plain type="success" @click="restTimer" :loading="timerActionLoading">休息</el-button>
</template> </template>
<el-button type="danger" plain @click="openSummaryDialog">结束会话</el-button> <el-button type="danger" plain @click="openSummaryDialog">结束会话</el-button>
</div> </div>
@@ -827,7 +846,7 @@ defineExpose({
/> />
<template #footer> <template #footer>
<el-button @click="closeFragmentDialog">取消</el-button> <el-button @click="closeFragmentDialog">取消</el-button>
<el-button type="success" @click="confirmGenerateFragment(taskInfo.sessionNum)">确定生成</el-button> <el-button type="success" :loading="creatingFragment" @click="confirmGenerateFragment(taskInfo.sessionNum)">确定生成</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -887,7 +906,7 @@ defineExpose({
</div> </div>
<template #footer> <template #footer>
<el-button @click="closeSummaryDialog">取消</el-button> <el-button @click="closeSummaryDialog">取消</el-button>
<el-button type="success" @click="endTimer(summaryContent)">确认结束</el-button> <el-button type="success" :loading="endingSession" @click="endTimer(summaryContent)">确认结束</el-button>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
+6
View File
@@ -5,6 +5,7 @@ import {createFragments} from '@/api/reportFragments';
export const useStudyFragment = () => { export const useStudyFragment = () => {
const fragmentsDialogVisible = ref(false); const fragmentsDialogVisible = ref(false);
const fragmentContent = ref(''); const fragmentContent = ref('');
const creatingFragment = ref(false);
const openFragmentDialog = () => { const openFragmentDialog = () => {
fragmentContent.value = ''; fragmentContent.value = '';
@@ -26,6 +27,7 @@ export const useStudyFragment = () => {
}; };
const confirmGenerateFragment = async (sessionNum: string): Promise<boolean> => { const confirmGenerateFragment = async (sessionNum: string): Promise<boolean> => {
if (creatingFragment.value) return false;
if (!fragmentContent.value.trim()) { if (!fragmentContent.value.trim()) {
ElMessage.warning('请输入学习内容!'); ElMessage.warning('请输入学习内容!');
return false; return false;
@@ -43,6 +45,7 @@ export const useStudyFragment = () => {
} }
try { try {
creatingFragment.value = true;
const res = await createFragments(sessionNum, fragmentContent.value); const res = await createFragments(sessionNum, fragmentContent.value);
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('学习残片生成成功!'); ElMessage.success('学习残片生成成功!');
@@ -55,12 +58,15 @@ export const useStudyFragment = () => {
} catch (error: any) { } catch (error: any) {
ElMessage.error(error.message || '请求失败'); ElMessage.error(error.message || '请求失败');
return false; return false;
} finally {
creatingFragment.value = false;
} }
}; };
return { return {
fragmentsDialogVisible, fragmentsDialogVisible,
fragmentContent, fragmentContent,
creatingFragment,
openFragmentDialog, openFragmentDialog,
closeFragmentDialog, closeFragmentDialog,
confirmGenerateFragment, confirmGenerateFragment,