原文参考我的公众号文章 微信小程序自定义动静 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 => {})
}
})