Files
smartDriveEEUniApp/custom-tab-bar/index.vue
2026-01-31 21:10:40 +08:00

563 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="tabbar">
<!-- 调试信息显示当前可见的tabs -->
<view style="display: none;">
DEBUG: visibleTabs length: {{ visibleTabs.length }},
tabs: {{ visibleTabs.map(t => t.text).join(', ') }},
furnitureTabs length: {{ furnitureTabs.length }},
furnitureTabs: {{ furnitureTabs.map(t => t.text).join(', ') }},
roles: {{ roles.join(', ') }}
</view>
<!-- 强制显示所有家具角色的tab -->
<view
v-for="item in furnitureTabs"
:key="item.key"
class="tabbar-item"
:class="{ active: currentPath === item.pagePath }"
@click="switchTab(item)"
>
<image class="tabbar-icon" :src="currentPath === item.pagePath ? item.selectedIconPath : item.iconPath" />
<text class="tabbar-text">{{ item.text }}</text>
</view>
</view>
</template>
<script>
// 直接在全局作用域添加调试代码,确保能执行
console.log('CUSTOM TABBAR FILE LOADED - THIS MUST SHOW');
export default {
data() {
return {
roleName: '',
roles: [],
currentPath: '',
allTabs: [
{
key: 'furniture_reception',
text: '接待',
pagePath: '/pages/furniture_reception/furniture_reception',
iconPath: '/static/tabbar/reception.png',
selectedIconPath: '/static/tabbar/reception_active.png',
},
{
key: 'furniture_customer',
text: '客户',
pagePath: 'pages/furniture_customer/furniture_customer',
iconPath: '/static/tabbar/customer.png',
selectedIconPath: '/static/tabbar/customer_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',
},
{
key: 'ucenter',
text: '我的',
pagePath: 'pages/ucenter/ucenter',
iconPath: '/static/tabbar/me.png',
selectedIconPath: '/static/tabbar/me_active.png',
},
// 已从导航中移除,但保留代码
// {
// key: 'champion',
// text: '销冠',
// pagePath: '/pages/champion/champion',
// iconPath: '/static/tabbar/insight.png',
// selectedIconPath: '/static/tabbar/insight_active.png',
// },
// {
// key: 'workspace',
// text: '工作台',
// pagePath: '/pages/workspace/workspace',
// iconPath: '/static/tabbar/workspace.png',
// selectedIconPath: '/static/tabbar/workspace_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',
},
],
};
},
computed: {
furnitureTabs() {
// 强制返回所有家具角色的tab
return [
{
key: 'furniture_reception',
text: '接待',
pagePath: '/pages/furniture_reception/furniture_reception',
iconPath: '/static/tabbar/reception.png',
selectedIconPath: '/static/tabbar/reception_active.png',
},
{
key: 'furniture_customer',
text: '客户',
pagePath: '/pages/furniture_customer/furniture_customer',
iconPath: '/static/tabbar/customer.png',
selectedIconPath: '/static/tabbar/customer_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',
},
{
key: 'ucenter',
text: '我的',
pagePath: '/pages/ucenter/ucenter',
iconPath: '/static/tabbar/me.png',
selectedIconPath: '/static/tabbar/me_active.png',
}
];
},
visibleTabs() {
console.log('=== CUSTOM TABBAR VISIBLE TABS CALCULATED ===');
console.log('VISIBLE TABS CALC - THIS MUST SHOW IN H5');
const allowedKeys = this.getAllowedKeys();
console.log('[TabBar][visibleTabs] STEP1 - getAllowedKeys returned:', JSON.stringify(allowedKeys));
// 创建 key 到 tab 的映射
const tabMap = {};
this.allTabs.forEach(tab => {
tabMap[tab.key] = tab;
});
// 强制将 ucenter 移到最后一个位置:先收集所有非 ucenter 的 keys然后添加 ucenter
const otherKeys = [];
let hasUcenter = false;
allowedKeys.forEach(key => {
if (key !== 'ucenter') {
otherKeys.push(key);
} else {
hasUcenter = true;
}
});
console.log('[TabBar][visibleTabs] STEP2 - after filtering, otherKeys:', JSON.stringify(otherKeys), 'hasUcenter:', hasUcenter);
// 如果有 ucenter将其添加到最后一个位置
const finalKeys = hasUcenter ? [...otherKeys, 'ucenter'] : otherKeys;
console.log('[TabBar][visibleTabs] STEP3 - finalKeys after moving ucenter to end:', JSON.stringify(finalKeys));
// 按照排序后的 keys 顺序返回 tabs
const result = finalKeys
.filter(key => tabMap[key])
.map(key => tabMap[key]);
console.log('[TabBar][visibleTabs] STEP4 - final result keys:', result.map(t => t.key), 'texts:', result.map(t => t.text));
return result;
},
},
created() {
console.log('=== CUSTOM TABBAR COMPONENT CREATED ===');
console.log('CUSTOM TABBAR CREATED - THIS MUST SHOW IN H5');
this.refreshRole();
this.updateActivePath();
},
mounted() {
console.log('=== CUSTOM TABBAR COMPONENT MOUNTED ===');
console.log('CUSTOM TABBAR MOUNTED - THIS MUST SHOW IN CONSOLE');
// 强制执行,不依赖条件编译
this.forceH5TabBarSetup();
// 监听外部刷新事件(登录后、页面切换时可触发)
uni.$on('tabbar:refresh', this.handleExternalRefresh);
// 初始化时同步一次
this.handleExternalRefresh();
},
beforeDestroy() {
uni.$off('tabbar:refresh', this.handleExternalRefresh);
},
methods: {
refreshRole() {
try {
// 角色来源优先级:单独存储的 roleName > 登录响应里的 roleName
const storedRole = uni.getStorageSync('backend-role-name') || '';
const loginResp = uni.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');
this.roles = roles;
this.roleName = hasEdu ? 'speaking_training_teacher' : hasStudent ? 'speaking_training_students' : (roles[0] || '');
console.log('[TabBar][role] storedRole:', storedRole, 'respRole:', respRole, 'normalized:', roles, 'hasEdu:', hasEdu, 'hasStudent:', hasStudent, 'finalRoleName:', this.roleName);
// #ifdef H5
// H5 平台自定义 tabBar 无法生效,仍使用原生 DOM这里额外做一次 DOM 显隐
console.log('[TabBar][H5] refreshRole completed, will call toggleNativeTabs');
// 注意不在refreshRole中直接调用而是在setupH5TabBar中处理
// #endif
} catch (e) {
console.warn('读取角色失败:', e);
}
},
updateActivePath() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// currentPage.route 不带前导斜杠
this.currentPath = currentPage && currentPage.route ? `/${currentPage.route}` : '';
},
switchTab(item) {
// 切换前刷新一次角色,防止角色变更后仍看到教务/作业等
this.refreshRole();
const allowedKeys = this.getAllowedKeys();
if (!allowedKeys.includes(item.key)) {
uni.showToast({ title: '当前账号无此模块权限', icon: 'none', duration: 1200 });
console.warn('[TabBar][block-switch]', 'key:', item.key, 'path:', item.pagePath, 'allowed:', allowedKeys, 'roles:', this.roles);
return;
}
if (this.currentPath === item.pagePath) return;
// uni.switchTab 需要相对路径(去掉前导斜杠)
const url = item.pagePath.startsWith('/') ? item.pagePath.substring(1) : item.pagePath;
uni.switchTab({
url: url,
success: () => {
console.log('[TabBar][switchTab] success:', url);
this.updateActivePath();
},
fail: (err) => {
console.error('[TabBar][switchTab] fail:', err, 'url:', url);
// 如果 switchTab 失败,尝试使用 reLaunch适用于 tabBar 页面)
uni.reLaunch({
url: url,
fail: (err2) => {
console.error('[TabBar][reLaunch] fail:', err2, 'url:', url);
uni.showToast({
title: '页面跳转失败,请检查页面配置',
icon: 'none',
duration: 2000
});
},
success: () => {
this.updateActivePath();
}
});
},
complete: () => {
// 延迟更新,确保页面切换完成
setTimeout(() => {
this.updateActivePath();
}, 100);
},
});
},
handleExternalRefresh() {
// 每次收到事件时重新读取角色并刷新选中态
this.refreshRole();
this.updateActivePath();
console.log('[TabBar][refresh] currentPath:', this.currentPath, 'roleName:', this.roleName, 'visibleKeys:', this.visibleTabs.map(t => t.key), 'roles:', this.roles);
},
getAllowedKeys() {
const roles = this.roles || [];
console.log('=== CUSTOM TABBAR GET ALLOWED KEYS CALLED ===');
console.log('GET ALLOWED KEYS - THIS MUST SHOW IN H5, roles:', 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');
// 会议相关的tab key列表
const meetingTabKeys = ['meeting_summary'];
// 已从导航中移除的tab key列表代码保留但不显示
const removedTabKeys = ['champion', 'workspace'];
// 检查是否有任何角色名等于 "furniture" 字符串(精确匹配)
const hasFurnitureRole = roles.includes('furniture');
console.log('[TabBar][getAllowedKeys] role checks:', {
isAdminFurniture,
hasFurnitureRole,
isAdmin,
isMeetingAdmin,
roles
});
// admin_furniture 和 furniture 角色:显示 furniture_reception, furniture_customer, furniture_top_sales 和 ucenter相同顺序
if (isAdminFurniture || hasFurnitureRole) {
const furnitureTabs = ['furniture_reception', 'furniture_customer', 'furniture_top_sales', 'ucenter'];
console.log('[TabBar][allowed] furniture/admin_furniture role ->', furnitureTabs, 'roles:', roles);
return furnitureTabs;
}
if (isMeetingAdmin) {
// meeting_admin 角色显示会议相关的tab和ucenter我的
const meetingAdminTabs = [...meetingTabKeys, 'ucenter'];
console.log('[TabBar][allowed] meeting_admin ->', meetingAdminTabs);
return meetingAdminTabs;
}
// 非 meeting_admin 角色排除所有会议相关的tab
if (isAdmin) {
// admin 角色显示所有tab包括 ucenter但排除会议相关的tab、家具相关的tab、以及reception和customer
const all = this.allTabs
.map((t) => t.key)
.filter((key) => !meetingTabKeys.includes(key) && !['furniture_reception', 'common_begin_reception', 'furniture_customer', 'furniture_top_sales', 'reception', 'customer', ...removedTabKeys].includes(key));
// 确保 ucenter 被包含(用于"我的"页面)
console.log('[TabBar][allowed] admin ->', all, 'includes ucenter:', all.includes('ucenter'));
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;
}
// 其他角色:隐藏作业/教务/语音/表达同时排除会议相关的tab、家具相关的tab、以及reception和customer但确保包含ucenter
const otherTabs = this.allTabs
.map((t) => t.key)
.filter((key) => !['homework', 'edu', 'voice', 'expression', 'furniture_reception', 'common_begin_reception', 'furniture_customer', 'furniture_top_sales', 'reception', 'customer', ...meetingTabKeys, ...removedTabKeys].includes(key));
// 确保 ucenter 被包含(任何角色都应该能看到"我的"
if (!otherTabs.includes('ucenter')) {
otherTabs.push('ucenter');
}
console.log('[TabBar][allowed] other ->', otherTabs, 'roles:', roles);
return otherTabs;
},
// #ifdef H5
toggleNativeTabs() {
// H5 平台自定义 tabbar 不生效,直接根据角色显隐原生 tabbar 项
// 当前位置顺序(根据 pages.json 中的 tabBar.list 顺序):接待、客户、团队、总结和统计、家具接待、开始接待、家具客户、家具销冠、我的
const ORDER = ['furniture_reception', 'furniture_customer', 'furniture_top_sales', 'ucenter', 'reception', 'customer', 'team', 'meeting_summary', 'common_begin_reception'];
const allowed = this.getAllowedKeys();
// 限制重试次数,防止渲染时机未就绪
this._h5TabRetries = (this._h5TabRetries || 0) + 1;
try {
const items = document.querySelectorAll('.uni-tabbar .uni-tabbar__item');
if (!items || !items.length) {
// 原生 tabbar 可能尚未渲染,稍后重试一次
if (this._h5TabRetries <= 10) {
setTimeout(() => this.toggleNativeTabs(), 150);
} else {
console.warn('[TabBar][H5 DOM] retry limit reached, allowed:', allowed.join(','));
}
return;
}
ORDER.forEach((key, idx) => {
const node = items[idx];
if (!node) return;
node.style.display = allowed.includes(key) ? '' : 'none';
});
console.log('[TabBar][H5 DOM] allowed:', allowed.join(','), 'retries:', this._h5TabRetries);
this._h5TabRetries = 0;
} catch (e) {
console.warn('[TabBar][H5 DOM] toggle tabs failed:', e);
}
},
isInTabBar(pagePath) {
// 检查页面路径是否在原生tabBar配置中
const tabBarList = [
'pages/furniture_reception/furniture_reception',
'pages/furniture_customer/furniture_customer',
'pages/furniture_top_sales/furniture_top_sales',
'pages/ucenter/ucenter'
];
const normalizedPath = pagePath.startsWith('/') ? pagePath.substring(1) : pagePath;
const result = tabBarList.includes(normalizedPath);
console.log('[TabBar][isInTabBar]', { pagePath, normalizedPath, tabBarList, result });
return result;
},
forceH5TabBarSetup() {
console.log('=== FORCE H5 TABBAR SETUP CALLED ===');
// 立即执行toggleNativeTabs
setTimeout(() => {
console.log('FORCE: Executing toggleNativeTabs immediately');
this.forceToggleNativeTabs();
}, 100);
// 多次延迟执行
[500, 1000, 2000, 3000, 5000].forEach((delay, index) => {
setTimeout(() => {
console.log(`FORCE: Delayed execution ${index + 1} after ${delay}ms`);
this.forceToggleNativeTabs();
}, delay);
});
},
forceToggleNativeTabs() {
console.log('=== FORCE TOGGLE NATIVE TABS START ===');
try {
// 获取允许的tab
const allowed = this.getAllowedKeys();
console.log('FORCE: Current roles:', this.roles);
console.log('FORCE: Allowed keys:', allowed);
// 简化DOM查找 - 直接查找所有可能的tabBar元素
const allElements = document.querySelectorAll('*');
const tabElements = Array.from(allElements).filter(el =>
el.textContent && (
el.textContent.includes('接待') ||
el.textContent.includes('客户') ||
el.textContent.includes('销冠') ||
el.textContent.includes('我的')
)
);
console.log('FORCE: Found tab elements with text:', tabElements.length);
tabElements.forEach((el, idx) => {
console.log(`FORCE: Element ${idx}:`, {
tag: el.tagName,
class: el.className,
text: el.textContent,
currentDisplay: el.style.display,
computedDisplay: window.getComputedStyle(el).display
});
});
if (tabElements.length === 0) {
console.warn('FORCE: No tab elements found! Looking for containers...');
// 查找可能的容器
const containers = document.querySelectorAll('div, nav, section');
const possibleContainers = Array.from(containers).filter(el =>
el.children && el.children.length >= 4
);
console.log('FORCE: Possible containers:', possibleContainers.length);
possibleContainers.forEach((container, idx) => {
console.log(`FORCE: Container ${idx}:`, {
tag: container.tagName,
class: container.className,
children: container.children.length
});
});
return;
}
// 按文本内容排序:接待、客户、销冠、我的
const sortedElements = tabElements.sort((a, b) => {
const order = ['接待', '客户', '销冠', '我的'];
const aIndex = order.findIndex(text => a.textContent.includes(text));
const bIndex = order.findIndex(text => b.textContent.includes(text));
return aIndex - bIndex;
});
console.log('FORCE: Processing sorted elements...');
// 处理每个元素
sortedElements.forEach((element, index) => {
const keyMap = ['furniture_reception', 'furniture_customer', 'furniture_top_sales', 'ucenter'];
const key = keyMap[index];
const shouldShow = allowed.includes(key);
console.log(`FORCE: Processing ${key} (${element.textContent}): shouldShow=${shouldShow}`);
if (shouldShow) {
element.style.display = '';
element.style.visibility = 'visible';
// 确保父元素也可见
let parent = element.parentElement;
while (parent) {
parent.style.display = '';
parent.style.visibility = 'visible';
parent = parent.parentElement;
}
console.log(`FORCE: SHOWED ${key}`);
} else {
element.style.display = 'none';
element.style.visibility = 'hidden';
console.log(`FORCE: HID ${key}`);
}
});
console.log('=== FORCE TOGGLE NATIVE TABS END ===');
} catch (e) {
console.error('FORCE: Error in forceToggleNativeTabs:', e);
}
},
// #endif
},
};
</script>
<style>
.tabbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 52px;
background: #ffffff;
border-top: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: env(safe-area-inset-bottom);
}
.tabbar-item {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #7a7e83;
font-size: 12px;
}
.tabbar-item.active {
color: #007aff;
}
.tabbar-icon {
width: 24px;
height: 24px;
margin-bottom: 4px;
}
.tabbar-text {
line-height: 16px;
}
</style>