import { ref, watch } from "vue"; import dayjs from "dayjs"; import { defineStore, acceptHMRUpdate } from "pinia"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { TrayIcon } from "@tauri-apps/api/tray"; import { Image } from "@tauri-apps/api/image"; import { Menu, MenuItem } from "@tauri-apps/api/menu"; import { fetch } from "@tauri-apps/plugin-http"; import { isPermissionGranted, requestPermission, } from "@tauri-apps/plugin-notification"; import { enable as enableAutostart, disable as disableAutostart, isEnabled as isAutostartEnabled, } from "@tauri-apps/plugin-autostart"; import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { openUrl } from "@tauri-apps/plugin-opener"; import { check, type Update } from "@tauri-apps/plugin-updater"; import { relaunch } from "@tauri-apps/plugin-process"; export const APP_NAME = "工单系统监测"; const API_BASE = "https://crm.yunvip123.com/api"; const LOGIN_URL = `${API_BASE}/SystemUser/Login`; const CHECK_URL = `${API_BASE}/DemandManage/QueryIndexCount`; const WORK_ORDER_URL = `${API_BASE}/DemandManage/GetWorkOrderListPage`; export interface LogEntry { timestamp: string; level: string; message: string; category: string; } export interface TicketCounts { pending: number; stayclose: number; confirm: number; workOrderCount: number; lastCheck: string; } export const useMonitorStore = defineStore("monitor", () => { // ===== 响应式数据 ===== const username = ref(""); const password = ref(""); const rememberPassword = ref(false); const loginMode = ref<"password" | "token">("password"); const tokenSessionId = ref(""); const message = ref(""); const isLoading = ref(false); const isLoggedIn = ref(false); const isMonitoring = ref(false); const isPostponed = ref(false); const autoStartEnabled = ref(false); const logs = ref([]); const ticketCounts = ref({ pending: 0, stayclose: 0, confirm: 0, workOrderCount: 0, lastCheck: "", }); const networkStatus = ref({ is_connected: false, api_reachable: false, response_time: null as number | null, last_check: "", }); const config = ref({ check_interval: 60, auto_start: false, auto_monitor: false, silent_start: false, show_notifications: true, notification_sound: true, network_timeout: 30, close_action: "ask" as "ask" | "minimize" | "close", handle_pause_minutes: 20, }); const showCloseDialog = ref(false); const postponeResumeTime = ref(null); 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); // ===== 内部状态 ===== let sessionId = ""; let userGid = ""; let userName = ""; let monitorTimer: ReturnType | null = null; let postponeTimer: ReturnType | null = null; let unlistenNotification: UnlistenFn | null = null; let unlistenClose: UnlistenFn | null = null; let pendingUpdate: Update | null = null; let trayIconInstance: TrayIcon | null = null; let cachedSvgImg: HTMLImageElement | null = null; // ===== 工具函数 ===== function formatTime(date: Date): string { return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); } function addLog(level: string, msg: string, category: string) { logs.value.push({ timestamp: formatTime(new Date()), level, message: msg, category }); if (logs.value.length > 1000) logs.value.shift(); } function clearLogs() { logs.value = []; message.value = "日志已清除"; } function clearMessage() { message.value = ""; } // ===== 通知声音 ===== function playNotificationSound() { if (!config.value.notification_sound) return; try { const ctx = new AudioContext(); const oscillator = ctx.createOscillator(); const gain = ctx.createGain(); oscillator.connect(gain); gain.connect(ctx.destination); oscillator.frequency.setValueAtTime(880, ctx.currentTime); oscillator.frequency.setValueAtTime(660, ctx.currentTime + 0.15); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.4); oscillator.start(ctx.currentTime); oscillator.stop(ctx.currentTime + 0.4); } catch { /* 静默失败 */ } } // ===== 通知 ===== async function notify(title: string, body: string) { if (!config.value.show_notifications) return; let granted = await isPermissionGranted(); if (!granted) { const permission = await requestPermission(); granted = permission === "granted"; } if (granted) { try { await invoke("send_clickable_notification", { title, body }); playNotificationSound(); addLog("INFO", `通知: ${title} - ${body}`, "NOTIFICATION"); } catch (e: any) { addLog("ERROR", `发送通知失败: ${e}`, "NOTIFICATION"); } } } // ===== API ===== async function apiLogin(): Promise { try { const body = JSON.stringify({ Account: username.value, PassWord: password.value }); const resp = await fetch(LOGIN_URL, { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": String(new TextEncoder().encode(body).length), Host: "crm.yunvip123.com", "Cache-Control": "no-cache", Cookie: "", }, body, credentials: "omit", }); const setCookie = resp.headers.get("set-cookie") || ""; const match = setCookie.match(/ASP\.NET_SessionId=([^;]+)/); if (match) sessionId = match[1]; addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN"); const data = await resp.json(); if (data.success && data.data) { userGid = data.data.GID; userName = data.data.SU_UserName || "用户"; if (!sessionId) { addLog("WARN", "未能从响应头提取SessionId", "LOGIN"); } isLoggedIn.value = true; addLog("INFO", `登录成功,用户: ${userName}, GID: ${userGid}`, "LOGIN"); return true; } else { addLog("ERROR", `登录失败: ${data.msg}`, "LOGIN"); return false; } } catch (e: any) { addLog("ERROR", `登录请求失败: ${e.message || e}`, "LOGIN"); return false; } } async function apiCheckWorkOrders(): Promise { try { const resp = await fetch(WORK_ORDER_URL, { method: "POST", headers: { "Content-Type": "application/json", Cookie: `ASP.NET_SessionId=${sessionId}`, }, body: JSON.stringify({ isSelect: 8, isShow: 3, IsExport: 0, PageIndex: 1, PageSize: 20, }), }); const result = await resp.json(); if (result.success && result.data) { const count = result.data.DataCount || 0; addLog("INFO", `待审核检查完成 - 数据条数: ${count}`, "SCHEDULER"); return count; } else { addLog("WARN", `待审核 API 返回异常: ${result.msg}`, "SCHEDULER"); return 0; } } catch (e: any) { addLog("ERROR", `待审核 API 检查失败: ${e.message || e}`, "SCHEDULER"); return 0; } } async function apiCheckStatus(isRetry = false): Promise { if (!userGid && loginMode.value === "password") { addLog("WARN", "会话已失效,尝试重新登录", "SCHEDULER"); if (!isRetry) await tryReLogin(); return; } try { const body: Record = {}; 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(); 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(",")); } } else { addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER"); if (!isRetry && isSessionExpiredMsg(result.msg)) { await tryReLogin(); } } } catch (e: any) { addLog("ERROR", `API 检查失败: ${e.message || e}`, "SCHEDULER"); } } function isSessionExpiredMsg(msg: string | undefined): boolean { if (!msg) return false; const keywords = ["session", "expired", "unauthorized", "登录", "过期", "失效", "超时"]; const lower = msg.toLowerCase(); return keywords.some((k) => lower.includes(k)); } async function tryReLogin(): Promise { if (loginMode.value === "token") { addLog("ERROR", "Token 已失效,监测已停止,请更换新的 Token 后重新开始", "SESSION"); message.value = "Token 已失效,监测已停止"; stopMonitoring(); return; } if (!username.value || !password.value) { addLog("WARN", "无法自动重登录:缺少凭据", "SESSION"); return; } addLog("INFO", "会话可能已过期,正在自动重新登录...", "SESSION"); const ok = await apiLogin(); if (ok) { addLog("INFO", "自动重新登录成功,继续检查", "SESSION"); await apiCheckStatus(true); } else { addLog("ERROR", "自动重新登录失败,请手动重新登录", "SESSION"); message.value = "会话已过期且自动重登录失败,请手动操作"; } } // ===== 网络检测 ===== async function checkNetworkStatus() { isLoading.value = true; let isConnected = false; let apiReachable = false; let responseTime: number | null = null; try { const resp = await fetch("https://www.baidu.com", { method: "GET", connectTimeout: config.value.network_timeout * 1000, }); isConnected = resp.ok; } catch { /* 网络不通 */ } if (isConnected) { try { const apiStart = Date.now(); const resp = await fetch(LOGIN_URL, { method: "HEAD", connectTimeout: config.value.network_timeout * 1000, }); responseTime = Date.now() - apiStart; apiReachable = resp.status < 500; } catch { /* API 不可达 */ } } networkStatus.value = { is_connected: isConnected, api_reachable: apiReachable, response_time: responseTime, last_check: formatTime(new Date()), }; addLog("INFO", `网络检查完成 - 连接: ${isConnected}, API可达: ${apiReachable}`, "NETWORK"); message.value = "网络状态检查完成"; isLoading.value = false; } // ===== 凭据存储 ===== function loadCredentials() { try { const saved = localStorage.getItem("crm_credentials"); if (saved) { const data = JSON.parse(saved); username.value = data.username || ""; password.value = data.password || ""; rememberPassword.value = data.remember || false; loginMode.value = data.loginMode || "password"; tokenSessionId.value = data.tokenSessionId || ""; addLog("INFO", "已加载保存的凭据", "CONFIG"); } } catch (e: any) { addLog("ERROR", `加载凭据失败: ${e.message || e}`, "CONFIG"); } } function saveCredentials() { try { if (rememberPassword.value) { localStorage.setItem( "crm_credentials", JSON.stringify({ username: username.value, password: password.value, remember: true, loginMode: loginMode.value, tokenSessionId: tokenSessionId.value, }) ); addLog("INFO", "凭据已保存到本地", "CONFIG"); } else { localStorage.removeItem("crm_credentials"); addLog("INFO", "已清除本地凭据", "CONFIG"); } } catch (e: any) { addLog("ERROR", `保存凭据失败: ${e.message || e}`, "CONFIG"); } } function loadConfig() { try { const saved = localStorage.getItem("crm_config"); if (saved) { const data = JSON.parse(saved); config.value = { ...config.value, ...data }; addLog("INFO", "已加载保存的配置", "CONFIG"); } } catch (e: any) { addLog("ERROR", `加载配置失败: ${e.message || e}`, "CONFIG"); } } function saveConfigToStorage() { try { localStorage.setItem("crm_config", JSON.stringify(config.value)); } catch { /* ignore */ } } function saveSettings() { if (loginMode.value === "password") { if (!username.value.trim() || !password.value.trim()) { message.value = "请输入用户名和密码"; return; } } else { if (!tokenSessionId.value.trim()) { message.value = "请输入 Token (ASP.NET_SessionId)"; return; } } saveCredentials(); message.value = "设置保存成功"; addLog("INFO", "用户凭据已保存", "CONFIG"); } function exportLogs() { if (logs.value.length === 0) { message.value = "暂无日志可导出"; return; } const lines = logs.value.map( (l) => `[${l.timestamp}] [${l.level}] [${l.category}] ${l.message}` ); const blob = new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `monitor-logs-${formatTime(new Date()).replaceAll(/[: ]/g, "-")}.txt`; a.click(); URL.revokeObjectURL(url); addLog("INFO", `已导出 ${logs.value.length} 条日志`, "SYSTEM"); message.value = "日志导出成功"; } // ===== 监测控制 ===== async function startMonitoring() { if (loginMode.value === "password") { if (!username.value.trim() || !password.value.trim()) { message.value = "请先设置用户名和密码"; return; } isLoading.value = true; const ok = await apiLogin(); if (!ok) { message.value = "登录失败,无法开始监测"; isLoading.value = false; return; } } else { if (!tokenSessionId.value.trim()) { message.value = "请先输入 Token (ASP.NET_SessionId)"; return; } isLoading.value = true; sessionId = tokenSessionId.value.trim(); isLoggedIn.value = true; addLog("INFO", `使用 Token 登录,SessionId: ${sessionId.substring(0, 8)}...`, "LOGIN"); } isMonitoring.value = true; await apiCheckStatus(); monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000); addLog("INFO", "监测已启动", "MONITOR"); message.value = loginMode.value === "password" ? `监测已开始,欢迎 ${userName}!` : "监测已开始(Token 登录)"; isLoading.value = false; } function stopMonitoring() { if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; } if (postponeTimer) { clearTimeout(postponeTimer); postponeTimer = null; } isPostponed.value = false; postponeResumeTime.value = null; isMonitoring.value = false; isLoggedIn.value = false; sessionId = ""; userGid = ""; addLog("INFO", "监测已停止", "MONITOR"); message.value = "监测已停止"; } function postponeMonitoring() { if (!isMonitoring.value) return; if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; } if (postponeTimer) { clearTimeout(postponeTimer); } isPostponed.value = true; postponeResumeTime.value = Date.now() + 60 * 60 * 1000; addLog("INFO", "监测已推迟1小时", "MONITOR"); message.value = "监测已推迟1小时,将在1小时后自动恢复"; postponeTimer = setTimeout(() => { isPostponed.value = false; postponeResumeTime.value = null; postponeTimer = null; apiCheckStatus(); monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000); addLog("INFO", "推迟结束,监测已自动恢复", "MONITOR"); message.value = "推迟结束,监测已自动恢复"; }, 60 * 60 * 1000); } function pauseForHandle() { if (!isMonitoring.value) return; if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; } if (postponeTimer) { clearTimeout(postponeTimer); } isPostponed.value = true; const minutes = config.value.handle_pause_minutes; postponeResumeTime.value = Date.now() + minutes * 60 * 1000; addLog("INFO", `点击"去处理",监测已暂停 ${minutes} 分钟`, "MONITOR"); message.value = `监测已暂停 ${minutes} 分钟,将在 ${minutes} 分钟后自动恢复`; postponeTimer = setTimeout(() => { isPostponed.value = false; postponeResumeTime.value = null; postponeTimer = null; apiCheckStatus(); monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000); addLog("INFO", "暂停结束,监测已自动恢复", "MONITOR"); message.value = "暂停结束,监测已自动恢复"; }, minutes * 60 * 1000); } function resumeMonitoring() { if (!isMonitoring.value || !isPostponed.value) return; if (postponeTimer) { clearTimeout(postponeTimer); postponeTimer = null; } isPostponed.value = false; postponeResumeTime.value = null; apiCheckStatus(); monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000); addLog("INFO", "手动恢复监测", "MONITOR"); message.value = "监测已恢复"; } async function manualCheck() { if (!isLoggedIn.value) { message.value = "请先开始监测(登录)"; return; } isLoading.value = true; await apiCheckStatus(); message.value = "手动检查完成"; isLoading.value = false; } async function testNotification() { await notify("工单提醒(测试)", "待处理工单: 3,待关闭工单: 1,待确认工单: 2"); } // ===== 配置更新 ===== async function checkForUpdates() { try { addLog("INFO", "正在检查应用更新...", "UPDATER"); const update = await check(); if (update) { pendingUpdate = update; updateInfo.value = { version: update.version, notes: update.body || "", date: update.date || "", }; showUpdateDialog.value = true; addLog("INFO", `发现新版本: ${update.version}`, "UPDATER"); } else { addLog("INFO", "当前已是最新版本", "UPDATER"); } } catch (e: any) { addLog("WARN", `检查更新失败: ${e.message || e}`, "UPDATER"); } } async function confirmUpdate() { if (!pendingUpdate) return; isUpdating.value = true; updateProgress.value = { total: 0, downloaded: 0 }; try { addLog("INFO", "开始下载更新...", "UPDATER"); await pendingUpdate.downloadAndInstall((event) => { if (event.event === "Started" && event.data.contentLength) { updateProgress.value = { total: event.data.contentLength, downloaded: 0 }; } else if (event.event === "Progress") { if (updateProgress.value) { updateProgress.value.downloaded += event.data.chunkLength; } } else if (event.event === "Finished") { addLog("INFO", "更新下载完成,即将重启...", "UPDATER"); } }); await relaunch(); } catch (e: any) { addLog("ERROR", `更新安装失败: ${e.message || e}`, "UPDATER"); message.value = "更新安装失败"; isUpdating.value = false; updateProgress.value = null; } } function dismissUpdate() { showUpdateDialog.value = false; pendingUpdate = null; updateInfo.value = null; } async function updateConfig() { isLoading.value = true; try { if (config.value.auto_start) await enableAutostart(); else await disableAutostart(); autoStartEnabled.value = config.value.auto_start; addLog("INFO", `自启动设置: ${config.value.auto_start ? "启用" : "禁用"}`, "CONFIG"); } catch (e: any) { addLog("ERROR", `自启动设置失败: ${e.message || e}`, "CONFIG"); } if (isMonitoring.value && monitorTimer) { clearInterval(monitorTimer); monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000); addLog("INFO", `检查间隔已更新为: ${config.value.check_interval}秒`, "CONFIG"); } saveConfigToStorage(); message.value = "配置更新成功"; isLoading.value = false; } // ===== 系统托盘 ===== /** 加载 SVG 图片(带缓存) */ async function loadSvgImage(): Promise { if (cachedSvgImg) return cachedSvgImg; return new Promise((resolve, reject) => { const img = new window.Image(); img.onload = () => { cachedSvgImg = img; resolve(img); }; img.onerror = reject; img.src = "/gong.svg"; }); } /** 加载 SVG → Canvas 渲染 → 像素替色 → 返回 RGBA 数据 */ async function makeTrayIconRGBA(r: number, g: number, b: number, size = 32): Promise { const canvas = document.createElement("canvas"); canvas.width = size; canvas.height = size; const ctx = canvas.getContext("2d")!; try { const svgImg = await loadSvgImage(); ctx.drawImage(svgImg, 0, 0, size, size); const imageData = ctx.getImageData(0, 0, size, size); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { // 根据亮度判断:暗像素(文字) → 白色,亮像素(背景) → 状态色 const brightness = (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114); if (brightness < 128) { data[i] = data[i + 1] = data[i + 2] = 255; } else { data[i] = r; data[i + 1] = g; data[i + 2] = b; } data[i + 3] = 255; } ctx.putImageData(imageData, 0, 0); } catch { // SVG 加载失败,回退到纯 Canvas 绘制 addLog("WARN", "SVG 图标加载失败,使用回退方案", "SYSTEM"); ctx.fillStyle = `rgb(${r},${g},${b})`; ctx.fillRect(0, 0, size, size); ctx.fillStyle = "#ffffff"; ctx.font = `bold ${Math.round(size * 0.62)}px "Microsoft YaHei","SimHei",sans-serif`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("工", size / 2, size / 2 + 1); } const finalData = ctx.getImageData(0, 0, size, size); return new Uint8Array(finalData.data.buffer); } /** 根据当前应用状态更新托盘图标和提示文字 */ async function updateTrayIcon() { if (!trayIconInstance) return; try { const hasTickets = ticketCounts.value.pending > 0 || ticketCounts.value.stayclose > 0 || ticketCounts.value.confirm > 0 || ticketCounts.value.workOrderCount > 0; let color: [number, number, number]; let tooltip: string; if (!isMonitoring.value) { // 未开始监测 — 灰色 color = [158, 158, 158]; tooltip = `${APP_NAME} - 未开始`; } else if (isPostponed.value) { // 已暂停/推迟 — 橙色 color = [255, 152, 0]; tooltip = `${APP_NAME} - 已暂停`; } else if (hasTickets) { // 有待处理工单 — 红色 const total = ticketCounts.value.pending + ticketCounts.value.stayclose + ticketCounts.value.confirm + ticketCounts.value.workOrderCount; color = [244, 67, 54]; tooltip = `${APP_NAME} - 有 ${total} 条工单待处理!`; } else { // 监测中,无异常 — 绿色 color = [76, 175, 80]; tooltip = `${APP_NAME} - 监测中`; } 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"); } } async function buildTrayMenu() { const mainWindow = getCurrentWindow(); return await Menu.new({ items: [ await MenuItem.new({ id: "show", text: "显示面板", action: async () => { await mainWindow.unminimize(); await mainWindow.show(); await mainWindow.setFocus(); }, }), await MenuItem.new({ id: "toggle_monitor", text: isMonitoring.value ? "停止监测" : "开始监测", action: async () => { if (isMonitoring.value) { stopMonitoring(); } else { await mainWindow.unminimize(); await mainWindow.show(); await mainWindow.setFocus(); await startMonitoring(); } }, }), await MenuItem.new({ id: "open_website", text: "打开工单网站", action: async () => { await openUrl("https://crm.yunvip123.com"); }, }), await MenuItem.new({ id: "quit", text: "退出", action: async () => { await mainWindow.destroy(); }, }), ], }); } async function updateTrayMenu() { if (!trayIconInstance) return; try { const menu = await buildTrayMenu(); await trayIconInstance.setMenu(menu); } catch (e: any) { addLog("WARN", `更新托盘菜单失败: ${e.message || e}`, "SYSTEM"); } } async function setupTray() { try { const menu = await buildTrayMenu(); // 初始状态:灰色(未开始) const icon = await Image.new(await makeTrayIconRGBA(158, 158, 158), 32, 32); trayIconInstance = await TrayIcon.new({ icon, tooltip: `${APP_NAME} - 未开始`, menu, menuOnLeftClick: false, action: async (event) => { if (event.type === "Click" && event.button === "Left") { const win = getCurrentWindow(); await win.unminimize(); await win.show(); await win.setFocus(); } }, }); addLog("INFO", "系统托盘创建成功", "SYSTEM"); } catch (e: any) { addLog("ERROR", `系统托盘创建失败: ${e.message || e}`, "SYSTEM"); } } async function minimizeToTray() { try { await getCurrentWindow().hide(); } catch (e: any) { message.value = `最小化失败: ${e}`; } } async function handleCloseAction(action: "minimize" | "close", remember: boolean) { showCloseDialog.value = false; if (remember) { config.value.close_action = action; saveConfigToStorage(); addLog("INFO", `关闭行为已设为: ${action === "minimize" ? "最小化到托盘" : "直接退出"}`, "SYSTEM"); } if (action === "minimize") { await getCurrentWindow().hide(); } else { await getCurrentWindow().destroy(); } } // ===== 生命周期(由 App.vue 调用)===== async function initialize() { loadConfig(); loadCredentials(); await setupTray(); try { autoStartEnabled.value = await isAutostartEnabled(); config.value.auto_start = autoStartEnabled.value; } catch { /* ignore */ } unlistenNotification = await listen("notification-action", (event) => { if (event.payload === "postpone_1h") postponeMonitoring(); else if (event.payload === "go_handle") pauseForHandle(); }); unlistenClose = await listen("close-requested", async () => { const action = config.value.close_action; if (action === "minimize") { await getCurrentWindow().hide(); } else if (action === "close") { await getCurrentWindow().destroy(); } else { showCloseDialog.value = true; } }); addLog("INFO", "应用程序启动完成", "SYSTEM"); if (config.value.silent_start) { try { await getCurrentWindow().hide(); addLog("INFO", "静默启动:窗口已隐藏到托盘", "SYSTEM"); } catch { /* ignore */ } } checkNetworkStatus(); checkForUpdates(); if (config.value.auto_monitor && rememberPassword.value) { const canAutoStart = loginMode.value === "token" ? !!tokenSessionId.value : !!(username.value && password.value); if (canAutoStart) { addLog("INFO", "自动登录并开始监测...", "SYSTEM"); await startMonitoring(); } } } function cleanup() { if (monitorTimer) clearInterval(monitorTimer); if (postponeTimer) clearTimeout(postponeTimer); unlistenNotification?.(); unlistenClose?.(); } // 监听状态变化,自动更新托盘图标和菜单 watch([isMonitoring, isPostponed, ticketCounts], () => { updateTrayIcon(); updateTrayMenu(); }, { deep: true }); return { // 状态 username, password, rememberPassword, loginMode, tokenSessionId, message, isLoading, isLoggedIn, isMonitoring, isPostponed, postponeResumeTime, autoStartEnabled, logs, networkStatus, config, ticketCounts, showCloseDialog, showUpdateDialog, updateInfo, updateProgress, isUpdating, // 方法 initialize, cleanup, addLog, clearLogs, clearMessage, saveSettings, exportLogs, startMonitoring, stopMonitoring, resumeMonitoring, manualCheck, testNotification, checkNetworkStatus, updateConfig, minimizeToTray, handleCloseAction, checkForUpdates, confirmUpdate, dismissUpdate, }; }); if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useMonitorStore, import.meta.hot)); }