Component({ data: { roleName: '', roles: [], currentPath: '', visibleTabs: [], allTabs: [ { key: 'reception', text: '接待', pagePath: 'pages-subpackage/reception/reception', // 使用与 pages.json 一致的格式(不带前导斜杠) iconPath: '/static/tabbar/reception.png', selectedIconPath: '/static/tabbar/reception_active.png', }, { key: 'customer', text: '客户', pagePath: 'pages-subpackage/customer/customer', iconPath: '/static/tabbar/customer.png', selectedIconPath: '/static/tabbar/customer_active.png', }, { key: 'team', text: '团队', pagePath: 'pages/team/team', iconPath: '/static/tabbar/team.png', selectedIconPath: '/static/tabbar/team_active.png', }, { key: 'ucenter', text: '我的', pagePath: 'pages/ucenter/ucenter', iconPath: '/static/tabbar/me.png', selectedIconPath: '/static/tabbar/me_active.png', }, { key: 'meeting_summary', text: '总结和统计', pagePath: 'pages-subpackage/meeting_summary/meeting_summary', iconPath: '/static/tabbar/insight.png', selectedIconPath: '/static/tabbar/insight_active.png', }, { key: 'common_begin_reception', text: '开始接待', pagePath: 'pages/furniture_reception/common_begin_reception', iconPath: '/static/tabbar/reception.png', selectedIconPath: '/static/tabbar/reception_active.png', }, { key: 'furniture_customer', text: '客户', pagePath: 'pages-subpackage/furniture_customer/furniture_customer', iconPath: '/static/tabbar/customer.png', selectedIconPath: '/static/tabbar/customer_active.png', }, { key: 'workbench', text: '工作台', pagePath: 'pages/workbench/workbench', iconPath: '/static/tabbar/team.png', selectedIconPath: '/static/tabbar/team_active.png', }, { key: 'furniture_top_sales', text: '销冠', pagePath: 'pages/furniture_top_sales/furniture_top_sales', iconPath: '/static/tabbar/insight.png', selectedIconPath: '/static/tabbar/insight_active.png', }, ], }, lifetimes: { attached() { console.log('[TabBar][lifetimes] attached, 组件已挂载'); this.refreshRole(); this.updateActivePath(); // 监听全局事件,用于登录后刷新tabBar // 注意:微信小程序不支持uni.$on,需要通过其他方式实现 // 这里通过监听storage变化来实现 try { // 使用定时器定期检查角色变化(登录后) this._refreshTimer = setInterval(() => { const storedRole = wx.getStorageSync('backend-role-name') || ''; if (storedRole !== this.data.roleName) { this.refreshRole(); this.updateActivePath(); } }, 1000); } catch (e) { console.warn('设置tabBar刷新监听失败:', e); } // 记录组件挂载时的页面信息 try { const pages = getCurrentPages(); console.log('[TabBar][lifetimes] 组件挂载时的页面栈:', { pagesLength: pages.length, currentRoute: pages.length > 0 ? pages[pages.length - 1].route : 'unknown', allRoutes: pages.map(p => p.route) }); } catch (e) { console.warn('[TabBar][lifetimes] 获取页面栈失败:', e); } }, detached() { console.log('[TabBar][lifetimes] detached, 组件已卸载'); // 清理定时器 if (this._refreshTimer) { clearInterval(this._refreshTimer); this._refreshTimer = null; } }, }, pageLifetimes: { show() { this.refreshRole(); this.updateActivePath(); }, }, methods: { refreshRole() { try { const storedRole = wx.getStorageSync('backend-role-name') || ''; const loginResp = wx.getStorageSync('backend-login-response') || {}; const respRole = loginResp.roleName || ''; const normalize = (val) => typeof val === 'string' ? val .split(',') .map((s) => s.trim().toLowerCase()) .filter(Boolean) : []; const roles = [ ...normalize(storedRole), ...normalize(respRole), ]; const hasEdu = roles.includes('speaking_training_teacher'); const hasStudent = roles.includes('speaking_training_students'); const roleName = hasEdu ? 'speaking_training_teacher' : hasStudent ? 'speaking_training_students' : (roles[0] || ''); this.setData({ roles: roles, roleName: roleName, }); // 更新可见的tabs this.updateVisibleTabs(); console.log('[TabBar][role] storedRole:', storedRole, 'respRole:', respRole, 'normalized:', roles); } catch (e) { console.warn('读取角色失败:', e); } }, updateActivePath() { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; // currentPage.route 格式为 pages/xxx/xxx(不带前导斜杠) // 为了匹配 allTabs 中的 pagePath,我们也使用不带前导斜杠的格式 const currentPath = currentPage && currentPage.route ? currentPage.route : ''; this.setData({ currentPath: currentPath, }); // 更新可见的tabs this.updateVisibleTabs(); }, updateVisibleTabs() { const allowedKeys = this.getAllowedKeys(); console.log('[TabBar][visibleTabs] getAllowedKeys returned:', JSON.stringify(allowedKeys)); const tabMap = {}; this.data.allTabs.forEach(tab => { tabMap[tab.key] = tab; }); const otherKeys = []; let hasUcenter = false; allowedKeys.forEach(key => { if (key !== 'ucenter') { otherKeys.push(key); } else { hasUcenter = true; } }); const finalKeys = hasUcenter ? [...otherKeys, 'ucenter'] : otherKeys; const result = finalKeys .filter(key => tabMap[key]) .map(key => tabMap[key]); console.log('[TabBar][visibleTabs] final result keys:', result.map(t => t.key)); this.setData({ visibleTabs: result, }); }, // 外部调用刷新tabBar(登录成功后调用) refresh() { this.refreshRole(); this.updateActivePath(); console.log('[TabBar][refresh] currentPath:', this.data.currentPath, 'roleName:', this.data.roleName, 'visibleKeys:', this.data.visibleTabs.map(t => t.key), 'roles:', this.data.roles); }, switchTab(e) { // 只从 dataset 获取 key,避免路径传递问题 const dataset = e.currentTarget.dataset || {}; const key = dataset.key; if (!key) { console.error('[TabBar][switchTab] 缺少必要参数 key:', dataset); return; } // 根据key找到对应的item const item = this.data.allTabs.find(tab => tab.key === key); if (!item) { console.error('[TabBar][switchTab] 未找到对应的tab项:', key); return; } // 直接使用 item 中的 pagePath,确保路径正确 // 重要:创建一个全新的字符串,避免任何引用或编码问题 let pagePath = String(item.pagePath); // 移除所有前导和尾部空白字符、斜杠 pagePath = pagePath.trim().replace(/^\/+/, '').replace(/\/+$/, ''); console.log('[TabBar][switchTab] 准备跳转:', { key: key, pagePath: pagePath, currentPath: this.data.currentPath, itemPagePath: item.pagePath }); this.refreshRole(); const allowedKeys = this.getAllowedKeys(); if (!allowedKeys.includes(key)) { wx.showToast({ title: '当前账号无此模块权限', icon: 'none', duration: 1200 }); console.warn('[TabBar][block-switch]', 'key:', key, 'path:', pagePath, 'allowed:', allowedKeys); return; } // 检查是否已经是当前页面 const normalizedCurrent = (this.data.currentPath || '').trim().replace(/^\/+/, '').replace(/\/+$/, ''); const normalizedTarget = pagePath.trim().replace(/^\/+/, '').replace(/\/+$/, ''); if (normalizedCurrent === normalizedTarget) { console.log('[TabBar][switchTab] 已经是当前页面,跳过跳转'); return; } // switchTab 的 url 必须与 pages.json 中 tabBar.list 的 pagePath 完全一致 // 格式:pages/xxx/xxx(不带前导斜杠) // 重要:使用字符串字面量重新构建路径,确保是全新的字符串 // 根据 key 直接构建路径,避免使用 item.pagePath 可能存在的引用问题 const pathMap = { 'reception': 'pages-subpackage/reception/reception', 'customer': 'pages-subpackage/customer/customer', 'team': 'pages/team/team', 'ucenter': 'pages/ucenter/ucenter', 'meeting_summary': 'pages-subpackage/meeting_summary/meeting_summary', 'common_begin_reception': 'pages/furniture_reception/common_begin_reception', 'workbench': 'pages/workbench/workbench', 'furniture_customer': 'pages-subpackage/furniture_customer/furniture_customer', 'furniture_top_sales': 'pages/furniture_top_sales/furniture_top_sales' }; // 直接从 pathMap 获取路径,确保路径是全新的字符串字面量 let switchTabUrl = pathMap[key]; if (!switchTabUrl) { console.error('[TabBar][switchTab] 未找到对应的路径映射:', key); wx.showToast({ title: '页面路径不存在', icon: 'none', duration: 2000 }); return; } // 获取当前页面信息,用于调试 const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentRoute = currentPage ? currentPage.route : 'unknown'; const currentOptions = currentPage ? currentPage.options : {}; // 验证路径格式 if (!switchTabUrl.match(/^pages\/[^\/]+\/[^\/]+$/)) { console.error('[TabBar][switchTab] 路径格式错误:', switchTabUrl); return; } // 创建全新的字符串,确保没有任何引用或隐藏字符 // 使用 JSON 序列化再反序列化,确保是全新的字符串 switchTabUrl = JSON.parse(JSON.stringify(switchTabUrl)); // 再次验证 if (!switchTabUrl.match(/^pages\/[^\/]+\/[^\/]+$/)) { console.error('[TabBar][switchTab] JSON处理后路径格式错误:', switchTabUrl); return; } // 详细日志:记录所有相关信息 console.log('[TabBar][switchTab] ========== 详细调试信息 =========='); console.log('[TabBar][switchTab] 1. 基本信息:', { key: key, switchTabUrl: switchTabUrl, switchTabUrlType: typeof switchTabUrl, switchTabUrlLength: switchTabUrl.length, switchTabUrlCharCodes: switchTabUrl.split('').map(c => c.charCodeAt(0)), switchTabUrlJSON: JSON.stringify(switchTabUrl) }); console.log('[TabBar][switchTab] 2. 当前页面信息:', { currentPath: this.data.currentPath, currentRoute: currentRoute, currentOptions: currentOptions, pagesLength: pages.length, allRoutes: pages.map(p => p.route) }); console.log('[TabBar][switchTab] 3. 路径对比:', { switchTabUrl: switchTabUrl, itemPagePath: item.pagePath, pathMapValue: pathMap[key], areEqual: switchTabUrl === pathMap[key], startsWithPages: switchTabUrl.startsWith('pages/'), endsWithSlash: switchTabUrl.endsWith('/') }); console.log('[TabBar][switchTab] 4. 准备调用 wx.switchTab'); console.log('[TabBar][switchTab] ===================================='); // 尝试使用 getApp() 获取全局配置,检查是否有路径配置 try { const app = getApp({ allowDefault: true }); console.log('[TabBar][switchTab] 5. App全局信息:', { hasApp: !!app, globalData: app && app.globalData ? Object.keys(app.globalData) : [] }); } catch (e) { console.warn('[TabBar][switchTab] 获取App实例失败:', e); } // 尝试获取 tabBar 配置 try { const tabBarConfig = wx.getTabBar ? wx.getTabBar() : null; console.log('[TabBar][switchTab] 6. TabBar配置:', { hasTabBarConfig: !!tabBarConfig, tabBarType: typeof tabBarConfig }); } catch (e) { console.warn('[TabBar][switchTab] 获取TabBar配置失败:', e); } // 根本解决方案:根据微信小程序文档和错误分析 // 问题:在自定义 tabBar 组件中直接调用 wx.switchTab 时,微信小程序会错误地将当前页面路径拼接 // 解决方案:通过页面实例调用,或者使用事件机制通知页面调用 // 方法:通过页面实例的 selectComponent 或直接调用页面方法 // 但更好的方法是:使用 wx.navigateTo 先跳转到一个中间页面,然后立即 switchTab // 或者:直接使用 wx.reLaunch(虽然文档说 tabBar 不支持,但实际可以) console.log('[TabBar][switchTab] ========== 尝试解决路径拼接问题 =========='); console.log('[TabBar][switchTab] 目标路径:', switchTabUrl); console.log('[TabBar][switchTab] 当前页面:', this.data.currentPath); // 关键发现:根据错误信息,微信小程序会将当前页面路径错误拼接 // 解决方案:使用 wx.reLaunch 替代 wx.switchTab // reLaunch 会关闭所有页面,然后打开指定页面,不会进行路径拼接 console.log('[TabBar][switchTab] 使用 wx.reLaunch 替代 wx.switchTab(避免路径拼接)'); wx.reLaunch({ url: '/' + switchTabUrl, // reLaunch 需要带前导斜杠 success: () => { console.log('[TabBar][switchTab] reLaunch 成功:', '/' + switchTabUrl); setTimeout(() => { this.updateActivePath(); }, 100); }, fail: (err) => { console.error('[TabBar][switchTab] reLaunch 失败:', err); // 如果 reLaunch 失败,尝试使用 switchTab(带前导斜杠) console.log('[TabBar][switchTab] reLaunch 失败,尝试 switchTab(带前导斜杠)'); wx.switchTab({ url: '/' + switchTabUrl, success: () => { console.log('[TabBar][switchTab] switchTab(带前导斜杠)成功'); setTimeout(() => { this.updateActivePath(); }, 100); }, fail: (err2) => { console.error('[TabBar][switchTab] switchTab(带前导斜杠)也失败:', err2); // 最后尝试:不带前导斜杠的 switchTab console.log('[TabBar][switchTab] 最后尝试:switchTab(不带前导斜杠)'); this.callWxSwitchTab(switchTabUrl, key, pagePath); } }); } }); }, // 单独的方法调用 wx.switchTab,便于调试和错误处理 callWxSwitchTab(switchTabUrl, key, pagePath) { console.log('[TabBar][callWxSwitchTab] 调用 wx.switchTab:', { url: switchTabUrl, key: key, urlType: typeof switchTabUrl, urlLength: switchTabUrl.length }); wx.switchTab({ url: switchTabUrl, success: () => { console.log('[TabBar][callWxSwitchTab] success:', switchTabUrl); setTimeout(() => { this.updateActivePath(); }, 100); }, fail: (err) => { console.error('[TabBar][callWxSwitchTab] ========== 失败详情 =========='); console.error('[TabBar][callWxSwitchTab] 错误对象:', err); console.error('[TabBar][callWxSwitchTab] 传递的url:', { url: switchTabUrl, urlType: typeof switchTabUrl, urlLength: switchTabUrl.length, urlCharCodes: switchTabUrl.split('').map(c => c.charCodeAt(0)), urlJSON: JSON.stringify(switchTabUrl), urlString: String(switchTabUrl), urlSlice: switchTabUrl.slice(0), urlSubstring: switchTabUrl.substring(0) }); console.error('[TabBar][callWxSwitchTab] 错误消息:', err.errMsg); console.error('[TabBar][callWxSwitchTab] 当前页面:', { currentPath: this.data.currentPath, currentRoute: getCurrentPages().length > 0 ? getCurrentPages()[getCurrentPages().length - 1].route : 'unknown', allRoutes: getCurrentPages().map(p => p.route) }); // 解析错误消息中的路径 const errorPathMatch = err.errMsg ? err.errMsg.match(/page\s+"([^"]+)"/) : null; const actualSearchedPath = errorPathMatch ? errorPathMatch[1] : null; console.error('[TabBar][callWxSwitchTab] 路径分析:', { passedUrl: switchTabUrl, errorPathInMsg: actualSearchedPath, isPathConcatenated: actualSearchedPath && (actualSearchedPath.includes('pages/reception/pages/') || actualSearchedPath.includes('pages-subpackage/reception/pages/')), expectedPath: switchTabUrl, actualSearchedPath: actualSearchedPath, pathDifference: actualSearchedPath ? actualSearchedPath.replace(switchTabUrl, '') : null, currentPagePrefix: this.data.currentPath ? this.data.currentPath.split('/').slice(0, -1).join('/') : null }); // 检查是否是路径拼接问题 if (actualSearchedPath && (actualSearchedPath.includes('pages/reception/pages/') || actualSearchedPath.includes('pages-subpackage/reception/pages/'))) { console.error('[TabBar][callWxSwitchTab] 确认是路径拼接问题!'); console.error('[TabBar][callWxSwitchTab] 当前页面路径被错误地拼接到了目标路径前面'); console.error('[TabBar][callWxSwitchTab] 这可能是因为微信小程序在处理自定义 tabBar 的 switchTab 时的 bug'); } console.error('[TabBar][callWxSwitchTab] ===================================='); // 如果 switchTab 失败,尝试检查是否是路径问题 // 注意:不能使用 reLaunch 替代 switchTab,因为 reLaunch 不支持 tabBar 页面 wx.showToast({ title: '页面跳转失败,请检查页面配置', icon: 'none', duration: 2000 }); }, complete: () => { // 延迟更新,确保页面切换完成 setTimeout(() => { this.updateActivePath(); }, 200); }, }); }, getAllowedKeys() { const roles = this.data.roles || []; const isTeacher = roles.includes('speaking_training_teacher'); const isStudent = roles.includes('speaking_training_students'); const isAdmin = roles.includes('admin'); const isMeetingAdmin = roles.includes('meeting_admin'); const isAdminFurniture = roles.includes('admin_furniture'); const meetingTabKeys = ['meeting_summary']; const removedTabKeys = ['champion', 'workspace']; const hasFurnitureRole = roles.includes('furniture'); if (isAdminFurniture || hasFurnitureRole) { const furnitureTabs = ['workbench', 'ucenter']; console.log('[TabBar][allowed] furniture/admin_furniture role ->', furnitureTabs); return furnitureTabs; } if (isMeetingAdmin) { const meetingAdminTabs = [...meetingTabKeys, 'ucenter']; console.log('[TabBar][allowed] meeting_admin ->', meetingAdminTabs); return meetingAdminTabs; } if (isAdmin) { const all = this.data.allTabs .map((t) => t.key) .filter((key) => !meetingTabKeys.includes(key) && !['furniture_reception', 'common_begin_reception', 'furniture_customer', 'ai_qa', 'ai_analysis', 'furniture_top_sales', 'reception', 'customer', ...removedTabKeys].includes(key)); console.log('[TabBar][allowed] admin ->', all); return all; } if (isTeacher && !isAdmin) { const teacherTabs = ['ucenter']; console.log('[TabBar][allowed] teacher ->', teacherTabs); return teacherTabs; } if (isStudent && !isAdmin) { const studentTabs = ['ucenter']; console.log('[TabBar][allowed] student ->', studentTabs); return studentTabs; } const otherTabs = this.data.allTabs .map((t) => t.key) .filter((key) => !['homework', 'edu', 'voice', 'expression', 'furniture_reception', 'common_begin_reception', 'furniture_customer', 'ai_qa', 'ai_analysis', 'furniture_top_sales', 'reception', 'customer', ...meetingTabKeys, ...removedTabKeys].includes(key)); if (!otherTabs.includes('ucenter')) { otherTabs.push('ucenter'); } console.log('[TabBar][allowed] other ->', otherTabs); return otherTabs; }, }, observers: { 'roles, currentPath': function() { this.updateVisibleTabs(); }, }, });