diff --git a/.env b/.env index 6172fd55..8cc0bf56 100644 --- a/.env +++ b/.env @@ -1,28 +1,31 @@ # 版本号 -SHOPRO_VERSION = v1.8.3 +SHOPRO_VERSION=v2.3.0 # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) -SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn +SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) -SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080 +SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080 ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 +SHOPRO_UPLOAD_TYPE=server + # 后端接口前缀(一般不建议调整) -SHOPRO_API_PATH = /app-api +SHOPRO_API_PATH=/app-api # 后端 websocket 接口前缀 -SHOPRO_WEBSOCKET_PATH = /infra/ws +SHOPRO_WEBSOCKET_PATH=/infra/ws # 开发环境运行端口 -SHOPRO_DEV_PORT = 3000 +SHOPRO_DEV_PORT=3000 # 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀 -SHOPRO_STATIC_URL = http://test.yudao.iocoder.cn +SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn ### SHOPRO_STATIC_URL = https://file.sheepjs.com # 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启) -SHOPRO_MPLIVE_ON = 0 +SHOPRO_MPLIVE_ON=0 # 租户ID 默认 1 -SHOPRO_TENANT_ID = 1 +SHOPRO_TENANT_ID=1 diff --git a/manifest.json b/manifest.json index ac575dbe..47b8960f 100644 --- a/manifest.json +++ b/manifest.json @@ -218,7 +218,7 @@ "template": "index.html", "router": { "mode": "history", - "base": "./" + "base": "/" }, "sdkConfigs": { "maps": {} diff --git a/package.json b/package.json index d2e3bb5f..647aeea7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "id": "shopro", "name": "shopro", "displayName": "芋道商城", - "version": "2.2.0", + "version": "2.3.0", "description": "芋道商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能", "scripts": { "prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"" diff --git a/pages/commission/components/commission-info.vue b/pages/commission/components/commission-info.vue index 65f2c592..79f7cd86 100644 --- a/pages/commission/components/commission-info.vue +++ b/pages/commission/components/commission-info.vue @@ -31,9 +31,10 @@ \ No newline at end of file + diff --git a/pages/commission/components/commission-log.vue b/pages/commission/components/commission-log.vue index 809d747e..cee3b3cb 100644 --- a/pages/commission/components/commission-log.vue +++ b/pages/commission/components/commission-log.vue @@ -63,7 +63,7 @@ list: [], total: 0, pageNo: 1, - pageSize: 1, + pageSize: 8, }, }); diff --git a/pages/commission/goods.vue b/pages/commission/goods.vue index 4a3d9eba..7435e402 100644 --- a/pages/commission/goods.vue +++ b/pages/commission/goods.vue @@ -70,7 +70,7 @@ list: [], total: 0, pageNo: 1, - pageSize: 1, + pageSize: 8, }, loadStatus: '', shareInfo: {}, diff --git a/pages/commission/order.vue b/pages/commission/order.vue index c759d2a6..51952c8f 100644 --- a/pages/commission/order.vue +++ b/pages/commission/order.vue @@ -97,7 +97,7 @@ list: [], total: 0, pageNo: 1, - pageSize: 1, + pageSize: 8, }, }); diff --git a/pages/commission/promoter.vue b/pages/commission/promoter.vue index b7d0a89b..bdf1e1e7 100644 --- a/pages/commission/promoter.vue +++ b/pages/commission/promoter.vue @@ -163,7 +163,7 @@ } .PromoterRank .header .nav { - width: 450rpx; + width: 440rpx; height: 66rpx; border: 1px solid #fff; border-radius: 33rpx; @@ -294,4 +294,4 @@ width: 175rpx; text-align: right; } - \ No newline at end of file + diff --git a/pages/coupon/detail.vue b/pages/coupon/detail.vue index 2fca5257..f0fa1131 100644 --- a/pages/coupon/detail.vue +++ b/pages/coupon/detail.vue @@ -164,7 +164,7 @@ list: [], total: 0, pageNo: 1, - pageSize: 1, + pageSize: 8, }, categoryId: 0, // 选中的商品分类编号 tabMaps: [], // 指定分类时,每个分类构成一个 tab diff --git a/pages/goods/comment/list.vue b/pages/goods/comment/list.vue index ed43e1b7..ed55988b 100644 --- a/pages/goods/comment/list.vue +++ b/pages/goods/comment/list.vue @@ -48,7 +48,7 @@ list: [], total: 0, pageNo: 1, - pageSize: 1, + pageSize: 8, }, }); diff --git a/pages/goods/seckill.vue b/pages/goods/seckill.vue index 004efc74..4e055337 100644 --- a/pages/goods/seckill.vue +++ b/pages/goods/seckill.vue @@ -7,7 +7,9 @@ import sheep from '@/sheep'; - import { onLoad } from '@dcloudio/uni-app'; + import { onLoad, onShow } from '@dcloudio/uni-app'; import { reactive, ref } from 'vue'; import { isEmpty } from 'lodash-es'; import { @@ -345,11 +345,20 @@ return; } - // 正常的确认收货流程 - const { code } = await OrderApi.receiveOrder(orderId); - if (code === 0) { - await getOrderDetail(orderId); - } + uni.showModal({ + title: '提示', + content: '确认收货吗?', + success: async function (res) { + if (!res.confirm) { + return; + } + // 正常的确认收货流程 + const { code } = await OrderApi.receiveOrder(orderId); + if (code === 0) { + await getOrderDetail(orderId); + } + }, + }); } // #ifdef MP-WEIXIN @@ -420,6 +429,11 @@ } } + onShow(async () => { + //onShow中获取订单列表,保证跳转后页面为最新状态 + await getOrderDetail(state.orderInfo.id); + }) + onLoad(async (options) => { let id = 0; if (options.id) { @@ -430,7 +444,7 @@ if (state.comeinType === 'wechat') { state.merchantTradeNo = options.merchant_trade_no; } - await getOrderDetail(id); + state.orderInfo.id = id }); diff --git a/pages/order/list.vue b/pages/order/list.vue index 926ecfde..fc02fedc 100644 --- a/pages/order/list.vue +++ b/pages/order/list.vue @@ -223,12 +223,21 @@ return; } - // 正常的确认收货流程 - const { code } = await OrderApi.receiveOrder(order.id); - if (code === 0) { - resetPagination(state.pagination); - await getOrderList(); - } + uni.showModal({ + title: '提示', + content: '确认收货吗?', + success: async function (res) { + if (!res.confirm) { + return; + } + // 正常的确认收货流程 + const { code } = await OrderApi.receiveOrder(order.id); + if (code === 0) { + resetPagination(state.pagination); + await getOrderList(); + } + }, + }); } // #ifdef MP-WEIXIN diff --git a/sheep/api/infra/file.js b/sheep/api/infra/file.js index f7f2a964..fdf59ac5 100644 --- a/sheep/api/infra/file.js +++ b/sheep/api/infra/file.js @@ -1,4 +1,5 @@ import { baseUrl, apiPath, tenantId } from '@/sheep/config'; +import request from '@/sheep/request'; const FileApi = { // 上传文件 @@ -40,6 +41,26 @@ const FileApi = { }); }); }, + + // 获取文件预签名地址 + getFilePresignedUrl: (path) => { + return request({ + url: '/infra/file/presigned-url', + method: 'GET', + params: { + path, + }, + }); + }, + + // 创建文件 + createFile: (data) => { + return request({ + url: '/infra/file/create', // 请求的 URL + method: 'POST', // 请求方法 + data: data, // 要发送的数据 + }); + }, }; export default FileApi; diff --git a/sheep/components/s-discount-list/s-discount-list.vue b/sheep/components/s-discount-list/s-discount-list.vue index 652cd600..0526a736 100644 --- a/sheep/components/s-discount-list/s-discount-list.vue +++ b/sheep/components/s-discount-list/s-discount-list.vue @@ -17,7 +17,7 @@ > - + {{ item.description }} diff --git a/sheep/components/s-groupon-block/s-groupon-block.vue b/sheep/components/s-groupon-block/s-groupon-block.vue index 1a5a5918..b8415567 100644 --- a/sheep/components/s-groupon-block/s-groupon-block.vue +++ b/sheep/components/s-groupon-block/s-groupon-block.vue @@ -261,6 +261,9 @@ // 查找对应的 spu 并更新价格 const spu = state.spuList.find((spu) => activity.spuId === spu.id); if (spu) { + // 赋值活动名称 + // TODO 芋艿:暂定活动名。会在调研一些类似有赞、淘宝、京东的选择 + spu.name = activity.name; // 赋值最低价格 spu.price = Math.min(combinationPrice, spu.price); // 赋值活动ID,为了点击跳转详情页 diff --git a/sheep/components/s-seckill-block/s-seckill-block.vue b/sheep/components/s-seckill-block/s-seckill-block.vue index 4eced427..b5b21a3a 100644 --- a/sheep/components/s-seckill-block/s-seckill-block.vue +++ b/sheep/components/s-seckill-block/s-seckill-block.vue @@ -261,6 +261,9 @@ // 查找对应的 spu 并更新价格 const spu = state.spuList.find((spu) => activity.spuId === spu.id); if (spu) { + // 赋值活动名称 + // TODO 芋艿:暂定活动名。会在调研一些类似有赞、淘宝、京东的选择 + spu.name = activity.name; // 赋值最低价格 spu.price = Math.min(seckillPrice, spu.price); // 赋值活动ID,为了点击跳转详情页 diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js index 604ff9d8..d3c5bccc 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -116,6 +116,28 @@ function normalizeChooseAndUploadFileRes(res, fileType) { return res; } +function convertToArrayBuffer(uniFile) { + return new Promise((resolve, reject) => { + const fs = uni.getFileSystemManager(); + + fs.readFile({ + filePath: uniFile.path, // 确保路径正确 + success: (fileRes) => { + try { + // 将读取的内容转换为 ArrayBuffer + const arrayBuffer = new Uint8Array(fileRes.data).buffer; + resolve(arrayBuffer); + } catch (error) { + reject(new Error(`转换为 ArrayBuffer 失败: ${error.message}`)); + } + }, + fail: (error) => { + reject(new Error(`读取文件失败: ${error.errMsg}`)); + }, + }); + }); +} + function uploadCloudFiles(files, max = 5, onUploadProgress) { files = JSON.parse(JSON.stringify(files)); const len = files.length; @@ -165,36 +187,58 @@ function uploadCloudFiles(files, max = 5, onUploadProgress) { }); } -function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { - return choosePromise - .then((res) => { - if (onChooseFile) { - const customChooseRes = onChooseFile(res); - if (typeof customChooseRes !== 'undefined') { - return Promise.resolve(customChooseRes).then((chooseRes) => - typeof chooseRes === 'undefined' ? res : chooseRes, - ); - } +async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { + // 获取选择的文件 + const res = await choosePromise; + // 处理文件选择回调 + let files = res.tempFiles || []; + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + files = await Promise.resolve(customChooseRes); + if (typeof files === 'undefined') { + files = res.tempFiles || []; // Fallback } - return res; - }) - .then((res) => { - if (res === false) { - return { - errMsg: ERR_MSG_OK, - tempFilePaths: [], - tempFiles: [], - }; - } - return res; - }) - .then(async (files) => { - for (let file of files.tempFiles) { - const { data } = await FileApi.uploadFile(file.path); - file.url = data; - } - return files; - }); + } + } + + // 如果是前端直连上传 + if (UPLOAD_TYPE.CLIENT === import.meta.env.SHOPRO_UPLOAD_TYPE) { + for (const file of files) { + // 1.1 获取文件预签名地址 + const { data: presignedInfo } = await FileApi.getFilePresignedUrl(file.name); + // 1.2 获取二进制文件对象 + const fileBuffer = await convertToArrayBuffer(file); + // 1.3 上传文件 + await uni.request({ + url: presignedInfo.uploadUrl, // 预签名的上传 URL + method: 'PUT', // 使用 PUT 方法 + header: { + 'Content-Type': file.fileType + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型 + }, + data: fileBuffer, // 文件的路径,适用于小程序 + success: (res) => { + // 1.4. 记录文件信息到后端(异步) + createFile(presignedInfo, file); + // 1.5. 重新赋值 + file.url = presignedInfo.url; + console.log('上传成功:', res); + }, + fail: (err) => { + console.error('上传失败:', err); + }, + }); + } + return files; + } else { + // 后端上传 + for (let file of files) { + const { data } = await FileApi.uploadFile(file.path); + file.url = data; + } + + return files; + } } function chooseAndUploadFile( @@ -210,4 +254,32 @@ function chooseAndUploadFile( return uploadFiles(chooseAll(opts), opts); } +/** + * 创建文件信息 + * @param vo 文件预签名信息 + * @param file 文件 + */ +function createFile(vo, file) { + const fileVo = { + configId: vo.configId, + url: vo.url, + path: file.name, + name: file.name, + type: file.fileType, + size: file.size, + }; + FileApi.createFile(fileVo); + return fileVo; +} + +/** + * 上传类型 + */ +const UPLOAD_TYPE = { + // 客户端直接上传(只支持S3服务) + CLIENT: 'client', + // 客户端发送到后端上传 + SERVER: 'server', +}; + export { chooseAndUploadFile, uploadCloudFiles }; diff --git a/sheep/components/s-uploader/s-uploader.vue b/sheep/components/s-uploader/s-uploader.vue index 95cfb057..a459d84f 100644 --- a/sheep/components/s-uploader/s-uploader.vue +++ b/sheep/components/s-uploader/s-uploader.vue @@ -369,7 +369,7 @@ }, }) .then((result) => { - this.setSuccessAndError(result.tempFiles); + this.setSuccessAndError(result); }) .catch((err) => { console.log('选择失败', err); @@ -453,7 +453,7 @@ if (index === -1 || !this.files) break; if (item.errMsg === 'request:fail') { - this.files[index].url = item.path; + this.files[index].url = item.url; this.files[index].status = 'error'; this.files[index].errMsg = item.errMsg; // this.files[index].progress = -1 @@ -587,7 +587,7 @@ path: v.path, size: v.size, fileID: v.fileID, - url: v.url, + url: v.path, }); }); return newFilesData; diff --git a/sheep/hooks/useWebSocket.js b/sheep/hooks/useWebSocket.js index 18316176..81bc0b39 100644 --- a/sheep/hooks/useWebSocket.js +++ b/sheep/hooks/useWebSocket.js @@ -1,6 +1,7 @@ import { onBeforeUnmount, reactive, ref } from 'vue'; import { baseUrl, websocketPath } from '@/sheep/config'; import { copyValueToTarget } from '@/sheep/util'; +import { getRefreshToken } from '@/sheep/request'; /** * WebSocket 创建 hook @@ -8,12 +9,8 @@ import { copyValueToTarget } from '@/sheep/util'; * @return {{options: *}} */ export function useWebSocket(opt) { - const getAccessToken = () => { - return uni.getStorageSync('token'); - }; - const options = reactive({ - url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getAccessToken(), // ws 地址 + url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getRefreshToken(), // ws 地址 isReconnecting: false, // 正在重新连接 reconnectInterval: 3000, // 重连间隔,单位毫秒 heartBeatInterval: 5000, // 心跳间隔,单位毫秒 @@ -22,12 +19,9 @@ export function useWebSocket(opt) { destroy: false, // 是否销毁 pingTimeout: null, // 心跳检测定时器 reconnectTimeout: null, // 重连定时器ID的属性 - onConnected: () => { - }, // 连接成功时触发 - onClosed: () => { - }, // 连接关闭时触发 - onMessage: (data) => { - }, // 收到消息 + onConnected: () => {}, // 连接成功时触发 + onClosed: () => {}, // 连接关闭时触发 + onMessage: (data) => {}, // 收到消息 }); const SocketTask = ref(null); // SocketTask 由 uni.connectSocket() 接口创建 @@ -58,7 +52,8 @@ export function useWebSocket(opt) { // 情况一:实例销毁 if (options.destroy) { options.onClosed(); - } else { // 情况二:连接失败重连 + } else { + // 情况二:连接失败重连 // 停止心跳检查 stopHeartBeat(); // 重连 @@ -140,10 +135,8 @@ export function useWebSocket(opt) { copyValueToTarget(options, opt); SocketTask.value = uni.connectSocket({ url: options.url, - complete: () => { - }, - success: () => { - }, + complete: () => {}, + success: () => {}, }); initEventListeners(); }; diff --git a/sheep/platform/share.js b/sheep/platform/share.js index d200f671..a3cf4124 100644 --- a/sheep/platform/share.js +++ b/sheep/platform/share.js @@ -140,7 +140,7 @@ const decryptSpm = (spm) => { query = shareParamsArray[2].split(','); shareParams.query = { id: query[0], - activity_id: query[1], // TODO 芋艿:接入分享后,应该统一成 id 参数 + activity_id: query[1], }; break; case '4': diff --git a/sheep/request/index.js b/sheep/request/index.js index 492f08c9..b6e4b9e0 100644 --- a/sheep/request/index.js +++ b/sheep/request/index.js @@ -12,224 +12,226 @@ import AuthUtil from '@/sheep/api/member/auth'; import { getTerminal } from '@/sheep/util/const'; const options = { - // 显示操作成功消息 默认不显示 - showSuccess: false, - // 成功提醒 默认使用后端返回值 - successMsg: '', - // 显示失败消息 默认显示 - showError: true, - // 失败提醒 默认使用后端返回信息 - errorMsg: '', - // 显示请求时loading模态框 默认显示 - showLoading: true, - // loading提醒文字 - loadingMsg: '加载中', - // 需要授权才能请求 默认放开 - auth: false, - // ... + // 显示操作成功消息 默认不显示 + showSuccess: false, + // 成功提醒 默认使用后端返回值 + successMsg: '', + // 显示失败消息 默认显示 + showError: true, + // 失败提醒 默认使用后端返回信息 + errorMsg: '', + // 显示请求时loading模态框 默认显示 + showLoading: true, + // loading提醒文字 + loadingMsg: '加载中', + // 需要授权才能请求 默认放开 + auth: false, + // ... }; // Loading全局实例 let LoadingInstance = { - target: null, - count: 0, + target: null, + count: 0, }; /** * 关闭loading */ function closeLoading() { - if (LoadingInstance.count > 0) LoadingInstance.count--; - if (LoadingInstance.count === 0) uni.hideLoading(); + if (LoadingInstance.count > 0) LoadingInstance.count--; + if (LoadingInstance.count === 0) uni.hideLoading(); } /** * @description 请求基础配置 可直接使用访问自定义请求 */ const http = new Request({ - baseURL: baseUrl + apiPath, - timeout: 8000, - method: 'GET', - header: { - Accept: 'text/json', - 'Content-Type': 'application/json;charset=UTF-8', - platform: $platform.name, - }, - // #ifdef APP-PLUS - sslVerify: false, - // #endif - // #ifdef H5 - // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) - withCredentials: false, - // #endif - custom: options, + baseURL: baseUrl + apiPath, + timeout: 8000, + method: 'GET', + header: { + Accept: 'text/json', + 'Content-Type': 'application/json;charset=UTF-8', + platform: $platform.name, + }, + // #ifdef APP-PLUS + sslVerify: false, + // #endif + // #ifdef H5 + // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) + withCredentials: false, + // #endif + custom: options, }); /** * @description 请求拦截器 */ http.interceptors.request.use( - (config) => { + (config) => { // 自定义处理【auth 授权】:必须登录的接口,则跳出 AuthModal 登录弹窗 - if (config.custom.auth && !$store('user').isLogin) { - showAuthModal(); - return Promise.reject(); - } + if (config.custom.auth && !$store('user').isLogin) { + showAuthModal(); + return Promise.reject(); + } // 自定义处理【loading 加载中】:如果需要显示 loading,则显示 loading - if (config.custom.showLoading) { - LoadingInstance.count++; - LoadingInstance.count === 1 && - uni.showLoading({ - title: config.custom.loadingMsg, - mask: true, - fail: () => { - uni.hideLoading(); - }, - }); - } + if (config.custom.showLoading) { + LoadingInstance.count++; + LoadingInstance.count === 1 && + uni.showLoading({ + title: config.custom.loadingMsg, + mask: true, + fail: () => { + uni.hideLoading(); + }, + }); + } // 增加 token 令牌、terminal 终端、tenant 租户的请求头 - const token = getAccessToken(); - if (token) { - config.header['Authorization'] = token; - } - config.header['terminal'] = getTerminal(); + const token = getAccessToken(); + if (token) { + config.header['Authorization'] = token; + } + config.header['terminal'] = getTerminal(); config.header['Accept'] = '*/*'; config.header['tenant-id'] = tenantId; - return config; - }, - (error) => { - return Promise.reject(error); - }, + return config; + }, + (error) => { + return Promise.reject(error); + }, ); /** * @description 响应拦截器 */ http.interceptors.response.use( - (response) => { - // 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌 - if (response.config.url.indexOf('/member/auth/') >= 0 && response.data?.data?.accessToken) { - $store('user').setToken(response.data.data.accessToken, response.data.data.refreshToken); - } + (response) => { + // 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌 + if (response.config.url.indexOf('/member/auth/') >= 0 && response.data?.data?.accessToken) { + $store('user').setToken(response.data.data.accessToken, response.data.data.refreshToken); + } // 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading - response.config.custom.showLoading && closeLoading(); + response.config.custom.showLoading && closeLoading(); // 自定义处理【error 错误提示】:如果需要显示错误提示,则显示错误提示 - if (response.data.code !== 0) { + if (response.data.code !== 0) { // 特殊:如果 401 错误码,则跳转到登录页 or 刷新令牌 if (response.data.code === 401) { return refreshToken(response.config); } // 错误提示 - if (response.config.custom.showError) { - uni.showToast({ - title: response.data.msg || '服务器开小差啦,请稍后再试~', - icon: 'none', - mask: true, - }); + if (response.config.custom.showError) { + uni.showToast({ + title: response.data.msg || '服务器开小差啦,请稍后再试~', + icon: 'none', + mask: true, + }); } - } + } - // 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示 - if (response.config.custom.showSuccess - && response.config.custom.successMsg !== '' - && response.data.code === 0) { + // 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示 + if ( + response.config.custom.showSuccess && + response.config.custom.successMsg !== '' && + response.data.code === 0 + ) { uni.showToast({ - title: response.config.custom.successMsg, - icon: 'none', - }); - } + title: response.config.custom.successMsg, + icon: 'none', + }); + } // 返回结果:包括 code + data + msg - return Promise.resolve(response.data); - }, - (error) => { - const userStore = $store('user'); - const isLogin = userStore.isLogin; - let errorMessage = '网络请求出错'; - if (error !== undefined) { - switch (error.statusCode) { - case 400: - errorMessage = '请求错误'; - break; - case 401: + return Promise.resolve(response.data); + }, + (error) => { + const userStore = $store('user'); + const isLogin = userStore.isLogin; + let errorMessage = '网络请求出错'; + if (error !== undefined) { + switch (error.statusCode) { + case 400: + errorMessage = '请求错误'; + break; + case 401: errorMessage = isLogin ? '您的登陆已过期' : '请先登录'; // 正常情况下,后端不会返回 401 错误,所以这里不处理 handleAuthorized break; - case 403: - errorMessage = '拒绝访问'; - break; - case 404: - errorMessage = '请求出错'; - break; - case 408: - errorMessage = '请求超时'; - break; - case 429: - errorMessage = '请求频繁, 请稍后再访问'; - break; - case 500: - errorMessage = '服务器开小差啦,请稍后再试~'; - break; - case 501: - errorMessage = '服务未实现'; - break; - case 502: - errorMessage = '网络错误'; - break; - case 503: - errorMessage = '服务不可用'; - break; - case 504: - errorMessage = '网络超时'; - break; - case 505: - errorMessage = 'HTTP 版本不受支持'; - break; - } - if (error.errMsg.includes('timeout')) errorMessage = '请求超时'; - // #ifdef H5 - if (error.errMsg.includes('Network')) - errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接'; - // #endif - } + case 403: + errorMessage = '拒绝访问'; + break; + case 404: + errorMessage = '请求出错'; + break; + case 408: + errorMessage = '请求超时'; + break; + case 429: + errorMessage = '请求频繁, 请稍后再访问'; + break; + case 500: + errorMessage = '服务器开小差啦,请稍后再试~'; + break; + case 501: + errorMessage = '服务未实现'; + break; + case 502: + errorMessage = '网络错误'; + break; + case 503: + errorMessage = '服务不可用'; + break; + case 504: + errorMessage = '网络超时'; + break; + case 505: + errorMessage = 'HTTP 版本不受支持'; + break; + } + if (error.errMsg.includes('timeout')) errorMessage = '请求超时'; + // #ifdef H5 + if (error.errMsg.includes('Network')) + errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接'; + // #endif + } - if (error && error.config) { - if (error.config.custom.showError === false) { - uni.showToast({ - title: error.data?.msg || errorMessage, - icon: 'none', - mask: true, - }); - } - error.config.custom.showLoading && closeLoading(); - } + if (error && error.config) { + if (error.config.custom.showError === false) { + uni.showToast({ + title: error.data?.msg || errorMessage, + icon: 'none', + mask: true, + }); + } + error.config.custom.showLoading && closeLoading(); + } - return false; - }, + return false; + }, ); // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 -let requestList = [] // 请求队列 -let isRefreshToken = false // 是否正在刷新中 +let requestList = []; // 请求队列 +let isRefreshToken = false; // 是否正在刷新中 const refreshToken = async (config) => { // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error) if (config.url.indexOf('/member/auth/refresh-token') >= 0) { - return Promise.reject('error') + return Promise.reject('error'); } // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 if (!isRefreshToken) { - isRefreshToken = true + isRefreshToken = true; // 1. 如果获取不到刷新令牌,则只能执行登出操作 - const refreshToken = getRefreshToken() + const refreshToken = getRefreshToken(); if (!refreshToken) { - return handleAuthorized() + return handleAuthorized(); } // 2. 进行刷新访问令牌 try { @@ -240,34 +242,34 @@ const refreshToken = async (config) => { throw new Error('刷新令牌失败'); } // 2.1 刷新成功,则回放队列的请求 + 当前请求 - config.header.Authorization = 'Bearer ' + getAccessToken() + config.header.Authorization = 'Bearer ' + getAccessToken(); requestList.forEach((cb) => { - cb() - }) - requestList = [] - return request(config) + cb(); + }); + requestList = []; + return request(config); } catch (e) { // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 // 2.2 刷新失败,只回放队列的请求 requestList.forEach((cb) => { - cb() - }) + cb(); + }); // 提示是否要登出。即不回放当前请求!不然会形成递归 - return handleAuthorized() + return handleAuthorized(); } finally { - requestList = [] - isRefreshToken = false + requestList = []; + isRefreshToken = false; } } else { // 添加到队列,等待刷新获取到新的令牌 return new Promise((resolve) => { requestList.push(() => { - config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 - resolve(request(config)) - }) - }) + config.header.Authorization = 'Bearer ' + getAccessToken(); // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(request(config)); + }); + }); } -} +}; /** * 处理 401 未登录的错误 @@ -279,22 +281,22 @@ const handleAuthorized = () => { // 登录超时 return Promise.reject({ code: 401, - msg: userStore.isLogin ? '您的登陆已过期' : '请先登录' - }) -} + msg: userStore.isLogin ? '您的登陆已过期' : '请先登录', + }); +}; /** 获得访问令牌 */ -const getAccessToken = () => { +export const getAccessToken = () => { return uni.getStorageSync('token'); -} +}; /** 获得刷新令牌 */ -const getRefreshToken = () => { +export const getRefreshToken = () => { return uni.getStorageSync('refresh-token'); -} +}; const request = (config) => { - return http.middleware(config); + return http.middleware(config); }; export default request; diff --git a/sheep/store/app.js b/sheep/store/app.js index 4ffdb092..17680971 100644 --- a/sheep/store/app.js +++ b/sheep/store/app.js @@ -43,7 +43,7 @@ const app = defineStore({ }, }, shareInfo: {}, // 全局分享信息 - has_wechat_trade_managed: 0 // 小程序发货信息管理 0 没有 || 1 有 + has_wechat_trade_managed: 0, // 小程序发货信息管理 0 没有 || 1 有 }), actions: { // 获取Shopro应用配置和模板 @@ -55,14 +55,14 @@ const app = defineStore({ } // 加载装修配置 - await adaptTemplate(this.template, templateId) + await adaptTemplate(this.template, templateId); // TODO 芋艿:未来支持管理后台可配;对应 https://api.shopro.sheepjs.com/shop/api/init if (true) { this.info = { name: '芋道商城', logo: 'https://static.iocoder.cn/ruoyi-vue-pro-logo.png', - version: '2.2.0', + version: '2.3.0', copyright: '全部开源,个人与企业可 100% 免费使用', copytime: 'Copyright© 2018-2024', @@ -71,15 +71,15 @@ const app = defineStore({ }; this.platform = { share: { - methods: ["poster", "link"], - linkAddress: "http://127.0.0.1:3000", // TODO 芋艿:可以考虑改到 .env 那 + methods: ['poster', 'link'], + linkAddress: 'http://127.0.0.1:3000', // TODO 芋艿:可以考虑改到 .env 那 posterInfo: { - "user_bg": "/static/img/shop/config/user-poster-bg.png", - "goods_bg": "/static/img/shop/config/goods-poster-bg.png", - "groupon_bg": "/static/img/shop/config/groupon-poster-bg.png" - } + user_bg: '/static/img/shop/config/user-poster-bg.png', + goods_bg: '/static/img/shop/config/goods-poster-bg.png', + groupon_bg: '/static/img/shop/config/groupon-poster-bg.png', + }, }, - bind_mobile: 0 + bind_mobile: 0, }; this.has_wechat_trade_managed = 0; @@ -111,24 +111,24 @@ const app = defineStore({ // todo: @owen 先做数据适配,后期重构 const adaptTemplate = async (appTemplate, templateId) => { const { data: diyTemplate } = templateId - // 查询指定模板,一般是预览时使用 - ? await DiyApi.getDiyTemplate(templateId) - : await DiyApi.getUsedDiyTemplate(); + ? // 查询指定模板,一般是预览时使用 + await DiyApi.getDiyTemplate(templateId) + : await DiyApi.getUsedDiyTemplate(); // 模板不存在 if (!diyTemplate) { $router.error('TemplateError'); - return + return; } const tabBar = diyTemplate?.property?.tabBar; if (tabBar) { - appTemplate.basic.tabbar = tabBar + appTemplate.basic.tabbar = tabBar; if (tabBar?.theme) { appTemplate.basic.theme = tabBar?.theme; } } appTemplate.home = diyTemplate?.home; appTemplate.user = diyTemplate?.user; -} +}; export default app;