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": {
"defaultMode": "acceptEdits",
"allow": [
"Read(./)",
"Edit(./)",
"Write(./)",
"Read",
"Edit",
"Write",
"Bash(pnpm build *)",
"Bash(pnpm install:*)",

View File

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

View File

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

View File

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

View File

@ -1,22 +1,28 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
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(() => {
if (!updateProgress.value || !updateProgress.value.total) return 0;
return Math.round((updateProgress.value.downloaded / updateProgress.value.total) * 100);
const progress = updateProgress.value;
if (!progress || !progress.total) return 0;
return Math.round((progress.downloaded / progress.total) * 100);
});
const downloadedMB = computed(() => {
if (!updateProgress.value) return "0";
return (updateProgress.value.downloaded / 1024 / 1024).toFixed(1);
const progress = updateProgress.value;
if (!progress) return "0";
return (progress.downloaded / 1024 / 1024).toFixed(1);
});
const totalMB = computed(() => {
if (!updateProgress.value || !updateProgress.value.total) return "0";
return (updateProgress.value.total / 1024 / 1024).toFixed(1);
const progress = updateProgress.value;
if (!progress || !progress.total) return "0";
return (progress.total / 1024 / 1024).toFixed(1);
});
</script>

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import { useTray } from "@/composables/useTray";
import { LOGIN_URL, CHECK_URL, WORK_ORDER_URL } from "@/constants/api";
import { useAppStore } from "@/stores/app";
import { useLogStore } from "@/stores/log";
import { formatTime } from "@/utils/common";
import { formatTime, getErrorMessage } from "@/utils/common";
export interface TicketCounts {
pending: number;
@ -73,7 +73,7 @@ export const useMonitorStore = defineStore("monitor", () => {
});
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];
addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN");
@ -91,8 +91,8 @@ export const useMonitorStore = defineStore("monitor", () => {
addLog("ERROR", `登录失败: ${data.msg}`, "LOGIN");
return false;
}
} catch (e: any) {
addLog("ERROR", `登录请求失败: ${e.message || e}`, "LOGIN");
} catch (e: unknown) {
addLog("ERROR", `登录请求失败: ${getErrorMessage(e)}`, "LOGIN");
return false;
}
}
@ -123,65 +123,94 @@ export const useMonitorStore = defineStore("monitor", () => {
addLog("WARN", `待审核 API 返回异常: ${result.msg}`, "SCHEDULER");
return 0;
}
} catch (e: any) {
addLog("ERROR", `待审核 API 检查失败: ${e.message || e}`, "SCHEDULER");
} catch (e: unknown) {
addLog(
"ERROR",
`待审核 API 检查失败: ${getErrorMessage(e)}`,
"SCHEDULER",
);
return 0;
}
}
/** 发送工单状态检查请求 */
async function performStatusCheckRequest() {
const body: Record<string, string> = {};
if (userGid) body.UserGID = userGid;
const resp = await fetch(CHECK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `ASP.NET_SessionId=${sessionId}`,
},
body: JSON.stringify(body),
});
return await resp.json();
}
/** 处理检查到的工单数据并更新状态 */
async function updateTicketCountsAndLog(data: any) {
const pending = data.PendingCount || 0;
const stayclose = data.StaycloseCount || 0;
const confirm = data.ConfirmCount || 0;
const workOrderCount = await apiCheckWorkOrders();
const counts = {
pending,
stayclose,
confirm,
workOrderCount,
lastCheck: formatTime(new Date()),
};
ticketCounts.value = counts;
addLog(
"INFO",
`检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}, 待审核: ${workOrderCount}`,
"SCHEDULER",
);
return counts;
}
/** 发送工单提醒通知 */
async function sendTicketNotifications(counts: TicketCounts) {
const msgs: string[] = [];
if (counts.pending > 0) msgs.push(`待处理工单: ${counts.pending}`);
if (counts.stayclose > 0) msgs.push(`待关闭工单: ${counts.stayclose}`);
if (counts.confirm > 0) msgs.push(`待确认工单: ${counts.confirm}`);
if (counts.workOrderCount > 0)
msgs.push(`待审核: ${counts.workOrderCount}`);
if (msgs.length > 0) {
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 body: Record<string, string> = {};
if (userGid) body.UserGID = userGid;
const resp = await fetch(CHECK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `ASP.NET_SessionId=${sessionId}`,
},
body: JSON.stringify(body),
});
const result = await resp.json();
const result = await performStatusCheckRequest();
if (result.success && result.data) {
const d = result.data;
const pending = d.PendingCount || 0;
const stayclose = d.StaycloseCount || 0;
const confirm = d.ConfirmCount || 0;
const workOrderCount = await apiCheckWorkOrders();
ticketCounts.value = {
pending,
stayclose,
confirm,
workOrderCount,
lastCheck: formatTime(new Date()),
};
addLog(
"INFO",
`检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}, 待审核: ${workOrderCount}`,
"SCHEDULER",
);
if (pending > 0 || stayclose > 0 || confirm > 0 || workOrderCount > 0) {
const msgs: string[] = [];
if (pending > 0) msgs.push(`待处理工单: ${pending}`);
if (stayclose > 0) msgs.push(`待关闭工单: ${stayclose}`);
if (confirm > 0) msgs.push(`待确认工单: ${confirm}`);
if (workOrderCount > 0) msgs.push(`待审核: ${workOrderCount}`);
await notify("工单提醒", msgs.join(""));
}
// 2. 处理成功检查
const counts = await updateTicketCountsAndLog(result.data);
await sendTicketNotifications(counts);
} else {
// 3. 处理业务失败
addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER");
if (!isRetry && isSessionExpiredMsg(result.msg)) {
await tryReLogin();
}
}
} catch (e: any) {
addLog("ERROR", `API 检查失败: ${e.message || e}`, "SCHEDULER");
} catch (e: unknown) {
// 4. 处理网络/系统异常
addLog("ERROR", `API 检查失败: ${getErrorMessage(e)}`, "SCHEDULER");
}
}
@ -242,8 +271,8 @@ export const useMonitorStore = defineStore("monitor", () => {
tokenSessionId.value = data.tokenSessionId || "";
addLog("INFO", "已加载保存的凭据", "CONFIG");
}
} catch (e: any) {
addLog("ERROR", `加载凭据失败: ${e.message || e}`, "CONFIG");
} catch (e: unknown) {
addLog("ERROR", `加载凭据失败: ${getErrorMessage(e)}`, "CONFIG");
}
}
@ -266,8 +295,8 @@ export const useMonitorStore = defineStore("monitor", () => {
localStorage.removeItem("crm_credentials");
addLog("INFO", "已清除本地凭据", "CONFIG");
}
} catch (e: any) {
addLog("ERROR", `保存凭据失败: ${e.message || e}`, "CONFIG");
} catch (e: unknown) {
addLog("ERROR", `保存凭据失败: ${getErrorMessage(e)}`, "CONFIG");
}
}

View File

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

View File

@ -1,5 +1,10 @@
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' 字符串 */
export function formatTime(date: Date): string {
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" {
import type { DefineComponent } from "vue";
import type { Component } from "vue";
const component: DefineComponent<{}, {}, any>;
const component: Component;
export default component;
}