feat: 大量优化和bug修复
This commit is contained in:
+5
-55
@@ -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
@@ -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
@@ -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 值,跟随当前模式 */
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user