原文参考我的公众号文章 微信小程序自定义动静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-barjs里,在lifetimesattached里调用和【权限&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 => {})  }})