- 监控
+ 监测
diff --git a/src/App2.vue b/src/App2.vue
deleted file mode 100644
index 9686596..0000000
--- a/src/App2.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
登录设置
-
-
-
-
-
-
-
-
-
-
-
-
正在监控...
-
程序已最小化到系统托盘。
-
-
-
-
-
\ No newline at end of file
diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue
index 0d38e1e..a2487fe 100644
--- a/src/components/LoginForm.vue
+++ b/src/components/LoginForm.vue
@@ -55,10 +55,10 @@ const store = useMonitorStore();
保存设置
- 开始监控
+ 开始监测
- 停止监控
+ 停止监测
diff --git a/src/components/MonitorControl.vue b/src/components/MonitorControl.vue
index f90430c..e6ad3f1 100644
--- a/src/components/MonitorControl.vue
+++ b/src/components/MonitorControl.vue
@@ -1,4 +1,7 @@
@@ -23,12 +54,22 @@ const store = useMonitorStore();
+
上次检查:{{ store.ticketCounts.lastCheck }}
+
+
监测已暂停,
{{ countdown }} 后自动恢复
+
+ 立即恢复
+
+
+
@@ -56,7 +97,7 @@ const store = useMonitorStore();
}
.ticket-dashboard {
display: grid;
- grid-template-columns: repeat(3, 1fr);
+ grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 16px;
}
@@ -77,9 +118,28 @@ const store = useMonitorStore();
background: #e6f4ff;
border: 1px solid #91caff;
}
+.ticket-card--workorder {
+ background: #f9f0ff;
+ border: 1px solid #d3adf7;
+}
.last-check {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
+.postpone-countdown {
+ font-size: 12px;
+ color: #874d00;
+ background: #fff7e6;
+ border: 1px solid #ffd591;
+ border-radius: 6px;
+ padding: 6px 10px;
+ margin-top: 6px;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+}
+.countdown-time {
+ font-weight: 600;
+}
diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue
index d55efc0..0ed2ccc 100644
--- a/src/components/SettingsPanel.vue
+++ b/src/components/SettingsPanel.vue
@@ -1,5 +1,5 @@
@@ -37,34 +48,38 @@ const intervalTime = computed({
style="width: 100%"
/>
-
-
-
-
-
-
-
-
-
- 需同时开启「记住密码」
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ 启动后自动监测
+
需同时开启「记住密码」
+
+
+
+
+
+
+
+
({
style="width: 100%"
/>
+
每次询问
@@ -80,11 +96,6 @@ const intervalTime = computed({
直接退出
-
-
- 保存配置
-
-
@@ -92,6 +103,26 @@ const intervalTime = computed({
.form-hint {
font-size: 12px;
color: #999;
- margin-top: 4px;
+ margin-top: 2px;
+}
+
+.switch-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 10px;
+ margin: 0 -10px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background-color 0.15s;
+}
+
+.switch-row:hover {
+ background-color: #f5f5f5;
+}
+
+.switch-label {
+ flex: 1;
+ font-size: 14px;
}
diff --git a/src/components/StatusHeader.vue b/src/components/StatusHeader.vue
index 40769c9..a0b31da 100644
--- a/src/components/StatusHeader.vue
+++ b/src/components/StatusHeader.vue
@@ -32,7 +32,7 @@ function getNetworkStatusText() {
- {{ store.isPostponed ? "已推迟1小时" : "监控中" }}
+ {{ store.isPostponed ? "已推迟1小时" : "监测中" }}
([]);
const ticketCounts = ref({
- pending: 0, stayclose: 0, confirm: 0, lastCheck: "",
+ pending: 0, stayclose: 0, confirm: 0, workOrderCount: 0, lastCheck: "",
});
const networkStatus = ref({
is_connected: false,
@@ -172,6 +174,37 @@ export function useMonitor() {
}
}
+ 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) {
addLog("WARN", "会话已失效,尝试重新登录", "SCHEDULER");
@@ -193,16 +226,18 @@ export function useMonitor() {
const pending = d.PendingCount || 0;
const stayclose = d.StaycloseCount || 0;
const confirm = d.ConfirmCount || 0;
+ const workOrderCount = await apiCheckWorkOrders();
ticketCounts.value = {
- pending, stayclose, confirm,
+ pending, stayclose, confirm, workOrderCount,
lastCheck: formatTime(new Date()),
};
- addLog("INFO", `检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}`, "SCHEDULER");
- if (pending > 0 || stayclose > 0 || confirm > 0) {
+ 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 {
@@ -362,7 +397,7 @@ export function useMonitor() {
message.value = "日志导出成功";
}
- // ===== 监控控制 =====
+ // ===== 监测控制 =====
async function startMonitoring() {
if (!username.value.trim() || !password.value.trim()) {
message.value = "请先设置用户名和密码";
@@ -371,15 +406,15 @@ export function useMonitor() {
isLoading.value = true;
const ok = await apiLogin();
if (!ok) {
- message.value = "登录失败,无法开始监控";
+ message.value = "登录失败,无法开始监测";
isLoading.value = false;
return;
}
isMonitoring.value = true;
await apiCheckStatus();
monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000);
- addLog("INFO", "监控已启动", "MONITOR");
- message.value = `监控已开始,欢迎 ${userName}!`;
+ addLog("INFO", "监测已启动", "MONITOR");
+ message.value = `监测已开始,欢迎 ${userName}!`;
isLoading.value = false;
}
@@ -391,8 +426,8 @@ export function useMonitor() {
isLoggedIn.value = false;
sessionId = "";
userGid = "";
- addLog("INFO", "监控已停止", "MONITOR");
- message.value = "监控已停止";
+ addLog("INFO", "监测已停止", "MONITOR");
+ message.value = "监测已停止";
}
function postponeMonitoring() {
@@ -400,20 +435,20 @@ export function useMonitor() {
if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; }
if (postponeTimer) { clearTimeout(postponeTimer); }
isPostponed.value = true;
- addLog("INFO", "监控已推迟1小时", "MONITOR");
- message.value = "监控已推迟1小时,将在1小时后自动恢复";
+ addLog("INFO", "监测已推迟1小时", "MONITOR");
+ message.value = "监测已推迟1小时,将在1小时后自动恢复";
postponeTimer = setTimeout(() => {
isPostponed.value = false;
postponeTimer = null;
apiCheckStatus();
monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000);
- addLog("INFO", "推迟结束,监控已自动恢复", "MONITOR");
- message.value = "推迟结束,监控已自动恢复";
+ addLog("INFO", "推迟结束,监测已自动恢复", "MONITOR");
+ message.value = "推迟结束,监测已自动恢复";
}, 60 * 60 * 1000);
}
async function manualCheck() {
- if (!isLoggedIn.value) { message.value = "请先开始监控(登录)"; return; }
+ if (!isLoggedIn.value) { message.value = "请先开始监测(登录)"; return; }
isLoading.value = true;
await apiCheckStatus();
message.value = "手动检查完成";
@@ -522,7 +557,7 @@ export function useMonitor() {
checkNetworkStatus();
if (config.value.auto_monitor && rememberPassword.value && username.value && password.value) {
- addLog("INFO", "自动登录并开始监控...", "SYSTEM");
+ addLog("INFO", "自动登录并开始监测...", "SYSTEM");
await startMonitoring();
}
});
diff --git a/src/stores/monitor.ts b/src/stores/monitor.ts
index 91ef6a7..3ece053 100644
--- a/src/stores/monitor.ts
+++ b/src/stores/monitor.ts
@@ -1,5 +1,6 @@
-import { ref } from "vue";
-import { defineStore } from "pinia";
+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";
@@ -16,12 +17,13 @@ import {
} from "@tauri-apps/plugin-autostart";
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { resolveResource } from "@tauri-apps/api/path";
+import { openUrl } from "@tauri-apps/plugin-opener";
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;
@@ -34,6 +36,7 @@ export interface TicketCounts {
pending: number;
stayclose: number;
confirm: number;
+ workOrderCount: number;
lastCheck: string;
}
@@ -52,7 +55,7 @@ export const useMonitorStore = defineStore("monitor", () => {
const autoStartEnabled = ref(false);
const logs = ref([]);
const ticketCounts = ref({
- pending: 0, stayclose: 0, confirm: 0, lastCheck: "",
+ pending: 0, stayclose: 0, confirm: 0, workOrderCount: 0, lastCheck: "",
});
const networkStatus = ref({
is_connected: false,
@@ -64,6 +67,7 @@ export const useMonitorStore = defineStore("monitor", () => {
check_interval: 60,
auto_start: false,
auto_monitor: false,
+ silent_start: false,
show_notifications: true,
notification_sound: true,
network_timeout: 30,
@@ -71,6 +75,7 @@ export const useMonitorStore = defineStore("monitor", () => {
handle_pause_minutes: 20,
});
const showCloseDialog = ref(false);
+ const postponeResumeTime = ref(null);
// ===== 内部状态 =====
let sessionId = "";
@@ -80,10 +85,11 @@ export const useMonitorStore = defineStore("monitor", () => {
let postponeTimer: ReturnType | null = null;
let unlistenNotification: UnlistenFn | null = null;
let unlistenClose: UnlistenFn | null = null;
+ let trayIconInstance: TrayIcon | null = null;
// ===== 工具函数 =====
function formatTime(date: Date): string {
- return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}`;
+ return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
}
function addLog(level: string, msg: string, category: string) {
@@ -179,6 +185,37 @@ export const useMonitorStore = defineStore("monitor", () => {
}
}
+ 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");
@@ -202,16 +239,18 @@ export const useMonitorStore = defineStore("monitor", () => {
const pending = d.PendingCount || 0;
const stayclose = d.StaycloseCount || 0;
const confirm = d.ConfirmCount || 0;
+ const workOrderCount = await apiCheckWorkOrders();
ticketCounts.value = {
- pending, stayclose, confirm,
+ pending, stayclose, confirm, workOrderCount,
lastCheck: formatTime(new Date()),
};
- addLog("INFO", `检查完成 - 待处理: ${pending}, 待关闭: ${stayclose}, 待确认: ${confirm}`, "SCHEDULER");
- if (pending > 0 || stayclose > 0 || confirm > 0) {
+ 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 {
@@ -234,8 +273,8 @@ export const useMonitorStore = defineStore("monitor", () => {
async function tryReLogin(): Promise {
if (loginMode.value === "token") {
- addLog("ERROR", "Token 已失效,监控已停止,请更换新的 Token 后重新开始", "SESSION");
- message.value = "Token 已失效,监控已停止";
+ addLog("ERROR", "Token 已失效,监测已停止,请更换新的 Token 后重新开始", "SESSION");
+ message.value = "Token 已失效,监测已停止";
stopMonitoring();
return;
}
@@ -392,7 +431,7 @@ export const useMonitorStore = defineStore("monitor", () => {
message.value = "日志导出成功";
}
- // ===== 监控控制 =====
+ // ===== 监测控制 =====
async function startMonitoring() {
if (loginMode.value === "password") {
if (!username.value.trim() || !password.value.trim()) {
@@ -402,7 +441,7 @@ export const useMonitorStore = defineStore("monitor", () => {
isLoading.value = true;
const ok = await apiLogin();
if (!ok) {
- message.value = "登录失败,无法开始监控";
+ message.value = "登录失败,无法开始监测";
isLoading.value = false;
return;
}
@@ -419,10 +458,10 @@ export const useMonitorStore = defineStore("monitor", () => {
isMonitoring.value = true;
await apiCheckStatus();
monitorTimer = setInterval(() => apiCheckStatus(), config.value.check_interval * 1000);
- addLog("INFO", "监控已启动", "MONITOR");
+ addLog("INFO", "监测已启动", "MONITOR");
message.value = loginMode.value === "password"
- ? `监控已开始,欢迎 ${userName}!`
- : "监控已开始(Token 登录)";
+ ? `监测已开始,欢迎 ${userName}!`
+ : "监测已开始(Token 登录)";
isLoading.value = false;
}
@@ -430,12 +469,13 @@ export const useMonitorStore = defineStore("monitor", () => {
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 = "监控已停止";
+ addLog("INFO", "监测已停止", "MONITOR");
+ message.value = "监测已停止";
}
function postponeMonitoring() {
@@ -443,15 +483,17 @@ export const useMonitorStore = defineStore("monitor", () => {
if (monitorTimer) { clearInterval(monitorTimer); monitorTimer = null; }
if (postponeTimer) { clearTimeout(postponeTimer); }
isPostponed.value = true;
- addLog("INFO", "监控已推迟1小时", "MONITOR");
- message.value = "监控已推迟1小时,将在1小时后自动恢复";
+ 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 = "推迟结束,监控已自动恢复";
+ addLog("INFO", "推迟结束,监测已自动恢复", "MONITOR");
+ message.value = "推迟结束,监测已自动恢复";
}, 60 * 60 * 1000);
}
@@ -461,20 +503,33 @@ export const useMonitorStore = defineStore("monitor", () => {
if (postponeTimer) { clearTimeout(postponeTimer); }
isPostponed.value = true;
const minutes = config.value.handle_pause_minutes;
- addLog("INFO", `点击"去处理",监控已暂停 ${minutes} 分钟`, "MONITOR");
- message.value = `监控已暂停 ${minutes} 分钟,将在 ${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 = "暂停结束,监控已自动恢复";
+ 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; }
+ if (!isLoggedIn.value) { message.value = "请先开始监测(登录)"; return; }
isLoading.value = true;
await apiCheckStatus();
message.value = "手动检查完成";
@@ -507,41 +562,143 @@ export const useMonitorStore = defineStore("monitor", () => {
}
// ===== 系统托盘 =====
- async function setupTray() {
+
+ /** 用 Canvas 渲染彩色圆形背景 + 白色"工"字,返回 RGBA 像素数据 */
+ function makeTrayIconRGBA(r: number, g: number, b: number, size = 32): Uint8Array {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d")!;
+
+ // 圆形背景
+ ctx.clearRect(0, 0, size, size);
+ ctx.beginPath();
+ ctx.arc(size / 2, size / 2, size / 2 - 1, 0, Math.PI * 2);
+ ctx.fillStyle = `rgb(${r},${g},${b})`;
+ ctx.fill();
+
+ // 白色"工"字
+ 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 imageData = ctx.getImageData(0, 0, size, size);
+ return new Uint8Array(imageData.data.buffer);
+ }
+
+ /** 根据当前应用状态更新托盘图标和提示文字 */
+ async function updateTrayIcon() {
+ if (!trayIconInstance) return;
try {
- const mainWindow = getCurrentWindow();
- const menu = await Menu.new({
- items: [
- await MenuItem.new({
- id: "show",
- text: "显示面板",
- action: async () => {
+ 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(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 MenuItem.new({
- id: "quit",
- text: "退出",
- action: async () => { await mainWindow.destroy(); },
- }),
- ],
- });
+ 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(); },
+ }),
+ ],
+ });
+ }
- const iconPath = await resolveResource("icons/32x32.png");
- const icon = await Image.fromPath(iconPath);
+ async function updateTrayMenu() {
+ if (!trayIconInstance) return;
+ try {
+ const menu = await buildTrayMenu();
+ await trayIconInstance.setMenu(menu);
+ } catch (e: any) {
+ addLog("WARN", `更新托盘菜单失败: ${e.message || e}`, "SYSTEM");
+ }
+ }
- await TrayIcon.new({
+ async function setupTray() {
+ try {
+ const menu = await buildTrayMenu();
+
+ // 初始状态:灰色(未开始)
+ const icon = await Image.new(makeTrayIconRGBA(158, 158, 158), 32, 32);
+
+ trayIconInstance = await TrayIcon.new({
icon,
- tooltip: APP_NAME,
+ tooltip: `${APP_NAME} - 未开始`,
menu,
menuOnLeftClick: false,
action: async (event) => {
if (event.type === "Click" && event.button === "Left") {
- await mainWindow.unminimize();
- await mainWindow.show();
- await mainWindow.setFocus();
+ const win = getCurrentWindow();
+ await win.unminimize();
+ await win.show();
+ await win.setFocus();
}
},
});
@@ -597,6 +754,14 @@ export const useMonitorStore = defineStore("monitor", () => {
}
});
addLog("INFO", "应用程序启动完成", "SYSTEM");
+
+ if (config.value.silent_start) {
+ try {
+ await getCurrentWindow().hide();
+ addLog("INFO", "静默启动:窗口已隐藏到托盘", "SYSTEM");
+ } catch { /* ignore */ }
+ }
+
checkNetworkStatus();
if (config.value.auto_monitor && rememberPassword.value) {
@@ -604,7 +769,7 @@ export const useMonitorStore = defineStore("monitor", () => {
? !!tokenSessionId.value
: !!(username.value && password.value);
if (canAutoStart) {
- addLog("INFO", "自动登录并开始监控...", "SYSTEM");
+ addLog("INFO", "自动登录并开始监测...", "SYSTEM");
await startMonitoring();
}
}
@@ -617,18 +782,28 @@ export const useMonitorStore = defineStore("monitor", () => {
unlistenClose?.();
}
+ // 监听状态变化,自动更新托盘图标和菜单
+ watch([isMonitoring, isPostponed, ticketCounts], () => {
+ updateTrayIcon();
+ updateTrayMenu();
+ }, { deep: true });
+
return {
// 状态
username, password, rememberPassword, loginMode, tokenSessionId,
message, isLoading,
- isLoggedIn, isMonitoring, isPostponed, autoStartEnabled,
+ isLoggedIn, isMonitoring, isPostponed, postponeResumeTime, autoStartEnabled,
logs, networkStatus, config, ticketCounts,
showCloseDialog,
// 方法
initialize, cleanup,
addLog, clearLogs, clearMessage, saveSettings, exportLogs,
- startMonitoring, stopMonitoring, manualCheck,
+ startMonitoring, stopMonitoring, resumeMonitoring, manualCheck,
testNotification, checkNetworkStatus, updateConfig,
minimizeToTray, handleCloseAction,
};
});
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useMonitorStore, import.meta.hot));
+}