关于微信小程序:轻松使用微信小程序动态tabbar

4次阅读

共计 9801 个字符,预计需要花费 25 分钟才能阅读完成。

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