diff --git a/.claude/settings.local.json b/.claude/settings.local.json index dbcb83a..2aac387 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,8 @@ "Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES LIKE ''%order%'';\")", "Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES;\")", "Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE athlete;\")", - "Bash(tree:*)" + "Bash(tree:*)", + "Bash(find:*)" ], "deny": [], "ask": [] diff --git a/api/schedule.js b/api/schedule.js new file mode 100644 index 0000000..ff156af --- /dev/null +++ b/api/schedule.js @@ -0,0 +1,103 @@ +/** + * 赛程编排相关API接口 + */ + +import request from '@/utils/request.js' + +/** + * 获取赛程编排结果 + * @param {Number} competitionId - 赛事ID + */ +export function getScheduleResult(competitionId) { + return request.get('/martial/schedule/result', { + params: { competitionId } + }) +} + +/** + * 触发自动编排 + * @param {Number} competitionId - 赛事ID + */ +export function triggerAutoArrange(competitionId) { + return request.post('/martial/schedule/auto-arrange', { + competitionId + }) +} + +/** + * 保存编排草稿 + * @param {Object} data - 编排草稿数据 + * @param {Number} data.competitionId - 赛事ID + * @param {Boolean} data.isDraft - 是否为草稿 + * @param {Array} data.competitionGroups - 竞赛分组数据 + */ +export function saveDraftSchedule(data) { + return request.post('/martial/schedule/save-draft', data) +} + +/** + * 保存并锁定赛程编排 + * @param {Number} competitionId - 赛事ID + */ +export function saveAndLockSchedule(competitionId) { + return request.post('/martial/schedule/save-and-lock', { + competitionId + }) +} + +/** + * 移动赛程分组到指定场地和时间段 + * @param {Object} data - 移动请求数据 + * @param {Number} data.groupId - 分组ID + * @param {Number} data.targetVenueId - 目标场地ID + * @param {Number} data.targetTimeSlotIndex - 目标时间段索引 + */ +export function moveScheduleGroup(data) { + return request.post('/martial/schedule/move-group', data) +} + +/** + * 获取调度数据 + * @param {Object} params - 查询参数 + * @param {Number} params.competitionId - 赛事ID + * @param {Number} params.venueId - 场地ID + * @param {Number} params.timeSlotIndex - 时间段索引 + */ +export function getDispatchData(params) { + return request.get('/martial/schedule/dispatch-data', { + params + }) +} + +/** + * 调整出场顺序 + * @param {Object} data - 调整请求数据 + * @param {Number} data.detailId - 编排明细ID + * @param {Number} data.participantId - 参赛者记录ID + * @param {String} data.action - 调整动作(move_up/move_down/swap) + * @param {Number} data.targetOrder - 目标顺序(交换时使用) + */ +export function adjustOrder(data) { + return request.post('/martial/schedule/adjust-order', data) +} + +/** + * 批量保存调度 + * @param {Object} data - 保存调度数据 + * @param {Number} data.competitionId - 赛事ID + * @param {Array} data.adjustments - 调整列表 + */ +export function saveDispatch(data) { + return request.post('/martial/schedule/save-dispatch', data) +} + +export default { + getScheduleResult, + triggerAutoArrange, + saveDraftSchedule, + saveAndLockSchedule, + moveScheduleGroup, + getDispatchData, + adjustOrder, + saveDispatch +} diff --git a/api/user.js b/api/user.js index 24c09d2..5ab7acf 100644 --- a/api/user.js +++ b/api/user.js @@ -3,8 +3,90 @@ */ import request from '@/utils/request.js' +import md5 from '@/utils/md5.js' +import { base64Encode } from '@/utils/base64.js' + +/** + * 用户登录 + * 使用password模式(无需验证码) + */ +export function login(data) { + // 构建URL参数 + const params = { + tenantId: '000000', + username: data.username, + password: md5(data.password), + grant_type: 'password', // 使用password模式 + scope: 'all', + type: 'account' + } + + // 转换为URL查询字符串 + const queryString = Object.keys(params) + .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) + .join('&') + + // 使用saber3客户端凭证 + const basicAuth = 'Basic ' + base64Encode('saber3:saber3_secret') + + return request.post(`/blade-auth/oauth/token?${queryString}`, null, { + header: { + 'Authorization': basicAuth, + 'Tenant-Id': '000000' + } + }) +} + +/** + * 用户注册 + */ +export function register(data) { + return request.post('/blade-system/user/register', { + tenantId: '000000', + userType: 2, // 2-app + account: data.account, + password: md5(data.password), + phone: data.phone, + realName: data.realName, + sex: data.sex, + code: data.code + }) +} + +/** + * 获取验证码 + */ +export function getCaptcha(phone) { + return request.post('/blade-auth/captcha/send', { + phone: phone + }) +} + +/** + * 刷新Token + */ +export function refreshToken(refreshToken) { + return request.post('/blade-auth/oauth/token', { + tenantId: '000000', + refresh_token: refreshToken, + grant_type: 'refresh_token' + }) +} + +/** + * 退出登录 + */ +export function logout() { + return request.post('/blade-auth/logout') +} export default { + login, + register, + getCaptcha, + refreshToken, + logout, + /** * 获取用户信息 * @returns {Promise} @@ -15,11 +97,15 @@ export default { /** * 修改密码 - * @param {Object} data { oldPassword, newPassword, confirmPassword } + * @param {Object} data { oldPassword, newPassword, newPassword1 } * @returns {Promise} */ updatePassword(data) { - return request.post('/blade-system/user/update-password', data) + return request.post('/blade-system/user/update-password', { + oldPassword: md5(data.oldPassword), + newPassword: md5(data.newPassword), + newPassword1: md5(data.newPassword1) + }) }, /** @@ -28,6 +114,6 @@ export default { * @returns {Promise} */ updateUserInfo(data) { - return request.post('/blade-system/user/update-info', data) + return request.post('/blade-system/user/update', data) } } diff --git a/config/api.config.js b/config/api.config.js index 8c76468..7479a49 100644 --- a/config/api.config.js +++ b/config/api.config.js @@ -5,11 +5,9 @@ // 开发环境配置 const development = { - // 使用代理,请求会被转发到 vue.config.js 中配置的目标地址 - baseURL: 'http://localhost:8123', - timeout: 30000, - // 如果需要代理,可以配置 - baseURL: '/api' + // 使用代理路径(vue.config.js会将/api代理到http://localhost:8123) + baseURL: '/api', + timeout: 30000 } // 测试环境配置 diff --git a/pages.json b/pages.json index 12746cb..2ef98ea 100644 --- a/pages.json +++ b/pages.json @@ -1,5 +1,19 @@ { "pages": [ + { + "path": "pages/login/login", + "style": { + "navigationBarTitleText": "登录", + "navigationStyle": "custom" + } + }, + { + "path": "pages/register/register", + "style": { + "navigationBarTitleText": "注册", + "navigationStyle": "custom" + } + }, { "path": "pages/home/home", "style": { diff --git a/pages/change-password/change-password.vue b/pages/change-password/change-password.vue index cf2c6b3..e020d13 100644 --- a/pages/change-password/change-password.vue +++ b/pages/change-password/change-password.vue @@ -133,7 +133,7 @@ export default { await userAPI.updatePassword({ oldPassword: this.formData.oldPassword, newPassword: this.formData.newPassword, - confirmPassword: this.formData.confirmPassword + newPassword1: this.formData.confirmPassword }) uni.hideLoading() diff --git a/pages/login/login.vue b/pages/login/login.vue new file mode 100644 index 0000000..731c9ea --- /dev/null +++ b/pages/login/login.vue @@ -0,0 +1,501 @@ + + + + + diff --git a/pages/profile/profile.vue b/pages/profile/profile.vue index 3f36e49..a094cdb 100644 --- a/pages/profile/profile.vue +++ b/pages/profile/profile.vue @@ -49,6 +49,7 @@ + + diff --git a/pages/schedule/schedule-example.vue b/pages/schedule/schedule-example.vue new file mode 100644 index 0000000..334b70b --- /dev/null +++ b/pages/schedule/schedule-example.vue @@ -0,0 +1,448 @@ + + + + + diff --git a/utils/auth.js b/utils/auth.js new file mode 100644 index 0000000..45f48fa --- /dev/null +++ b/utils/auth.js @@ -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() +} diff --git a/utils/base64.js b/utils/base64.js new file mode 100644 index 0000000..538684f --- /dev/null +++ b/utils/base64.js @@ -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 +} diff --git a/utils/md5.js b/utils/md5.js new file mode 100644 index 0000000..02787e5 --- /dev/null +++ b/utils/md5.js @@ -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 diff --git a/utils/request.js b/utils/request.js index 9d8ddd7..861706b 100644 --- a/utils/request.js +++ b/utils/request.js @@ -9,6 +9,7 @@ class Request { constructor() { this.baseURL = config.baseURL this.timeout = config.timeout + this.isRedirecting = false // 防止重复跳转登录页 } /** @@ -76,12 +77,13 @@ class Request { * @returns {Object} */ getHeaders(customHeader = {}) { - // 获取token - const token = uni.getStorageSync('token') || '' + // 获取token - 使用access_token作为key + const token = uni.getStorageSync('access_token') || '' return { 'Content-Type': 'application/json', - 'Blade-Auth': token ? `Bearer ${token}` : '', + 'Blade-Auth': token ? `bearer ${token}` : '', + 'Tenant-Id': '000000', ...customHeader } } @@ -95,24 +97,41 @@ class Request { handleResponse(res, resolve, reject) { const data = res.data - // 判断HTTP状态码 - // 2xx 和 304 都是成功的状态码 + // 判断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 || '请求失败' + const errorMsg = data.msg || data.message || data.error_description || '请求失败' // 特殊错误码处理 if (data.code === 401 || data.code === 403) { @@ -122,6 +141,7 @@ class Request { this.showError(errorMsg) reject({ + statusCode: res.statusCode, code: data.code, message: errorMsg, data: data.data @@ -174,23 +194,85 @@ class Request { * 处理token过期 */ handleTokenExpired() { - // 清除token - uni.removeStorageSync('token') + 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: 2000 + duration: 1500 }) - // 跳转到登录页(如果有) - setTimeout(() => { - // uni.reLaunch({ - // url: '/pages/login/login' - // }) - }, 2000) + // 立即跳转,不等待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 + } } /** @@ -212,7 +294,7 @@ class Request { /** * POST请求 * @param {String} url 请求地址 - * @param {Object} data 请求参数 + * @param {Object|String} data 请求参数(可以是对象或字符串) * @param {Object} options 额外配置 * @returns {Promise} */