first commit

This commit is contained in:
2025-11-24 16:06:44 +08:00
commit e57d497751
165 changed files with 59349 additions and 0 deletions

295
src/utils/index.js Normal file
View File

@@ -0,0 +1,295 @@
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
// 设置dayjs语言
dayjs.locale('zh-cn')
/**
* 日期格式化
* @param {Date|string} date 日期
* @param {string} format 格式
* @returns {string} 格式化后的日期字符串
*/
export const formatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
if (!date) return ''
return dayjs(date).format(format)
}
/**
* 相对时间
* @param {Date|string} date 日期
* @returns {string} 相对时间字符串
*/
export const fromNow = (date) => {
if (!date) return ''
return dayjs(date).fromNow()
}
/**
* 手机号格式化
* @param {string} phone 手机号
* @returns {string} 格式化后的手机号
*/
export const formatPhone = (phone) => {
if (!phone) return ''
return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
}
/**
* 金额格式化
* @param {number} amount 金额
* @param {number} decimals 小数位数
* @returns {string} 格式化后的金额
*/
export const formatMoney = (amount, decimals = 2) => {
if (amount === null || amount === undefined) return '0.00'
return Number(amount).toFixed(decimals)
}
/**
* 文件大小格式化
* @param {number} bytes 字节数
* @returns {string} 格式化后的文件大小
*/
export const formatFileSize = (bytes) => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
/**
* 防抖函数
* @param {Function} func 要防抖的函数
* @param {number} wait 等待时间
* @returns {Function} 防抖后的函数
*/
export const debounce = (func, wait) => {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
/**
* 节流函数
* @param {Function} func 要节流的函数
* @param {number} limit 限制时间
* @returns {Function} 节流后的函数
*/
export const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
/**
* 深拷贝
* @param {any} obj 要拷贝的对象
* @returns {any} 拷贝后的对象
*/
export const deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj.getTime())
if (obj instanceof Array) return obj.map(item => deepClone(item))
if (typeof obj === 'object') {
const clonedObj = {}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clonedObj[key] = deepClone(obj[key])
}
}
return clonedObj
}
}
/**
* 生成UUID
* @returns {string} UUID字符串
*/
export const generateUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
/**
* 验证手机号
* @param {string} phone 手机号
* @returns {boolean} 是否有效
*/
export const validatePhone = (phone) => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(phone)
}
/**
* 验证邮箱
* @param {string} email 邮箱
* @returns {boolean} 是否有效
*/
export const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
/**
* 验证身份证号
* @param {string} idCard 身份证号
* @returns {boolean} 是否有效
*/
export const validateIdCard = (idCard) => {
const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
return idCardRegex.test(idCard)
}
/**
* 获取URL参数
* @param {string} name 参数名
* @returns {string|null} 参数值
*/
export const getUrlParam = (name) => {
const urlParams = new URLSearchParams(window.location.search)
return urlParams.get(name)
}
/**
* 设置URL参数
* @param {string} name 参数名
* @param {string} value 参数值
*/
export const setUrlParam = (name, value) => {
const url = new URL(window.location)
url.searchParams.set(name, value)
window.history.replaceState({}, '', url)
}
/**
* 移除URL参数
* @param {string} name 参数名
*/
export const removeUrlParam = (name) => {
const url = new URL(window.location)
url.searchParams.delete(name)
window.history.replaceState({}, '', url)
}
/**
* 复制文本到剪贴板
* @param {string} text 要复制的文本
* @returns {Promise<boolean>} 是否复制成功
*/
export const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text)
return true
} catch (err) {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
try {
document.execCommand('copy')
document.body.removeChild(textArea)
return true
} catch (err) {
document.body.removeChild(textArea)
return false
}
}
}
/**
* 下载文件
* @param {string} url 文件URL
* @param {string} filename 文件名
*/
export const downloadFile = (url, filename) => {
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
/**
* 获取浏览器信息
* @returns {object} 浏览器信息
*/
export const getBrowserInfo = () => {
const ua = navigator.userAgent
const browser = {
name: '',
version: '',
os: ''
}
// 检测浏览器
if (ua.includes('Chrome')) {
browser.name = 'Chrome'
} else if (ua.includes('Firefox')) {
browser.name = 'Firefox'
} else if (ua.includes('Safari')) {
browser.name = 'Safari'
} else if (ua.includes('Edge')) {
browser.name = 'Edge'
} else if (ua.includes('MSIE') || ua.includes('Trident')) {
browser.name = 'IE'
}
// 检测操作系统
if (ua.includes('Windows')) {
browser.os = 'Windows'
} else if (ua.includes('Mac')) {
browser.os = 'Mac'
} else if (ua.includes('Linux')) {
browser.os = 'Linux'
} else if (ua.includes('Android')) {
browser.os = 'Android'
} else if (ua.includes('iOS')) {
browser.os = 'iOS'
}
return browser
}
/**
* 检查是否为移动设备
* @returns {boolean} 是否为移动设备
*/
export const isMobile = () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
/**
* 检查是否为微信浏览器
* @returns {boolean} 是否为微信浏览器
*/
export const isWeChat = () => {
return /MicroMessenger/i.test(navigator.userAgent)
}
/**
* 检查是否为支付宝浏览器
* @returns {boolean} 是否为支付宝浏览器
*/
export const isAlipay = () => {
return /AlipayClient/i.test(navigator.userAgent)
}

193
src/utils/performance.js Normal file
View File

@@ -0,0 +1,193 @@
/**
* 渲染性能优化工具函数
*/
// 检测设备性能等级
export const getDevicePerformanceLevel = () => {
const hardwareConcurrency = navigator.hardwareConcurrency || 4
const memory = navigator.deviceMemory || 4
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
const isLowEnd = hardwareConcurrency <= 4 || memory <= 4 || isMobile
if (isLowEnd) {
return 'low'
} else if (hardwareConcurrency <= 8 || memory <= 8) {
return 'medium'
} else {
return 'high'
}
}
// 检测是否支持硬件加速
export const supportsHardwareAcceleration = () => {
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
return !!gl
}
// 检测是否支持backdrop-filter
export const supportsBackdropFilter = () => {
return CSS.supports('backdrop-filter', 'blur(1px)')
}
// 检测用户是否偏好减少动画
export const prefersReducedMotion = () => {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}
// 应用渲染性能优化策略
export const applyRenderOptimizations = () => {
const performanceLevel = getDevicePerformanceLevel()
const supportsHardware = supportsHardwareAcceleration()
const supportsBackdrop = supportsBackdropFilter()
const reducedMotion = prefersReducedMotion()
// 添加性能等级类到body
document.body.classList.add(`performance-${performanceLevel}`)
// 如果不支持硬件加速,添加降级类
if (!supportsHardware) {
document.body.classList.add('no-hardware-acceleration')
}
// 如果不支持backdrop-filter添加降级类
if (!supportsBackdrop) {
document.body.classList.add('no-backdrop-filter')
}
// 如果用户偏好减少动画,添加相应类
if (reducedMotion) {
document.body.classList.add('reduced-motion')
}
return {
performanceLevel,
supportsHardware,
supportsBackdrop,
reducedMotion
}
}
// 懒加载优化
export const createIntersectionObserver = (callback, options = {}) => {
const defaultOptions = {
root: null,
rootMargin: '50px',
threshold: 0.1,
...options
}
return new IntersectionObserver(callback, defaultOptions)
}
// 防抖函数
export const debounce = (func, wait) => {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
// 节流函数
export const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 优化图片加载
export const optimizeImageLoading = () => {
const images = document.querySelectorAll('img[data-src]')
const imageObserver = createIntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
img.classList.remove('lazy')
imageObserver.unobserve(img)
}
})
})
images.forEach(img => imageObserver.observe(img))
}
// 优化动画性能
export const optimizeAnimations = () => {
const animatedElements = document.querySelectorAll('[class*="animate-"]')
animatedElements.forEach(element => {
// 添加硬件加速
element.style.transform = 'translateZ(0)'
element.style.willChange = 'transform'
})
}
// 减少重绘和重排
export const optimizeLayout = () => {
// 批量DOM操作
const batchDOMUpdates = (updates) => {
requestAnimationFrame(() => {
updates.forEach(update => update())
})
}
return { batchDOMUpdates }
}
// 内存优化
export const optimizeMemory = () => {
// 清理事件监听器
const cleanupEventListeners = () => {
// 在组件卸载时调用
}
// 清理定时器
const cleanupTimers = () => {
// 清理所有定时器
}
return { cleanupEventListeners, cleanupTimers }
}
// 初始化渲染性能优化
export const initRenderOptimizations = () => {
// 应用基础优化
const config = applyRenderOptimizations()
// 根据性能等级应用不同策略
switch (config.performanceLevel) {
case 'low':
// 低端设备禁用复杂动画简化UI
document.body.classList.add('low-performance-mode')
break
case 'medium':
// 中端设备:适度优化
document.body.classList.add('medium-performance-mode')
break
case 'high':
// 高端设备:启用所有功能
document.body.classList.add('high-performance-mode')
break
}
// 优化图片加载
optimizeImageLoading()
// 优化动画
optimizeAnimations()
return config
}

242
src/utils/permission.js Normal file
View File

@@ -0,0 +1,242 @@
// 权限指令
export const permission = {
inserted(el, binding) {
const { value } = binding
const roles = value.roles || []
const permissions = value.permissions || []
const userRole = el._vue.$store.getters.userRole || 'user'
const userPermissions = el._vue.$store.getters.userPermissions || []
let hasPermission = true
// 检查角色权限
if (roles.length > 0) {
if (!roles.includes(userRole)) {
hasPermission = false
}
}
// 检查功能权限
if (permissions.length > 0) {
const hasAllPermissions = permissions.every(permission =>
userPermissions.includes(permission)
)
if (!hasAllPermissions) {
hasPermission = false
}
}
if (!hasPermission) {
el.style.display = 'none'
}
},
update(el, binding) {
const { value } = binding
const roles = value.roles || []
const permissions = value.permissions || []
const userRole = el._vue.$store.getters.userRole || 'user'
const userPermissions = el._vue.$store.getters.userPermissions || []
let hasPermission = true
// 检查角色权限
if (roles.length > 0) {
if (!roles.includes(userRole)) {
hasPermission = false
}
}
// 检查功能权限
if (permissions.length > 0) {
const hasAllPermissions = permissions.every(permission =>
userPermissions.includes(permission)
)
if (!hasAllPermissions) {
hasPermission = false
}
}
el.style.display = hasPermission ? '' : 'none'
}
}
// 角色指令
export const role = {
inserted(el, binding) {
const { value } = binding
const userRole = el._vue.$store.getters.userRole || 'user'
if (!value.includes(userRole)) {
el.style.display = 'none'
}
},
update(el, binding) {
const { value } = binding
const userRole = el._vue.$store.getters.userRole || 'user'
el.style.display = value.includes(userRole) ? '' : 'none'
}
}
// 权限检查工具函数
export const checkPermission = {
// 检查角色权限
hasRole(userRole, requiredRoles) {
if (!requiredRoles || requiredRoles.length === 0) {
return true
}
return requiredRoles.includes(userRole)
},
// 检查功能权限
hasPermission(userPermissions, requiredPermissions) {
if (!requiredPermissions || requiredPermissions.length === 0) {
return true
}
return requiredPermissions.every(permission =>
userPermissions.includes(permission)
)
},
// 检查综合权限
hasAccess(userRole, userPermissions, requiredRoles, requiredPermissions) {
const hasRoleAccess = this.hasRole(userRole, requiredRoles)
const hasPermissionAccess = this.hasPermission(userPermissions, requiredPermissions)
return hasRoleAccess && hasPermissionAccess
}
}
// 权限常量
export const PERMISSIONS = {
// 统计相关权限
STATISTICS_VIEW: 'statistics:view',
STATISTICS_CREATE: 'statistics:create',
STATISTICS_UPDATE: 'statistics:update',
STATISTICS_DELETE: 'statistics:delete',
STATISTICS_EXPORT: 'statistics:export',
// 仪表板相关权限
DASHBOARD_VIEW: 'dashboard:view',
DASHBOARD_CREATE: 'dashboard:create',
DASHBOARD_UPDATE: 'dashboard:update',
DASHBOARD_DELETE: 'dashboard:delete',
// 报告相关权限
REPORT_VIEW: 'report:view',
REPORT_CREATE: 'report:create',
REPORT_UPDATE: 'report:update',
REPORT_DELETE: 'report:delete',
REPORT_EXPORT: 'report:export',
// 分析相关权限
ANALYSIS_VIEW: 'analysis:view',
ANALYSIS_RUN: 'analysis:run',
ANALYSIS_EXPORT: 'analysis:export',
// 管理相关权限
ADMIN_VIEW: 'admin:view',
ADMIN_MANAGE: 'admin:manage',
ADMIN_SETTINGS: 'admin:settings'
}
// 角色常量
export const ROLES = {
ADMIN: 'admin',
MANAGER: 'manager',
ANALYST: 'analyst',
USER: 'user'
}
// 角色权限映射
export const ROLE_PERMISSIONS = {
[ROLES.ADMIN]: [
PERMISSIONS.STATISTICS_VIEW,
PERMISSIONS.STATISTICS_CREATE,
PERMISSIONS.STATISTICS_UPDATE,
PERMISSIONS.STATISTICS_DELETE,
PERMISSIONS.STATISTICS_EXPORT,
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.DASHBOARD_CREATE,
PERMISSIONS.DASHBOARD_UPDATE,
PERMISSIONS.DASHBOARD_DELETE,
PERMISSIONS.REPORT_VIEW,
PERMISSIONS.REPORT_CREATE,
PERMISSIONS.REPORT_UPDATE,
PERMISSIONS.REPORT_DELETE,
PERMISSIONS.REPORT_EXPORT,
PERMISSIONS.ANALYSIS_VIEW,
PERMISSIONS.ANALYSIS_RUN,
PERMISSIONS.ANALYSIS_EXPORT,
PERMISSIONS.ADMIN_VIEW,
PERMISSIONS.ADMIN_MANAGE,
PERMISSIONS.ADMIN_SETTINGS
],
[ROLES.MANAGER]: [
PERMISSIONS.STATISTICS_VIEW,
PERMISSIONS.STATISTICS_EXPORT,
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.DASHBOARD_CREATE,
PERMISSIONS.DASHBOARD_UPDATE,
PERMISSIONS.REPORT_VIEW,
PERMISSIONS.REPORT_CREATE,
PERMISSIONS.REPORT_EXPORT,
PERMISSIONS.ANALYSIS_VIEW,
PERMISSIONS.ANALYSIS_RUN,
PERMISSIONS.ANALYSIS_EXPORT
],
[ROLES.ANALYST]: [
PERMISSIONS.STATISTICS_VIEW,
PERMISSIONS.STATISTICS_EXPORT,
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.REPORT_VIEW,
PERMISSIONS.REPORT_EXPORT,
PERMISSIONS.ANALYSIS_VIEW,
PERMISSIONS.ANALYSIS_RUN,
PERMISSIONS.ANALYSIS_EXPORT
],
[ROLES.USER]: [
PERMISSIONS.STATISTICS_VIEW,
PERMISSIONS.DASHBOARD_VIEW,
PERMISSIONS.REPORT_VIEW,
PERMISSIONS.ANALYSIS_VIEW
]
}
// 获取用户权限
export const getUserPermissions = (userRole) => {
return ROLE_PERMISSIONS[userRole] || []
}
// 检查用户是否有特定权限
export const hasUserPermission = (userRole, permission) => {
const userPermissions = getUserPermissions(userRole)
return userPermissions.includes(permission)
}
// 检查用户是否有任意权限
export const hasAnyPermission = (userRole, permissions) => {
const userPermissions = getUserPermissions(userRole)
return permissions.some(permission => userPermissions.includes(permission))
}
// 检查用户是否有所有权限
export const hasAllPermissions = (userRole, permissions) => {
const userPermissions = getUserPermissions(userRole)
return permissions.every(permission => userPermissions.includes(permission))
}
export default {
permission,
role,
checkPermission,
PERMISSIONS,
ROLES,
ROLE_PERMISSIONS,
getUserPermissions,
hasUserPermission,
hasAnyPermission,
hasAllPermissions
}

303
src/utils/request.js Normal file
View File

@@ -0,0 +1,303 @@
import router from '@/router'
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 创建axios实例
const request = axios.create({
baseURL: '/api/v1',
timeout: 60000, // 1分钟超时用于调试页面
headers: {
'Content-Type': 'application/json',
}
})
// 简单的认证错误事件总线
const authEventBus = {
listeners: [],
onAuthError(callback) {
this.listeners.push(callback)
},
emitAuthError(message) {
this.listeners.forEach(callback => {
try {
callback(message)
} catch (error) {
console.error('认证错误处理回调执行失败:', error)
}
})
}
}
// 请求拦截器
request.interceptors.request.use(
config => {
// 添加认证token
const token = localStorage.getItem('access_token')
const tokenType = localStorage.getItem('token_type') || 'Bearer'
if (token) {
config.headers.Authorization = `${tokenType} ${token}`
}
// 添加请求ID用于追踪
config.headers['X-Request-ID'] = Date.now().toString()
return config
},
error => {
console.error('请求拦截器错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
response => {
const { data, status } = response
// 检查HTTP状态码
if (status >= 200 && status < 300) {
// 如果是blob响应文件下载直接返回data
if (data instanceof Blob) {
return data
}
// 严格按照后端响应格式处理
if (data.success === true) {
// 成功响应返回data字段
return {
success: true,
data: data.data,
message: data.message,
requestId: data.request_id,
timestamp: data.timestamp,
pagination: data.pagination,
meta: data.meta
}
} else if (data.success === false) {
// 业务逻辑错误
const message = data.message || '请求失败'
// 检查是否有验证错误
if (data.errors && typeof data.errors === 'object') {
// 处理验证错误
const errorMessages = []
Object.keys(data.errors).forEach(field => {
const fieldErrors = data.errors[field]
if (Array.isArray(fieldErrors)) {
// 将字段的所有错误信息添加到数组中
errorMessages.push(...fieldErrors)
} else if (typeof fieldErrors === 'string') {
// 单个错误字符串
errorMessages.push(fieldErrors)
}
})
// 显示所有验证错误
if (errorMessages.length > 0) {
errorMessages.forEach(errorMsg => {
ElMessage.error(errorMsg)
})
} else {
ElMessage.error(message)
}
} else {
// 普通业务错误
ElMessage.error(message)
}
return Promise.reject({
type: 'business',
message,
errors: data.errors,
requestId: data.request_id,
timestamp: data.timestamp
})
} else {
// 没有success字段可能是直接返回数据
return {
success: true,
data: data,
message: '请求成功'
}
}
} else {
// HTTP状态码错误
const message = data?.message || `HTTP错误: ${status}`
ElMessage.error(message)
return Promise.reject({
type: 'http',
status,
message,
data: data
})
}
},
error => {
console.error('响应拦截器错误:', error)
// 网络错误
if (!error.response) {
ElMessage.error('网络连接失败,请检查网络设置')
return Promise.reject({
type: 'network',
message: '网络连接失败'
})
}
const { status, data } = error.response
// 根据状态码处理不同类型的错误
let errorMessage = ''
switch (status) {
case 400:
// 请求参数错误
errorMessage = data?.message || '请求参数错误'
ElMessage.error(errorMessage)
break
case 401:
// 认证失败 - 使用事件总线通知
errorMessage = data?.message || '登录已过期,请重新登录'
handleAuthError(errorMessage)
break
case 403:
// 权限不足
errorMessage = data?.message || '权限不足,无法访问此资源'
ElMessage.error(errorMessage)
break
case 404:
// 资源不存在
errorMessage = data?.message || '请求的资源不存在'
ElMessage.error(errorMessage)
break
case 409:
// 资源冲突
errorMessage = data?.message || '资源冲突,请检查输入信息'
ElMessage.error(errorMessage)
break
case 422:
// 参数校验失败
errorMessage = data?.message || '请求参数验证失败'
// 检查是否有验证错误
if (data?.errors && typeof data.errors === 'object') {
// 处理验证错误
const errorMessages = []
Object.keys(data.errors).forEach(field => {
const fieldErrors = data.errors[field]
if (Array.isArray(fieldErrors)) {
// 将字段的所有错误信息添加到数组中
errorMessages.push(...fieldErrors)
} else if (typeof fieldErrors === 'string') {
// 单个错误字符串
errorMessages.push(fieldErrors)
}
})
// 显示所有验证错误
if (errorMessages.length > 0) {
errorMessages.forEach(errorMsg => {
ElMessage.error(errorMsg)
})
} else {
ElMessage.error(errorMessage)
}
} else {
ElMessage.error(errorMessage)
}
break
case 429:
// 请求频率限制
errorMessage = data?.message || '请求过于频繁,请稍后再试'
ElMessage.error(errorMessage)
break
case 500:
// 服务器内部错误
errorMessage = data?.message || '服务器内部错误,请稍后再试'
ElMessage.error(errorMessage)
break
default:
// 其他错误
errorMessage = data?.message || `请求失败 (${status})`
ElMessage.error(errorMessage)
}
return Promise.reject({
type: 'http',
status,
message: data?.message || `HTTP错误: ${status}`,
data: data,
requestId: data?.request_id,
timestamp: data?.timestamp
})
}
)
// 处理认证错误的函数
const handleAuthError = (message) => {
// 清理localStorage
localStorage.removeItem('access_token')
localStorage.removeItem('token_type')
// 通过事件总线通知其他组件
authEventBus.emitAuthError(message)
// 显示错误消息
ElMessage.error(message)
// 跳转到登录页面(如果不在登录页面)
if (router.currentRoute.value.path !== '/auth/login') {
router.push('/auth/login')
}
}
// 导出请求实例
export default request
// 导出认证事件总线
export { authEventBus }
// 导出响应处理工具函数
export const handleResponse = (response) => {
if (response.success) {
return response.data
}
throw new Error(response.message || '请求失败')
}
// 导出错误处理工具函数
export const handleError = (error) => {
if (error.type === 'business') {
// 业务逻辑错误
return {
success: false,
message: error.message,
errors: error.errors
}
} else if (error.type === 'http') {
// HTTP错误
return {
success: false,
message: error.message,
status: error.status
}
} else if (error.type === 'network') {
// 网络错误
return {
success: false,
message: error.message
}
}
// 未知错误
return {
success: false,
message: '未知错误'
}
}

239
src/utils/version.js Normal file
View File

@@ -0,0 +1,239 @@
/**
* 版本管理工具
* 用于管理应用版本和token版本实现自动退出登录和页面刷新
*/
// 版本配置
export const VERSION_CONFIG = {
// 当前token版本修改JWT密钥时需要更新
TOKEN_VERSION: '1.0.0',
// 当前应用版本,前端更新时需要更新
APP_VERSION: '1.0.0',
// 版本检查间隔(毫秒)
CHECK_INTERVAL: 60000, // 60秒检查一次
// 是否启用自动版本检查
AUTO_CHECK: true
}
// 本地存储键名
const STORAGE_KEYS = {
TOKEN_VERSION: 'token_version',
APP_VERSION: 'app_version',
LAST_CHECK_TIME: 'last_version_check_time'
}
/**
* 获取本地存储的版本信息
*/
export const getLocalVersions = () => {
return {
tokenVersion: localStorage.getItem(STORAGE_KEYS.TOKEN_VERSION) || '1.0.0',
appVersion: localStorage.getItem(STORAGE_KEYS.APP_VERSION) || '1.0.0',
lastCheckTime: localStorage.getItem(STORAGE_KEYS.LAST_CHECK_TIME) || '0'
}
}
/**
* 保存版本信息到本地存储
*/
export const saveLocalVersions = (versions) => {
if (versions.tokenVersion) {
localStorage.setItem(STORAGE_KEYS.TOKEN_VERSION, versions.tokenVersion)
}
if (versions.appVersion) {
localStorage.setItem(STORAGE_KEYS.APP_VERSION, versions.appVersion)
}
localStorage.setItem(STORAGE_KEYS.LAST_CHECK_TIME, Date.now().toString())
}
/**
* 清除版本信息
*/
export const clearLocalVersions = () => {
localStorage.removeItem(STORAGE_KEYS.TOKEN_VERSION)
localStorage.removeItem(STORAGE_KEYS.APP_VERSION)
localStorage.removeItem(STORAGE_KEYS.LAST_CHECK_TIME)
}
/**
* 比较版本号
* @param {string} version1 版本1
* @param {string} version2 版本2
* @returns {number} 1: version1 > version2, -1: version1 < version2, 0: 相等
*/
export const compareVersions = (version1, version2) => {
const v1Parts = version1.split('.').map(Number)
const v2Parts = version2.split('.').map(Number)
const maxLength = Math.max(v1Parts.length, v2Parts.length)
for (let i = 0; i < maxLength; i++) {
const v1 = v1Parts[i] || 0
const v2 = v2Parts[i] || 0
if (v1 > v2) return 1
if (v1 < v2) return -1
}
return 0
}
/**
* 检查是否需要更新
*/
export const checkForUpdates = () => {
const localVersions = getLocalVersions()
const currentTime = Date.now()
const lastCheckTime = parseInt(localVersions.lastCheckTime)
// 检查是否到了检查时间
if (currentTime - lastCheckTime < VERSION_CONFIG.CHECK_INTERVAL) {
return { needsCheck: false }
}
// 检查token版本
const tokenVersionMatch = localVersions.tokenVersion === VERSION_CONFIG.TOKEN_VERSION
if (!tokenVersionMatch) {
return {
needsCheck: true,
needsLogout: true,
reason: 'token_version_mismatch',
current: localVersions.tokenVersion,
expected: VERSION_CONFIG.TOKEN_VERSION
}
}
// 检查应用版本
const appVersionMatch = localVersions.appVersion === VERSION_CONFIG.APP_VERSION
if (!appVersionMatch) {
return {
needsCheck: true,
needsRefresh: true,
reason: 'app_version_mismatch',
current: localVersions.appVersion,
expected: VERSION_CONFIG.APP_VERSION
}
}
// 更新检查时间
saveLocalVersions({})
return { needsCheck: true, upToDate: true }
}
/**
* 版本检查器类
*/
export class VersionChecker {
constructor() {
this.checkInterval = null
this.isChecking = false
}
/**
* 开始自动版本检查
*/
startAutoCheck() {
if (!VERSION_CONFIG.AUTO_CHECK || this.checkInterval) {
return
}
this.checkInterval = setInterval(() => {
this.performCheck()
}, VERSION_CONFIG.CHECK_INTERVAL)
console.log('版本自动检查已启动,检查间隔:', VERSION_CONFIG.CHECK_INTERVAL, 'ms')
}
/**
* 停止自动版本检查
*/
stopAutoCheck() {
if (this.checkInterval) {
clearInterval(this.checkInterval)
this.checkInterval = null
console.log('版本自动检查已停止')
}
}
/**
* 执行版本检查
*/
async performCheck() {
if (this.isChecking) {
return
}
this.isChecking = true
try {
const result = checkForUpdates()
if (result.needsCheck && !result.upToDate) {
// 触发版本更新事件
this.handleVersionUpdate(result)
}
} catch (error) {
console.error('版本检查失败:', error)
} finally {
this.isChecking = false
}
}
/**
* 处理版本更新
*/
handleVersionUpdate(result) {
if (result.needsLogout) {
console.warn('Token版本不匹配需要重新登录:', result)
// 触发退出登录事件
const event = new CustomEvent('version:logout', {
detail: {
reason: result.reason,
current: result.current,
expected: result.expected
}
})
window.dispatchEvent(event)
}
if (result.needsRefresh) {
console.log('应用版本已更新,需要刷新页面:', result)
// 触发页面刷新事件
const event = new CustomEvent('version:refresh', {
detail: {
reason: result.reason,
current: result.current,
expected: result.expected
}
})
window.dispatchEvent(event)
}
}
/**
* 手动触发版本检查
*/
async manualCheck() {
await this.performCheck()
}
}
// 创建全局版本检查器实例
export const versionChecker = new VersionChecker()
// 导出默认配置
export default {
VERSION_CONFIG,
getLocalVersions,
saveLocalVersions,
clearLocalVersions,
compareVersions,
checkForUpdates,
versionChecker
}