feat: 大量优化和bug修复

This commit is contained in:
2026-05-23 22:23:29 +08:00
parent 92b063c4c4
commit fc71e924e7
39 changed files with 1381 additions and 302 deletions
+5 -55
View File
@@ -3,14 +3,12 @@ import { defineStore } from "pinia";
import { toast } from "vue-sonner";
import type {
IHealthResponse,
IQaRecord,
ISearchSuccessResponse,
IStatsResponse,
QuestionType
} from "@/interfaces";
import { AnswerService } from "@/services";
import { formatUptime, getClientErrorMessage } from "~/utils";
import { getClientErrorMessage } from "~/utils";
/** Dashboard 最近记录表格每页数量,组件直接复用,避免多个地方写死 */
export const DASHBOARD_RECORD_PAGE_SIZE = 10;
@@ -56,16 +54,10 @@ export const useAnswerStore = defineStore("answer", () => {
const searchErrorMessage = ref("");
// ---- Dashboard 状态 ----
/** 健康检查信息 */
const health = ref<IHealthResponse>();
/** 运行统计信息 */
const stats = ref<IStatsResponse>();
/** 最近问答记录 */
const records = ref<IQaRecord[]>([]);
/** Dashboard 错误文案 */
const dashboardErrorMessage = ref("");
/** 统计信息加载状态 */
const statsLoading = ref(false);
/** 最近问答记录加载状态 */
const recordsLoading = ref(false);
/** 缓存清理状态 */
@@ -82,11 +74,6 @@ export const useAnswerStore = defineStore("answer", () => {
/** Dashboard 表格当前页数据,页码切换会重新请求服务端分页接口 */
const pagedRecords = computed(() => records.value);
/** Dashboard 顶部运行时长展示 */
const uptimeText = computed(() => {
return stats.value ? formatUptime(stats.value.uptime) : "-";
});
/** 选择表格记录,交给详情弹窗展示完整题目、选项和答案 */
const selectRecord = (record?: IQaRecord) => {
selectedRecord.value = record;
@@ -129,7 +116,7 @@ export const useAnswerStore = defineStore("answer", () => {
if (res.data.code === 1) {
answerResult.value = res.data;
recordsPage.value = 1;
await Promise.allSettled([getStats(), getRecords()]);
await getRecords();
return;
}
@@ -143,37 +130,6 @@ export const useAnswerStore = defineStore("answer", () => {
}
};
/** 读取健康检查,失败时只写本地错误文案 */
const getHealth = async () => {
try {
const res = await AnswerService.getHealth();
health.value = res.data.data;
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"健康检查失败"
);
toast.error(dashboardErrorMessage.value);
}
};
/** 读取运行统计 */
const getStats = async () => {
statsLoading.value = true;
try {
const res = await AnswerService.getStats();
stats.value = res.data.data;
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
"统计信息读取失败"
);
toast.error(dashboardErrorMessage.value);
} finally {
statsLoading.value = false;
}
};
/** 读取最近问答记录;服务端按 page/size 返回当前页数据 */
const getRecords = async (page = recordsPage.value) => {
recordsLoading.value = true;
@@ -197,10 +153,10 @@ export const useAnswerStore = defineStore("answer", () => {
}
};
/** Dashboard 首次加载时并行拉取运行状态、统计和最近记录 */
/** Dashboard 首次加载时拉取最近记录 */
const loadDashboard = async () => {
dashboardErrorMessage.value = "";
await Promise.allSettled([getHealth(), getStats(), getRecords()]);
await getRecords();
};
/** 清空缓存后刷新统计与表格;$fetch 非 2xx 时会抛出,错误由 catch 处理 */
@@ -211,7 +167,7 @@ export const useAnswerStore = defineStore("answer", () => {
try {
await AnswerService.clearCache();
toast.success("缓存已清空");
await Promise.allSettled([getStats(), getRecords(1)]);
await getRecords(1);
} catch (error) {
dashboardErrorMessage.value = getClientErrorMessage(
error,
@@ -230,11 +186,8 @@ export const useAnswerStore = defineStore("answer", () => {
searchLoading,
answerResult,
searchErrorMessage,
health,
stats,
records,
dashboardErrorMessage,
statsLoading,
recordsLoading,
cacheClearing,
recordsPage,
@@ -242,10 +195,7 @@ export const useAnswerStore = defineStore("answer", () => {
selectedRecord,
pagedRecords,
recordCount,
uptimeText,
searchAnswer,
getHealth,
getStats,
getRecords,
loadDashboard,
clearCache,
+7 -2
View File
@@ -11,6 +11,8 @@ interface AuthUser {
email: string;
/** 油猴脚本使用此 token 在 body 中传递身份 */
apiToken?: string | null;
/** 用户角色:user / admin / superadmin */
role?: string | null;
}
export const useAuthStore = defineStore("auth", () => {
@@ -41,7 +43,8 @@ export const useAuthStore = defineStore("auth", () => {
name: session.data.user.name,
email: session.data.user.email,
apiToken: (session.data.user as { apiToken?: string | null })
.apiToken
.apiToken,
role: (session.data.user as { role?: string | null }).role
}
: null;
} catch {
@@ -62,12 +65,14 @@ export const useAuthStore = defineStore("auth", () => {
name: string;
email: string;
apiToken?: string | null;
role?: string | null;
}) => {
user.value = {
id: u.id,
name: u.name,
email: u.email,
apiToken: u.apiToken ?? null
apiToken: u.apiToken ?? null,
role: u.role ?? null
};
initialized.value = true;
};
+11 -5
View File
@@ -1,14 +1,20 @@
// app/stores/global.ts - 全局通用状态(主题等),持久化由 @nuxtjs/color-mode 统一管理(localStorage/cookie
// app/stores/global.ts - 全局通用状态(主题等)
//
// 颜色模式改用 useCookie 持久化,这样 SSR 和客户端都能读到同一份值,
// 避免因 localStorage 在 SSR 阶段不可用而产生水合不匹配(hydration mismatch)。
export const useGlobalStore = defineStore("theme", () => {
// useColorMode 会自动读写持久化存储,class 也由其挂到 <html>
const colorMode = useColorMode();
// 用 Nuxt 的 useCookie:服务端从 request cookie 读取,客户端和正常 cookie 一致
const _pref = useCookie<"light" | "dark">("color-mode", {
default: () => "light",
sameSite: "lax"
});
/** 当前是否深色模式 */
const isDark = computed(() => colorMode.value === "dark");
const isDark = computed(() => _pref.value === "dark");
/** 切换深色 / 浅色 */
const toggle = () => {
colorMode.value = isDark.value ? "light" : "dark";
_pref.value = isDark.value ? "light" : "dark";
};
/** vue-sonner Toaster 的 theme prop 值,跟随当前模式 */
+116
View File
@@ -0,0 +1,116 @@
// app/stores/settings.ts - 用户自定义配置和系统配置状态管理
import { defineStore } from "pinia";
import { toast } from "vue-sonner";
import type {
ISystemConfigPublic,
ISystemConfigUpdateRequest,
IUserSettings,
IUserSettingsUpdateRequest
} from "@/interfaces";
import { SettingsService } from "@/services/settings_service";
const getApiErrorMessage = (error: unknown, fallback: string) => {
const data = (error as { data?: { msg?: string } })?.data;
return data?.msg || fallback;
};
export const useSettingsStore = defineStore("settings", () => {
/** 用户自定义配置,null 表示尚未加载 */
const userSettings = ref<IUserSettings | null>(null);
/** 系统配置(仅 superadmin),null 表示尚未加载或无权限 */
const systemConfig = ref<ISystemConfigPublic | null>(null);
/** 加载/保存中状态 */
const userLoading = ref(false); // 仅用于保存
const userFetching = ref(false); // 仅用于 GET 拉取
const sysLoading = ref(false);
const sysFetching = ref(false);
/** 拉取当前用户的自定义配置 */
const fetchUserSettings = async () => {
userFetching.value = true;
try {
const res = await SettingsService.getUserSettings();
if (res.data.code === 0 && res.data.data) {
userSettings.value = res.data.data;
}
} catch {
// 静默失败,不影响主功能
} finally {
userFetching.value = false;
}
};
/** 保存用户自定义配置 */
const updateUserSettings = async (
req: IUserSettingsUpdateRequest
): Promise<boolean> => {
userLoading.value = true;
try {
const res = await SettingsService.updateUserSettings(req);
const data = res.data;
if (data.code === 0) {
await fetchUserSettings();
toast.success("配置已保存");
return true;
}
toast.error(data.msg || "保存失败");
return false;
} catch (error) {
toast.error(getApiErrorMessage(error, "保存失败,请重试"));
return false;
} finally {
userLoading.value = false;
}
};
/** 拉取系统配置(仅 superadmin 调用此方法) */
const fetchSystemConfig = async () => {
sysFetching.value = true;
try {
const res = await SettingsService.getSystemConfig();
if (res.data.code === 0 && res.data.data) {
systemConfig.value = res.data.data;
}
} catch {
// 无权限或其他错误,静默处理
} finally {
sysFetching.value = false;
}
};
/** 保存系统配置(仅 superadmin 调用) */
const updateSystemConfig = async (
req: ISystemConfigUpdateRequest
): Promise<boolean> => {
sysLoading.value = true;
try {
const res = await SettingsService.updateSystemConfig(req);
if (res.data.code === 0) {
toast.success("系统配置已更新");
await fetchSystemConfig();
return true;
}
toast.error(res.data.msg || "更新失败");
return false;
} catch (error) {
toast.error(getApiErrorMessage(error, "更新失败,请重试"));
return false;
} finally {
sysLoading.value = false;
}
};
return {
userSettings,
systemConfig,
userLoading,
userFetching,
sysLoading,
sysFetching,
fetchUserSettings,
updateUserSettings,
fetchSystemConfig,
updateSystemConfig
};
});