diff --git a/src/__tests__/api/reportFragments.spec.ts b/src/__tests__/api/reportFragments.spec.ts index 5a928c4..77428cb 100644 --- a/src/__tests__/api/reportFragments.spec.ts +++ b/src/__tests__/api/reportFragments.spec.ts @@ -22,10 +22,14 @@ describe('reportFragments API', () => { it('createFragments sends POST with sessionNum and content', async () => { mockedRequest.post.mockResolvedValue({ code: 200, data: { id: 1 } }) const result = await createFragments('S001', '学习了递归') - expect(mockedRequest.post).toHaveBeenCalledWith('/report-fragments', { - sessionNum: 'S001', - content: '学习了递归', - }) + expect(mockedRequest.post).toHaveBeenCalledWith( + '/report-fragments', + { + sessionNum: 'S001', + content: '学习了递归', + }, + { timeout: 300000 }, + ) expect(result).toEqual({ code: 200, data: { id: 1 } }) }) }) diff --git a/src/__tests__/api/studySessions.spec.ts b/src/__tests__/api/studySessions.spec.ts index 7a92e8e..9b501e9 100644 --- a/src/__tests__/api/studySessions.spec.ts +++ b/src/__tests__/api/studySessions.spec.ts @@ -58,7 +58,8 @@ describe('studySessions API', () => { await endSession('S001') expect(mockedRequest.post).toHaveBeenCalledWith( '/study-sessions/S001/study-sessions/ended', - { content: '任务结束' } + { content: '任务结束' }, + { timeout: 300000 } ) }) @@ -67,7 +68,8 @@ describe('studySessions API', () => { await endSession('S001', '自定义结束') expect(mockedRequest.post).toHaveBeenCalledWith( '/study-sessions/S001/study-sessions/ended', - { content: '自定义结束' } + { content: '自定义结束' }, + { timeout: 300000 } ) }) it('abortSession sends POST with confirmation phrase', async () => { diff --git a/src/api/reportFragments.ts b/src/api/reportFragments.ts index 66f416d..2b54e64 100644 --- a/src/api/reportFragments.ts +++ b/src/api/reportFragments.ts @@ -1,14 +1,17 @@ // src/api/reportFragments.ts import request from "@/utils/request"; +// 残片生成走 AI 聚合,耗时较长,放宽到 5 分钟(默认全局 30s) +const AI_TIMEOUT = { timeout: 300_000 }; + export const createFragments = (sessionNum: string, content: string) => { - return request.post(`/report-fragments`, {sessionNum, content}) + return request.post(`/report-fragments`, { sessionNum, content }, AI_TIMEOUT) } export const updateFragments = (id: number, content: string) => { - return request.put(`/report-fragments/${id}`, {content}) + return request.put(`/report-fragments/${id}`, { content }) } export const getFragmentsBySession = (sessionNum: string) => { return request.get(`/report-fragments/session/${sessionNum}`) -} \ No newline at end of file +} diff --git a/src/api/review.ts b/src/api/review.ts index a82bca9..b97adb0 100644 --- a/src/api/review.ts +++ b/src/api/review.ts @@ -24,15 +24,15 @@ export interface ReviewTaskStats { } export const getReviewFeed = (limit: number = 30, mode: "recent" | "random" | "smart" = "recent") => { - return request.get("/review/feed", { limit, mode }); + return request.get("/review/feed", { limit, mode }); }; export const getReviewTaskStats = () => { - return request.get("/review/tasks"); + return request.get("/review/tasks"); }; export const getReviewTaskStatsByTask = (taskNum: string) => { - return request.get(`/review/tasks/${taskNum}`); + return request.get(`/review/tasks/${taskNum}`); }; export const getReportDetail = (id: number) => { diff --git a/src/api/standardMindMap.ts b/src/api/standardMindMap.ts index 4a4263a..6d36632 100644 --- a/src/api/standardMindMap.ts +++ b/src/api/standardMindMap.ts @@ -1,4 +1,5 @@ import request from "@/utils/request"; +import type { ApiResponse } from "@/utils/request"; // ============ 标准思维导图 ============ @@ -31,14 +32,17 @@ export interface RecallRecord { createdTime: string; } -/** 获取/自动生成标准思维导图 */ +// AI 聚合类接口耗时较长,统一放宽到 5 分钟(默认全局 30s) +const AI_TIMEOUT = { timeout: 300_000 }; + +/** 获取/自动生成标准思维导图(首次访问会触发 AI 生成) */ export const getStandardMindMap = (taskNum: string) => { - return request.get(`/review/standard-mind-map/${taskNum}`); + return request.get(`/review/standard-mind-map/${taskNum}`, {}, AI_TIMEOUT); }; /** 强制重新生成标准思维导图(全量或增量) */ export const regenerateStandardMindMap = (taskNum: string, mode: "full" | "incremental" = "full") => { - return request.post(`/review/standard-mind-map/${taskNum}/regenerate?mode=${mode}`); + return request.post(`/review/standard-mind-map/${taskNum}/regenerate?mode=${mode}`, null, AI_TIMEOUT); }; /** 用户编辑标准思维导图(大纲文本) */ @@ -47,18 +51,22 @@ export const updateStandardMindMap = (taskNum: string, outline: string) => { }; /** 用户提交回忆大纲,与标准导图对比(可选指定起始节点路径) */ -export const recallCompare = (taskNum: string, recallOutline: string, focusPath?: string) => { +export const recallCompare = ( + taskNum: string, + recallOutline: string, + focusPath?: string, +): Promise> => { const body: Record = { recallOutline }; if (focusPath) body.focusPath = focusPath; - return request.post(`/review/standard-mind-map/${taskNum}/recall`, body); + return request.post(`/review/standard-mind-map/${taskNum}/recall`, body, AI_TIMEOUT); }; /** 在标准导图中查找与内容最匹配的节点 */ export const findNode = (taskNum: string, content: string) => { - return request.post(`/review/standard-mind-map/${taskNum}/find-node`, { content }); + return request.post<{ path: string }>(`/review/standard-mind-map/${taskNum}/find-node`, { content }); }; /** 获取该任务的所有回忆对比记录 */ export const listRecallRecords = (taskNum: string) => { - return request.get(`/review/standard-mind-map/${taskNum}/recall-records`); + return request.get(`/review/standard-mind-map/${taskNum}/recall-records`); }; diff --git a/src/api/studySessions.ts b/src/api/studySessions.ts index dfddbb6..b88df07 100644 --- a/src/api/studySessions.ts +++ b/src/api/studySessions.ts @@ -1,6 +1,40 @@ -// src/api/studySession.ts +// src/api/studySessions.ts import request from "@/utils/request"; +/** 学习会话详情(start-or-continue / detail 接口返回的会话核心字段) */ +export interface SessionDetail { + sessionNum: string; + sessionState: "ONGOING" | "PAUSED" | "ENDED"; + taskNum: string; + taskId: number; + taskName?: string; + materialUrl?: string; + startTime?: string; + endTime?: string; + lastStartTime?: string; + actualTime?: number; + effectiveTime?: number; + effectivenessRatio?: number | string; + pointerPosition?: number; + systemMessage?: string; +} + +/** 当前活跃会话摘要(跨页面恢复用) */ +export interface ActiveSessionInfo { + taskNum: string; + sessionNum: string; + taskName?: string; +} + +/** 后端 MyBatis-Plus 分页包装 */ +export interface PagedResult { + records: T[]; + total: number; +} + +// AI 聚合类接口耗时较长,统一放宽到 5 分钟(默认全局 30s) +const AI_TIMEOUT = { timeout: 300_000 }; + export const continueSession = (sessionNum: string) => { return request.post(`/study-sessions/${sessionNum}/study-sessions/continue`); }; @@ -10,8 +44,9 @@ export const pauseSession = (sessionNum: string, endTime?: Date) => { return request.post(`/study-sessions/${sessionNum}/study-sessions/pause`, null, { params }); }; +/** 结束会话会触发后端报告生成,耗时较长 */ export const endSession = (sessionNum: string, content: string = "任务结束") => { - return request.post(`/study-sessions/${sessionNum}/study-sessions/ended`, { content }); + return request.post(`/study-sessions/${sessionNum}/study-sessions/ended`, { content }, AI_TIMEOUT); }; /** 误操作结束学习会话:需输入确认语,严格零数据关闭(删除会话与预期) */ @@ -20,16 +55,16 @@ export const abortSession = (sessionNum: string, confirmation: string) => { }; export const startOrContinueStudySession = (taskNum: string) => { - return request.get(`/tasks/${taskNum}/study-sessions/start-or-continue`); + return request.get(`/tasks/${taskNum}/study-sessions/start-or-continue`); }; export const getSessionDetail = (sessionNum: string) => { - return request.get(`/study-sessions/${sessionNum}`); + return request.get(`/study-sessions/${sessionNum}`); }; /** 获取会话的学习预期 */ export const getExpectation = (sessionNum: string) => { - return request.get(`/study-sessions/${sessionNum}/expectation`); + return request.get<{ description: string } | null>(`/study-sessions/${sessionNum}/expectation`); }; /** 创建/更新会话的学习预期 */ @@ -37,28 +72,28 @@ export const upsertExpectation = (sessionNum: string, description: string) => { return request.put(`/study-sessions/${sessionNum}/expectation`, { description }); }; -/** 获取报告草稿(AI 聚合残片,不可用时为拼接) */ +/** 获取报告草稿(AI 聚合残片,不可用时为拼接),生成失败不阻塞手写总结 */ export const getReportDraft = (sessionNum: string) => { - return request.get(`/study-sessions/${sessionNum}/report-draft`); + return request.get(`/study-sessions/${sessionNum}/report-draft`, {}, AI_TIMEOUT); }; /** 查询当前是否有活跃会话 */ export const getActiveSession = (excludeTaskNum?: string) => { const params: Record = {}; if (excludeTaskNum) params.excludeTaskNum = excludeTaskNum; - return request.get('/study-sessions/active', params); + 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 = { page, size }; if (keyword) params.keyword = keyword; - return request.get(`/study-sessions/tasks/${taskNum}/fragments`, params); + return request.get>(`/study-sessions/tasks/${taskNum}/fragments`, params); }; /** 分页查询任务的历史报告 */ -export const getTaskReports = (taskNum: string, page: number, size: number, keyword?: string) => { +export const getTaskReports = (taskNum: string, page: number, size: number, keyword?: string) => { const params: Record = { page, size }; if (keyword) params.keyword = keyword; - return request.get(`/study-sessions/tasks/${taskNum}/reports`, params); + return request.get>(`/study-sessions/tasks/${taskNum}/reports`, params); }; diff --git a/src/api/tasks.ts b/src/api/tasks.ts index f5dd0b1..d513dd1 100644 --- a/src/api/tasks.ts +++ b/src/api/tasks.ts @@ -12,7 +12,7 @@ export interface TaskApplication { } export const getTaskApplications = (taskNum: string) => { - return request.get(`/tasks/${taskNum}/applications`); + return request.get(`/tasks/${taskNum}/applications`); }; export const createTaskApplication = ( @@ -24,7 +24,7 @@ export const createTaskApplication = ( status?: TaskApplication["status"]; }, ) => { - return request.post(`/tasks/${taskNum}/applications`, payload); + return request.post(`/tasks/${taskNum}/applications`, payload); }; export const updateTaskApplication = ( @@ -36,7 +36,7 @@ export const updateTaskApplication = ( status?: TaskApplication["status"]; }, ) => { - return request.put(`/tasks/applications/${id}`, payload); + return request.put(`/tasks/applications/${id}`, payload); }; export const deleteTaskApplication = (id: number) => { @@ -54,10 +54,10 @@ export interface PriorityWeights { } export const getPriorityWeights = () => { - return request.get("/tasks/priority-weights"); + return request.get("/tasks/priority-weights"); }; /** 保存权重配置并触发全部任务优先级重算 */ export const savePriorityWeights = (weights: PriorityWeights) => { - return request.put("/tasks/priority-weights", weights); + return request.put("/tasks/priority-weights", weights); }; diff --git a/src/utils/request.ts b/src/utils/request.ts index 1c94872..3ba7d8e 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -2,11 +2,21 @@ import axios from 'axios'; import { ElMessage } from "element-plus"; import router from "@/router"; -const basic_url = import.meta.env.VITE_BASE_URL; +/** 后端统一响应包装:code === 200 表示业务成功 */ +export interface ApiResponse { + code: number; + message?: string; + data: T; +} + +const baseURL = import.meta.env.VITE_BASE_URL; + +// 默认 30s:普通 CRUD 足够;AI 聚合等长耗时接口需在 api 层显式覆写 timeout +const DEFAULT_TIMEOUT = 30_000; const axiosInstance = axios.create({ - baseURL: basic_url, - timeout: 600000, + baseURL, + timeout: DEFAULT_TIMEOUT, withCredentials: true }); @@ -51,43 +61,43 @@ const validateResponse = (res: any) => { return result; }; -function get(url: string, params: Record): Promise; -function get(url: string): Promise; - -function get(url: string, params: Record = {}) { - return axiosInstance.get(url, { params }) +const get = ( + url: string, + params: Record = {}, + config: Record = {}, +): Promise> => + axiosInstance.get(url, { params, ...config }) .then(validateResponse) .catch(handleError); -} -const post = (url: string, data: any = null, config: any = {}) => { - - if (config.params) { - // 如果传入 config.params,则作为 URL 参数 - return axiosInstance.post(url, data, { params: config.params, ...config }) - .then(validateResponse) - .catch(handleError); - } else { - // 默认 POST JSON - return axiosInstance.post(url, data, config) - .then(validateResponse) - .catch(handleError); - } -}; - -const put = (url: string, data: any, config: any = {}) => { - return axiosInstance.put(url, data, config) +const post = ( + url: string, + data: any = null, + config: Record = {}, +): Promise> => + axiosInstance.post(url, data, config) .then(validateResponse) .catch(handleError); -}; -const del = (url: string, params: Record = {}, config: any = {}) => { - return axiosInstance.delete(url, { params, ...config }) +const put = ( + url: string, + data: any, + config: Record = {}, +): Promise> => + axiosInstance.put(url, data, config) .then(validateResponse) .catch(handleError); -}; -const requestNotImplemented = (method: string) => { +const del = ( + url: string, + params: Record = {}, + config: Record = {}, +): Promise> => + axiosInstance.delete(url, { params, ...config }) + .then(validateResponse) + .catch(handleError); + +const requestNotImplemented = () => { ElMessage.error("该功能暂不可用,请刷新后重试"); throw new Error("该功能暂不可用,请刷新后重试"); }; @@ -98,7 +108,7 @@ const request = (method: string, url: string, paramsOrData: any) => { case 'post': return post(url, paramsOrData); case 'put': return put(url, paramsOrData); case 'delete': return del(url, paramsOrData); - default: return requestNotImplemented(method); + default: return requestNotImplemented(); } };