将 web-admin 从子模块改为仓库内普通目录,便于单仓克隆与维护。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
2e9d020d50
commit
c87732fcd5
515 files changed
+74372
-1
No files matched your search
@@ -0,0 +1,13 @@
|
||||
// ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A
|
||||
const asciiArt = `
|
||||
\x1b[32m欢迎使用 Art Design Pro!
|
||||
\x1b[0m
|
||||
\x1b[36m哇!你居然在用我的项目~ 好用的话别忘了去 GitHub 点个 ★Star 呀,你的支持就是我更新的超强动力!祝使用体验满分💯
|
||||
\x1b[0m
|
||||
\x1b[33mGitHub: https://github.com/Daymychen/art-design-pro
|
||||
\x1b[0m
|
||||
\x1b[31m技术支持(QQ群): 1038930070,和开发者一起交流~ 群里有小伙伴实时答疑,遇到问题不用慌!
|
||||
\x1b[0m
|
||||
`
|
||||
|
||||
console.log(asciiArt)
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 全局错误处理模块
|
||||
*
|
||||
* 提供统一的错误捕获和处理机制
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - Vue 运行时错误捕获(组件错误、生命周期错误等)
|
||||
* - 全局脚本错误捕获(语法错误、运行时错误等)
|
||||
* - Promise 未捕获错误处理(unhandledrejection)
|
||||
* - 静态资源加载错误监控(图片、脚本、样式等)
|
||||
* - 错误日志记录和上报
|
||||
* - 统一的错误处理入口
|
||||
*
|
||||
* ## 使用场景
|
||||
* - 应用启动时安装全局错误处理器
|
||||
* - 捕获和记录所有类型的错误
|
||||
* - 错误上报到监控平台
|
||||
* - 提升应用稳定性和可维护性
|
||||
* - 问题排查和调试
|
||||
*
|
||||
* ## 错误类型
|
||||
*
|
||||
* - VueError: Vue 组件相关错误
|
||||
* - ScriptError: JavaScript 脚本错误
|
||||
* - PromiseError: Promise 未捕获的 rejection
|
||||
* - ResourceError: 静态资源加载失败
|
||||
*
|
||||
* @module utils/sys/error-handle
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
import type { App } from 'vue'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue 运行时错误处理
|
||||
*/
|
||||
export function vueErrorHandler(err: unknown, instance: any, info: string) {
|
||||
console.error('[VueError]', err, info, instance)
|
||||
// 这里可以上报到服务端,比如:
|
||||
// reportError({ type: 'vue', err, info })
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局脚本错误处理
|
||||
*/
|
||||
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 })
|
||||
// reportError({ type: 'script', message, source, lineno, colno, error })
|
||||
return true // 阻止默认控制台报错,可根据需求改
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise 未捕获错误处理
|
||||
*/
|
||||
export function registerPromiseErrorHandler() {
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('[PromiseError]', event.reason)
|
||||
// reportError({ type: 'promise', reason: event.reason })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源加载错误处理 (img, script, css...)
|
||||
*/
|
||||
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
|
||||
})
|
||||
// reportError({ type: 'resource', target })
|
||||
}
|
||||
},
|
||||
true // 捕获阶段才能监听到资源错误
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装统一错误处理
|
||||
*/
|
||||
export function setupErrorHandle(app: App) {
|
||||
app.config.errorHandler = vueErrorHandler
|
||||
window.onerror = scriptErrorHandler
|
||||
registerPromiseErrorHandler()
|
||||
registerResourceErrorHandler()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 系统管理相关工具函数统一导出
|
||||
*/
|
||||
|
||||
export * from './upgrade'
|
||||
export { default as mittBus } from './mittBus'
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 全局事件总线模块
|
||||
*
|
||||
* 基于 mitt 库实现的类型安全的事件总线
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 跨组件通信(发布/订阅模式)
|
||||
* - 类型安全的事件定义和调用
|
||||
* - 全局事件管理(烟花效果、设置面板、搜索对话框等)
|
||||
* - 解耦组件间的直接依赖
|
||||
*
|
||||
* ## 使用场景
|
||||
*
|
||||
* - 跨层级组件通信
|
||||
* - 全局功能触发(设置、搜索、聊天、锁屏等)
|
||||
* - 特效触发(烟花效果)
|
||||
* - 避免 props 层层传递
|
||||
*
|
||||
* ## 用法示例
|
||||
*
|
||||
* ```typescript
|
||||
* // 订阅事件
|
||||
* mittBus.on('openSetting', () => { ... })
|
||||
*
|
||||
* // 发布事件
|
||||
* mittBus.emit('openSetting')
|
||||
*
|
||||
* // 带参数的事件
|
||||
* mittBus.emit('triggerFireworks', 'image-url')
|
||||
* ```
|
||||
*
|
||||
* ## 已定义的事件
|
||||
*
|
||||
* - triggerFireworks: 触发烟花效果(可选图片URL)
|
||||
* - openSetting: 打开设置面板
|
||||
* - openSearchDialog: 打开搜索对话框
|
||||
* - openChat: 打开聊天窗口
|
||||
* - openLockScreen: 打开锁屏
|
||||
*
|
||||
* @module utils/sys/mittBus
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
import mitt, { type Emitter } from 'mitt'
|
||||
|
||||
// 定义事件类型映射
|
||||
type Events = {
|
||||
// 烟花效果事件 - 可选的图片URL参数
|
||||
triggerFireworks: string | undefined
|
||||
// 打开设置面板事件 - 无参数
|
||||
openSetting: void
|
||||
// 打开搜索对话框事件 - 无参数
|
||||
openSearchDialog: void
|
||||
// 打开聊天窗口事件 - 无参数
|
||||
openChat: void
|
||||
// 打开锁屏事件 - 无参数
|
||||
openLockScreen: void
|
||||
}
|
||||
|
||||
// 创建类型安全的事件总线实例
|
||||
const mittBus: Emitter<Events> = mitt<Events>()
|
||||
|
||||
export default mittBus
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* 系统版本升级管理模块
|
||||
*
|
||||
* 提供完整的应用版本升级检测和处理功能
|
||||
*
|
||||
* ## 主要功能
|
||||
*
|
||||
* - 版本号比较和升级检测
|
||||
* - 首次访问识别和处理
|
||||
* - 旧版本数据自动清理
|
||||
* - 升级日志展示和通知
|
||||
* - 强制重新登录控制(根据升级日志配置)
|
||||
* - 版本号规范化处理
|
||||
* - 旧存储结构迁移和清理
|
||||
* - 升级流程延迟执行(确保应用完全加载)
|
||||
*
|
||||
* @module utils/sys/upgrade
|
||||
* @author Art Design Pro Team
|
||||
*/
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { StorageConfig } from '@/utils/storage/storage-config'
|
||||
import { fetchGetLatestUpgradeLog } from '@/api/system'
|
||||
|
||||
interface UpgradeLog {
|
||||
version: string
|
||||
title: string
|
||||
date: string
|
||||
detail?: string[]
|
||||
requireReLogin?: boolean
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本管理器
|
||||
* 负责处理版本比较、升级检测和数据清理
|
||||
*/
|
||||
class VersionManager {
|
||||
/**
|
||||
* 规范化版本号字符串,移除前缀 'v'
|
||||
*/
|
||||
private normalizeVersion(version: string): string {
|
||||
return version.replace(/^v/, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的版本号
|
||||
*/
|
||||
private getStoredVersion(): string | null {
|
||||
return localStorage.getItem(StorageConfig.VERSION_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置版本号到存储
|
||||
*/
|
||||
private setStoredVersion(version: string): void {
|
||||
localStorage.setItem(StorageConfig.VERSION_KEY, version)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该跳过升级处理
|
||||
*/
|
||||
private shouldSkipUpgrade(): boolean {
|
||||
return StorageConfig.CURRENT_VERSION === StorageConfig.SKIP_UPGRADE_VERSION
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为首次访问
|
||||
*/
|
||||
private isFirstVisit(storedVersion: string | null): boolean {
|
||||
return !storedVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查版本是否相同
|
||||
*/
|
||||
private isSameVersion(storedVersion: string): boolean {
|
||||
return storedVersion === StorageConfig.CURRENT_VERSION
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找旧的存储结构
|
||||
*/
|
||||
private findLegacyStorage(): { oldSysKey: string | null; oldVersionKeys: string[] } {
|
||||
const storageKeys = Object.keys(localStorage)
|
||||
const currentVersionPrefix = StorageConfig.generateStorageKey('').slice(0, -1) // 移除末尾的 '-'
|
||||
|
||||
// 查找旧的单一存储结构
|
||||
const oldSysKey =
|
||||
storageKeys.find(
|
||||
(key) =>
|
||||
StorageConfig.isVersionedKey(key) && key !== currentVersionPrefix && !key.includes('-')
|
||||
) || null
|
||||
|
||||
// 查找旧版本的分离存储键
|
||||
const oldVersionKeys = storageKeys.filter(
|
||||
(key) =>
|
||||
StorageConfig.isVersionedKey(key) &&
|
||||
!StorageConfig.isCurrentVersionKey(key) &&
|
||||
key.includes('-')
|
||||
)
|
||||
|
||||
return { oldSysKey, oldVersionKeys }
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要重新登录
|
||||
*/
|
||||
private shouldRequireReLogin(storedVersion: string, latestLog: UpgradeLog | null): boolean {
|
||||
if (!latestLog) return false
|
||||
|
||||
const normalizedCurrent = this.normalizeVersion(StorageConfig.CURRENT_VERSION)
|
||||
const normalizedStored = this.normalizeVersion(storedVersion)
|
||||
const itemVersion = this.normalizeVersion(latestLog.version)
|
||||
|
||||
return (
|
||||
latestLog.requireReLogin &&
|
||||
itemVersion > normalizedStored &&
|
||||
itemVersion <= normalizedCurrent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建升级通知消息
|
||||
*/
|
||||
private buildUpgradeMessage(latestLog: UpgradeLog): string {
|
||||
const messageParts = [
|
||||
`<p style="color: var(--art-gray-800) !important; padding-bottom: 5px;">`,
|
||||
`系统已升级到 ${StorageConfig.CURRENT_VERSION} 版本,此次更新带来了以下改进:`,
|
||||
`</p>`,
|
||||
latestLog.title
|
||||
]
|
||||
|
||||
if (latestLog.requireReLogin) {
|
||||
messageParts.push(
|
||||
`<p style="color: var(--theme-color); padding-top: 5px;">升级完成,请重新登录后继续使用。</p>`
|
||||
)
|
||||
}
|
||||
|
||||
return messageParts.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示升级通知
|
||||
*/
|
||||
private showUpgradeNotification(message: string): void {
|
||||
ElNotification({
|
||||
title: '系统升级公告',
|
||||
message,
|
||||
duration: 0,
|
||||
type: 'success',
|
||||
dangerouslyUseHTMLString: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理旧版本数据
|
||||
*/
|
||||
private cleanupLegacyData(oldSysKey: string | null, oldVersionKeys: string[]): void {
|
||||
// 清理旧的单一存储结构
|
||||
if (oldSysKey) {
|
||||
localStorage.removeItem(oldSysKey)
|
||||
console.info(`[Upgrade] 已清理旧存储: ${oldSysKey}`)
|
||||
}
|
||||
|
||||
// 清理旧版本的分离存储
|
||||
oldVersionKeys.forEach((key) => {
|
||||
localStorage.removeItem(key)
|
||||
console.info(`[Upgrade] 已清理旧存储: ${key}`)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行升级后的登出操作
|
||||
*/
|
||||
private performLogout(): void {
|
||||
try {
|
||||
useUserStore().logOut()
|
||||
console.info('[Upgrade] 已执行升级后登出')
|
||||
} catch (error) {
|
||||
console.error('[Upgrade] 升级后登出失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行升级流程
|
||||
*/
|
||||
private async executeUpgrade(
|
||||
storedVersion: string,
|
||||
legacyStorage: ReturnType<typeof this.findLegacyStorage>,
|
||||
latestLog: UpgradeLog | null
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!latestLog) {
|
||||
console.warn('[Upgrade] 升级日志为空')
|
||||
return
|
||||
}
|
||||
|
||||
const requireReLogin = this.shouldRequireReLogin(storedVersion, latestLog)
|
||||
const message = this.buildUpgradeMessage(latestLog)
|
||||
|
||||
// 显示升级通知
|
||||
this.showUpgradeNotification(message)
|
||||
|
||||
// 更新版本号
|
||||
this.setStoredVersion(StorageConfig.CURRENT_VERSION)
|
||||
|
||||
// 清理旧数据
|
||||
this.cleanupLegacyData(legacyStorage.oldSysKey, legacyStorage.oldVersionKeys)
|
||||
|
||||
// 执行登出(如果需要)
|
||||
if (requireReLogin) {
|
||||
this.performLogout()
|
||||
}
|
||||
|
||||
console.info(`[Upgrade] 升级完成: ${storedVersion} → ${StorageConfig.CURRENT_VERSION}`)
|
||||
} catch (error) {
|
||||
console.error('[Upgrade] 系统升级处理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统升级处理主流程
|
||||
*/
|
||||
async processUpgrade(): Promise<void> {
|
||||
// 跳过特定版本
|
||||
if (this.shouldSkipUpgrade()) {
|
||||
console.debug('[Upgrade] 跳过版本升级检查')
|
||||
return
|
||||
}
|
||||
|
||||
const storedVersion = this.getStoredVersion()
|
||||
|
||||
// 首次访问处理
|
||||
if (this.isFirstVisit(storedVersion)) {
|
||||
this.setStoredVersion(StorageConfig.CURRENT_VERSION)
|
||||
// console.info('[Upgrade] 首次访问,已设置当前版本')
|
||||
return
|
||||
}
|
||||
|
||||
// 版本相同,无需升级
|
||||
if (this.isSameVersion(storedVersion!)) {
|
||||
// console.debug('[Upgrade] 版本相同,无需升级')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有需要升级的旧数据
|
||||
const legacyStorage = this.findLegacyStorage()
|
||||
if (!legacyStorage.oldSysKey && legacyStorage.oldVersionKeys.length === 0) {
|
||||
this.setStoredVersion(StorageConfig.CURRENT_VERSION)
|
||||
console.info('[Upgrade] 无旧数据,已更新版本号')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取最新升级日志
|
||||
let latestLog: UpgradeLog | null = null
|
||||
try {
|
||||
const { data } = await fetchGetLatestUpgradeLog()
|
||||
latestLog = data
|
||||
} catch (error) {
|
||||
console.error('[Upgrade] 获取升级日志失败:', error)
|
||||
}
|
||||
|
||||
// 延迟执行升级流程,确保应用已完全加载
|
||||
setTimeout(() => {
|
||||
this.executeUpgrade(storedVersion!, legacyStorage, latestLog)
|
||||
}, StorageConfig.UPGRADE_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建版本管理器实例
|
||||
const versionManager = new VersionManager()
|
||||
|
||||
/**
|
||||
* 系统升级处理入口函数
|
||||
*/
|
||||
export async function systemUpgrade(): Promise<void> {
|
||||
await versionManager.processUpgrade()
|
||||
}
|
||||
Reference in new issue
Block a user