原文参考我的公众号文章 微信小程序自定义动态tabBar
总结了一下小程序官方推荐的动态
tabBar
使用。通过自己的实践和落地实施,对使用动态tabBar
时需要处理的问题(多角色不同tabBar
动态渲染,页面加载后tabBar
自动选中,操作权限判断,页面逻辑和tabBar
初始化的先后关系控制。。。)进行了逻辑整合。
前置需求
- 多角色
- 动态tabBar
- 操作权限控制
tabBar
完整代码和页面内的使用手法在文末,这里先分步看看这些问题当时都是如何处理的吧。
tabBar组件封装
组件结构的封装可参考官方代码,之后需要在组件js文件内对动态tabBar做处理即可;
import {
geneAuthMenus,
updateAppGlobalAuthData
} from './tabBarCtrl'
import {
userLogin
} from '../models/UserApi'
import {
showToast
} from '../utils/WxApi'
Component({
data: {
selected: 0,
list: [],
},
methods: {
switchTab(e) {
const data = e.currentTarget.dataset
const url = data.path
wx.switchTab({
url
})
this.setData({
selected: data.index
})
}
},
lifetimes: {
attached() {
let appAuthMenus = [...getApp().globalAuth.menus];
if (!getApp().globalAuth.received) {
/**登录后App内记录 可访问页面、可操作动作 */
userLogin({
useCache: false
}).then(res => {
let {
auth_list = [], action_list = [], role_id = 0, role_name = '小白'
} = res?.data?.role_auth || {};
let authList = geneAuthMenus(auth_list);
updateAppGlobalAuthData({
received: true,
menus: authList,
actions: action_list,
roleId: role_id,
roleName: role_name,
});
this.setData({
list: authList
})
}).catch(err => {
showToast(err.msg || '登陆失败')
updateAppGlobalAuthData({
received: true,
menus: geneAuthMenus([]),
actions: [],
roleId: 0,
roleName: '小白',
});
})
} else {
this.setData({
list: appAuthMenus
})
}
}
}
})
tabBar
数据全局数据访问
App({
globalAuth: {
received: false, // 当状态变为true,代表auth相关数据已经拿到,与auth相关的逻辑现在可以执行了
menus: [],
actions: [],
role_id: 0,
role_name: '普通用户',
roleHash: {
0: '普通用户',
1: '管理员',
2: '运营',
3: '维修师傅',
}
}
})
tabBar
数据来自【权限&menu】
接口
在custom-tab-bar
的js
里,在lifetimes
的attached
里调用和【权限&menu】
相关接口,接口调用结束(无论成功或失败)后把getApp().globalAuth.received
设为true
lifetimes: {
attached() {
let appAuthMenus = [...getApp().globalAuth.menus];
if (!getApp().globalAuth.received) {
/**登录后App内记录 可访问页面、可操作动作 */
userLogin({
useCache: false
}).then(res => {
let {
auth_list = [], action_list = [], role_id = 0, role_name = '小白'
} = res?.data?.role_auth || {};
let authList = geneAuthMenus(auth_list);
updateAppGlobalAuthData({
received: true,
menus: authList,
actions: action_list,
roleId: role_id,
roleName: role_name,
});
this.setData({
list: authList
})
}).catch(err => {
showToast(err.msg || '登陆失败')
updateAppGlobalAuthData({
received: true,
menus: geneAuthMenus([]),
actions: [],
roleId: 0,
roleName: '普通用户',
});
})
} else {
this.setData({
list: appAuthMenus
})
}
}
}
解决动态tabBar带来的问题
如何保证页面逻辑在【权限&menu】
接口请求结束之后执行:通过访问全局状态字段getApp().globalAuth.received
状态来判断;
// 通过无限轮询globalAuth.received状态的变化,监测到变化后再执行后续
export function doSthAfterDependentChangedPromise(computed = () => {}) {
let loopTicker = null;
const dependentTargetChanged = (resolver) => {
if (getAppGlobalAuthData('received')) {
console.log('doSthAfterDependentChangedPromise=>' + computed.getName())
clearTimeout(loopTicker);
resolver(computed());
} else {
loopTicker = setTimeout(() => {
dependentTargetChanged(resolver)
}, 200);
}
}
return new Promise(resolve => {
dependentTargetChanged(resolve)
})
}
2.默认页面问题;
由于上面已经可以做到在【权限&menu】
接口结束后再执行其他逻辑,那么就可以做到拿到 menu
数据后调用 wx.redirectTo
到角色默认的页面;(由于我的项目默认页面是一个所有角色公有的页面,所以就没做这方面处理)
含tabBar
页面自动更新 tab
索引
在使用tabBar
的页面调用selectTabBar
方法,因为方法核心是通过Page实例调用getTabBar
方法,所以把this
传递进去了。参考官方说法:如需实现tab
选中态,要在当前页面下,通过getTabBar
接口获取组件实例,并调用setData
更新选中态。
onShow: function () {
selectTabBar(this);
},
export function selectTabBar(context) {
const computed = () => {
let authMenus = [...getAppGlobalAuthData('menus')];
let currentPath = getCurrentRoute();
let pageIndex = authMenus.findIndex(item => item.pagePath.includes(currentPath));
pageIndex = pageIndex == -1 ? 0 : pageIndex;
if (typeof context.getTabBar === 'function' &&
context.getTabBar()) {
context.getTabBar().setData({
selected: pageIndex
})
console.log('select current path:', currentPath)
}
return 1;
}
return doSthAfterDependentChangedPromise(computed)
}
3.含tabBar
的页面超过了五个:小程序在app.json
内通过"tabBar"
字段定义了list
只能有五项,所以可以把这些页面作为某一个tabBar
页面的二级入口。
对代码进行整合
上述实现中,将tabBar相关数据挂载在App实例中,为了让tabBar组件相关功能更加紧密,逻辑更加清晰,所以把功能和数据整合到了一起,形成了 custom-tab-bar/model.js
文件。
function getCurrentRoute() {
let route = '/pages/home/home'
let pages = getCurrentPages();
if (pages.length) {
route = pages[pages.length - 1].route;
}
return route;
}
const PAGE_ENVIRONMENT = {
"pagePath": "/pages/pkgStoreInspection/Environment/Environment",
"text": "页面0",
"iconPath": "/resources/icon/lubanya/tabbar/Env.png",
"selectedIconPath": "/resources/icon/lubanya/tabbar/Env_cur.png"
}
const PAGE_NG = {
"pagePath": "/pages/pkgStoreInspection/NG/NG",
"text": "页面1",
"iconPath": "/resources/icon/lubanya/tabbar/Nogood.png",
"selectedIconPath": "/resources/icon/lubanya/tabbar/Nogood_cur.png"
}
const PAGE_INSPECTION = {
"pagePath": "/pages/pkgStoreInspection/inspection/inspection",
"text": "页面2",
"iconPath": "/resources/icon/lubanya/tabbar/Store.png",
"selectedIconPath": "/resources/icon/lubanya/tabbar/Store_cur.png"
}
const PAGE_RECORD = {
"pagePath": "/pages/pkgStoreInspection/record/record",
"text": "页面3",
"iconPath": "/resources/icon/lubanya/tabbar/History.png",
"selectedIconPath": "/resources/icon/lubanya/tabbar/History_cur.png"
}
const PAGE_MACHINE_EMULATOR = {
"pagePath": "/pkgElse/pages/machineEmulator/machineEmulator",
"text": "页面4",
"iconPath": "/resources/icon/lubanya/tabbar/History.png",
"selectedIconPath": "/resources/icon/lubanya/tabbar/History_cur.png"
}
const PAGE_USER = {
"pagePath": "/pages/me/me",
"text": "页面5",
"iconPath": "/resources/images/tabbar/mine.png",
"selectedIconPath": "/resources/images/tabbar/mine_active.png"
}
const AUTH_PAGE_HASH = {
'PAGE_ENVIRONMENT': PAGE_ENVIRONMENT,
'PAGE_NG': PAGE_NG,
'PAGE_INSPECTION': PAGE_INSPECTION,
'PAGE_RECORD': PAGE_RECORD,
'PAGE_MACHINE_EMULATOR': PAGE_MACHINE_EMULATOR,
'PAGE_USER': PAGE_USER,
}
/**
* TabBar数据和行为控制的单例类
*/
let CreateSingletonTabBar = (function () {
let instance = null;
return function (roleId) {
if (instance) {
return instance
}
this.index = 0;
this.roleNameHash = {
0: '普通用户',
1: '管理员',
2: '运营',
3: '维修师傅',
}
this.authData = {
received: false,
pages: [],
actions: [],
roleId: roleId,
roleName: this.roleNameHash[roleId],
}
return instance = this;
}
})()
/**记录auth接口请求是否已经结束 */
CreateSingletonTabBar.prototype.getReceive = function () {
return this.authData.received;
}
/**获取有权限的pages */
CreateSingletonTabBar.prototype.getAuthPages = function () {
return this.authData.pages;
}
/**获取有权限的actions */
CreateSingletonTabBar.prototype.getAuthActions = function () {
return this.authData.actions;
}
/**通过AUTH_CODE生成符合小程序tabBar数据格式的authPages */
CreateSingletonTabBar.prototype.geneAuthPage = function (auth_list = []) {
console.log('got auth_list:',auth_list)
let pages = [];
if (auth_list && auth_list.length) {
auth_list.map((item, index) => {
pages.push({
index,
...AUTH_PAGE_HASH[item]
});
})
} else {
pages = [AUTH_PAGE_HASH['PAGE_ENVIRONMENT'], AUTH_PAGE_HASH['PAGE_USER']];
}
return pages;
}
/**更新内部tabBar相关数据 */
CreateSingletonTabBar.prototype.updateAuthData = function (objData = {}) {
this.authData = {
...this.authData,
...objData
};
}
/**选中tabBar:在含tabBar的页面内调用 selectTabBar(this) */
CreateSingletonTabBar.prototype.selectTabBar = function (context) {
let that = this;
const computed = () => {
let authMenus = [...that.getAuthPages()];
let currentPath = getCurrentRoute();
let pageIndex = authMenus.findIndex(item => item.pagePath.includes(currentPath));
pageIndex = pageIndex == -1 ? 0 : pageIndex;
that.index = pageIndex;
if (typeof context.getTabBar === 'function' &&
context.getTabBar()) {
context.getTabBar().setData({
selected: pageIndex
})
}
return 1;
}
return that.doSthAfterDependentChangedPromise(computed)
}
/**判断角色是否拥有某个action权限 */
CreateSingletonTabBar.prototype.checkAuthAction = function (act_code) {
let that = this;
let computedCheckAuthAction = () => {
return that.authData.actions.includes(act_code)
}
return that.doSthAfterDependentChangedPromise(computedCheckAuthAction)
}
/**获取角色role_id */
CreateSingletonTabBar.prototype.getRoleId = function () {
let that = this;
let computedGetRoleId = () => {
return that.authData.roleId
}
return that.doSthAfterDependentChangedPromise(computedGetRoleId)
}
/**如果某些逻辑需要在auth接口请求结束后执行,可以用此方法包装调用 */
CreateSingletonTabBar.prototype.doSthAfterDependentChangedPromise = function (computed = () => {}) {
let loopTicker = null;
let that = this;
const dependentTargetChanged = (resolver) => {
if (that.authData.received) {
clearTimeout(loopTicker);
resolver(computed());
} else {
loopTicker = setTimeout(() => {
dependentTargetChanged(resolver)
}, 200);
}
}
return new Promise(resolve => {
dependentTargetChanged(resolve)
})
}
export const TBInstance = new CreateSingletonTabBar(0)
轻松使用!
在 custom-tab-bar
内实现动态 tabBar
,主要代码在 lifetimes
中
import {
userLogin
} from '../models/UserApi'
import {
showToast
} from '../utils/WxApi'
import {
TBInstance
} from './model'
Component({
data: {
selected: 0,
list: [],
},
methods: {
switchTab(e) {
const data = e.currentTarget.dataset
const url = data.path
wx.switchTab({
url
})
this.setData({
selected: data.index
})
}
},
/**以上代码为官方示例所有 */
lifetimes: {
/**这里是动态tabBar的关键代码 */
attached() {
let appAuthMenus = [...TBInstance.getAuthPages()];
if (!TBInstance.getReceive() || !appAuthMenus.length) {
/**登录后TBInstance内记录tabBar相关数据,如:可访问页面、可操作动作... */
userLogin({
useCache: false
}).then(res => {
let {
auth_list = [], action_list = [], role_id = 0, role_name = '普通用户'
} = res?.data?.role_auth || {};
let authList = TBInstance.geneAuthPage(auth_list);
TBInstance.updateAuthData({
received: true,
pages: authList,
actions: action_list,
roleId: role_id,
roleName: role_name,
})
this.setData({
list: authList
})
}).catch(err => {
console.log(err)
showToast(err.msg || '登陆失败')
TBInstance.updateAuthData({
received: true,
menus: TBInstance.geneAuthPage([]),
actions: [],
roleId: 0,
roleName: '普通用户',
});
})
} else {
this.setData({
list: appAuthMenus
})
}
}
}
})
实现 tab
选中
调用selectTabBar
选中当前页面对应的tab
,不需要传递index
,因为不同角色即便拥有的相同页面,对应的索引也可能是不一样的,所以这个动态的索引放到了selectTabBar
内部实现
import {
TBInstance
} from '../../../custom-tab-bar/model'
Page({
data: {},
onShow: function () {
// 选中当前页面对应的tabBar,不需要传递index,因为不同角色即便拥有的相同页面,对应的索引也可能是不一样的,所以这个动态的索引放到了selectTabBar内部实现
TBInstance.selectTabBar(this);
}
})
实现判断是当前角色否有某个 action
的权限
import {
TBInstance
} from '../../../custom-tab-bar/model'
Page({
data: {
showAddNgBtn: false
},
onShow: function () {
// 判断是否有ACT_ADD_NG操作权限
TBInstance.checkAuthAction('ACT_ADD_NG').then(res => {
this.setData({
showAddNgBtn: res
})
})
}
})
实现 tabBar
的初始化与页面逻辑同步执行
封装了doSthAfterDependentChangedPromise
方法自动检测tabBar
逻辑的执行情况,结束后才执行传入的代码逻辑
import {
fetchShopsEnv
} from '../../../models/InspectionApi'
import {
TBInstance
} from '../../../custom-tab-bar/model'
Page({
data: {
list: [],
},
onLoad: function () {
TBInstance.doSthAfterDependentChangedPromise(this.getShopEnv)
},
onShow: function () {
TBInstance.selectTabBar(this);
},
getShopEnv: function () {
fetchShopsEnv().then(res => {
this.setData({
list: res.data
})
}).catch(err => {})
}
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。