feat: 完成整体内容开发
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
// app/stores/answer.ts - 答题页和 Dashboard 的统一状态管理
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
import type {
|
||||
IHealthResponse,
|
||||
IQaRecord,
|
||||
ISearchSuccessResponse,
|
||||
IStatsResponse,
|
||||
QuestionType
|
||||
} from "@/interfaces";
|
||||
import { AnswerService } from "@/services";
|
||||
|
||||
/** Dashboard 最近记录表格每页数量,组件直接复用,避免多个地方写死 */
|
||||
export const DASHBOARD_RECORD_PAGE_SIZE = 10;
|
||||
|
||||
/** 题型下拉选项,value 保持 OCS / 旧 Python 服务使用的英文值 */
|
||||
export const QUESTION_TYPE_OPTIONS: Array<{
|
||||
label: string;
|
||||
value: QuestionType;
|
||||
}> = [
|
||||
{ label: "自动判断", value: "" },
|
||||
{ label: "单选题", value: "single" },
|
||||
{ label: "多选题", value: "multiple" },
|
||||
{ label: "判断题", value: "judgement" },
|
||||
{ label: "填空题", value: "completion" }
|
||||
];
|
||||
|
||||
/** 题型展示文案,Dashboard 表格用它把英文值转成本地业务表达 */
|
||||
export const QUESTION_TYPE_LABELS: Record<string, string> = {
|
||||
"": "自动判断",
|
||||
single: "单选题",
|
||||
multiple: "多选题",
|
||||
judgement: "判断题",
|
||||
completion: "填空题"
|
||||
};
|
||||
|
||||
/** 从 `$fetch` 错误里提取安全文案;服务端已经保证 message/msg 不含敏感上游细节 */
|
||||
const getClientErrorMessage = (error: unknown, fallback: string) => {
|
||||
if (typeof error !== "object" || error === null) return fallback;
|
||||
|
||||
const data = "data" in error ? (error as { data?: unknown }).data : undefined;
|
||||
if (typeof data === "object" && data !== null) {
|
||||
const message = (data as { message?: unknown; msg?: unknown }).message;
|
||||
const msg = (data as { message?: unknown; msg?: unknown }).msg;
|
||||
|
||||
if (typeof message === "string" && message) return message;
|
||||
if (typeof msg === "string" && msg) return msg;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
};
|
||||
|
||||
/** 把运行秒数格式化为 dashboard 更容易扫读的时长 */
|
||||
const formatUptime = (seconds: number) => {
|
||||
const safeSeconds = Math.max(0, Math.floor(seconds));
|
||||
const days = Math.floor(safeSeconds / 86_400);
|
||||
const hours = Math.floor((safeSeconds % 86_400) / 3_600);
|
||||
const minutes = Math.floor((safeSeconds % 3_600) / 60);
|
||||
|
||||
if (days > 0) return `${days} 天 ${hours} 小时`;
|
||||
if (hours > 0) return `${hours} 小时 ${minutes} 分钟`;
|
||||
return `${Math.max(1, minutes)} 分钟`;
|
||||
};
|
||||
|
||||
/** OCS AI 答题服务前端状态管理 */
|
||||
export const useAnswerStore = defineStore("answer", () => {
|
||||
// ---- 答题表单状态 ----
|
||||
const question = ref("");
|
||||
const questionType = ref<QuestionType>("");
|
||||
const options = ref("");
|
||||
const searchLoading = ref(false);
|
||||
const answerResult = ref<ISearchSuccessResponse>();
|
||||
const searchErrorMessage = ref("");
|
||||
|
||||
// ---- Dashboard 状态 ----
|
||||
const health = ref<IHealthResponse>();
|
||||
const stats = ref<IStatsResponse>();
|
||||
const records = ref<IQaRecord[]>([]);
|
||||
const dashboardErrorMessage = ref("");
|
||||
const statsLoading = ref(false);
|
||||
const recordsLoading = ref(false);
|
||||
const cacheClearing = ref(false);
|
||||
const recordsPage = ref(1);
|
||||
const recordsPageSize = ref(DASHBOARD_RECORD_PAGE_SIZE);
|
||||
const selectedRecord = ref<IQaRecord>();
|
||||
|
||||
/** Dashboard 表格当前页数据,分页只作用在前端的最近 100 条内存记录上 */
|
||||
const pagedRecords = computed(() => {
|
||||
const start = (recordsPage.value - 1) * recordsPageSize.value;
|
||||
return records.value.slice(start, start + recordsPageSize.value);
|
||||
});
|
||||
|
||||
/** 表格总数,用于 TableCom 分页器 */
|
||||
const recordCount = computed(() => records.value.length);
|
||||
|
||||
/** Dashboard 顶部运行时长展示 */
|
||||
const uptimeText = computed(() => {
|
||||
return stats.value ? formatUptime(stats.value.uptime) : "-";
|
||||
});
|
||||
|
||||
/** 选择表格记录,交给详情弹窗展示完整题目、选项和答案 */
|
||||
const selectRecord = (record?: IQaRecord) => {
|
||||
selectedRecord.value = record;
|
||||
};
|
||||
|
||||
/** 切换最近记录表格页码 */
|
||||
const setRecordsPage = (page: number) => {
|
||||
recordsPage.value = page;
|
||||
};
|
||||
|
||||
/** 调用答题 API,返回 OCS 兼容结构但只在 store 中维护 UI 状态 */
|
||||
const searchAnswer = async () => {
|
||||
const title = question.value.trim();
|
||||
const requestOptions = options.value.trim();
|
||||
|
||||
answerResult.value = undefined;
|
||||
searchErrorMessage.value = "";
|
||||
|
||||
if (!title) {
|
||||
searchErrorMessage.value = "未提供问题内容";
|
||||
return;
|
||||
}
|
||||
|
||||
searchLoading.value = true;
|
||||
try {
|
||||
const res = await AnswerService.search({
|
||||
title,
|
||||
type: questionType.value,
|
||||
options: requestOptions
|
||||
});
|
||||
|
||||
if (res.data.code === 1) {
|
||||
answerResult.value = res.data;
|
||||
await Promise.allSettled([getStats(), getRecords()]);
|
||||
return;
|
||||
}
|
||||
|
||||
searchErrorMessage.value = res.data.msg || "答题失败";
|
||||
} catch (error) {
|
||||
searchErrorMessage.value = getClientErrorMessage(
|
||||
error,
|
||||
"服务器内部错误"
|
||||
);
|
||||
} finally {
|
||||
searchLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 读取健康检查,失败时只写本地错误文案 */
|
||||
const getHealth = async () => {
|
||||
try {
|
||||
const res = await AnswerService.getHealth();
|
||||
health.value = res.data;
|
||||
} catch (error) {
|
||||
dashboardErrorMessage.value = getClientErrorMessage(
|
||||
error,
|
||||
"健康检查失败"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** 读取运行统计 */
|
||||
const getStats = async () => {
|
||||
statsLoading.value = true;
|
||||
try {
|
||||
const res = await AnswerService.getStats();
|
||||
stats.value = res.data;
|
||||
} catch (error) {
|
||||
dashboardErrorMessage.value = getClientErrorMessage(
|
||||
error,
|
||||
"统计信息读取失败"
|
||||
);
|
||||
} finally {
|
||||
statsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 读取最近问答记录,并在数量变少时把页码拉回合法范围 */
|
||||
const getRecords = async () => {
|
||||
recordsLoading.value = true;
|
||||
try {
|
||||
const res = await AnswerService.getRecords();
|
||||
records.value = res.data.records;
|
||||
|
||||
const maxPage = Math.max(
|
||||
1,
|
||||
Math.ceil(records.value.length / recordsPageSize.value)
|
||||
);
|
||||
if (recordsPage.value > maxPage) recordsPage.value = maxPage;
|
||||
} catch (error) {
|
||||
dashboardErrorMessage.value = getClientErrorMessage(
|
||||
error,
|
||||
"问答记录读取失败"
|
||||
);
|
||||
} finally {
|
||||
recordsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** Dashboard 首次加载时并行拉取运行状态、统计和最近记录 */
|
||||
const loadDashboard = async () => {
|
||||
dashboardErrorMessage.value = "";
|
||||
await Promise.allSettled([getHealth(), getStats(), getRecords()]);
|
||||
};
|
||||
|
||||
/** 清空缓存后刷新统计;问答记录不会被清空,保持旧服务语义 */
|
||||
const clearCache = async () => {
|
||||
cacheClearing.value = true;
|
||||
dashboardErrorMessage.value = "";
|
||||
|
||||
try {
|
||||
const res = await AnswerService.clearCache();
|
||||
if (!res.data.success) {
|
||||
dashboardErrorMessage.value = res.data.message;
|
||||
}
|
||||
await getStats();
|
||||
} catch (error) {
|
||||
dashboardErrorMessage.value = getClientErrorMessage(
|
||||
error,
|
||||
"缓存清理失败"
|
||||
);
|
||||
} finally {
|
||||
cacheClearing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
question,
|
||||
questionType,
|
||||
options,
|
||||
searchLoading,
|
||||
answerResult,
|
||||
searchErrorMessage,
|
||||
health,
|
||||
stats,
|
||||
records,
|
||||
dashboardErrorMessage,
|
||||
statsLoading,
|
||||
recordsLoading,
|
||||
cacheClearing,
|
||||
recordsPage,
|
||||
recordsPageSize,
|
||||
selectedRecord,
|
||||
pagedRecords,
|
||||
recordCount,
|
||||
uptimeText,
|
||||
searchAnswer,
|
||||
getHealth,
|
||||
getStats,
|
||||
getRecords,
|
||||
loadDashboard,
|
||||
clearCache,
|
||||
selectRecord,
|
||||
setRecordsPage
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user