feat: 请求层泛型化并收紧默认超时
This commit is contained in:
@@ -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', {
|
||||
expect(mockedRequest.post).toHaveBeenCalledWith(
|
||||
'/report-fragments',
|
||||
{
|
||||
sessionNum: 'S001',
|
||||
content: '学习了递归',
|
||||
})
|
||||
},
|
||||
{ timeout: 300000 },
|
||||
)
|
||||
expect(result).toEqual({ code: 200, data: { id: 1 } })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// 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) => {
|
||||
|
||||
+3
-3
@@ -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<ReviewFeedItem[]>("/review/feed", { limit, mode });
|
||||
};
|
||||
|
||||
export const getReviewTaskStats = () => {
|
||||
return request.get("/review/tasks");
|
||||
return request.get<ReviewTaskStats[]>("/review/tasks");
|
||||
};
|
||||
|
||||
export const getReviewTaskStatsByTask = (taskNum: string) => {
|
||||
return request.get(`/review/tasks/${taskNum}`);
|
||||
return request.get<ReviewTaskStats>(`/review/tasks/${taskNum}`);
|
||||
};
|
||||
|
||||
export const getReportDetail = (id: number) => {
|
||||
|
||||
@@ -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<StandardMindMap>(`/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<StandardMindMap>(`/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<ApiResponse<StandardMindMap>> => {
|
||||
const body: Record<string, any> = { 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<RecallRecord[]>(`/review/standard-mind-map/${taskNum}/recall-records`);
|
||||
};
|
||||
|
||||
+47
-12
@@ -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<T> {
|
||||
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<SessionDetail>(`/tasks/${taskNum}/study-sessions/start-or-continue`);
|
||||
};
|
||||
|
||||
export const getSessionDetail = (sessionNum: string) => {
|
||||
return request.get(`/study-sessions/${sessionNum}`);
|
||||
return request.get<SessionDetail>(`/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<string>(`/study-sessions/${sessionNum}/report-draft`, {}, AI_TIMEOUT);
|
||||
};
|
||||
|
||||
/** 查询当前是否有活跃会话 */
|
||||
export const getActiveSession = (excludeTaskNum?: string) => {
|
||||
const params: Record<string, any> = {};
|
||||
if (excludeTaskNum) params.excludeTaskNum = excludeTaskNum;
|
||||
return request.get('/study-sessions/active', params);
|
||||
return request.get<ActiveSessionInfo | null>('/study-sessions/active', params);
|
||||
};
|
||||
|
||||
/** 分页查询任务的历史残片 */
|
||||
export const getTaskFragments = (taskNum: string, page: number, size: number, keyword?: string) => {
|
||||
export const getTaskFragments = <T = any>(taskNum: string, page: number, size: number, keyword?: string) => {
|
||||
const params: Record<string, any> = { page, size };
|
||||
if (keyword) params.keyword = keyword;
|
||||
return request.get(`/study-sessions/tasks/${taskNum}/fragments`, params);
|
||||
return request.get<PagedResult<T>>(`/study-sessions/tasks/${taskNum}/fragments`, params);
|
||||
};
|
||||
|
||||
/** 分页查询任务的历史报告 */
|
||||
export const getTaskReports = (taskNum: string, page: number, size: number, keyword?: string) => {
|
||||
export const getTaskReports = <T = any>(taskNum: string, page: number, size: number, keyword?: string) => {
|
||||
const params: Record<string, any> = { page, size };
|
||||
if (keyword) params.keyword = keyword;
|
||||
return request.get(`/study-sessions/tasks/${taskNum}/reports`, params);
|
||||
return request.get<PagedResult<T>>(`/study-sessions/tasks/${taskNum}/reports`, params);
|
||||
};
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ export interface TaskApplication {
|
||||
}
|
||||
|
||||
export const getTaskApplications = (taskNum: string) => {
|
||||
return request.get(`/tasks/${taskNum}/applications`);
|
||||
return request.get<TaskApplication[]>(`/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<TaskApplication>(`/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<TaskApplication>(`/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<PriorityWeights>("/tasks/priority-weights");
|
||||
};
|
||||
|
||||
/** 保存权重配置并触发全部任务优先级重算 */
|
||||
export const savePriorityWeights = (weights: PriorityWeights) => {
|
||||
return request.put("/tasks/priority-weights", weights);
|
||||
return request.put<PriorityWeights>("/tasks/priority-weights", weights);
|
||||
};
|
||||
|
||||
+40
-30
@@ -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<T = any> {
|
||||
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<string, any>): Promise<any>;
|
||||
function get(url: string): Promise<any>;
|
||||
|
||||
function get(url: string, params: Record<string, any> = {}) {
|
||||
return axiosInstance.get(url, { params })
|
||||
const get = <T = any>(
|
||||
url: string,
|
||||
params: Record<string, any> = {},
|
||||
config: Record<string, any> = {},
|
||||
): Promise<ApiResponse<T>> =>
|
||||
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 })
|
||||
const post = <T = any>(
|
||||
url: string,
|
||||
data: any = null,
|
||||
config: Record<string, any> = {},
|
||||
): Promise<ApiResponse<T>> =>
|
||||
axiosInstance.post(url, data, config)
|
||||
.then(validateResponse)
|
||||
.catch(handleError);
|
||||
} else {
|
||||
// 默认 POST JSON
|
||||
return axiosInstance.post(url, data, config)
|
||||
|
||||
const put = <T = any>(
|
||||
url: string,
|
||||
data: any,
|
||||
config: Record<string, any> = {},
|
||||
): Promise<ApiResponse<T>> =>
|
||||
axiosInstance.put(url, data, config)
|
||||
.then(validateResponse)
|
||||
.catch(handleError);
|
||||
}
|
||||
};
|
||||
|
||||
const put = (url: string, data: any, config: any = {}) => {
|
||||
return axiosInstance.put(url, data, config)
|
||||
const del = <T = any>(
|
||||
url: string,
|
||||
params: Record<string, any> = {},
|
||||
config: Record<string, any> = {},
|
||||
): Promise<ApiResponse<T>> =>
|
||||
axiosInstance.delete(url, { params, ...config })
|
||||
.then(validateResponse)
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const del = (url: string, params: Record<string, any> = {}, config: any = {}) => {
|
||||
return axiosInstance.delete(url, { params, ...config })
|
||||
.then(validateResponse)
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const requestNotImplemented = (method: string) => {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user