refactor(monitor): ♻️ 优化代码

This commit is contained in:
tsl 2026-04-01 10:05:53 +08:00
parent 0b2c1ca83f
commit cc30c088c4
13 changed files with 156 additions and 108 deletions

View File

@ -2,9 +2,9 @@
"permissions": { "permissions": {
"defaultMode": "acceptEdits", "defaultMode": "acceptEdits",
"allow": [ "allow": [
"Read(./)", "Read",
"Edit(./)", "Edit",
"Write(./)", "Write",
"Bash(pnpm build *)", "Bash(pnpm build *)",
"Bash(pnpm install:*)", "Bash(pnpm install:*)",

View File

@ -16,6 +16,7 @@
"version:major": "node scripts/bump-version.mjs major", "version:major": "node scripts/bump-version.mjs major",
"publish": "node scripts/publish.mjs", "publish": "node scripts/publish.mjs",
"deploy": "npm run tauri-build && npm run publish", "deploy": "npm run tauri-build && npm run publish",
"type-check": "vue-tsc --noEmit",
"lint": "eslint src", "lint": "eslint src",
"lint:fix": "eslint src --fix", "lint:fix": "eslint src --fix",
"commit": "git-cz", "commit": "git-cz",

View File

@ -14,17 +14,17 @@ import MonitorControl from "@/components/MonitorControl.vue";
import SettingsPanel from "@/components/SettingsPanel.vue"; import SettingsPanel from "@/components/SettingsPanel.vue";
import StatusHeader from "@/components/StatusHeader.vue"; import StatusHeader from "@/components/StatusHeader.vue";
import UpdateDialog from "@/components/UpdateDialog.vue"; import UpdateDialog from "@/components/UpdateDialog.vue";
import { useUpdater } from "@/composables/useUpdater";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { useMonitorStore } from "@/stores/monitor"; import { useMonitorStore } from "@/stores/monitor";
import { useNetworkStore } from "@/stores/network"; import { useNetworkStore } from "@/stores/network";
import { useUpdaterStore } from "@/stores/updater";
const activeTab = ref("monitor"); const activeTab = ref("monitor");
const store = useMonitorStore(); const store = useMonitorStore();
const appStore = useAppStore(); const appStore = useAppStore();
const logStore = useLogStore(); const logStore = useLogStore();
const updater = useUpdater(); const updater = useUpdaterStore();
const networkStore = useNetworkStore(); const networkStore = useNetworkStore();
/** 通知动作事件的取消监听函数 */ /** 通知动作事件的取消监听函数 */
@ -88,7 +88,7 @@ onMounted(async () => {
getVersion() getVersion()
.then((v) => { .then((v) => {
updater.currentVersion.value = v; updater.currentVersion = v;
}) })
.catch(() => {}); .catch(() => {});

View File

@ -3,11 +3,11 @@ import { CloudSyncOutlined } from "@ant-design/icons-vue";
import dayjs, { type Dayjs } from "dayjs"; import dayjs, { type Dayjs } from "dayjs";
import { debounce } from "lodash-es"; import { debounce } from "lodash-es";
import { computed, watch } from "vue"; import { computed, watch } from "vue";
import { useUpdater } from "@/composables/useUpdater";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useUpdaterStore } from "@/stores/updater";
const appStore = useAppStore(); const appStore = useAppStore();
const { currentVersion, isCheckingUpdates, isUpdating, checkForUpdates } = useUpdater(); const { currentVersion, isCheckingUpdates, isUpdating, checkForUpdates } = useUpdaterStore();
// ===== TimePicker Dayjs ===== // ===== TimePicker Dayjs =====
const intervalTime = computed<Dayjs>({ const intervalTime = computed<Dayjs>({

View File

@ -1,22 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from "pinia";
import { computed } from "vue"; import { computed } from "vue";
import { useUpdater } from "@/composables/useUpdater"; import { useUpdaterStore } from "@/stores/updater";
const { showUpdateDialog, updateInfo, updateProgress, isUpdating, confirmUpdate, dismissUpdate } = useUpdater(); const store = useUpdaterStore();
const { showUpdateDialog, updateInfo, updateProgress, isUpdating } = storeToRefs(store);
const { confirmUpdate, dismissUpdate } = store;
const progressPercent = computed(() => { const progressPercent = computed(() => {
if (!updateProgress.value || !updateProgress.value.total) return 0; const progress = updateProgress.value;
return Math.round((updateProgress.value.downloaded / updateProgress.value.total) * 100); if (!progress || !progress.total) return 0;
return Math.round((progress.downloaded / progress.total) * 100);
}); });
const downloadedMB = computed(() => { const downloadedMB = computed(() => {
if (!updateProgress.value) return "0"; const progress = updateProgress.value;
return (updateProgress.value.downloaded / 1024 / 1024).toFixed(1); if (!progress) return "0";
return (progress.downloaded / 1024 / 1024).toFixed(1);
}); });
const totalMB = computed(() => { const totalMB = computed(() => {
if (!updateProgress.value || !updateProgress.value.total) return "0"; const progress = updateProgress.value;
return (updateProgress.value.total / 1024 / 1024).toFixed(1); if (!progress || !progress.total) return "0";
return (progress.total / 1024 / 1024).toFixed(1);
}); });
</script> </script>

View File

@ -6,6 +6,7 @@ import {
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { getErrorMessage } from "@/utils/common";
/** 通知 Composable */ /** 通知 Composable */
export function useNotification() { export function useNotification() {
@ -44,8 +45,8 @@ export function useNotification() {
await invoke("send_clickable_notification", { title, body }); await invoke("send_clickable_notification", { title, body });
playNotificationSound(); playNotificationSound();
addLog("INFO", `通知: ${title} - ${body}`, "NOTIFICATION"); addLog("INFO", `通知: ${title} - ${body}`, "NOTIFICATION");
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `发送通知失败: ${e}`, "NOTIFICATION"); addLog("ERROR", `发送通知失败: ${getErrorMessage(e)}`, "NOTIFICATION");
} }
} }
} }

View File

@ -17,6 +17,7 @@ import { type Ref } from "vue";
import { CRM_URL } from "@/constants/app"; import { CRM_URL } from "@/constants/app";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { type TicketCounts } from "@/stores/monitor"; import { type TicketCounts } from "@/stores/monitor";
import { getErrorMessage } from "@/utils/common";
/** /**
* @param isMonitoring * @param isMonitoring
@ -161,8 +162,8 @@ export function useTray(
const icon = await Image.new(await makeTrayIconRGBA(...color), 32, 32); const icon = await Image.new(await makeTrayIconRGBA(...color), 32, 32);
await trayIconInstance.setIcon(icon); await trayIconInstance.setIcon(icon);
await trayIconInstance.setTooltip(tooltip); await trayIconInstance.setTooltip(tooltip);
} catch (e: any) { } catch (e: unknown) {
addLog("WARN", `更新托盘图标失败: ${e.message || e}`, "SYSTEM"); addLog("WARN", `更新托盘图标失败: ${getErrorMessage(e)}`, "SYSTEM");
} }
} }
@ -228,8 +229,8 @@ export function useTray(
try { try {
const menu = await buildTrayMenu(); const menu = await buildTrayMenu();
await trayIconInstance.setMenu(menu); await trayIconInstance.setMenu(menu);
} catch (e: any) { } catch (e: unknown) {
addLog("WARN", `更新托盘菜单失败: ${e.message || e}`, "SYSTEM"); addLog("WARN", `更新托盘菜单失败: ${getErrorMessage(e)}`, "SYSTEM");
} }
} }
@ -264,8 +265,8 @@ export function useTray(
}, },
}); });
addLog("INFO", "系统托盘创建成功", "SYSTEM"); addLog("INFO", "系统托盘创建成功", "SYSTEM");
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `系统托盘创建失败: ${e.message || e}`, "SYSTEM"); addLog("ERROR", `系统托盘创建失败: ${getErrorMessage(e)}`, "SYSTEM");
} }
} }

View File

@ -6,6 +6,7 @@ import {
import { defineStore, acceptHMRUpdate } from "pinia"; import { defineStore, acceptHMRUpdate } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { getErrorMessage } from "@/utils/common";
export const useAppStore = defineStore("app", () => { export const useAppStore = defineStore("app", () => {
const message = ref(""); // 界面状态提示消息 const message = ref(""); // 界面状态提示消息
@ -45,8 +46,8 @@ export const useAppStore = defineStore("app", () => {
config.value = { ...config.value, ...data }; config.value = { ...config.value, ...data };
addLog("INFO", "已加载保存的配置", "CONFIG"); addLog("INFO", "已加载保存的配置", "CONFIG");
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `加载配置失败: ${e.message || e}`, "CONFIG"); addLog("ERROR", `加载配置失败: ${getErrorMessage(e)}`, "CONFIG");
} }
} }
@ -71,8 +72,8 @@ export const useAppStore = defineStore("app", () => {
`自启动设置: ${config.value.auto_start ? "启用" : "禁用"}`, `自启动设置: ${config.value.auto_start ? "启用" : "禁用"}`,
"CONFIG", "CONFIG",
); );
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `自启动设置失败: ${e.message || e}`, "CONFIG"); addLog("ERROR", `自启动设置失败: ${getErrorMessage(e)}`, "CONFIG");
} }
saveConfigToStorage(); saveConfigToStorage();
setMessage("配置更新成功"); setMessage("配置更新成功");

View File

@ -6,7 +6,7 @@ import { useTray } from "@/composables/useTray";
import { LOGIN_URL, CHECK_URL, WORK_ORDER_URL } from "@/constants/api"; import { LOGIN_URL, CHECK_URL, WORK_ORDER_URL } from "@/constants/api";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { formatTime } from "@/utils/common"; import { formatTime, getErrorMessage } from "@/utils/common";
export interface TicketCounts { export interface TicketCounts {
pending: number; pending: number;
@ -73,7 +73,7 @@ export const useMonitorStore = defineStore("monitor", () => {
}); });
const setCookie = resp.headers.get("set-cookie") || ""; const setCookie = resp.headers.get("set-cookie") || "";
const match = setCookie.match(/ASP\.NET_SessionId=([^;]+)/); const match = /ASP\.NET_SessionId=([^;]+)/.exec(setCookie);
if (match) sessionId = match[1]; if (match) sessionId = match[1];
addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN"); addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN");
@ -91,8 +91,8 @@ export const useMonitorStore = defineStore("monitor", () => {
addLog("ERROR", `登录失败: ${data.msg}`, "LOGIN"); addLog("ERROR", `登录失败: ${data.msg}`, "LOGIN");
return false; return false;
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `登录请求失败: ${e.message || e}`, "LOGIN"); addLog("ERROR", `登录请求失败: ${getErrorMessage(e)}`, "LOGIN");
return false; return false;
} }
} }
@ -123,20 +123,18 @@ export const useMonitorStore = defineStore("monitor", () => {
addLog("WARN", `待审核 API 返回异常: ${result.msg}`, "SCHEDULER"); addLog("WARN", `待审核 API 返回异常: ${result.msg}`, "SCHEDULER");
return 0; return 0;
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `待审核 API 检查失败: ${e.message || e}`, "SCHEDULER"); addLog(
"ERROR",
`待审核 API 检查失败: ${getErrorMessage(e)}`,
"SCHEDULER",
);
return 0; return 0;
} }
} }
/** 拉取工单状态计数触发通知isRetry=true 时不再递归重登 */ /** 发送工单状态检查请求 */
async function apiCheckStatus(isRetry = false): Promise<void> { async function performStatusCheckRequest() {
if (!userGid && loginMode.value === "password") {
addLog("WARN", "会话已失效,尝试重新登录", "SCHEDULER");
if (!isRetry) await tryReLogin();
return;
}
try {
const body: Record<string, string> = {}; const body: Record<string, string> = {};
if (userGid) body.UserGID = userGid; if (userGid) body.UserGID = userGid;
const resp = await fetch(CHECK_URL, { const resp = await fetch(CHECK_URL, {
@ -147,41 +145,72 @@ export const useMonitorStore = defineStore("monitor", () => {
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const result = await resp.json(); return await resp.json();
if (result.success && result.data) { }
const d = result.data;
const pending = d.PendingCount || 0; /** 处理检查到的工单数据并更新状态 */
const stayclose = d.StaycloseCount || 0; async function updateTicketCountsAndLog(data: any) {
const confirm = d.ConfirmCount || 0; const pending = data.PendingCount || 0;
const stayclose = data.StaycloseCount || 0;
const confirm = data.ConfirmCount || 0;
const workOrderCount = await apiCheckWorkOrders(); const workOrderCount = await apiCheckWorkOrders();
ticketCounts.value = {
const counts = {
pending, pending,
stayclose, stayclose,
confirm, confirm,
workOrderCount, workOrderCount,
lastCheck: formatTime(new Date()), lastCheck: formatTime(new Date()),
}; };
ticketCounts.value = counts;
addLog( addLog(
"INFO", "INFO",
`检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}, 待审核: ${workOrderCount}`, `检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}, 待审核: ${workOrderCount}`,
"SCHEDULER", "SCHEDULER",
); );
if (pending > 0 || stayclose > 0 || confirm > 0 || workOrderCount > 0) { return counts;
}
/** 发送工单提醒通知 */
async function sendTicketNotifications(counts: TicketCounts) {
const msgs: string[] = []; const msgs: string[] = [];
if (pending > 0) msgs.push(`待处理工单: ${pending}`); if (counts.pending > 0) msgs.push(`待处理工单: ${counts.pending}`);
if (stayclose > 0) msgs.push(`待关闭工单: ${stayclose}`); if (counts.stayclose > 0) msgs.push(`待关闭工单: ${counts.stayclose}`);
if (confirm > 0) msgs.push(`待确认工单: ${confirm}`); if (counts.confirm > 0) msgs.push(`待确认工单: ${counts.confirm}`);
if (workOrderCount > 0) msgs.push(`待审核: ${workOrderCount}`); if (counts.workOrderCount > 0)
msgs.push(`待审核: ${counts.workOrderCount}`);
if (msgs.length > 0) {
await notify("工单提醒", msgs.join("")); await notify("工单提醒", msgs.join(""));
} }
}
/** 拉取工单状态计数触发通知isRetry=true 时不再递归重登 */
async function apiCheckStatus(isRetry = false): Promise<void> {
// 1. 预检查:如果会话失效且是密码模式,尝试重登
if (!userGid && loginMode.value === "password") {
addLog("WARN", "会话已失效,尝试重新登录", "SCHEDULER");
if (!isRetry) await tryReLogin();
return;
}
try {
const result = await performStatusCheckRequest();
if (result.success && result.data) {
// 2. 处理成功检查
const counts = await updateTicketCountsAndLog(result.data);
await sendTicketNotifications(counts);
} else { } else {
// 3. 处理业务失败
addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER"); addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER");
if (!isRetry && isSessionExpiredMsg(result.msg)) { if (!isRetry && isSessionExpiredMsg(result.msg)) {
await tryReLogin(); await tryReLogin();
} }
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `API 检查失败: ${e.message || e}`, "SCHEDULER"); // 4. 处理网络/系统异常
addLog("ERROR", `API 检查失败: ${getErrorMessage(e)}`, "SCHEDULER");
} }
} }
@ -242,8 +271,8 @@ export const useMonitorStore = defineStore("monitor", () => {
tokenSessionId.value = data.tokenSessionId || ""; tokenSessionId.value = data.tokenSessionId || "";
addLog("INFO", "已加载保存的凭据", "CONFIG"); addLog("INFO", "已加载保存的凭据", "CONFIG");
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `加载凭据失败: ${e.message || e}`, "CONFIG"); addLog("ERROR", `加载凭据失败: ${getErrorMessage(e)}`, "CONFIG");
} }
} }
@ -266,8 +295,8 @@ export const useMonitorStore = defineStore("monitor", () => {
localStorage.removeItem("crm_credentials"); localStorage.removeItem("crm_credentials");
addLog("INFO", "已清除本地凭据", "CONFIG"); addLog("INFO", "已清除本地凭据", "CONFIG");
} }
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `保存凭据失败: ${e.message || e}`, "CONFIG"); addLog("ERROR", `保存凭据失败: ${getErrorMessage(e)}`, "CONFIG");
} }
} }

View File

@ -1,9 +1,13 @@
import { relaunch } from "@tauri-apps/plugin-process"; import { relaunch } from "@tauri-apps/plugin-process";
import { check, type Update } from "@tauri-apps/plugin-updater"; import { check, type Update } from "@tauri-apps/plugin-updater";
import { acceptHMRUpdate, defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import { useLogStore } from "@/stores/log"; import { useLogStore } from "@/stores/log";
import { getErrorMessage } from "@/utils/common";
export const useUpdaterStore = defineStore("updater", () => {
const { addLog } = useLogStore();
// ===== 模块级单例状态 =====
/** 是否显示更新对话框 */ /** 是否显示更新对话框 */
const showUpdateDialog = ref(false); const showUpdateDialog = ref(false);
/** 新版本信息 */ /** 新版本信息 */
@ -13,7 +17,9 @@ const updateInfo = ref<{
date: string; date: string;
} | null>(null); } | null>(null);
/** 下载进度 */ /** 下载进度 */
const updateProgress = ref<{ total: number; downloaded: number } | null>(null); const updateProgress = ref<{ total: number; downloaded: number } | null>(
null,
);
/** 是否正在更新中 */ /** 是否正在更新中 */
const isUpdating = ref(false); const isUpdating = ref(false);
/** 当前应用版本号 */ /** 当前应用版本号 */
@ -24,19 +30,13 @@ const isCheckingUpdates = ref(false);
/** 待安装的更新对象 */ /** 待安装的更新对象 */
let pendingUpdate: Update | null = null; let pendingUpdate: Update | null = null;
/**
* Composable
*/
export function useUpdater() {
const { addLog } = useLogStore();
/** 检查是否有可用的应用更新,发现新版本时展示更新对话框 */ /** 检查是否有可用的应用更新,发现新版本时展示更新对话框 */
async function checkForUpdates() { async function checkForUpdates() {
if (isCheckingUpdates.value || isUpdating.value) return; if (isCheckingUpdates.value || isUpdating.value) return;
isCheckingUpdates.value = true; isCheckingUpdates.value = true;
try { try {
const update = await check(); const update = await check();
if (update?.available) { if (update) {
pendingUpdate = update; pendingUpdate = update;
updateInfo.value = { updateInfo.value = {
version: update.version, version: update.version,
@ -48,8 +48,8 @@ export function useUpdater() {
} else { } else {
addLog("INFO", "当前已是最新版本", "UPDATER"); addLog("INFO", "当前已是最新版本", "UPDATER");
} }
} catch (e: any) { } catch (e: unknown) {
addLog("WARN", `检查更新失败: ${e.message || e}`, "UPDATER"); addLog("WARN", `检查更新失败: ${getErrorMessage(e)}`, "UPDATER");
} finally { } finally {
isCheckingUpdates.value = false; isCheckingUpdates.value = false;
} }
@ -76,8 +76,8 @@ export function useUpdater() {
} }
}); });
await relaunch(); await relaunch();
} catch (e: any) { } catch (e: unknown) {
addLog("ERROR", `更新安装失败: ${e.message || e}`, "UPDATER"); addLog("ERROR", `更新安装失败: ${getErrorMessage(e)}`, "UPDATER");
isUpdating.value = false; isUpdating.value = false;
} }
} }
@ -98,4 +98,8 @@ export function useUpdater() {
confirmUpdate, confirmUpdate,
dismissUpdate, dismissUpdate,
}; };
});
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUpdaterStore, import.meta.hot));
} }

View File

@ -1,5 +1,10 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
/** 从 unknown 类型的 catch 错误中提取可读的错误信息 */
export function getErrorMessage(e: unknown): string {
return e instanceof Error ? e.message : String(e);
}
/** 将 Date 对象格式化为 'YYYY-MM-DD HH:mm:ss' 字符串 */ /** 将 Date 对象格式化为 'YYYY-MM-DD HH:mm:ss' 字符串 */
export function formatTime(date: Date): string { export function formatTime(date: Date): string {
return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); return dayjs(date).format("YYYY-MM-DD HH:mm:ss");

4
src/vite-env.d.ts vendored
View File

@ -9,8 +9,8 @@ interface ImportMeta {
} }
declare module "*.vue" { declare module "*.vue" {
import type { DefineComponent } from "vue"; import type { Component } from "vue";
const component: DefineComponent<{}, {}, any>; const component: Component;
export default component; export default component;
} }