import { msg } from "@/utils/message"; /** System helpers (flattened). */ import type { App } from "vue"; import mitt, { type Emitter } from "mitt"; import VersionAPI from "@/api/module_system/version"; import { useUserStore } from "@stores"; import { Auth, StorageConfig } from "@utils"; import { BANNER } from "../../../build/banner"; // ----------------------------- // Console banner:ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=testall&f=Fire+Font-k&t=dpb&x=none&v=4&h=4&w=80&we=false // ----------------------------- export function printConsoleBanner(): void { const asciiArt = ` \x1b[32m欢迎使用 ${StorageConfig.appName}-v${StorageConfig.CURRENT_VERSION}! \x1b[0m \x1b[32m${BANNER} \x1b[0m \x1b[36m你的支持就是我更新的超强动力!祝使用体验满分💯 \x1b[0m \x1b[33m技术支持: 326030087(微信) \x1b[0m \x1b[31m广成科技: https://www.gonsun.com \x1b[0m `; console.log(asciiArt); } // ----------------------------- // Mitt bus // ----------------------------- type SysEvents = { triggerFireworks: string | undefined; openSetting: void; openSearchDialog: void; openChat: void; openLockScreen: void; }; export const mittBus: Emitter = mitt(); // ----------------------------- // Error handling // ----------------------------- const IGNORABLE_SCRIPT_ERRORS = [ "ResizeObserver loop completed with undelivered notifications.", "ResizeObserver loop limit exceeded", ]; function normalizeErrorMessage(message: Event | string): string { if (typeof message === "string") return message; if ("message" in message && typeof message.message === "string") return message.message; return ""; } function isIgnorableScriptError(message: Event | string, source?: string): boolean { const normalizedMessage = normalizeErrorMessage(message); if (!normalizedMessage) return false; if (IGNORABLE_SCRIPT_ERRORS.some((item) => normalizedMessage.includes(item))) { // 浏览器/扩展在布局抖动时常见的 ResizeObserver 噪声,不作为真实异常处理 return true; } // 浏览器扩展注入脚本偶发的跨域 Script error 也没有排查价值 if (normalizedMessage === "Script error." && source === "") return true; return false; } export function vueErrorHandler(err: unknown, instance: any, info: string) { console.error("[VueError]", err, info, instance); } export function scriptErrorHandler( message: Event | string, source?: string, lineno?: number, colno?: number, error?: Error ): boolean { if (isIgnorableScriptError(message, source)) return true; console.error("[ScriptError]", { message, source, lineno, colno, error }); return true; } export function registerPromiseErrorHandler() { window.addEventListener("unhandledrejection", (event) => { console.error("[PromiseError]", event.reason); }); } export function registerResourceErrorHandler() { window.addEventListener( "error", (event: Event) => { const target = event.target as HTMLElement; if ( target && (target.tagName === "IMG" || target.tagName === "SCRIPT" || target.tagName === "LINK") ) { console.error("[ResourceError]", { tagName: target.tagName, src: (target as HTMLImageElement).src || (target as HTMLScriptElement).src || (target as HTMLLinkElement).href, }); } }, true ); } export function initErrorHandle(app: App) { app.config.errorHandler = vueErrorHandler; window.onerror = scriptErrorHandler; registerPromiseErrorHandler(); registerResourceErrorHandler(); } // ----------------------------- // Upgrade // ----------------------------- /** * 版本升级管理器。 * * ── 检测逻辑 ── * 1. 跳过 1.0.0 版本(无需升级的基版本) * 2. 首次访问 → 写入当前版本号,不升级 * 3. 版本相同 → 无需升级 * 4. 版本不同 + 存在旧数据 → 执行升级(展示通知、清理旧 key、按需登出) * 5. 版本不同 + 无旧数据 → 仅更新版本号 */ class VersionManager { /** 轮询间隔(毫秒) */ private static readonly POLL_INTERVAL = 2 * 60 * 1000; // 2 分钟 /** localStorage key:上次检测到的构建 hash */ private static readonly DETECTED_HASH_KEY = "sys-detected-hash"; /** 当前页面的主脚本路径(Vite 构建时注入的 content hash) */ private readonly currentScriptSrc: string; constructor() { // 提取当前页面第一个