新增:全量优化

This commit is contained in:
yuantao
2025-11-05 16:20:06 +08:00
parent ca6bf7f211
commit 65656f1810
27 changed files with 2407 additions and 292 deletions

80
common/utils/env.js Normal file
View File

@@ -0,0 +1,80 @@
/**
* 环境变量验证工具
*/
/**
* 验证必需的环境变量
* @param {Array<string>} requiredVars 必需的环境变量键名数组
* @throws {Error} 如果有任何必需的环境变量缺失则抛出错误
*/
export function validateEnv(requiredVars = []) {
const missingVars = []
for (const key of requiredVars) {
if (!import.meta.env[key]) {
missingVars.push(key)
}
}
if (missingVars.length > 0) {
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`)
}
}
/**
* 获取环境变量,带默认值和类型转换
* @param {string} key 环境变量键名
* @param {any} defaultValue 默认值
* @param {string} type 类型 ('string' | 'number' | 'boolean' | 'array')
* @returns {any} 环境变量值
*/
export function getEnv(key, defaultValue = null, type = 'string') {
const value = import.meta.env[key]
if (value === undefined || value === null || value === '') {
return defaultValue
}
switch (type) {
case 'number':
const numValue = Number(value)
return isNaN(numValue) ? defaultValue : numValue
case 'boolean':
return value === 'true' || value === '1'
case 'array':
try {
return JSON.parse(value)
} catch (e) {
return Array.isArray(defaultValue) ? defaultValue : []
}
case 'string':
default:
return value
}
}
/**
* 环境变量配置
*/
export const EnvConfig = {
// API基础URL
BASE_URL: getEnv('VITE_BASE_URL', '', 'string'),
// 静态资源URL
ASSETS_URL: getEnv('VITE_ASSETSURL', '', 'string'),
// 小程序APPID
APP_ID: getEnv('VITE_APPID', '', 'string'),
// UNI-APPID
UNI_APP_ID: getEnv('VITE_UNI_APPID', '', 'string'),
// 是否为开发环境
IS_DEV: getEnv('NODE_ENV', 'development', 'string') === 'development',
// 是否为生产环境
IS_PROD: getEnv('NODE_ENV', 'development', 'string') === 'production',
// 调试模式
DEBUG: getEnv('VITE_DEBUG', false, 'boolean'),
// API超时时间毫秒
API_TIMEOUT: getEnv('VITE_API_TIMEOUT', 10000, 'number'),
}
// 验证必需的环境变量
try {
validateEnv(['VITE_BASE_URL', 'VITE_APPID'])
} catch (error) {
console.error('环境变量验证失败:', error.message)
// 在开发环境中抛出错误
if (EnvConfig.IS_DEV) {
throw error
}
}
export default EnvConfig

View File

@@ -1,6 +1,5 @@
const baseUrl = import.meta.env.VITE_BASE_URL
const assetsUrl = import.meta.env.VITE_ASSETSURL
/**
* 工具类 - 提供常用的工具方法
* @class Tool
@@ -13,11 +12,9 @@ class Tool {
SUCCESS: 1,
LOADING: 2,
}
// 字体加载状态缓存
this.loadedFonts = new Set()
}
/**
* 文字轻提示
* @param {string} str 提示文字
@@ -30,13 +27,11 @@ class Tool {
console.warn('alert方法需要提供提示文字')
return
}
const iconMap = {
[this.ICON_TYPES.NONE]: 'none',
[this.ICON_TYPES.SUCCESS]: 'success',
[this.ICON_TYPES.LOADING]: 'loading',
}
uni.showToast({
title: String(str),
icon: iconMap[icon] || 'none',
@@ -49,7 +44,6 @@ class Tool {
})
})
}
/**
* 显示loading加载
* @param {string} [title=' '] 加载文案
@@ -58,14 +52,12 @@ class Tool {
loading(title = ' ', mask = true) {
uni.showLoading({ title, mask })
}
/**
* 关闭loading提示框
*/
hideLoading() {
uni.hideLoading()
}
/**
* 统一处理URL格式确保以/开头
* @param {string} url 页面地址
@@ -76,17 +68,14 @@ class Tool {
if (!url || typeof url !== 'string') {
throw new Error('URL必须是字符串')
}
return url.startsWith('/') ? url : `/${url}`
}
/**
* 可返回跳转(导航到新页面)
* @param {string} url 页面地址
*/
navigateTo(url) {
const formattedUrl = this._formatUrl(url)
uni.navigateTo({
url: formattedUrl,
fail: err => {
@@ -95,7 +84,6 @@ class Tool {
},
})
}
/**
* 不可返回跳转(重定向到新页面)
* @param {string} url 页面地址
@@ -103,7 +91,6 @@ class Tool {
redirectTo(url) {
uni.redirectTo({ url: this._formatUrl(url) })
}
/**
* 清除页面栈跳转(重新启动到新页面)
* @param {string} url 页面地址
@@ -111,7 +98,6 @@ class Tool {
reLaunch(url) {
uni.reLaunch({ url: this._formatUrl(url) })
}
/**
* 跳转tabBar页
* @param {string} url 页面地址
@@ -119,7 +105,6 @@ class Tool {
switchTab(url) {
uni.switchTab({ url: this._formatUrl(url) })
}
/**
* 返回上一页面或指定页面
* @param {number} [delta=1] 返回的页面数
@@ -127,7 +112,6 @@ class Tool {
*/
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
const pages = getCurrentPages()
if (pages.length <= 1) {
console.warn('无上一页,使用回退地址')
uni.reLaunch({ url: fallbackUrl })
@@ -135,7 +119,6 @@ class Tool {
uni.navigateBack({ delta })
}
}
/**
* 操作本地缓存
* @param {string} key 缓存键值
@@ -146,24 +129,20 @@ class Tool {
if (typeof key !== 'string') {
throw new Error('key必须是字符串')
}
// 设置操作
if (value !== undefined && value !== null) {
uni.setStorageSync(key, value)
return
}
// 读取操作
if (key !== '#') {
return uni.getStorageSync(key)
}
// 特殊操作
if (key === '#') {
uni.clearStorageSync()
}
}
/**
* 删除指定缓存
* @param {string} key 要删除的缓存键
@@ -172,10 +151,8 @@ class Tool {
if (typeof key !== 'string') {
throw new Error('key必须是字符串')
}
uni.removeStorageSync(key)
}
/**
* 获取缓存信息
* @returns {Object} 缓存信息
@@ -183,7 +160,6 @@ class Tool {
getStorageInfo() {
return uni.getStorageInfoSync()
}
/**
* 复制文本到剪贴板
* @param {string} data 要复制的文本
@@ -194,7 +170,6 @@ class Tool {
this.alert('暂无内容')
return false
}
try {
await new Promise((resolve, reject) => {
uni.setClipboardData({
@@ -203,7 +178,6 @@ class Tool {
fail: reject,
})
})
this.alert('复制成功')
return true
} catch (error) {
@@ -212,7 +186,6 @@ class Tool {
return false
}
}
/**
* 导入外部字体
* @param {string} fontName 字体文件名(不含路径)
@@ -222,15 +195,12 @@ class Tool {
if (!fontName || typeof fontName !== 'string') {
throw new Error('字体名称必须是字符串')
}
// 检查是否已加载过
if (this.loadedFonts.has(fontName)) {
return true
}
try {
const fontFamily = fontName.replace(/\.[^/.]+$/, '') // 移除文件扩展名
await new Promise((resolve, reject) => {
uni.loadFontFace({
family: fontFamily,
@@ -240,7 +210,6 @@ class Tool {
fail: reject,
})
})
this.loadedFonts.add(fontName)
return true
} catch (error) {
@@ -248,7 +217,6 @@ class Tool {
return false
}
}
/**
* 保存图片到相册
* @param {string} url 图片URL
@@ -259,7 +227,6 @@ class Tool {
this.alert('图片地址不能为空')
return false
}
try {
// 检查权限
const { authSetting } = await new Promise((resolve, reject) => {
@@ -268,7 +235,6 @@ class Tool {
fail: reject,
})
})
if (!authSetting['scope.writePhotosAlbum']) {
// 请求权限
await new Promise((resolve, reject) => {
@@ -279,7 +245,6 @@ class Tool {
})
})
}
// 获取图片信息
const { path } = await new Promise((resolve, reject) => {
uni.getImageInfo({
@@ -288,7 +253,6 @@ class Tool {
fail: reject,
})
})
// 保存到相册
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
@@ -297,12 +261,10 @@ class Tool {
fail: reject,
})
})
this.alert('已保存到相册')
return true
} catch (error) {
console.error('保存图片失败:', error)
if (error.errMsg && error.errMsg.includes('auth')) {
// 权限相关错误
await new Promise(resolve => {
@@ -313,16 +275,240 @@ class Tool {
success: resolve,
})
})
uni.openSetting()
} else {
this.alert('保存失败,请重试')
}
return false
}
}
/**
* 防抖函数
* @param {Function} func 要防抖的函数
* @param {number} wait 延迟时间(ms)
* @param {boolean} immediate 是否立即执行
* @returns {Function} 防抖后的函数
*/
debounce(func, wait, immediate = false) {
let timeout
return function (...args) {
const later = () => {
timeout = null
if (!immediate) func.apply(this, args)
}
const callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(this, args)
}
}
/**
* 节流函数
* @param {Function} func 要节流的函数
* @param {number} wait 延迟时间(ms)
* @returns {Function} 节流后的函数
*/
throttle(func, wait) {
let timeout
let previous = 0
return function (...args) {
const now = Date.now()
const remaining = wait - (now - previous)
const context = this
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
func.apply(context, args)
} else if (!timeout) {
timeout = setTimeout(() => {
previous = Date.now()
timeout = null
func.apply(context, args)
}, remaining)
}
}
}
/**
* 日期格式化
* @param {Date|string|number} date 日期对象、字符串或时间戳
* @param {string} fmt 格式字符串, 默认为 'YYYY-MM-DD HH:mm:ss'
* @returns {string} 格式化后的日期字符串
*/
formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
// 如果传入的是时间戳转换为Date对象
if (typeof date === 'number') {
date = new Date(date)
} else if (typeof date === 'string') {
// 如果传入的是字符串尝试转换为Date对象
date = new Date(date)
}
if (!(date instanceof Date) || isNaN(date.getTime())) {
throw new Error('Invalid date')
}
const o = {
'Y+': date.getFullYear(),
'M+': date.getMonth() + 1,
'D+': date.getDate(),
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
let str = o[k] + ''
if (k === 'S') {
fmt = fmt.replace(RegExp.$1, str.padStart(3, '0'))
} else {
fmt = fmt.replace(RegExp.$1, str.length === 1 ? '0' + str : str)
}
}
}
return fmt
}
/**
* 深拷贝函数
* @param {*} obj 要深拷贝的对象
* @returns {*} 拷贝后的对象
*/
deepClone(obj) {
// 处理null、undefined和原始类型
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 => this.deepClone(item))
}
// 处理普通对象
const clonedObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key])
}
}
return clonedObj
}
/**
* 表单验证工具
* @class Validator
*/
getValidator() {
const rules = {
/**
* 必填验证
* @param {*} value 值
* @param {boolean} required 是否必填
* @returns {boolean} 验证结果
*/
required(value, required = true) {
if (!required) return true
return value !== undefined && value !== null && value !== ''
},
/**
* 邮箱验证
* @param {string} value 值
* @returns {boolean} 验证结果
*/
email(value) {
if (!value) return true
const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return reg.test(value)
},
/**
* 手机号验证
* @param {string} value 值
* @returns {boolean} 验证结果
*/
mobile(value) {
if (!value) return true
const reg = /^1[3-9]\d{9}$/
return reg.test(value)
},
/**
* 最小长度验证
* @param {string} value 值
* @param {number} length 最小长度
* @returns {boolean} 验证结果
*/
minLength(value, length) {
if (!value) return true
return value.length >= length
},
/**
* 最大长度验证
* @param {string} value 值
* @param {number} length 最大长度
* @returns {boolean} 验证结果
*/
maxLength(value, length) {
if (!value) return true
return value.length <= length
},
/**
* 数值范围验证
* @param {number} value 值
* @param {number} min 最小值
* @param {number} max 最大值
* @returns {boolean} 验证结果
*/
range(value, min, max) {
if (value === undefined || value === null) return true
return value >= min && value <= max
},
}
return {
/**
* 验证单个字段
* @param {*} value 值
* @param {Object} rule 规则
* @returns {string|null} 错误信息
*/
validateField(value, rule) {
// 必填验证
if (rule.required !== undefined && !rules.required(value, rule.required)) {
return rule.message || '此字段为必填项'
}
// 如果值为空且非必填,跳过其他验证
if (value === undefined || value === null || value === '') {
return null
}
// 其他验证规则
if (rule.type && rules[rule.type]) {
if (!rules[rule.type](value, rule.param1, rule.param2)) {
return rule.message || '字段格式不正确'
}
}
return null
},
/**
* 验证整个表单
* @param {Object} data 表单数据
* @param {Object} rules 验证规则
* @returns {Object} 验证结果 { isValid: boolean, errors: Object }
*/
validate(data, rules) {
const errors = {}
let isValid = true
for (let field in rules) {
const error = this.validateField(data[field], rules[field])
if (error) {
errors[field] = error
isValid = false
}
}
return { isValid, errors }
},
}
}
/**
* 微信支付
* @param {Object} paymentData 支付参数
@@ -338,6 +524,11 @@ class Tool {
})
})
}
/**
* 文件上传
* @param {string} filePath 文件路径
* @returns {Promise<Object>} 上传结果
*/
upload(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
@@ -357,7 +548,120 @@ class Tool {
})
})
}
/**
* 暗黑模式切换
* @param {boolean} isDark 是否启用暗黑模式
*/
toggleDarkMode(isDark) {
try {
if (isDark) {
// 启用暗黑模式
document.documentElement.setAttribute('data-theme', 'dark')
uni.setStorageSync('theme', 'dark')
} else {
// 启用亮色模式
document.documentElement.setAttribute('data-theme', 'light')
uni.setStorageSync('theme', 'light')
}
} catch (error) {
console.error('切换主题失败:', error)
}
}
/**
* 获取当前主题模式
* @returns {string} 当前主题 ('light' | 'dark')
*/
getCurrentTheme() {
try {
// 优先使用用户设置的主题
const savedTheme = uni.getStorageSync('theme')
if (savedTheme) {
return savedTheme
}
// 检查系统主题
const systemInfo = uni.getSystemInfoSync()
if (systemInfo.theme) {
return systemInfo.theme
}
// 默认返回亮色主题
return 'light'
} catch (error) {
console.error('获取主题失败:', error)
return 'light'
}
}
/**
* 权限检查
* @param {string} permission 权限标识
* @returns {boolean} 是否拥有权限
*/
hasPermission(permission) {
try {
// 获取用户权限列表
const permissions = uni.getStorageSync('permissions') || []
return permissions.includes(permission)
} catch (error) {
console.error('权限检查失败:', error)
return false
}
}
/**
* 设置用户权限
* @param {Array<string>} permissions 权限列表
*/
setPermissions(permissions) {
try {
uni.setStorageSync('permissions', Array.isArray(permissions) ? permissions : [])
} catch (error) {
console.error('设置权限失败:', error)
}
}
/**
* 获取用户所有权限
* @returns {Array<string>} 权限列表
*/
getUserPermissions() {
try {
return uni.getStorageSync('permissions') || []
} catch (error) {
console.error('获取权限失败:', error)
return []
}
}
/**
* 检查用户是否有任意权限
* @param {Array<string>} permissions 权限列表
* @returns {boolean} 是否有任意权限
*/
hasAnyPermission(permissions) {
if (!Array.isArray(permissions)) {
return false
}
const userPermissions = this.getUserPermissions()
return permissions.some(permission => userPermissions.includes(permission))
}
/**
* 检查用户是否拥有所有权限
* @param {Array<string>} permissions 权限列表
* @returns {boolean} 是否拥有所有权限
*/
hasAllPermissions(permissions) {
if (!Array.isArray(permissions)) {
return false
}
const userPermissions = this.getUserPermissions()
return permissions.every(permission => userPermissions.includes(permission))
}
/**
* 清除用户权限
*/
clearPermissions() {
try {
uni.removeStorageSync('permissions')
} catch (error) {
console.error('清除权限失败:', error)
}
}
}
// 创建单例并导出
export default new Tool()