diff --git a/api/index.js b/api/index.js index 5151b5f..fa4434f 100644 --- a/api/index.js +++ b/api/index.js @@ -11,6 +11,91 @@ let isRefreshing = false // 等待刷新完成的请求队列 let refreshSubscribers = [] +/** + * 检查当前是否在登录页面 + * @returns {Boolean} 是否在登录页面 + */ +function isInLoginPage() { + try { + const pages = getCurrentPages() + if (pages.length === 0) return false + const currentPage = pages[pages.length - 1] + const currentRoute = currentPage ? currentPage.route : '' + return currentRoute === 'pages/login/login' + } catch (error) { + console.error('检查登录页面状态失败:', error) + return false + } +} + +/** + * 检查是否刚刚从登录页面返回(页面栈中有登录页面) + * @returns {Boolean} 是否刚刚从登录页面返回 + */ +function isJustBackFromLogin() { + try { + const pages = getCurrentPages() + if (pages.length <= 1) return false + // 检查页面栈中是否有登录页面(除了当前页面) + const hasLoginPage = pages.some((page, index) => { + // 排除当前页面(最后一个) + if (index === pages.length - 1) return false + const route = page ? page.route : '' + return route === 'pages/login/login' + }) + return hasLoginPage + } catch (error) { + console.error('检查登录页面返回状态失败:', error) + return false + } +} + +/** + * 跳转到登录页面(如果不在登录页面才跳转) + * 如果已经在登录页面,则不进行跳转 + * 如果多次被弹出,退出时直接回到根目录 + */ +function navigateToLogin() { + // 如果已经在登录页面,不需要再次跳转 + if (isInLoginPage()) { + console.log('已在登录页面,无需重复跳转') + return + } + + // 获取当前页面栈 + const pages = getCurrentPages() + + // 如果页面栈中有多个页面,且包含登录页面,说明可能是多次弹出 + // 使用 reLaunch 直接回到根目录(首页),然后跳转到登录页 + if (pages.length > 1) { + // 检查页面栈中是否有登录页面 + const hasLoginPage = pages.some(page => { + const route = page ? page.route : '' + return route === 'pages/login/login' + }) + + if (hasLoginPage) { + // 多次被弹出,直接回到根目录 + uni.reLaunch({ + url: '/pages/login/login' + }) + return + } + } + + // 正常情况,跳转到登录页面 + uni.navigateTo({ + url: '/pages/login/login', + fail: (err) => { + // 如果 navigateTo 失败(可能因为页面栈已满),使用 reLaunch + console.warn('navigateTo 失败,使用 reLaunch:', err) + uni.reLaunch({ + url: '/pages/login/login' + }) + } + }) +} + /** * 刷新token * @param {String} refreshToken 刷新令牌 @@ -133,21 +218,36 @@ export function request(options = {}) { // 构建完整URL(处理BASE_URL末尾斜杠问题) const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url.startsWith('/') ? url : '/' + url}` - // 构建请求头 + // 构建请求头(先添加基础头,再添加传入的 header,最后添加 token,确保 token 不会被覆盖) const requestHeader = { 'Content-Type': 'application/json', 'tenant-id': '1', // 统一添加租户ID ...header } - // 添加token(如果需要认证) + // 添加token(如果需要认证,必须在最后添加,确保不会被 header 参数覆盖) if (needAuth) { const token = uni.getStorageSync('token') if (token) { requestHeader['Authorization'] = `Bearer ${token}` + console.log('[API] 请求已添加 Authorization 头:', url) + console.log('[API] Token 长度:', token.length, 'Token 前10位:', token.substring(0, 10)) + } else { + console.error('[API] 需要认证但未找到 token,请求可能失败:', url) + console.error('[API] 当前存储的所有 key:', uni.getStorageInfoSync().keys) + // 即使没有 token,也尝试发起请求(让后端返回 401) } } + // 调试:打印请求信息 + console.log('[API] 发起请求:', { + url: fullUrl, + method: method.toUpperCase(), + needAuth: needAuth, + hasAuthHeader: !!requestHeader['Authorization'], + headers: Object.keys(requestHeader) + }) + // 发起请求 uni.request({ url: fullUrl, @@ -169,6 +269,12 @@ export function request(options = {}) { if (res.data.code !== undefined) { // 处理业务错误码401(账号未登录) if (res.data.code === 401) { + // 如果已经在登录页面,不需要显示弹窗和跳转 + if (isInLoginPage()) { + reject(new Error('未授权')) + return + } + // 显示登录弹窗 uni.showModal({ title: '提示', @@ -184,9 +290,7 @@ export function request(options = {}) { uni.removeStorageSync('userId') uni.removeStorageSync('userInfo') // 跳转到登录页面 - uni.navigateTo({ - url: '/pages/login/login' - }) + navigateToLogin() } } }) @@ -213,64 +317,32 @@ export function request(options = {}) { resolve(res.data) } } else if (res.statusCode === 401) { - // token过期或未登录,尝试使用refreshToken刷新 - const refreshToken = uni.getStorageSync('refreshToken') - if (refreshToken) { - // 尝试刷新token - refreshAccessToken(refreshToken) - .then(() => { - // 刷新成功,重新发起原请求 - return request(options) - }) - .then(resolve) - .catch(() => { - // 刷新失败,显示登录弹窗 - const errorMsg = res.data?.msg || res.data?.message || '账号未登录,请前往登录' - uni.showModal({ - title: '提示', - content: errorMsg, - showCancel: false, - confirmText: '去登录', - success: (modalRes) => { - if (modalRes.confirm) { - // 清除token,跳转到登录页 - uni.removeStorageSync('token') - uni.removeStorageSync('refreshToken') - uni.removeStorageSync('tokenExpiresTime') - uni.removeStorageSync('userId') - uni.removeStorageSync('userInfo') - uni.navigateTo({ - url: '/pages/login/login' - }) - } - } - }) - reject(new Error('未授权')) - }) - } else { - // 没有refreshToken,显示登录弹窗 - const errorMsg = res.data?.msg || res.data?.message || '账号未登录,请前往登录' - uni.showModal({ - title: '提示', - content: errorMsg, - showCancel: false, - confirmText: '去登录', - success: (modalRes) => { - if (modalRes.confirm) { - // 清除token,跳转到登录页 - uni.removeStorageSync('token') - uni.removeStorageSync('refreshToken') - uni.removeStorageSync('tokenExpiresTime') - uni.removeStorageSync('userId') - uni.removeStorageSync('userInfo') - uni.navigateTo({ - url: '/pages/login/login' - }) - } - } - }) + // HTTP 401 状态码,直接显示登录弹窗 + // 如果已经在登录页面,不需要显示弹窗和跳转 + if (isInLoginPage()) { reject(new Error('未授权')) + return } + + const errorMsg = res.data?.msg || res.data?.message || '账号未登录,请前往登录' + uni.showModal({ + title: '提示', + content: errorMsg, + showCancel: false, + confirmText: '去登录', + success: (modalRes) => { + if (modalRes.confirm) { + // 清除token,跳转到登录页 + uni.removeStorageSync('token') + uni.removeStorageSync('refreshToken') + uni.removeStorageSync('tokenExpiresTime') + uni.removeStorageSync('userId') + uni.removeStorageSync('userInfo') + navigateToLogin() + } + } + }) + reject(new Error('未授权')) } else if (res.statusCode >= 500) { // 服务器错误 uni.showToast({ diff --git a/manifest.json b/manifest.json index 8ca22dc..a2ccaed 100644 --- a/manifest.json +++ b/manifest.json @@ -57,7 +57,12 @@ "usingComponents" : true, "requiredPrivateInfos" : [ "getLocation" - ] + ], + "permission" : { + "scope.userLocation" : { + "desc" : "你的位置信息将用于小程序位置接口的效果展示" + } + } }, "mp-alipay" : { "usingComponents" : true diff --git a/pages.json b/pages.json index 84d6a36..4ed235b 100644 --- a/pages.json +++ b/pages.json @@ -19,6 +19,11 @@ "style": { "navigationBarTitleText": "服务", "navigationStyle": "custom" + }, + "permission": { + "scope.userLocation": { + "desc": "你的位置信息将用于小程序位置接口的效果展示" + } } }, { diff --git a/pages/detail/mapDetail.vue b/pages/detail/mapDetail.vue index 8435892..2b099a2 100644 --- a/pages/detail/mapDetail.vue +++ b/pages/detail/mapDetail.vue @@ -32,7 +32,7 @@ diff --git a/pages/detail/serviceDetail.vue b/pages/detail/serviceDetail.vue index 7173f9e..8dfdd21 100644 --- a/pages/detail/serviceDetail.vue +++ b/pages/detail/serviceDetail.vue @@ -8,7 +8,7 @@ @@ -132,6 +132,8 @@ export default { categoryLabel: "", shopId: null, userInfo: {}, + hasCheckedLogin: false, // 是否已经检查过登录状态 + isLoadingAfterLogin: false, // 是否正在登录后重新加载 }; }, computed: { @@ -172,6 +174,43 @@ export default { }, 1500); } }, + onShow() { + // 页面显示时,检查登录状态并更新用户信息 + // 如果之前未登录,现在已登录,重新加载数据 + const token = uni.getStorageSync('token'); + const newUserInfo = uni.getStorageSync('userInfo') || {}; + + // 如果之前没有用户信息,现在有了(说明刚登录成功),重新加载数据 + if (token && (!this.userInfo || !this.userInfo.id) && newUserInfo && newUserInfo.id) { + this.userInfo = newUserInfo; + this.isLoadingAfterLogin = true; // 标记正在登录后重新加载 + + // 设置一个全局标记,表示刚刚登录返回,用于防止 401 弹窗 + uni.setStorageSync('justBackFromLogin', true); + // 5秒后清除标记 + setTimeout(() => { + uni.removeStorageSync('justBackFromLogin'); + }, 5000); + + // 延迟一下,确保 token 已经保存并生效,避免与之前的请求冲突 + setTimeout(() => { + // 如果有店铺ID,重新加载店铺数据(特别是菜单数据,可能需要登录才能查看) + if (this.shopId) { + this.loadStoreData(); + } + // 加载完成后清除标记 + setTimeout(() => { + this.isLoadingAfterLogin = false; + }, 1000); + }, 500); // 增加延迟时间,确保之前的请求已经完成 + } else if (token) { + // 如果已登录,更新用户信息(可能用户信息有更新) + this.userInfo = newUserInfo; + } + + // 标记已经检查过登录状态 + this.hasCheckedLogin = true; + }, methods: { // 加载店铺数据 async loadStoreData() { diff --git a/pages/index/index.vue b/pages/index/index.vue index 40acc78..8228e00 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,596 +1,596 @@ - - - - - + + + + + diff --git a/pages/login/login.vue b/pages/login/login.vue index 3981003..fa5b522 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -138,6 +138,12 @@ export default { const pages = getCurrentPages() if (pages.length > 1) { this.showBack = true + // 从其他页面跳转过来,说明可能有弹窗需要关闭 + // 延迟一下,确保页面已经加载完成,弹窗会自动关闭 + setTimeout(() => { + // 尝试隐藏可能存在的弹窗(虽然 uni 没有直接关闭 showModal 的 API) + // 但页面跳转后,弹窗应该会自动关闭 + }, 100) } this.getSystemInfo() }, @@ -186,9 +192,16 @@ export default { }) // 登录成功,保存token(兼容不同的响应格式) + console.log('[登录] 登录接口返回数据:', res) const token = res?.accessToken || res?.token || res?.data?.accessToken || res?.data?.token if (token) { uni.setStorageSync('token', token) + console.log('[登录] Token 已保存,长度:', token.length) + // 验证保存是否成功 + const savedToken = uni.getStorageSync('token') + console.log('[登录] 验证保存的 token:', savedToken ? '成功' : '失败') + } else { + console.error('[登录] 未找到 token,返回数据:', res) } // 保存refreshToken(用于刷新accessToken) @@ -235,19 +248,17 @@ export default { icon: 'success' }) - // 延迟跳转,让用户看到成功提示 + // 延迟跳转,让用户看到成功提示,统一跳转到首页 setTimeout(() => { - // 检查是否有返回路径 - const pages = getCurrentPages() - if (pages.length > 1) { - // 有上一页,返回上一页 - uni.navigateBack() - } else { - // 没有上一页,跳转到首页 - uni.switchTab({ - url: '/pages/index/index' - }) - } + uni.switchTab({ + url: '/pages/index/index', + fail: () => { + // 如果 switchTab 失败(可能不在 tabBar 页面),使用 reLaunch + uni.reLaunch({ + url: '/pages/index/index' + }) + } + }) }, 1500) } catch (error) { console.error('登录失败:', error) @@ -305,12 +316,16 @@ export default { // 登录成功,保存token // 根据接口返回:{ code: 0, data: { accessToken, refreshToken, expiresTime, userId, ... } } // request 函数在 code === 0 时返回 res.data.data || res.data,所以 res 就是 data 对象 + console.log('[登录] 登录接口返回数据:', res) const token = res?.accessToken || res?.data?.accessToken || res?.token || res?.data?.token if (token) { uni.setStorageSync('token', token) - console.log('Token 已保存:', token) + console.log('[登录] Token 已保存,长度:', token.length) + // 验证保存是否成功 + const savedToken = uni.getStorageSync('token') + console.log('[登录] 验证保存的 token:', savedToken ? '成功' : '失败') } else { - console.error('未找到 token,返回数据:', res) + console.error('[登录] 未找到 token,返回数据:', res) } // 保存refreshToken(用于刷新accessToken) @@ -358,16 +373,17 @@ export default { icon: 'success' }) - // 延迟跳转 + // 延迟跳转,让用户看到成功提示,统一跳转到首页 setTimeout(() => { - const pages = getCurrentPages() - if (pages.length > 1) { - uni.navigateBack() - } else { - uni.switchTab({ - url: '/pages/index/index' - }) - } + uni.switchTab({ + url: '/pages/index/index', + fail: () => { + // 如果 switchTab 失败(可能不在 tabBar 页面),使用 reLaunch + uni.reLaunch({ + url: '/pages/index/index' + }) + } + }) }, 1500) } catch (error) { console.error('一键登录失败:', error) diff --git a/pages/service/service.vue b/pages/service/service.vue index f754614..1189760 100644 --- a/pages/service/service.vue +++ b/pages/service/service.vue @@ -104,7 +104,7 @@ src="/static/service/location-icon.png" mode="aspectFill" > - {{ item.distance || 0 }}km + {{ formatDistance(item.distance) }} @@ -222,7 +222,7 @@ export default { onLoad() { this.getSystemInfo(); this.getServiceCategoryFun(); // 获取服务分类 - // this.checkAndRequestLocation(); // 检查并请求位置权限 + this.requestUserLocation(); // 请求用户位置授权并获取位置 }, onShow() { // 每次显示页面时刷新数据 @@ -242,6 +242,15 @@ export default { } }, methods: { + // 格式化距离显示:将米转换成千米 + formatDistance(distanceInMeters) { + if (!distanceInMeters || distanceInMeters === 0) { + return '0km'; + } + // 将米转换成千米,保留1位小数 + const distanceInKm = (distanceInMeters / 1000).toFixed(1); + return `${distanceInKm}km`; + }, // 获取系统信息 getSystemInfo() { const systemInfo = uni.getSystemInfoSync(); @@ -318,8 +327,8 @@ export default { this.loadServiceList(); }, - // 检查并请求位置权限 - checkAndRequestLocation() { + // 请求用户位置授权并获取位置(使用 wx.getLocation - 精确位置) + requestUserLocation() { // 先检查是否已有存储的位置信息 const savedLocation = uni.getStorageSync("userLocation"); if (savedLocation && savedLocation.latitude && savedLocation.longitude) { @@ -327,53 +336,100 @@ export default { return; } - // 如果没有位置信息,请求授权并获取位置 - uni.authorize({ - scope: "scope.userLocation", - success: () => { - // 授权成功,获取位置 - this.getUserLocation(); - }, - fail: () => { - // 授权失败,提示用户 - uni.showModal({ - title: "位置权限", - content: "需要获取您的位置信息以提供附近店铺服务,是否前往设置开启?", - confirmText: "去设置", - cancelText: "取消", - success: (res) => { - if (res.confirm) { - uni.openSetting({ - success: (settingRes) => { - if (settingRes.authSetting["scope.userLocation"]) { - // 用户开启了位置权限,获取位置 - this.getUserLocation(); + // 获取位置的统一方法 + const getLocation = () => { + uni.getLocation({ + type: "gcj02", // 使用 gcj02 坐标系(火星坐标系,国内常用) + success: (res) => { + console.log("获取位置成功1:", res); + const location = { + latitude: res.latitude, + longitude: res.longitude, + }; + // 存储位置信息 + uni.setStorageSync("userLocation", location); + + // 如果当前是店铺类型分类且已经有选中的分类,刷新列表以使用新的位置信息 + if (this.isStoreCategory() && this.currentCategory !== null) { + this.pageNo = 1; + this.serviceList = []; + this.hasMore = true; + this.loadServiceList(); + } + }, + fail: (err) => { + console.error("获取位置失败:", err); + }, + }); + }; + + // 微信小程序环境需要先检查授权 + // #ifdef MP-WEIXIN + uni.getSetting({ + success: (res) => { + if (res.authSetting["scope.userLocation"] === false) { + // 用户之前拒绝过授权,引导去设置页面 + uni.showModal({ + title: "位置权限", + content: "需要获取您的位置信息以提供附近店铺服务,是否前往设置开启?", + confirmText: "去设置", + cancelText: "取消", + success: (modalRes) => { + if (modalRes.confirm) { + uni.openSetting({ + success: (settingRes) => { + if (settingRes.authSetting["scope.userLocation"]) { + getLocation(); + } + }, + }); + } + }, + }); + } else if (res.authSetting["scope.userLocation"] === true) { + // 已经授权,直接获取位置 + getLocation(); + } else { + // 未询问过授权,请求授权 + uni.authorize({ + scope: "scope.userLocation", + success: () => { + getLocation(); + }, + fail: () => { + // 授权失败,提示用户 + uni.showModal({ + title: "位置权限", + content: "需要获取您的位置信息以提供附近店铺服务,是否前往设置开启?", + confirmText: "去设置", + cancelText: "取消", + success: (modalRes) => { + if (modalRes.confirm) { + uni.openSetting({ + success: (settingRes) => { + if (settingRes.authSetting["scope.userLocation"]) { + getLocation(); + } + }, + }); } }, }); - } - }, - }); - }, - }); - }, - - // 获取用户当前位置 - getUserLocation() { - uni.getLocation({ - type: "gcj02", - success: (res) => { - const location = { - latitude: res.latitude, - longitude: res.longitude, - }; - // 存储位置信息 - uni.setStorageSync("userLocation", location); - }, - fail: (err) => { - console.error("获取位置失败:", err); + }, + }); + } + }, + fail: () => { + // 获取设置失败,直接尝试获取位置 + getLocation(); }, }); + // #endif + + // #ifndef MP-WEIXIN + // 非微信小程序环境,直接获取位置 + getLocation(); + // #endif }, // 选择位置/距离 @@ -481,9 +537,16 @@ export default { name: this.searchKeyword, }; - // 如果选择了距离,添加 distance 参数 + // 获取用户位置信息 + const savedLocation = uni.getStorageSync("userLocation"); + if (savedLocation && savedLocation.latitude && savedLocation.longitude) { + params.myLatitude = savedLocation.latitude; + params.myLongitude = savedLocation.longitude; + } + + // 如果选择了距离,添加 distance 参数(将千米转换成米) if (this.selectedDistance !== null) { - params.distance = this.selectedDistance; + params.distance = this.selectedDistance * 1000; // 将 km 转换成 m } res = await getGuildStorePage(params); @@ -503,6 +566,9 @@ export default { const newList = res.list || []; this.total = res.total || 0; + // 处理列表数据:将接口返回的 distance(米)转换成千米用于显示 + // 注意:这里不修改原始数据,只在显示时转换 + if (isLoadMore) { // 加载更多,追加数据 this.serviceList = [...this.serviceList, ...newList]; diff --git a/pages/webview/webview.vue b/pages/webview/webview.vue index 5e6cf18..b86999f 100644 --- a/pages/webview/webview.vue +++ b/pages/webview/webview.vue @@ -78,3 +78,4 @@ export default { +