Files
smartDriveEEUniApp/custom-tab-bar/index.vue
2026-01-11 12:11:01 +08:00

389 lines
14 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">
<view
v-for="item in visibleTabs"
: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>
export default {
data() {
return {
roleName: '',
roles: [],
currentPath: '',
allTabs: [
{
key: 'reception',
text: '接待',
pagePath: '/pages/reception/reception',
iconPath: '/static/tabbar/reception.png',
selectedIconPath: '/static/tabbar/reception_active.png',
},
{
key: 'customer',
text: '客户',
pagePath: '/pages/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: '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/meeting_summary/meeting_summary',
iconPath: '/static/tabbar/insight.png',
selectedIconPath: '/static/tabbar/insight_active.png',
},
{
key: 'furniture_reception',
text: '接待',
pagePath: '/pages/furniture_reception/furniture_reception',
iconPath: '/static/tabbar/reception.png',
selectedIconPath: '/static/tabbar/reception_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/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',
},
],
};
},
computed: {
visibleTabs() {
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() {
this.refreshRole();
this.updateActivePath();
},
mounted() {
// 监听外部刷新事件(登录后、页面切换时可触发)
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 显隐
this.toggleNativeTabs();
// #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 || [];
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');
// 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 = ['reception', 'customer', 'team', 'meeting_summary', 'furniture_reception', 'common_begin_reception', 'furniture_customer', 'furniture_top_sales', 'ucenter'];
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);
}
},
// #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>