This commit is contained in:
2025-12-13 09:18:08 +08:00
parent 7807f4b3e4
commit 0042a0220f
79 changed files with 30697 additions and 2 deletions

80
src/utils/auth.js Normal file
View File

@@ -0,0 +1,80 @@
/**
* Token管理工具
*/
const TOKEN_KEY = 'access_token'
const REFRESH_TOKEN_KEY = 'refresh_token'
const USER_INFO_KEY = 'userInfo'
/**
* 获取Token
*/
export function getToken() {
return uni.getStorageSync(TOKEN_KEY)
}
/**
* 设置Token
*/
export function setToken(token) {
return uni.setStorageSync(TOKEN_KEY, token)
}
/**
* 移除Token
*/
export function removeToken() {
return uni.removeStorageSync(TOKEN_KEY)
}
/**
* 获取RefreshToken
*/
export function getRefreshToken() {
return uni.getStorageSync(REFRESH_TOKEN_KEY)
}
/**
* 设置RefreshToken
*/
export function setRefreshToken(token) {
return uni.setStorageSync(REFRESH_TOKEN_KEY, token)
}
/**
* 获取用户信息
*/
export function getUserInfo() {
const userInfo = uni.getStorageSync(USER_INFO_KEY)
return userInfo ? JSON.parse(userInfo) : null
}
/**
* 设置用户信息
*/
export function setUserInfo(userInfo) {
return uni.setStorageSync(USER_INFO_KEY, JSON.stringify(userInfo))
}
/**
* 移除用户信息
*/
export function removeUserInfo() {
return uni.removeStorageSync(USER_INFO_KEY)
}
/**
* 检查是否登录
*/
export function isLogin() {
return !!(getToken() && getUserInfo())
}
/**
* 清除所有认证信息
*/
export function clearAuth() {
removeToken()
uni.removeStorageSync(REFRESH_TOKEN_KEY)
removeUserInfo()
}

40
src/utils/base64.js Normal file
View File

@@ -0,0 +1,40 @@
/**
* Base64编码工具
*/
const base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
export function base64Encode(str) {
let out = ''
let i = 0
const len = str.length
let c1, c2, c3
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff
if (i === len) {
out += base64EncodeChars.charAt(c1 >> 2)
out += base64EncodeChars.charAt((c1 & 0x3) << 4)
out += '=='
break
}
c2 = str.charCodeAt(i++)
if (i === len) {
out += base64EncodeChars.charAt(c1 >> 2)
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))
out += base64EncodeChars.charAt((c2 & 0xF) << 2)
out += '='
break
}
c3 = str.charCodeAt(i++)
out += base64EncodeChars.charAt(c1 >> 2)
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4))
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6))
out += base64EncodeChars.charAt(c3 & 0x3F)
}
return out
}
export default {
base64Encode
}

212
src/utils/md5.js Normal file
View File

@@ -0,0 +1,212 @@
/**
* MD5加密工具
* 用于密码加密
*/
function md5(string) {
function md5_RotateLeft(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits))
}
function md5_AddUnsigned(lX, lY) {
let lX4, lY4, lX8, lY8, lResult
lX8 = (lX & 0x80000000)
lY8 = (lY & 0x80000000)
lX4 = (lX & 0x40000000)
lY4 = (lY & 0x40000000)
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF)
if (lX4 & lY4) {
return (lResult ^ 0x80000000 ^ lX8 ^ lY8)
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8)
} else {
return (lResult ^ 0x40000000 ^ lX8 ^ lY8)
}
} else {
return (lResult ^ lX8 ^ lY8)
}
}
function md5_F(x, y, z) {
return (x & y) | ((~x) & z)
}
function md5_G(x, y, z) {
return (x & z) | (y & (~z))
}
function md5_H(x, y, z) {
return (x ^ y ^ z)
}
function md5_I(x, y, z) {
return (y ^ (x | (~z)))
}
function md5_FF(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac))
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
}
function md5_GG(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac))
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
}
function md5_HH(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac))
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
}
function md5_II(a, b, c, d, x, s, ac) {
a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac))
return md5_AddUnsigned(md5_RotateLeft(a, s), b)
}
function md5_ConvertToWordArray(string) {
let lWordCount
const lMessageLength = string.length
const lNumberOfWords_temp1 = lMessageLength + 8
const lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64
const lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16
const lWordArray = Array(lNumberOfWords - 1)
let lBytePosition = 0
let lByteCount = 0
while (lByteCount < lMessageLength) {
lWordCount = (lByteCount - (lByteCount % 4)) / 4
lBytePosition = (lByteCount % 4) * 8
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition))
lByteCount++
}
lWordCount = (lByteCount - (lByteCount % 4)) / 4
lBytePosition = (lByteCount % 4) * 8
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition)
lWordArray[lNumberOfWords - 2] = lMessageLength << 3
lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29
return lWordArray
}
function md5_WordToHex(lValue) {
let WordToHexValue = '', WordToHexValue_temp = '', lByte, lCount
for (lCount = 0; lCount <= 3; lCount++) {
lByte = (lValue >>> (lCount * 8)) & 255
WordToHexValue_temp = '0' + lByte.toString(16)
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2)
}
return WordToHexValue
}
function md5_Utf8Encode(string) {
string = string.replace(/\r\n/g, '\n')
let utftext = ''
for (let n = 0; n < string.length; n++) {
const c = string.charCodeAt(n)
if (c < 128) {
utftext += String.fromCharCode(c)
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192)
utftext += String.fromCharCode((c & 63) | 128)
} else {
utftext += String.fromCharCode((c >> 12) | 224)
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
utftext += String.fromCharCode((c & 63) | 128)
}
}
return utftext
}
let x = []
let k, AA, BB, CC, DD, a, b, c, d
const S11 = 7, S12 = 12, S13 = 17, S14 = 22
const S21 = 5, S22 = 9, S23 = 14, S24 = 20
const S31 = 4, S32 = 11, S33 = 16, S34 = 23
const S41 = 6, S42 = 10, S43 = 15, S44 = 21
string = md5_Utf8Encode(string)
x = md5_ConvertToWordArray(string)
a = 0x67452301
b = 0xEFCDAB89
c = 0x98BADCFE
d = 0x10325476
for (k = 0; k < x.length; k += 16) {
AA = a
BB = b
CC = c
DD = d
a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478)
d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756)
c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB)
b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE)
a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF)
d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A)
c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613)
b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501)
a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8)
d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF)
c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1)
b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE)
a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122)
d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193)
c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E)
b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821)
a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562)
d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340)
c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51)
b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA)
a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D)
d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453)
c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681)
b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8)
a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6)
d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6)
c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87)
b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED)
a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905)
d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8)
c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9)
b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A)
a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942)
d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681)
c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122)
b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C)
a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44)
d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9)
c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60)
b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70)
a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6)
d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA)
c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085)
b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05)
a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039)
d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5)
c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8)
b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665)
a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244)
d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97)
c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7)
b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039)
a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3)
d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92)
c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D)
b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1)
a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F)
d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0)
c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314)
b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1)
a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82)
d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235)
c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB)
b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391)
a = md5_AddUnsigned(a, AA)
b = md5_AddUnsigned(b, BB)
c = md5_AddUnsigned(c, CC)
d = md5_AddUnsigned(d, DD)
}
return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase()
}
export default md5

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

@@ -0,0 +1,384 @@
/**
* HTTP请求封装
* 基于uni.request进行二次封装
*/
import config from '@/config/api.config.js'
class Request {
constructor() {
this.baseURL = config.baseURL
this.timeout = config.timeout
this.isRedirecting = false // 防止重复跳转登录页
}
/**
* 核心请求方法
* @param {Object} options 请求配置
* @returns {Promise}
*/
request(options) {
// 显示loading
if (options.loading !== false) {
uni.showLoading({
title: options.loadingText || '加载中...',
mask: true
})
}
// 打印请求信息仅POST请求
if (options.method === 'POST') {
console.log('=== HTTP POST 请求 ===')
console.log('URL:', this.baseURL + options.url)
console.log('Method:', options.method)
console.log('请求数据:', options.data)
console.log('请求头:', this.getHeaders(options.header))
}
return new Promise((resolve, reject) => {
uni.request({
url: this.baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: this.getHeaders(options.header),
timeout: options.timeout || this.timeout,
success: (res) => {
// 隐藏loading
if (options.loading !== false) {
uni.hideLoading()
}
console.log('=== HTTP 响应 ===')
console.log('状态码:', res.statusCode)
console.log('响应数据:', res.data)
// 处理响应
this.handleResponse(res, resolve, reject)
},
fail: (err) => {
// 隐藏loading
if (options.loading !== false) {
uni.hideLoading()
}
console.log('=== HTTP 请求失败 ===')
console.log('错误信息:', err)
// 处理错误
this.handleError(err, reject)
}
})
})
}
/**
* 获取请求头
* @param {Object} customHeader 自定义请求头
* @returns {Object}
*/
getHeaders(customHeader = {}) {
// 获取token - 使用access_token作为key
const token = uni.getStorageSync('access_token') || ''
return {
'Content-Type': 'application/json',
'Blade-Auth': token ? `bearer ${token}` : '',
'Tenant-Id': '000000',
...customHeader
}
}
/**
* 处理响应数据
* @param {Object} res 响应对象
* @param {Function} resolve Promise resolve
* @param {Function} reject Promise reject
*/
handleResponse(res, resolve, reject) {
const data = res.data
// 判断HTTP状态码 - 特别处理401
if (res.statusCode === 401) {
this.handleTokenExpired()
reject({
statusCode: 401,
code: 401,
message: '未登录或登录已过期'
})
return
}
// 其他HTTP错误状态码
if (res.statusCode < 200 || (res.statusCode >= 300 && res.statusCode !== 304)) {
this.showError('网络请求失败')
reject({
statusCode: res.statusCode,
code: res.statusCode,
message: '网络请求失败'
})
return
}
// 特殊处理OAuth2 token接口直接返回数据有access_token字段
if (data.access_token) {
resolve(data)
return
}
// 判断业务状态码
if (data.code === 200 || data.success === true) {
// 请求成功,返回数据
resolve(data.data)
} else {
// 业务错误处理
const errorMsg = data.msg || data.message || data.error_description || '请求失败'
// 特殊错误码处理
if (data.code === 401 || data.code === 403) {
// token过期或无权限
this.handleTokenExpired()
}
this.showError(errorMsg)
reject({
statusCode: res.statusCode,
code: data.code,
message: errorMsg,
data: data.data
})
}
}
/**
* 处理请求错误
* @param {Object} err 错误对象
* @param {Function} reject Promise reject
*/
handleError(err, reject) {
console.error('请求失败:', err)
console.error('请求URL:', this.baseURL)
let message = '网络请求失败'
if (err.errMsg) {
if (err.errMsg.includes('timeout')) {
message = '请求超时,请检查网络'
} else if (err.errMsg.includes('fail')) {
message = '网络连接失败,请检查服务器地址'
}
}
this.showError(message)
reject({
code: undefined,
message: '请求失败',
data: undefined,
error: err,
url: this.baseURL
})
}
/**
* 显示错误提示
* @param {String} message 错误信息
*/
showError(message) {
uni.showToast({
title: message,
icon: 'none',
duration: 2000
})
}
/**
* 处理token过期
*/
handleTokenExpired() {
console.log('=== handleTokenExpired 被调用 ===')
console.log('isRedirecting:', this.isRedirecting)
// 防止重复跳转
if (this.isRedirecting) {
console.log('已经在跳转中,跳过')
return
}
this.isRedirecting = true
console.log('开始清除认证信息')
// 清除所有认证信息
uni.removeStorageSync('access_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('userInfo')
console.log('认证信息已清除')
// 提示用户
console.log('显示Toast提示')
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
duration: 1500
})
// 立即跳转不等待Toast
console.log('准备跳转到登录页')
// 获取当前页面路径
const pages = getCurrentPages()
console.log('当前页面栈:', pages)
const currentPage = pages[pages.length - 1]
const currentRoute = currentPage ? currentPage.route : ''
console.log('当前页面路由:', currentRoute)
// 如果当前不在登录页,才跳转
if (currentRoute !== 'pages/login/login') {
console.log('开始执行跳转...')
// 使用 setTimeout 确保在下一个事件循环执行
setTimeout(() => {
this.isRedirecting = false
uni.reLaunch({
url: '/pages/login/login',
success: () => {
console.log('✅ reLaunch 跳转成功')
},
fail: (err) => {
console.error('❌ reLaunch 失败:', err)
// 如果reLaunch失败尝试使用redirectTo
uni.redirectTo({
url: '/pages/login/login',
success: () => {
console.log('✅ redirectTo 跳转成功')
},
fail: (err2) => {
console.error('❌ redirectTo 也失败:', err2)
// 最后尝试navigateTo
uni.navigateTo({
url: '/pages/login/login',
success: () => {
console.log('✅ navigateTo 跳转成功')
},
fail: (err3) => {
console.error('❌ navigateTo 也失败:', err3)
console.error('所有跳转方式都失败了!')
}
})
}
})
}
})
}, 100)
} else {
console.log('当前已在登录页,不需要跳转')
this.isRedirecting = false
}
}
/**
* GET请求
* @param {String} url 请求地址
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
get(url, data = {}, options = {}) {
return this.request({
url,
method: 'GET',
data,
...options
})
}
/**
* POST请求
* @param {String} url 请求地址
* @param {Object|String} data 请求参数(可以是对象或字符串)
* @param {Object} options 额外配置
* @returns {Promise}
*/
post(url, data = {}, options = {}) {
return this.request({
url,
method: 'POST',
data,
...options
})
}
/**
* PUT请求
* @param {String} url 请求<E8AFB7><E6B182>
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
put(url, data = {}, options = {}) {
return this.request({
url,
method: 'PUT',
data,
...options
})
}
/**
* DELETE请求
* @param {String} url 请求地址
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
delete(url, data = {}, options = {}) {
return this.request({
url,
method: 'DELETE',
data,
...options
})
}
/**
* 文件上传
* @param {String} url 上传地址
* @param {String} filePath 文件路径
* @param {Object} formData 额外数据
* @returns {Promise}
*/
upload(url, filePath, formData = {}) {
uni.showLoading({
title: '上传中...',
mask: true
})
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.baseURL + url,
filePath,
name: 'file',
formData,
header: this.getHeaders(),
success: (res) => {
uni.hideLoading()
const data = JSON.parse(res.data)
if (data.code === 200) {
resolve(data.data)
} else {
this.showError(data.msg || '上传失败')
reject(data)
}
},
fail: (err) => {
uni.hideLoading()
this.showError('上传失败')
reject(err)
}
})
})
}
}
// 导出实例
export default new Request()