fix: 学习会话按钮补防抖加载
This commit is contained in:
@@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user