refactor: StartTask 抽取功能域组合式函数
This commit is contained in:
+43
-151
@@ -15,10 +15,12 @@ import {
|
||||
} from "@/api/studySessions";
|
||||
import { useStudyFragment } from "@/components/composables/fragment";
|
||||
import { getFragmentsBySession, updateFragments } from "@/api/reportFragments";
|
||||
import { getExpectation, getReportDraft, getTaskFragments, getTaskReports, upsertExpectation } from "@/api/studySessions";
|
||||
import router from "@/router";
|
||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
||||
import { renderMarkdown } from "@/utils/markdown";
|
||||
import { useSessionHistory } from "@/components/composables/useSessionHistory";
|
||||
import { useSummaryReport } from "@/components/composables/useSummaryReport";
|
||||
import { useSessionExpectation } from "@/components/composables/useSessionExpectation";
|
||||
|
||||
const {
|
||||
fragmentsDialogVisible,
|
||||
@@ -41,18 +43,29 @@ const canAbortSession = computed(
|
||||
() => taskInfo.value.sessionState !== "ENDED" && fragmentsList.value.length === 0,
|
||||
);
|
||||
|
||||
const summaryDialogVisible = ref(false);
|
||||
const summaryContent = ref("");
|
||||
const summaryPreview = ref(false);
|
||||
const summaryLoading = ref(false);
|
||||
const summaryLoadingSeconds = ref(0);
|
||||
let summaryLoadingTimer: ReturnType<typeof setInterval> | null = null;
|
||||
// 结束会话总结弹窗(有残片时先取 AI 草稿,等待期间展示已等待秒数)
|
||||
const {
|
||||
summaryDialogVisible,
|
||||
summaryContent,
|
||||
summaryPreview,
|
||||
summaryLoading,
|
||||
summaryLoadingSeconds,
|
||||
openSummaryDialog,
|
||||
closeSummaryDialog,
|
||||
} = useSummaryReport({
|
||||
getSessionNum: () => taskInfo.value.sessionNum,
|
||||
hasFragments: () => fragmentsList.value.length > 0,
|
||||
});
|
||||
|
||||
// 学习预期
|
||||
const expectationDialogVisible = ref(false);
|
||||
const expectationContent = ref("");
|
||||
const expectationSaved = ref("");
|
||||
const savingExpectation = ref(false);
|
||||
// 学习预期:会话必须有预期才能开始计时
|
||||
const {
|
||||
expectationDialogVisible,
|
||||
expectationContent,
|
||||
expectationSaved,
|
||||
savingExpectation,
|
||||
loadExpectation,
|
||||
saveExpectation,
|
||||
} = useSessionExpectation(() => taskInfo.value.sessionNum);
|
||||
|
||||
// 当前会话碎片列表
|
||||
interface FragmentItem {
|
||||
@@ -64,88 +77,27 @@ const editingFragmentId = ref<number | null>(null);
|
||||
const editFragmentContent = ref("");
|
||||
const savingFragment = ref(false);
|
||||
|
||||
// 历史学习记录
|
||||
const showHistory = ref(false);
|
||||
const historyTab = ref("fragments");
|
||||
const historyKeyword = ref("");
|
||||
let historyDebounce: ReturnType<typeof setTimeout> | null = null;
|
||||
const loadingHistory = ref(false);
|
||||
const historyFragments = ref<any[]>([]);
|
||||
const fragmentsTotal = ref(0);
|
||||
const fragmentsPage = ref(1);
|
||||
const historyReports = ref<any[]>([]);
|
||||
const reportsTotal = ref(0);
|
||||
const reportsPage = ref(1);
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
watch(showHistory, (val) => {
|
||||
if (val) {
|
||||
// 展开时立即加载当前 tab 的数据
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
}
|
||||
});
|
||||
|
||||
const loadHistoryFragments = async () => {
|
||||
loadingHistory.value = true;
|
||||
try {
|
||||
const res = await getTaskFragments(taskNum, fragmentsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
|
||||
const data = res?.data;
|
||||
historyFragments.value = data?.records || [];
|
||||
fragmentsTotal.value = data?.total || 0;
|
||||
} catch {
|
||||
historyFragments.value = [];
|
||||
fragmentsTotal.value = 0;
|
||||
} finally {
|
||||
loadingHistory.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadHistoryReports = async () => {
|
||||
loadingHistory.value = true;
|
||||
try {
|
||||
const res = await getTaskReports(taskNum, reportsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
|
||||
const data = res?.data;
|
||||
historyReports.value = data?.records || [];
|
||||
reportsTotal.value = data?.total || 0;
|
||||
} catch {
|
||||
historyReports.value = [];
|
||||
reportsTotal.value = 0;
|
||||
} finally {
|
||||
loadingHistory.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const searchHistory = () => {
|
||||
if (historyDebounce) clearTimeout(historyDebounce);
|
||||
historyDebounce = setTimeout(() => {
|
||||
fragmentsPage.value = 1;
|
||||
reportsPage.value = 1;
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const onHistoryTabChange = () => {
|
||||
fragmentsPage.value = 1;
|
||||
reportsPage.value = 1;
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
};
|
||||
|
||||
const onFragmentsPageChange = (p: number) => {
|
||||
fragmentsPage.value = p;
|
||||
loadHistoryFragments();
|
||||
};
|
||||
|
||||
const onReportsPageChange = (p: number) => {
|
||||
reportsPage.value = p;
|
||||
loadHistoryReports();
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const taskNum = route.params.taskNum as string;
|
||||
|
||||
// 历史学习记录面板(分页查询、搜索防抖、tab 切换)
|
||||
const {
|
||||
showHistory,
|
||||
historyTab,
|
||||
historyKeyword,
|
||||
loadingHistory,
|
||||
historyFragments,
|
||||
fragmentsTotal,
|
||||
fragmentsPage,
|
||||
historyReports,
|
||||
reportsTotal,
|
||||
reportsPage,
|
||||
searchHistory,
|
||||
onHistoryTabChange,
|
||||
onFragmentsPageChange,
|
||||
onReportsPageChange,
|
||||
} = useSessionHistory(taskNum);
|
||||
|
||||
const taskInfo = ref({
|
||||
sessionNum: "",
|
||||
sessionState: "--",
|
||||
@@ -416,31 +368,6 @@ const endTimer = async (content: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const openSummaryDialog = async () => {
|
||||
summaryDialogVisible.value = true;
|
||||
// 有残片且未填写过总结时,用 AI 聚合草稿作为编辑起点
|
||||
if (!summaryContent.value.trim() && fragmentsList.value.length > 0) {
|
||||
summaryLoading.value = true;
|
||||
summaryLoadingSeconds.value = 0;
|
||||
summaryLoadingTimer = setInterval(() => { summaryLoadingSeconds.value++; }, 1000);
|
||||
try {
|
||||
const res = await getReportDraft(taskInfo.value.sessionNum);
|
||||
if (res?.code === 200 && res.data) {
|
||||
summaryContent.value = res.data;
|
||||
}
|
||||
} catch {
|
||||
// 草稿失败不阻塞手写
|
||||
} finally {
|
||||
summaryLoading.value = false;
|
||||
if (summaryLoadingTimer) { clearInterval(summaryLoadingTimer); summaryLoadingTimer = null; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeSummaryDialog = () => {
|
||||
summaryDialogVisible.value = false;
|
||||
if (summaryLoadingTimer) { clearInterval(summaryLoadingTimer); summaryLoadingTimer = null; }
|
||||
};
|
||||
const openAbortDialog = () => {
|
||||
abortConfirmation.value = "";
|
||||
abortDialogVisible.value = true;
|
||||
@@ -570,41 +497,6 @@ const loadTaskSession = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 学习预期:会话必须有预期才能开始计时
|
||||
const loadExpectation = async () => {
|
||||
if (!taskInfo.value.sessionNum) return;
|
||||
try {
|
||||
const res = await getExpectation(taskInfo.value.sessionNum);
|
||||
expectationSaved.value = res?.data?.description || "";
|
||||
} catch {
|
||||
expectationSaved.value = "";
|
||||
}
|
||||
if (!expectationSaved.value) {
|
||||
expectationDialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const saveExpectation = async () => {
|
||||
if (!expectationContent.value.trim()) {
|
||||
ElMessage.warning("学习预期不可为空");
|
||||
return;
|
||||
}
|
||||
savingExpectation.value = true;
|
||||
try {
|
||||
const res = await upsertExpectation(taskInfo.value.sessionNum, expectationContent.value);
|
||||
if (res?.code === 200) {
|
||||
expectationSaved.value = expectationContent.value;
|
||||
expectationDialogVisible.value = false;
|
||||
} else {
|
||||
ElMessage.error(res?.message || "保存失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || "请求失败");
|
||||
} finally {
|
||||
savingExpectation.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const initPage = async () => {
|
||||
try {
|
||||
const hasPermission = await checkAudioPermission();
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getExpectation, upsertExpectation } from '@/api/studySessions';
|
||||
|
||||
/**
|
||||
* 学习会话的学习预期:会话必须有预期才能开始计时。
|
||||
* - loadExpectation:读取已保存预期,无预期时自动弹出填写弹窗
|
||||
* - saveExpectation:保存预期并关闭弹窗
|
||||
*/
|
||||
export function useSessionExpectation(getSessionNum: () => string) {
|
||||
const expectationDialogVisible = ref(false);
|
||||
const expectationContent = ref("");
|
||||
const expectationSaved = ref("");
|
||||
const savingExpectation = ref(false);
|
||||
|
||||
const loadExpectation = async () => {
|
||||
if (!getSessionNum()) return;
|
||||
try {
|
||||
const res = await getExpectation(getSessionNum());
|
||||
expectationSaved.value = res?.data?.description || "";
|
||||
} catch {
|
||||
expectationSaved.value = "";
|
||||
}
|
||||
if (!expectationSaved.value) {
|
||||
expectationDialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const saveExpectation = async () => {
|
||||
if (!expectationContent.value.trim()) {
|
||||
ElMessage.warning("学习预期不可为空");
|
||||
return;
|
||||
}
|
||||
savingExpectation.value = true;
|
||||
try {
|
||||
const res = await upsertExpectation(getSessionNum(), expectationContent.value);
|
||||
if (res?.code === 200) {
|
||||
expectationSaved.value = expectationContent.value;
|
||||
expectationDialogVisible.value = false;
|
||||
} else {
|
||||
ElMessage.error(res?.message || "保存失败");
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || "请求失败");
|
||||
} finally {
|
||||
savingExpectation.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
expectationDialogVisible,
|
||||
expectationContent,
|
||||
expectationSaved,
|
||||
savingExpectation,
|
||||
loadExpectation,
|
||||
saveExpectation,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { ref, watch } from 'vue';
|
||||
import { getTaskFragments, getTaskReports } from '@/api/studySessions';
|
||||
|
||||
/** 历史残片 / 历史报告共用的列表项形状(模板只消费这些字段) */
|
||||
export interface SessionHistoryRecord {
|
||||
id: number;
|
||||
sessionNum: string;
|
||||
content: string;
|
||||
sessionExpectation?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学习会话页的“历史学习记录”面板:按任务分页查询历史残片与报告,
|
||||
* 支持关键字搜索(300ms 防抖)与 tab 切换。
|
||||
*/
|
||||
export function useSessionHistory(taskNum: string) {
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const showHistory = ref(false);
|
||||
const historyTab = ref("fragments");
|
||||
const historyKeyword = ref("");
|
||||
let historyDebounce: ReturnType<typeof setTimeout> | null = null;
|
||||
const loadingHistory = ref(false);
|
||||
const historyFragments = ref<SessionHistoryRecord[]>([]);
|
||||
const fragmentsTotal = ref(0);
|
||||
const fragmentsPage = ref(1);
|
||||
const historyReports = ref<SessionHistoryRecord[]>([]);
|
||||
const reportsTotal = ref(0);
|
||||
const reportsPage = ref(1);
|
||||
|
||||
watch(showHistory, (val) => {
|
||||
if (val) {
|
||||
// 展开时立即加载当前 tab 的数据
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
}
|
||||
});
|
||||
|
||||
const loadHistoryFragments = async () => {
|
||||
loadingHistory.value = true;
|
||||
try {
|
||||
const res = await getTaskFragments(taskNum, fragmentsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
|
||||
const data = res?.data;
|
||||
historyFragments.value = data?.records || [];
|
||||
fragmentsTotal.value = data?.total || 0;
|
||||
} catch {
|
||||
historyFragments.value = [];
|
||||
fragmentsTotal.value = 0;
|
||||
} finally {
|
||||
loadingHistory.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadHistoryReports = async () => {
|
||||
loadingHistory.value = true;
|
||||
try {
|
||||
const res = await getTaskReports(taskNum, reportsPage.value, PAGE_SIZE, historyKeyword.value || undefined);
|
||||
const data = res?.data;
|
||||
historyReports.value = data?.records || [];
|
||||
reportsTotal.value = data?.total || 0;
|
||||
} catch {
|
||||
historyReports.value = [];
|
||||
reportsTotal.value = 0;
|
||||
} finally {
|
||||
loadingHistory.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const searchHistory = () => {
|
||||
if (historyDebounce) clearTimeout(historyDebounce);
|
||||
historyDebounce = setTimeout(() => {
|
||||
fragmentsPage.value = 1;
|
||||
reportsPage.value = 1;
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const onHistoryTabChange = () => {
|
||||
fragmentsPage.value = 1;
|
||||
reportsPage.value = 1;
|
||||
if (historyTab.value === "fragments") loadHistoryFragments();
|
||||
else loadHistoryReports();
|
||||
};
|
||||
|
||||
const onFragmentsPageChange = (p: number) => {
|
||||
fragmentsPage.value = p;
|
||||
loadHistoryFragments();
|
||||
};
|
||||
|
||||
const onReportsPageChange = (p: number) => {
|
||||
reportsPage.value = p;
|
||||
loadHistoryReports();
|
||||
};
|
||||
|
||||
return {
|
||||
PAGE_SIZE,
|
||||
showHistory,
|
||||
historyTab,
|
||||
historyKeyword,
|
||||
loadingHistory,
|
||||
historyFragments,
|
||||
fragmentsTotal,
|
||||
fragmentsPage,
|
||||
historyReports,
|
||||
reportsTotal,
|
||||
reportsPage,
|
||||
searchHistory,
|
||||
onHistoryTabChange,
|
||||
onFragmentsPageChange,
|
||||
onReportsPageChange,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ref } from 'vue';
|
||||
import { getReportDraft } from '@/api/studySessions';
|
||||
import { useElapsedSeconds } from './useElapsedSeconds';
|
||||
|
||||
/**
|
||||
* “结束会话总结”弹窗:编辑/预览总结内容。
|
||||
* 有学习残片且尚未填写内容时,先取 AI 聚合草稿作为编辑起点(失败不阻塞手写),
|
||||
* 等待期间展示“已等待 N 秒”提示。
|
||||
*/
|
||||
export function useSummaryReport(options: {
|
||||
getSessionNum: () => string;
|
||||
hasFragments: () => boolean;
|
||||
}) {
|
||||
const summaryDialogVisible = ref(false);
|
||||
const summaryContent = ref("");
|
||||
const summaryPreview = ref(false);
|
||||
const summaryLoading = ref(false);
|
||||
const { seconds: summaryLoadingSeconds, start: startLoadingTimer, stop: stopLoadingTimer } = useElapsedSeconds();
|
||||
|
||||
const openSummaryDialog = async () => {
|
||||
summaryDialogVisible.value = true;
|
||||
// 有残片且未填写过总结时,用 AI 聚合草稿作为编辑起点
|
||||
if (!summaryContent.value.trim() && options.hasFragments()) {
|
||||
summaryLoading.value = true;
|
||||
startLoadingTimer();
|
||||
try {
|
||||
const res = await getReportDraft(options.getSessionNum());
|
||||
if (res?.code === 200 && res.data) {
|
||||
summaryContent.value = res.data;
|
||||
}
|
||||
} catch {
|
||||
// 草稿失败不阻塞手写
|
||||
} finally {
|
||||
summaryLoading.value = false;
|
||||
stopLoadingTimer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeSummaryDialog = () => {
|
||||
summaryDialogVisible.value = false;
|
||||
stopLoadingTimer();
|
||||
};
|
||||
|
||||
return {
|
||||
summaryDialogVisible,
|
||||
summaryContent,
|
||||
summaryPreview,
|
||||
summaryLoading,
|
||||
summaryLoadingSeconds,
|
||||
openSummaryDialog,
|
||||
closeSummaryDialog,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user