feat: 项目初始化及当前全部内容提交

This commit is contained in:
2025-07-15 17:37:50 +08:00
parent ec817067f1
commit e78f192d34
622 changed files with 75174 additions and 383 deletions
+140
View File
@@ -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)
}
+106
View File
@@ -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
}
+303
View File
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
// 处理换行
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)
}
}
}