diff --git a/.env b/.env index d69df8d..70cdf22 100644 --- a/.env +++ b/.env @@ -2,10 +2,10 @@ MALL_VERSION = v1.0.0 # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) -MALL_BASE_URL = http://api-dashboard.yudao.iocoder.cn +MALL_BASE_URL = https://mall.jiandyb.cn:49011 # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) -MALL_DEV_BASE_URL = https://mall-baclend-local.jiandyb9834.xyz +MALL_DEV_BASE_URL = https://mall.jiandyb.cn:49011 # 后端接口前缀(一般不建议调整) MALL_API_PATH = /merchant-api diff --git a/peach/api/member/auth.js b/peach/api/member/auth.js index f2e3200..d5098d8 100644 --- a/peach/api/member/auth.js +++ b/peach/api/member/auth.js @@ -1,56 +1,57 @@ import request from '@/peach/request' const AuthUtil = { - smsLogin: (data) => { - return request({ - url: '/particulars/member/auth/sms-login', - method: 'POST', - data, - custom: { - showSuccess: true, - loadingMsg: '登陆中', - successMsg: '登陆成功', - auth: false, - }, - }) - }, - // 退出登陆 - logout: () => { - return request({ - url: '/particulars/member/auth/logout', - method: 'POST', - }) - }, - // 发送手机验证码 - sendSmsCode: (mobile, scene) => { - return request({ - url: '/member/auth/send-sms-code', - method: 'POST', - data: { - mobile, - scene, - }, - custom: { - loadingMsg: '发送中', - showSuccess: true, - successMsg: '发送成功', - }, - }) - }, - // 刷新令牌 - refreshToken: (refreshToken) => { - return request({ - url: '/particulars/member/auth/refresh-token', - method: 'POST', - params: { - refreshToken, - }, - custom: { - loading: false, // 不用加载中 - showError: false, // 不展示错误提示 - }, - }) - }, + smsLogin: (data) => { + return request({ + url: '/particulars/member/auth/sms-login', + method: 'POST', + data, + custom: { + showSuccess: true, + loadingMsg: '登陆中', + successMsg: '登陆成功', + auth: false, + }, + }) + }, + // 退出登陆 + logout: () => { + return request({ + url: '/particulars/member/auth/logout', + method: 'POST', + }) + }, + // 发送手机验证码 + sendSmsCode: (mobile, scene) => { + return request({ + url: '/member/auth/send-sms-code', + method: 'POST', + data: { + mobile, + scene, + }, + custom: { + loadingMsg: '发送中', + showSuccess: true, + successMsg: '发送成功', + auth: false, + }, + }) + }, + // 刷新令牌 + refreshToken: (refreshToken) => { + return request({ + url: '/particulars/member/auth/refresh-token', + method: 'POST', + params: { + refreshToken, + }, + custom: { + loading: false, // 不用加载中 + showError: false, // 不展示错误提示 + }, + }) + }, } export default AuthUtil diff --git a/peach/hooks/useModal.js b/peach/hooks/useModal.js index 86d7678..4046d14 100644 --- a/peach/hooks/useModal.js +++ b/peach/hooks/useModal.js @@ -12,15 +12,15 @@ import AuthUtil from '@/peach/api/member/auth' * @returns {Type} */ export function showAuthModal(type = 'smsLogin') { - const modal = $store('modal') - if (modal.auth !== '') { - closeAuthModal() - setTimeout(() => { - modal.auth = type - }, 100) - } else { - modal.auth = type - } + const modal = $store('modal') + if (modal.auth !== '') { + closeAuthModal() + setTimeout(() => { + modal.auth = type + }, 100) + } else { + modal.auth = type + } } /** @@ -30,7 +30,7 @@ export function showAuthModal(type = 'smsLogin') { * @returns {Type} */ export function closeAuthModal() { - $store('modal').auth = '' + $store('modal').auth = '' } /** @@ -40,7 +40,7 @@ export function closeAuthModal() { * @returns {Type} */ export function showShareModal() { - $store('modal').share = true + $store('modal').share = true } /** @@ -50,7 +50,7 @@ export function showShareModal() { * @returns {Type} */ export function closeShareModal() { - $store('modal').share = false + $store('modal').share = false } /** @@ -60,7 +60,7 @@ export function closeShareModal() { * @returns {Type} */ export function showMenuTools() { - $store('modal').menu = true + $store('modal').menu = true } /** @@ -71,7 +71,7 @@ export function showMenuTools() { */ // 关闭快捷菜单 export function closeMenuTools() { - $store('modal').menu = false + $store('modal').menu = false } /** @@ -82,46 +82,48 @@ export function closeMenuTools() { * @returns {Type} */ export function getSmsCode(event, mobile) { - const modalStore = $store('modal') - const lastSendTimer = modalStore.lastTimer[event] - if (typeof lastSendTimer === 'undefined') { - $helper.toast('短信发送事件错误') - return - } + const modalStore = $store('modal') + const lastSendTimer = modalStore.lastTimer[event] - const duration = dayjs().unix() - lastSendTimer - const canSend = duration >= 60 - if (!canSend) { - $helper.toast('请稍后再试') - return - } - // 只有 mobile 非空时才校验。因为部分场景(修改密码),不需要输入手机 - if (mobile && !test.mobile(mobile)) { - $helper.toast('手机号码格式不正确') - return - } + if (typeof lastSendTimer === 'undefined') { + $helper.toast('短信发送事件错误') + return + } - // 发送验证码 + 更新上次发送验证码时间 - let scene = -1 - switch (event) { - case 'resetPassword': - scene = 4 - break - case 'changePassword': - scene = 3 - break - case 'changeMobile': - scene = 2 - break - case 'smsLogin': - scene = 1 - break - } - AuthUtil.sendSmsCode(mobile, scene).then((res) => { - if (res.code === 0) { - modalStore.lastTimer[event] = dayjs().unix() - } - }) + const duration = dayjs().unix() - lastSendTimer + const canSend = duration >= 60 + if (!canSend) { + $helper.toast('请稍后再试') + return + } + // 只有 mobile 非空时才校验。因为部分场景(修改密码),不需要输入手机 + if (mobile && !test.mobile(mobile)) { + $helper.toast('手机号码格式不正确') + return + } + + // 发送验证码 + 更新上次发送验证码时间 + let scene = -1 + switch (event) { + case 'resetPassword': + scene = 4 + break + case 'changePassword': + scene = 3 + break + case 'changeMobile': + scene = 2 + break + case 'smsLogin': + scene = 1 + break + } + + AuthUtil.sendSmsCode(mobile, scene).then((res) => { + // if (res.code === 0) { + // modalStore.lastTimer[event] = dayjs().unix() + // } + }) } /** @@ -131,27 +133,27 @@ export function getSmsCode(event, mobile) { * @returns {Type} */ export function getSmsTimer(event) { - const modalStore = $store('modal') - const lastSendTimer = modalStore.lastTimer[event] + const modalStore = $store('modal') + const lastSendTimer = modalStore.lastTimer[event] - if (typeof lastSendTimer === 'undefined') { - $helper.toast('短信发送事件错误') - return - } + if (typeof lastSendTimer === 'undefined') { + $helper.toast('短信发送事件错误') + return + } - const duration = ref(dayjs().unix() - lastSendTimer - 60) - const canSend = duration.value >= 0 + const duration = ref(dayjs().unix() - lastSendTimer - 60) + const canSend = duration.value >= 0 - if (canSend) { - return '获取验证码' - } + if (canSend) { + return '获取验证码' + } - if (!canSend) { - setTimeout(() => { - duration.value++ - }, 1000) - return -duration.value.toString() + ' 秒' - } + if (!canSend) { + setTimeout(() => { + duration.value++ + }, 1000) + return -duration.value.toString() + ' 秒' + } } /** @@ -161,11 +163,11 @@ export function getSmsTimer(event) { * @returns {Type} */ export function saveAdvHistory(adv) { - const modal = $store('modal') + const modal = $store('modal') - modal.$patch((state) => { - if (!state.advHistory.includes(adv.imgUrl)) { - state.advHistory.push(adv.imgUrl) - } - }) + modal.$patch((state) => { + if (!state.advHistory.includes(adv.imgUrl)) { + state.advHistory.push(adv.imgUrl) + } + }) } diff --git a/peach/request/index.js b/peach/request/index.js index 4f4cedf..89e55a1 100644 --- a/peach/request/index.js +++ b/peach/request/index.js @@ -10,26 +10,26 @@ import $store from '@/peach/store' import AuthUtil from '@/peach/api/member/auth' const options = { - // 显示操作成功消息 默认不显示 - showSuccess: false, - // 成功提醒 默认使用后端返回值 - successMsg: '', - // 显示失败消息 默认显示 - showError: true, - // 失败提醒 默认使用后端返回信息 - errorMsg: '', - // 显示请求时loading模态框 默认显示 - showLoading: true, - // loading提醒文字 - loadingMsg: '加载中', - // 需要授权才能请求 默认放开 - auth: true, + // 显示操作成功消息 默认不显示 + showSuccess: false, + // 成功提醒 默认使用后端返回值 + successMsg: '', + // 显示失败消息 默认显示 + showError: true, + // 失败提醒 默认使用后端返回信息 + errorMsg: '', + // 显示请求时loading模态框 默认显示 + showLoading: true, + // loading提醒文字 + loadingMsg: '加载中', + // 需要授权才能请求 默认放开 + auth: true, } // Loading全局实例 let LoadingInstance = { - target: null, - count: 0, + target: null, + count: 0, } /** @@ -39,20 +39,20 @@ let LoadingInstance = { * @returns {Type} */ 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() } // 请求实例 const http = new Request({ - baseURL: baseUrl + apiPath, - timeout: 8000, - method: 'GET', - header: { - Accept: 'text/json', - 'Content-Type': 'application/json;charset=UTF-8', - }, - custom: options, + baseURL: baseUrl + apiPath, + timeout: 8000, + method: 'GET', + header: { + Accept: 'text/json', + 'Content-Type': 'application/json;charset=UTF-8', + }, + custom: options, }) /** @@ -63,39 +63,40 @@ const http = new Request({ */ http.interceptors.request.use( - (config) => { - // 自定义处理【auth 授权】:必须登录的接口,否则提示登录 - if (config.custom.auth && !$store('user').isLogin) { - // 处理登录 - peach.$router.go('/pages/index/login') - 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() - }, - }) - } - - // 增加 token 令牌、terminal 终端、tenant 租户的请求头 - const token = getAccessToken() - if (token) { - config.header['Authorization'] = token - } - config.header['Accept'] = '*/*' - config.header['tenant-id'] = '1' - return config - }, - (error) => { - return Promise.reject(error) + (config) => { + console.log(config) + // 自定义处理【auth 授权】:必须登录的接口,否则提示登录 + if (config.custom.auth && !$store('user').isLogin) { + // 处理登录 + peach.$router.go('/pages/index/login') + 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() + }, + }) + } + + // 增加 token 令牌、terminal 终端、tenant 租户的请求头 + const token = getAccessToken() + if (token) { + config.header['Authorization'] = token + } + config.header['Accept'] = '*/*' + config.header['tenant-id'] = '1' + return config + }, + (error) => { + return Promise.reject(error) + } ) /** @@ -105,194 +106,189 @@ http.interceptors.request.use( * @returns {Type} */ http.interceptors.response.use( - (response) => { - // console.log('response', 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() - - // 自定义处理【error 错误提示】:如果需要显示错误提示,则显示错误提示 - 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, - }) - return Promise.reject(false) - } - } - - // 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示 - if ( - response.config.custom.showSuccess && - response.config.custom.successMsg !== '' && - response.data.code === 0 - ) { - uni.showToast({ - title: response.config.custom.successMsg, - icon: 'none', - }) - } - - // 返回结果:包括 code + data + msg - return Promise.resolve(response.data) - }, - (error) => { - console.log('error', 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 - } - - if (error && error.config) { - if (error.config.custom.showError === true) { - uni.showToast({ - title: error.data?.msg || errorMessage, - icon: 'none', - mask: true, - }) - } - error.config.custom.showLoading && closeLoading() - } - - return Promise.reject(false) + (response) => { + // console.log('response', 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() + + // 自定义处理【error 错误提示】:如果需要显示错误提示,则显示错误提示 + 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, + }) + return Promise.reject(false) + } + } + + // 自定义处理【showSuccess 成功提示】:如果需要显示成功提示,则显示成功提示 + if (response.config.custom.showSuccess && response.config.custom.successMsg !== '' && response.data.code === 0) { + uni.showToast({ + title: response.config.custom.successMsg, + icon: 'none', + }) + } + + // 返回结果:包括 code + data + msg + return Promise.resolve(response.data) + }, + (error) => { + console.log('error', 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 + } + + if (error && error.config) { + if (error.config.custom.showError === true) { + uni.showToast({ + title: error.data?.msg || errorMessage, + icon: 'none', + mask: true, + }) + } + error.config.custom.showLoading && closeLoading() + } + + return Promise.reject(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') - } + // 如果当前已经是 refresh-token 的 URL 地址,并且还是 401 错误,说明是刷新令牌失败了,直接返回 Promise.reject(error) + if (config.url.indexOf('/member/auth/refresh-token') >= 0) { + return Promise.reject('error') + } - // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 - if (!isRefreshToken) { - isRefreshToken = true - // 1. 如果获取不到刷新令牌,则只能执行登出操作 - const refreshToken = getRefreshToken() - if (!refreshToken) { - return handleAuthorized() - } - // 2. 进行刷新访问令牌 - try { - const refreshTokenResult = await AuthUtil.refreshToken(refreshToken) - if (refreshTokenResult.code !== 0) { - // 如果刷新不成功,直接抛出 e 触发 2.2 的逻辑 - // noinspection ExceptionCaughtLocallyJS - throw new Error('刷新令牌失败') - } - // 2.1 刷新成功,则回放队列的请求 + 当前请求 - config.header.Authorization = 'Bearer ' + getAccessToken() - requestList.forEach((cb) => { - cb() - }) - requestList = [] - return request(config) - } catch (e) { - // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 - // 2.2 刷新失败,只回放队列的请求 - requestList.forEach((cb) => { - cb() - }) - // 提示是否要登出。即不回放当前请求!不然会形成递归 - return handleAuthorized() - } finally { - requestList = [] - isRefreshToken = false - } - } else { - // 添加到队列,等待刷新获取到新的令牌 - return new Promise((resolve) => { - requestList.push(() => { - config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 - resolve(request(config)) - }) - }) + // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 + if (!isRefreshToken) { + isRefreshToken = true + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + const refreshToken = getRefreshToken() + if (!refreshToken) { + return handleAuthorized() } + // 2. 进行刷新访问令牌 + try { + const refreshTokenResult = await AuthUtil.refreshToken(refreshToken) + if (refreshTokenResult.code !== 0) { + // 如果刷新不成功,直接抛出 e 触发 2.2 的逻辑 + // noinspection ExceptionCaughtLocallyJS + throw new Error('刷新令牌失败') + } + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + config.header.Authorization = 'Bearer ' + getAccessToken() + requestList.forEach((cb) => { + cb() + }) + requestList = [] + return request(config) + } catch (e) { + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 + // 2.2 刷新失败,只回放队列的请求 + requestList.forEach((cb) => { + cb() + }) + // 提示是否要登出。即不回放当前请求!不然会形成递归 + return handleAuthorized() + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise((resolve) => { + requestList.push(() => { + config.header.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(request(config)) + }) + }) + } } /** 处理 401 未登陆的错误 */ const handleAuthorized = () => { - const userStore = $store('user') - userStore.logOut() - peach.$router.go('/pages/index/login') - // 登录超时 - return Promise.reject({ - code: 401, - msg: userStore.isLogin ? '您的登陆已过期' : '请先登录', - }) + const userStore = $store('user') + userStore.logOut() + peach.$router.go('/pages/index/login') + // 登录超时 + return Promise.reject({ + code: 401, + msg: userStore.isLogin ? '您的登陆已过期' : '请先登录', + }) } /** 获得访问令牌 */ const getAccessToken = () => { - return uni.getStorageSync('token') + return uni.getStorageSync('token') } /** 获得刷新令牌 */ const getRefreshToken = () => { - return uni.getStorageSync('refresh-token') + return uni.getStorageSync('refresh-token') } const request = (config) => { - return http.middleware(config) + return http.middleware(config) } export default request diff --git a/peach/ui/pb-layout/pb-layout.vue b/peach/ui/pb-layout/pb-layout.vue index caa66e6..da72268 100644 --- a/peach/ui/pb-layout/pb-layout.vue +++ b/peach/ui/pb-layout/pb-layout.vue @@ -1,47 +1,47 @@ diff --git a/peach/ui/pb-navbar/pb-navbar.vue b/peach/ui/pb-navbar/pb-navbar.vue index ccb8736..edbcb51 100644 --- a/peach/ui/pb-navbar/pb-navbar.vue +++ b/peach/ui/pb-navbar/pb-navbar.vue @@ -1,80 +1,76 @@