import { defineStore, acceptHMRUpdate, storeToRefs } from "pinia"; import { ref, watch } from "vue"; import { login, checkWorkOrders, checkStatus } from "@/api/monitor"; import { useNotification } from "@/composables/useNotification"; import { useTray } from "@/composables/useTray"; import { useAppStore } from "@/stores/app"; import { useLogStore } from "@/stores/log"; import { formatTime, getErrorMessage } from "@/utils/common"; import type { TicketCounts, CheckStatusResponse } from "@/api/types"; export const useMonitorStore = defineStore("monitor", () => { // ===== 响应式数据 ===== const username = ref(""); // 登录用户名 const password = ref(""); // 登录密码 const rememberPassword = ref(false); // 是否记住密码 const loginMode = ref<"password" | "token">("password"); // 登录模式:密码 或 Token const tokenSessionId = ref(""); // Token 模式下手动填入的 ASP.NET_SessionId const isLoggedIn = ref(false); // 是否已完成登录 const isMonitoring = ref(false); // 是否正在轮询监测 const isPostponed = ref(false); // 监测是否处于推迟/暂停状态 const ticketCounts = ref({ pending: 0, // 待处理工单数 stayclose: 0, // 待关闭工单数 confirm: 0, // 待确认工单数 workOrderCount: 0, // 待审核工单数 lastCheck: "", // 最近一次检查时间 }); const postponeResumeTime = ref(null); // 推迟/暂停结束的时间戳(ms) // ===== 应用配置(来自 appStore)===== const appStore = useAppStore(); const { config } = storeToRefs(appStore); // ===== 内部状态 ===== let sessionId = ""; let userGid = ""; let userName = ""; let monitorTimer: ReturnType | null = null; let postponeTimer: ReturnType | null = null; // ===== 日志管理 ===== const { addLog } = useLogStore(); // ===== 通知 ===== const { notify } = useNotification(); // ===== API 请求 ===== /** 使用用户名/密码登录,成功后提取并存储 SessionId,返回是否成功 */ async function apiLogin(): Promise { try { const { data, sessionId: newSessionId } = await login( username.value, password.value, ); if (newSessionId) sessionId = newSessionId; addLog("DEBUG", `SessionId: ${sessionId || "(未提取)"}`, "LOGIN"); 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: unknown) { addLog("ERROR", `登录请求失败: ${getErrorMessage(e)}`, "LOGIN"); return false; } } /** 查询待审核工单数量(分页接口,取 DataCount 字段) */ async function apiCheckWorkOrders(): Promise { try { const result = await checkWorkOrders(sessionId); 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: unknown) { addLog( "ERROR", `待审核 API 检查失败: ${getErrorMessage(e)}`, "SCHEDULER", ); return 0; } } /** 发送工单状态检查请求 */ async function performStatusCheckRequest() { return await checkStatus(sessionId, userGid); } /** * 处理检查到的工单数据并更新状态 * @param data 接口返回的原始计数数据 */ async function updateTicketCountsAndLog(data: CheckStatusResponse) { 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 { // 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 { // 3. 处理业务失败 addLog("WARN", `API 返回异常: ${result.msg}`, "SCHEDULER"); if (!isRetry && isSessionExpiredMsg(result.msg)) { await tryReLogin(); } } } catch (e: unknown) { // 4. 处理网络/系统异常 addLog("ERROR", `API 检查失败: ${getErrorMessage(e)}`, "SCHEDULER"); } } /** 判断 API 返回消息是否属于会话过期类错误 */ 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)); } /** 会话过期后自动重新登录;Token 模式下直接停止监测 */ async function tryReLogin(): Promise { if (loginMode.value === "token") { addLog( "ERROR", "Token 已失效,监测已停止,请更换新的 Token 后重新开始", "SESSION", ); appStore.setMessage("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"); appStore.setMessage("会话已过期且自动重登录失败,请手动操作"); } } // ===== 认证与凭据 ===== /** 从 localStorage 加载已保存的登录凭据 */ 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: unknown) { addLog("ERROR", `加载凭据失败: ${getErrorMessage(e)}`, "CONFIG"); } } /** 根据 rememberPassword 决定写入或清除 localStorage 中的凭据 */ 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: unknown) { addLog("ERROR", `保存凭据失败: ${getErrorMessage(e)}`, "CONFIG"); } } /** 校验凭据不为空后保存,同时记录日志并更新状态消息 */ function saveSettings() { if (loginMode.value === "password") { if (!username.value.trim() || !password.value.trim()) { appStore.setMessage("请输入用户名和密码"); return; } } else { if (!tokenSessionId.value.trim()) { appStore.setMessage("请输入 Token (ASP.NET_SessionId)"); return; } } saveCredentials(); appStore.setMessage("设置保存成功"); addLog("INFO", "用户凭据已保存", "CONFIG"); } // ===== 监测调度 ===== /** 登录(密码模式)或设置 Token(Token 模式),然后启动定时轮询 */ async function startMonitoring() { if (loginMode.value === "password") { if (!username.value.trim() || !password.value.trim()) { appStore.setMessage("请先设置用户名和密码"); return; } const ok = await apiLogin(); if (!ok) { appStore.setMessage("登录失败,无法开始监测"); return; } } else { if (!tokenSessionId.value.trim()) { appStore.setMessage("请先输入 Token (ASP.NET_SessionId)"); return; } 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"); appStore.setMessage( loginMode.value === "password" ? `监测已开始,欢迎 ${userName}!` : "监测已开始(Token 登录)", ); } /** 清除所有定时器,重置登录与监测状态 */ 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"); appStore.setMessage("监测已停止"); } /** 将监测推迟 1 小时,到时自动恢复 */ 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"); appStore.setMessage("监测已推迟1小时,将在1小时后自动恢复"); postponeTimer = setTimeout( () => { isPostponed.value = false; postponeResumeTime.value = null; postponeTimer = null; apiCheckStatus(); monitorTimer = setInterval( () => apiCheckStatus(), config.value.check_interval * 1000, ); addLog("INFO", "推迟结束,监测已自动恢复", "MONITOR"); appStore.setMessage("推迟结束,监测已自动恢复"); }, 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"); appStore.setMessage( `监测已暂停 ${minutes} 分钟,将在 ${minutes} 分钟后自动恢复`, ); postponeTimer = setTimeout( () => { isPostponed.value = false; postponeResumeTime.value = null; postponeTimer = null; apiCheckStatus(); monitorTimer = setInterval( () => apiCheckStatus(), config.value.check_interval * 1000, ); addLog("INFO", "暂停结束,监测已自动恢复", "MONITOR"); appStore.setMessage("暂停结束,监测已自动恢复"); }, 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"); appStore.setMessage("监测已恢复"); } /** 手动触发一次工单状态检查 */ async function manualCheck() { if (!isLoggedIn.value) { appStore.setMessage("请先开始监测(登录)"); return; } await apiCheckStatus(); appStore.setMessage("手动检查完成"); } /** 发送一条测试通知,用于验证通知权限和声音配置 */ async function testNotification() { await notify( "工单提醒(测试)", "待处理工单: 3,待关闭工单: 1,待确认工单: 2", ); } // ===== 系统托盘 ===== const { setupTray, updateTrayIcon, updateTrayMenu, minimizeToTray } = useTray( isMonitoring, isPostponed, ticketCounts, startMonitoring, stopMonitoring, ); // 检查间隔变化时重启定时器 watch( () => config.value.check_interval, (interval) => { if (isMonitoring.value && monitorTimer) { clearInterval(monitorTimer); monitorTimer = setInterval(() => apiCheckStatus(), interval * 1000); addLog("INFO", `检查间隔已更新为: ${interval}秒`, "CONFIG"); } }, ); // 监听状态变化,自动更新托盘图标和菜单 watch( [isMonitoring, isPostponed, ticketCounts], () => { updateTrayIcon(); updateTrayMenu(); }, { deep: true }, ); return { // 状态 username, password, rememberPassword, loginMode, tokenSessionId, isLoggedIn, isMonitoring, isPostponed, postponeResumeTime, ticketCounts, // 方法 saveSettings, loadCredentials, setupTray, startMonitoring, stopMonitoring, postponeMonitoring, pauseForHandle, resumeMonitoring, manualCheck, testNotification, minimizeToTray, }; }); if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useMonitorStore, import.meta.hot)); }