feat(会话):跨页面自动恢复 + 阻止多任务同时进行
后端: - StudySessionsServiceImpl.getActiveSession(excludeTaskNum): 查询 ONGOING/PAUSED 状态的活跃会话,可排除指定任务 - Controller 新增 GET /study-sessions/active?excludeTaskNum= 前端: - router beforeEach 守卫:读取 localStorage 中 activeSession, 非 start-task 页面时自动重定向恢复 - Study.vue 开始任务按钮:调用 getActiveSession() 检测是否 有其他任务正在进行,提示用户前往继续 - StartTask.vue loadTaskSession 成功后将活跃会话写入 localStorage(taskNum/sessionNum/taskName) - endSession 成功时清除 localStorage 中的 activeSession - 路由参数 ?resume=true 时页面顶部显示恢复提示已恢复上次
This commit is contained in:
@@ -37,6 +37,13 @@ export const getReportDraft = (sessionNum: string) => {
|
|||||||
return request.get(`/study-sessions/${sessionNum}/report-draft`);
|
return request.get(`/study-sessions/${sessionNum}/report-draft`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 查询当前是否有活跃会话 */
|
||||||
|
export const getActiveSession = (excludeTaskNum?: string) => {
|
||||||
|
const params: Record<string, any> = {};
|
||||||
|
if (excludeTaskNum) params.excludeTaskNum = excludeTaskNum;
|
||||||
|
return request.get('/study-sessions/active', params);
|
||||||
|
};
|
||||||
|
|
||||||
/** 分页查询任务的历史残片 */
|
/** 分页查询任务的历史残片 */
|
||||||
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
|
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
|
||||||
const params: Record<string, any> = { page, size };
|
const params: Record<string, any> = { page, size };
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ const BREAK_END_KEY = computed(() => `breakEnd_${taskNum}`);
|
|||||||
const isBreak = ref(false);
|
const isBreak = ref(false);
|
||||||
const breakAudio = new Audio('/resource/notification.mp3');
|
const breakAudio = new Audio('/resource/notification.mp3');
|
||||||
|
|
||||||
|
// 恢复提示
|
||||||
|
const showResumeHint = ref(route.query.resume === "true");
|
||||||
|
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
const total = isBreak.value ? 5 * 60 : 25 * 60;
|
const total = isBreak.value ? 5 * 60 : 25 * 60;
|
||||||
const remaining = timerMinutes.value * 60 + timerSeconds.value;
|
const remaining = timerMinutes.value * 60 + timerSeconds.value;
|
||||||
@@ -303,6 +306,7 @@ const endTimer = async (content: string) => {
|
|||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
clearBreakState();
|
clearBreakState();
|
||||||
|
localStorage.removeItem("activeSession");
|
||||||
await router.push("/study");
|
await router.push("/study");
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error("结束会话失败,请重试");
|
ElMessage.error("结束会话失败,请重试");
|
||||||
@@ -389,6 +393,13 @@ const loadTaskSession = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 持久化活跃会话(用于跨页面恢复 + 阻止多任务)
|
||||||
|
localStorage.setItem("activeSession", JSON.stringify({
|
||||||
|
taskNum: taskInfo.value.taskNum,
|
||||||
|
sessionNum: taskInfo.value.sessionNum,
|
||||||
|
taskName: taskInfo.value.taskName,
|
||||||
|
}));
|
||||||
|
|
||||||
if (taskInfo.value.sessionState === "ONGOING") {
|
if (taskInfo.value.sessionState === "ONGOING") {
|
||||||
startTimer();
|
startTimer();
|
||||||
} else {
|
} else {
|
||||||
@@ -516,6 +527,14 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<section class="start-page">
|
<section class="start-page">
|
||||||
|
<el-alert
|
||||||
|
v-if="showResumeHint"
|
||||||
|
title="已恢复上次的学习会话,继续学习吧"
|
||||||
|
type="success"
|
||||||
|
show-icon
|
||||||
|
:closable="true"
|
||||||
|
@close="showResumeHint = false"
|
||||||
|
/>
|
||||||
<div class="task-info-bar">
|
<div class="task-info-bar">
|
||||||
<el-tag type="success" effect="plain">任务编号:{{ taskInfo.taskNum }}</el-tag>
|
<el-tag type="success" effect="plain">任务编号:{{ taskInfo.taskNum }}</el-tag>
|
||||||
<span class="status-text">状态:{{ statusText }}</span>
|
<span class="status-text">状态:{{ statusText }}</span>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import request from "@/utils/request";
|
|||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { getReviewTaskStatsByTask } from "@/api/review";
|
import { getReviewTaskStatsByTask } from "@/api/review";
|
||||||
|
import { getActiveSession } from "@/api/studySessions";
|
||||||
import {
|
import {
|
||||||
getPriorityWeights,
|
getPriorityWeights,
|
||||||
getTaskApplications,
|
getTaskApplications,
|
||||||
@@ -174,12 +175,30 @@ watch(selectedTaskId, (newId) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const startTask = () => {
|
const startTask = async () => {
|
||||||
if (!selectedTask.value) {
|
if (!selectedTask.value) {
|
||||||
ElMessage.warning("请先选择一个任务");
|
ElMessage.warning("请先选择一个任务");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/start-task/${encodeURIComponent(selectedTask.value.taskNum)}`);
|
const taskNum = selectedTask.value.taskNum;
|
||||||
|
try {
|
||||||
|
const res = await getActiveSession(taskNum);
|
||||||
|
if (res?.code === 200 && res.data) {
|
||||||
|
// 有其他任务的活跃会话
|
||||||
|
const active = res.data;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`你还有一个未完成的学习任务「${active.taskName}」(${active.sessionNum}),是否前往继续?`,
|
||||||
|
"有其他任务正在进行",
|
||||||
|
{ confirmButtonText: "前往继续", cancelButtonText: "取消", type: "warning" }
|
||||||
|
).then(() => {
|
||||||
|
router.push(`/start-task/${encodeURIComponent(active.taskNum)}`);
|
||||||
|
}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 查询失败不阻塞,直接跳转
|
||||||
|
}
|
||||||
|
router.push(`/start-task/${encodeURIComponent(taskNum)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTask = () => {
|
const addTask = () => {
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ router.beforeEach((to) => {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return "/login";
|
return "/login";
|
||||||
}
|
}
|
||||||
|
// 如果有活跃会话且未处于 start-task 页面,自动恢复
|
||||||
|
if (!to.path.startsWith("/start-task")) {
|
||||||
|
const stored = localStorage.getItem("activeSession");
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
const { taskNum } = JSON.parse(stored);
|
||||||
|
if (taskNum) {
|
||||||
|
return `/start-task/${encodeURIComponent(taskNum)}?resume=true`;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user