Files
template-MP/common/utils/tool.js

618 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 环境变量处理
const getEnvVar = (key, defaultValue = '') => {
if (typeof import !== 'undefined' && import.meta && import.meta.env) {
return import.meta.env[key] || defaultValue
}
if (typeof process !== 'undefined' && process.env) {
return process.env[key] || defaultValue
}
return defaultValue
}
const baseUrl = getEnvVar('VITE_BASE_URL', 'https://api.example.com')
const assetsUrl = getEnvVar('VITE_ASSETSURL', '/assets/')
// 错误监控实例
let errorMonitor = null
/**
* 初始化错误监控实例
*/
function initErrorMonitorInstance() {
if (errorMonitor) return errorMonitor
try {
// 在 Node.js 环境下不尝试导入 uniapp-error-monitor
if (typeof window === 'undefined' && typeof uni === 'undefined') {
throw new Error('非浏览器/UniApp 环境,跳过 uniapp-error-monitor 导入')
}
// 尝试导入 uniapp-error-monitor
const ErrorMonitorModule = require('uniapp-error-monitor')
if (ErrorMonitorModule.default) {
errorMonitor = new ErrorMonitorModule.default()
} else if (ErrorMonitorModule.ErrorMonitor) {
errorMonitor = new ErrorMonitorModule.ErrorMonitor()
} else {
throw new Error('未找到可用的 ErrorMonitor 类')
}
console.log('[Tool] 使用 uniapp-error-monitor')
} catch (error) {
console.warn('[Tool] uniapp-error-monitor 导入失败,将使用本地错误监控实现:', error.message)
// 本地错误监控实现
errorMonitor = {
errorStats: {
total: 0,
global: 0,
promise: 0,
console: 0,
miniProgram: 0,
api: 0,
network: 0,
manual: 0,
lastErrorTime: null,
},
init(config = {}) {
console.log('[Tool] 使用本地错误监控实现')
// 这里可以添加本地错误监控逻辑
},
reportError(type = 'manual', error, context = {}, forceSend = false) {
this.errorStats.total++
this.errorStats[type] = (this.errorStats[type] || 0) + 1
this.errorStats.lastErrorTime = Date.now()
console.log(`[Tool] 错误上报: ${type}`, error)
},
wrapPromise(promise) {
return promise.catch(error => {
this.reportError('promise', error)
throw error
})
},
getErrorStats() {
return { ...this.errorStats }
},
resetErrorStats() {
this.errorStats = {
total: 0,
global: 0,
promise: 0,
console: 0,
miniProgram: 0,
api: 0,
network: 0,
manual: 0,
lastErrorTime: null,
}
},
getEnvironmentInfo() {
return {
isProduction: getEnvVar('MODE') === 'production',
mode: getEnvVar('MODE', 'development'),
platform: 'Unknown',
errorMonitorEnabled: true,
timestamp: Date.now(),
}
}
}
}
return errorMonitor
}
// 初始化错误监控实例
const monitorInstance = initErrorMonitorInstance()
/**
* 工具类 - 提供常用的工具方法
* @class Tool
*/
class Tool {
constructor() {
// 图标类型映射
this.ICON_TYPES = {
NONE: 0,
SUCCESS: 1,
LOADING: 2,
ERROR: 3,
WARNING: 4,
}
// 字体加载状态缓存
this.loadedFonts = new Set()
// 错误监控配置
this.config = null
}
/**
* 文字轻提示
* @param {string} str 提示文字
* @param {number} [icon=0] 提示icon (0: none, 1: success, 2: loading)
* @param {number} [duration=1500] 提示时间(毫秒)
*/
alert(str, icon = this.ICON_TYPES.NONE, duration = 1500) {
return new Promise((resolve, reject) => {
if (!str && str !== 0) {
console.warn('alert方法需要提供提示文字')
return
}
const iconMap = {
[this.ICON_TYPES.NONE]: 'none',
[this.ICON_TYPES.SUCCESS]: 'success',
[this.ICON_TYPES.LOADING]: 'loading',
[this.ICON_TYPES.ERROR]: 'error',
[this.ICON_TYPES.WARNING]: 'none', // uni-app toast不支持warning图标
}
uni.showToast({
title: String(str),
icon: iconMap[icon] || 'none',
mask: true,
duration,
success: () => {
setTimeout(resolve, duration)
},
fail: reject,
})
})
}
/**
* 显示loading加载
* @param {string} [title=' '] 加载文案
* @param {boolean} [mask=true] 是否显示遮罩
*/
loading(title = ' ', mask = true) {
uni.showLoading({ title, mask })
}
/**
* 关闭loading提示框
*/
hideLoading() {
uni.hideLoading()
}
/**
* 统一处理URL格式确保以/开头
* @param {string} url 页面地址
* @returns {string} 格式化后的URL
* @private
*/
_formatUrl(url) {
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 => {
console.warn('navigateTo失败尝试switchTab:', err)
uni.switchTab({ url: formattedUrl })
},
})
}
/**
* 不可返回跳转(重定向到新页面)
* @param {string} url 页面地址
*/
redirectTo(url) {
uni.redirectTo({ url: this._formatUrl(url) })
}
/**
* 清除页面栈跳转(重新启动到新页面)
* @param {string} url 页面地址
*/
reLaunch(url) {
uni.reLaunch({ url: this._formatUrl(url) })
}
/**
* 跳转tabBar页
* @param {string} url 页面地址
*/
switchTab(url) {
uni.switchTab({ url: this._formatUrl(url) })
}
/**
* 返回上一页面或指定页面
* @param {number} [delta=1] 返回的页面数
* @param {string} [fallbackUrl='/pages/index/index'] 无上一页时的回退地址
*/
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
const pages = getCurrentPages()
if (pages.length <= 1) {
console.warn('无上一页,使用回退地址')
uni.reLaunch({ url: fallbackUrl })
} else {
uni.navigateBack({ delta })
}
}
/**
* 操作本地缓存
* @param {string} key 缓存键值
* @param {any} [value] 缓存数据,不传则为读取
* @returns {any|undefined} 读取操作时返回数据
*/
storage(key, value) {
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 要删除的缓存键
*/
removeStorage(key) {
if (typeof key !== 'string') {
throw new Error('key必须是字符串')
}
uni.removeStorageSync(key)
}
/**
* 获取缓存信息
* @returns {Object} 缓存信息
*/
getStorageInfo() {
return uni.getStorageInfoSync()
}
/**
* 复制文本到剪贴板
* @param {string} data 要复制的文本
* @returns {Promise<boolean>} 复制是否成功
*/
async copy(data) {
if (!data && data !== 0) {
this.alert('暂无内容')
return false
}
try {
await new Promise((resolve, reject) => {
uni.setClipboardData({
data: String(data),
success: resolve,
fail: reject,
})
})
this.alert('复制成功')
return true
} catch (error) {
console.error('复制失败:', error)
this.alert('复制失败,请重试')
return false
}
}
/**
* 导入外部字体
* @param {string} fontName 字体文件名(不含路径)
* @returns {Promise<boolean>} 字体加载是否成功
*/
async loadFont(fontName) {
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,
source: `url(${assetsUrl}${fontName})`,
global: true,
success: resolve,
fail: reject,
})
})
this.loadedFonts.add(fontName)
return true
} catch (error) {
console.error(`字体加载失败: ${fontName}`, error)
return false
}
}
/**
* 保存图片到相册
* @param {string} url 图片URL
* @returns {Promise<boolean>} 保存是否成功
*/
async saveImageToPhotos(url) {
if (!url) {
this.alert('图片地址不能为空')
return false
}
try {
// 检查权限
const { authSetting } = await new Promise((resolve, reject) => {
uni.getSetting({
success: resolve,
fail: reject,
})
})
if (!authSetting['scope.writePhotosAlbum']) {
// 请求权限
await new Promise((resolve, reject) => {
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: resolve,
fail: reject,
})
})
}
// 获取图片信息
const { path } = await new Promise((resolve, reject) => {
uni.getImageInfo({
src: url,
success: resolve,
fail: reject,
})
})
// 保存到相册
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
filePath: path,
success: resolve,
fail: reject,
})
})
this.alert('已保存到相册')
return true
} catch (error) {
console.error('保存图片失败:', error)
if (error.errMsg && error.errMsg.includes('auth')) {
// 权限相关错误
await new Promise(resolve => {
uni.showModal({
title: '保存失败',
content: '请开启访问手机相册权限',
showCancel: false,
success: resolve,
})
})
uni.openSetting()
} else {
this.alert('保存失败,请重试')
}
return false
}
}
/**
* 微信支付
* @param {Object} paymentData 支付参数
* @returns {Promise<Object>} 支付结果
*/
requestPayment(paymentData) {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: 'wxpay',
...paymentData,
success: resolve,
fail: reject,
})
})
}
/**
* 处理支付流程的完整方法
* @param {Object} paymentData 支付参数
* @param {Function} successCallback 成功回调
* @param {Function} errorCallback 错误回调
* @returns {Promise<Object>} 支付结果
*/
async handlePayment(paymentData, successCallback = null, errorCallback = null) {
try {
// 调用微信支付
const res = await this.requestPayment(paymentData)
this.alert('支付完成,正在验证结果...', this.ICON_TYPES.LOADING)
// 如果有成功回调,调用它
if (successCallback && typeof successCallback === 'function') {
await successCallback(res)
}
return res
} catch (error) {
console.log('支付失败:', error)
// 错误处理
if (error.errMsg && error.errMsg.includes('cancel')) {
await this.alert('支付已取消', this.ICON_TYPES.WARNING)
} else {
await this.alert('支付失败,请重试', this.ICON_TYPES.ERROR)
}
// 如果有错误回调,调用它
if (errorCallback && typeof errorCallback === 'function') {
await errorCallback(error)
}
throw error
}
}
upload(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${baseUrl}/j1732/api/front/upload/image`,
fileType: 'image',
header: {
'Authori-zation': `${this.storage('token')}`,
},
filePath,
name: 'multipart',
formData: {
model: 'user', // 用户模块
pid: 7, // 前台用户分类ID
},
success: ({ data }) => {
resolve(JSON.parse(data))
},
fail: error => {
reject(error)
},
})
})
}
/**
* 初始化全局错误监控
* @param {Object} options 配置选项
* @param {boolean} [options.enableGlobalError=true] 是否启用全局错误捕获
* @param {boolean} [options.enablePromiseError=true] 是否启用Promise错误捕获
* @param {boolean} [options.enableConsoleError=true] 是否启用console.error捕获
* @param {string} [options.webhookUrl] 自定义webhook地址不传则使用环境变量
* @param {number} [options.maxRetries=3] 发送失败时最大重试次数
* @param {number} [options.retryDelay=1000] 重试延迟时间(毫秒)
* @param {boolean} [options.forceEnable=false] 强制启用错误监控(忽略环境检查)
*/
initErrorMonitor(options = {}) {
const config = {
enableGlobalError: true,
enablePromiseError: true,
enableConsoleError: false,
webhookUrl: import.meta.env.VITE_WEBHOOK,
maxRetries: 3,
retryDelay: 1000,
forceEnable: false,
...options,
}
this.config = config
// 使用 uniapp-error-monitor 或本地实现初始化错误监控
monitorInstance.init(config)
console.log('错误监控已初始化(基于 uniapp-error-monitor')
}
/**
* 手动上报错误
* @param {string} type 错误类型 ('manual', 'api', 'network', 'global', 'promise', 'console', 'miniProgram')
* @param {Error|Object} error 错误对象或错误信息
* @param {Object} [context] 错误上下文信息
* @param {boolean} [forceSend=false] 强制发送(忽略环境检查)
*/
reportError(type = 'manual', error, context = {}, forceSend = false) {
monitorInstance.reportError(type, error, context, forceSend)
}
/**
* 包装Promise自动捕获Promise错误
* @param {Promise} promise 要包装的Promise
* @returns {Promise} 包装后的Promise
*/
wrapPromise(promise) {
return monitorInstance.wrapPromise(promise)
}
/**
* 获取错误统计信息
* @returns {Object} 错误统计信息
*/
getErrorStats() {
return monitorInstance.getErrorStats()
}
/**
* 重置错误统计
*/
resetErrorStats() {
monitorInstance.resetErrorStats()
}
/**
* 获取当前环境信息
* @returns {Object} 环境信息
*/
getEnvironmentInfo() {
return monitorInstance.getEnvironmentInfo()
}
/**
* 确认对话框
* @param {string} content 确认内容
* @param {string} [title='提示'] 标题
* @returns {Promise<boolean>} 用户选择结果true为确认false为取消
*/
confirm(content, title = '提示') {
return new Promise(resolve => {
uni.showModal({
title,
content,
confirmText: '确认',
cancelText: '取消',
success: res => {
resolve(res.confirm)
},
fail: () => {
resolve(false)
},
})
})
}
}
// 创建单例并导出
export default new Tool()