feat: 项目初始化及当前全部内容提交
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { userApi } from '@/api/user'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
/**
|
||||
* 认证工具类
|
||||
*/
|
||||
export class AuthUtils {
|
||||
static TOKEN_KEY = 'token'
|
||||
static REFRESH_TOKEN_KEY = 'refreshToken'
|
||||
static USER_KEY = 'emotion_museum_user'
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*/
|
||||
static getToken() {
|
||||
return localStorage.getItem(this.TOKEN_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取刷新token
|
||||
*/
|
||||
static getRefreshToken() {
|
||||
return localStorage.getItem(this.REFRESH_TOKEN_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置token
|
||||
*/
|
||||
static setToken(token, refreshToken) {
|
||||
localStorage.setItem(this.TOKEN_KEY, token)
|
||||
if (refreshToken) {
|
||||
localStorage.setItem(this.REFRESH_TOKEN_KEY, refreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除token
|
||||
*/
|
||||
static clearTokens() {
|
||||
localStorage.removeItem(this.TOKEN_KEY)
|
||||
localStorage.removeItem(this.REFRESH_TOKEN_KEY)
|
||||
localStorage.removeItem(this.USER_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查token是否存在
|
||||
*/
|
||||
static hasToken() {
|
||||
return !!this.getToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查token是否即将过期(提前5分钟刷新)
|
||||
*/
|
||||
static isTokenExpiringSoon(token) {
|
||||
if (!token) return true
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||||
const exp = payload.exp * 1000 // 转换为毫秒
|
||||
const now = Date.now()
|
||||
const fiveMinutes = 5 * 60 * 1000
|
||||
|
||||
return (exp - now) < fiveMinutes
|
||||
} catch (error) {
|
||||
console.warn('解析token失败:', error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
static async refreshToken() {
|
||||
const refreshToken = this.getRefreshToken()
|
||||
if (!refreshToken) {
|
||||
throw new Error('没有刷新token')
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await userApi.refreshToken(refreshToken)
|
||||
if (response.success) {
|
||||
const { accessToken, refreshToken: newRefreshToken } = response.data
|
||||
this.setToken(accessToken, newRefreshToken)
|
||||
return accessToken
|
||||
} else {
|
||||
throw new Error(response.message || '刷新token失败')
|
||||
}
|
||||
} catch (error) {
|
||||
// 刷新失败,清除所有token
|
||||
this.clearTokens()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动刷新token(如果需要)
|
||||
*/
|
||||
static async autoRefreshToken() {
|
||||
const token = this.getToken()
|
||||
if (!token) return null
|
||||
|
||||
if (this.isTokenExpiringSoon(token)) {
|
||||
try {
|
||||
return await this.refreshToken()
|
||||
} catch (error) {
|
||||
console.warn('自动刷新token失败:', error)
|
||||
// 跳转到登录页
|
||||
const userStore = useUserStore()
|
||||
userStore.clearUser()
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
static async logout() {
|
||||
const userStore = useUserStore()
|
||||
await userStore.logout()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置token自动刷新定时器
|
||||
*/
|
||||
export function setupTokenRefreshTimer() {
|
||||
// 每5分钟检查一次token是否需要刷新
|
||||
setInterval(async () => {
|
||||
if (AuthUtils.hasToken()) {
|
||||
await AuthUtils.autoRefreshToken()
|
||||
}
|
||||
}, 5 * 60 * 1000)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 环境配置使用示例
|
||||
*
|
||||
* 这个文件展示了如何在项目中使用环境变量配置
|
||||
*/
|
||||
|
||||
import { ENV_CONFIG, isDev, isTest, isProd, debugLog, getApiUrl, printEnvInfo } from '@/config/env'
|
||||
|
||||
// 示例1: 基础环境判断
|
||||
export const exampleEnvironmentCheck = () => {
|
||||
if (isDev()) {
|
||||
console.log('当前是开发环境')
|
||||
// 开发环境特有逻辑
|
||||
} else if (isTest()) {
|
||||
console.log('当前是测试环境')
|
||||
// 测试环境特有逻辑
|
||||
} else if (isProd()) {
|
||||
console.log('当前是生产环境')
|
||||
// 生产环境特有逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 示例2: 使用调试日志
|
||||
export const exampleDebugLog = () => {
|
||||
debugLog('这条日志只在调试模式下显示')
|
||||
debugLog('用户操作:', { action: 'click', target: 'button' })
|
||||
}
|
||||
|
||||
// 示例3: 获取API地址
|
||||
export const exampleApiCall = async () => {
|
||||
const userApiUrl = getApiUrl('/user/profile')
|
||||
debugLog('API地址:', userApiUrl)
|
||||
|
||||
// 使用fetch或axios调用API
|
||||
try {
|
||||
const response = await fetch(userApiUrl)
|
||||
const data = await response.json()
|
||||
debugLog('API响应:', data)
|
||||
return data
|
||||
} catch (error) {
|
||||
debugLog('API错误:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 示例4: 根据环境配置不同的行为
|
||||
export const exampleConditionalBehavior = () => {
|
||||
// 根据环境显示不同的标题
|
||||
document.title = ENV_CONFIG.APP_TITLE
|
||||
|
||||
// 在开发环境启用额外的调试工具
|
||||
if (ENV_CONFIG.DEBUG_MODE) {
|
||||
// 启用Vue DevTools
|
||||
window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.__VUE_DEVTOOLS_GLOBAL_HOOK__ || {}
|
||||
|
||||
// 打印环境信息
|
||||
printEnvInfo()
|
||||
}
|
||||
|
||||
// 根据环境配置不同的错误处理
|
||||
if (isProd()) {
|
||||
// 生产环境:静默处理错误,发送到监控系统
|
||||
window.addEventListener('error', (event) => {
|
||||
// 发送错误到监控系统
|
||||
console.error('生产环境错误:', event.error)
|
||||
})
|
||||
} else {
|
||||
// 开发/测试环境:显示详细错误信息
|
||||
window.addEventListener('error', (event) => {
|
||||
debugLog('开发环境错误:', event.error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 示例5: 环境特定的配置
|
||||
export const getEnvironmentSpecificConfig = () => {
|
||||
const config = {
|
||||
// 基础配置
|
||||
apiTimeout: ENV_CONFIG.API_TIMEOUT,
|
||||
debugMode: ENV_CONFIG.DEBUG_MODE,
|
||||
|
||||
// 环境特定配置
|
||||
enableAnalytics: isProd(), // 只在生产环境启用分析
|
||||
enableMocking: ENV_CONFIG.MOCK_DATA, // 根据环境变量决定是否启用模拟数据
|
||||
logLevel: isDev() ? 'debug' : isProd() ? 'error' : 'info',
|
||||
|
||||
// 功能开关
|
||||
features: {
|
||||
newFeature: isDev() || isTest(), // 新功能只在开发和测试环境启用
|
||||
betaFeature: !isProd(), // Beta功能在非生产环境启用
|
||||
experimentalFeature: isDev() // 实验性功能只在开发环境启用
|
||||
}
|
||||
}
|
||||
|
||||
debugLog('环境特定配置:', config)
|
||||
return config
|
||||
}
|
||||
|
||||
// 导出所有示例函数
|
||||
export default {
|
||||
exampleEnvironmentCheck,
|
||||
exampleDebugLog,
|
||||
exampleApiCall,
|
||||
exampleConditionalBehavior,
|
||||
getEnvironmentSpecificConfig
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
|
||||
// 配置 dayjs
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param {string|Date} time - 时间
|
||||
* @param {string} format - 格式化模板
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
export function formatTime(time, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if (!time) return ''
|
||||
|
||||
const now = dayjs()
|
||||
const target = dayjs(time)
|
||||
const diffInHours = now.diff(target, 'hour')
|
||||
const diffInDays = now.diff(target, 'day')
|
||||
|
||||
// 如果是今天
|
||||
if (diffInDays === 0) {
|
||||
if (diffInHours === 0) {
|
||||
return target.fromNow() // 几分钟前
|
||||
} else {
|
||||
return target.format('HH:mm') // 今天的时间
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是昨天
|
||||
if (diffInDays === 1) {
|
||||
return `昨天 ${target.format('HH:mm')}`
|
||||
}
|
||||
|
||||
// 如果是本周
|
||||
if (diffInDays < 7) {
|
||||
return target.format('dddd HH:mm')
|
||||
}
|
||||
|
||||
// 如果是今年
|
||||
if (target.year() === now.year()) {
|
||||
return target.format('MM-DD HH:mm')
|
||||
}
|
||||
|
||||
// 其他情况使用完整格式
|
||||
return target.format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化相对时间
|
||||
* @param {string|Date} time - 时间
|
||||
* @returns {string} 相对时间字符串
|
||||
*/
|
||||
export function formatRelativeTime(time) {
|
||||
if (!time) return ''
|
||||
return dayjs(time).fromNow()
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化消息内容
|
||||
* @param {string} content - 消息内容
|
||||
* @returns {string} 格式化后的HTML内容
|
||||
*/
|
||||
export function formatMessage(content) {
|
||||
if (!content) return ''
|
||||
|
||||
// 转义HTML特殊字符
|
||||
const escaped = content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
|
||||
// 处理换行
|
||||
let formatted = escaped.replace(/\n/g, '<br>')
|
||||
|
||||
// 处理链接
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g
|
||||
formatted = formatted.replace(urlRegex, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>')
|
||||
|
||||
// 处理邮箱
|
||||
const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g
|
||||
formatted = formatted.replace(emailRegex, '<a href="mailto:$1">$1</a>')
|
||||
|
||||
// 处理电话号码
|
||||
const phoneRegex = /(\d{3}-\d{4}-\d{4}|\d{11})/g
|
||||
formatted = formatted.replace(phoneRegex, '<a href="tel:$1">$1</a>')
|
||||
|
||||
// 处理表情符号(简单的文本表情)
|
||||
const emoticons = {
|
||||
':)': '😊',
|
||||
':-)': '😊',
|
||||
':(': '😢',
|
||||
':-(': '😢',
|
||||
':D': '😃',
|
||||
':-D': '😃',
|
||||
':P': '😛',
|
||||
':-P': '😛',
|
||||
';)': '😉',
|
||||
';-)': '😉',
|
||||
':o': '😮',
|
||||
':-o': '😮',
|
||||
':|': '😐',
|
||||
':-|': '😐',
|
||||
'<3': '❤️',
|
||||
'</3': '💔'
|
||||
}
|
||||
|
||||
Object.entries(emoticons).forEach(([text, emoji]) => {
|
||||
const regex = new RegExp(escapeRegExp(text), 'g')
|
||||
formatted = formatted.replace(regex, emoji)
|
||||
})
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义正则表达式特殊字符
|
||||
* @param {string} string - 要转义的字符串
|
||||
* @returns {string} 转义后的字符串
|
||||
*/
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes - 字节数
|
||||
* @returns {string} 格式化后的文件大小
|
||||
*/
|
||||
export function 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 {number} num - 数字
|
||||
* @param {number} precision - 精度
|
||||
* @returns {string} 格式化后的数字
|
||||
*/
|
||||
export function formatNumber(num, precision = 0) {
|
||||
if (typeof num !== 'number') return '0'
|
||||
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(precision) + 'M'
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(precision) + 'K'
|
||||
} else {
|
||||
return num.toFixed(precision)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化百分比
|
||||
* @param {number} value - 值
|
||||
* @param {number} total - 总数
|
||||
* @param {number} precision - 精度
|
||||
* @returns {string} 百分比字符串
|
||||
*/
|
||||
export function formatPercentage(value, total, precision = 1) {
|
||||
if (total === 0) return '0%'
|
||||
return ((value / total) * 100).toFixed(precision) + '%'
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断文本
|
||||
* @param {string} text - 文本
|
||||
* @param {number} maxLength - 最大长度
|
||||
* @param {string} suffix - 后缀
|
||||
* @returns {string} 截断后的文本
|
||||
*/
|
||||
export function truncateText(text, maxLength = 100, suffix = '...') {
|
||||
if (!text || text.length <= maxLength) return text
|
||||
return text.substring(0, maxLength - suffix.length) + suffix
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化持续时间
|
||||
* @param {number} seconds - 秒数
|
||||
* @returns {string} 格式化后的持续时间
|
||||
*/
|
||||
export function formatDuration(seconds) {
|
||||
if (seconds < 60) {
|
||||
return `${Math.round(seconds)}秒`
|
||||
} else if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.round(seconds % 60)
|
||||
return remainingSeconds > 0 ? `${minutes}分${remainingSeconds}秒` : `${minutes}分钟`
|
||||
} else {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
return minutes > 0 ? `${hours}小时${minutes}分钟` : `${hours}小时`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化货币
|
||||
* @param {number} amount - 金额
|
||||
* @param {string} currency - 货币符号
|
||||
* @returns {string} 格式化后的货币
|
||||
*/
|
||||
export function formatCurrency(amount, currency = '¥') {
|
||||
if (typeof amount !== 'number') return `${currency}0.00`
|
||||
return `${currency}${amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱格式
|
||||
* @param {string} email - 邮箱地址
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
export function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号格式
|
||||
* @param {string} phone - 手机号
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
export function isValidPhone(phone) {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
return phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机ID
|
||||
* @param {number} length - 长度
|
||||
* @returns {string} 随机ID
|
||||
*/
|
||||
export function generateId(length = 8) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝对象
|
||||
* @param {any} obj - 要拷贝的对象
|
||||
* @returns {any} 拷贝后的对象
|
||||
*/
|
||||
export function 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 (obj.hasOwnProperty(key)) {
|
||||
clonedObj[key] = deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func - 要防抖的函数
|
||||
* @param {number} wait - 等待时间
|
||||
* @returns {Function} 防抖后的函数
|
||||
*/
|
||||
export function 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 function throttle(func, limit) {
|
||||
let inThrottle
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args)
|
||||
inThrottle = true
|
||||
setTimeout(() => inThrottle = false, limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user