微信小程序自定义tabbar——动态改变pagePath

6,202 阅读7分钟

引言:要做就做点跟别人不同的。我基本不写博客,原因在于作为安卓开发,入行又比较晚的我基本想写的什么都有大佬前辈已经留下无数的经验和代码,本人又喜欢做点与众不同的东西,苦于无处下笔,终于,,有东西可以写了。

废话少说上图

故事的发生还要从那一天说起:公司的设计不懂微信小程序,他针对不一样身份的用户画了两套UI,两套UI里面各有一个不同的tabbar,而这两套UI需要在一个微信小程序里面展现。

那么问题来了。

1、微信小程序只提供一个tabbar,且一个小程序最多注册5个tabPage,而我此时的要求是两个tabbar,8个tabPage,这根本不够用嘛!

2、如果有研究过微信小程序的应该知道,微信是有分包机制的,主包的所有page都会在第一次加载的时候被缓存,后续被初始化,后续不管怎么操作都只能触发page的生命周期,而不会触发page的初始化代码以及data的赋值过程,也就意味着,不管是page实例还是tabPage的实例,只要初始化完成之后,app.json文件中配置的tabPage就会被固定死,打个比方,我的小程序默认的用户身份是teacher,那么app.json配置的第一个页面是page-teacher.js,在小程序第一次加载的时候,tabbar的第一个页面就会被固定死page-teacher.js,如果这个时候用户登录的是student的账号呢?按理来说我应该显示page-student.js,那么这个时候是没有办法修改已经缓存好了的page实例的,官方也没有提供相应的api(若后面官方提供了api,那么我的这部分内容就可以见鬼去了)。

刚才提到在小程序加载以后不会再触发data的赋值过程,同样的data的数据也是不可变的(这里可能会有看官会疑惑:“明明可以进行赋值操作,你怎么说不可变呢,你在胡说你个菜鸡程序员@#¥%&……*!” 没错哈!就是不可变的,为啥呢,后面会讲到),那么tabPage的实例都不可以改变了,我该怎么根据不一样的身份去展示不一样的tabPage呢?

在经过我的简单的思考之后迅速得出解决办法(其实想了几天,,,)。

根据问题1:我们肯定不能将8个tabPage一一注册进app.json,强行注册也会报错,所以我打算用四个中间页占位(我称呼中间页为傀儡页,以下都叫傀儡页;提供逻辑实际显示的叫实际页,以下都叫实际页),具体显示哪个page据情况而定。

根据问题2:因为傀儡页page一旦被初始化缓存之后,所有的代码都不可变了,所以如何写好固定代码逻辑使得可以适应不同身份显示不同实际页就格外重要。

那么隆重介绍我的解决办法,本项目以官方给出的自定义tabbar为基础,加上本人自己的理解和封装。下面是需要配置的地方:

firsttab.js 傀儡页示例 code1.1

import tabpage from '../../../../coner/tabpage.js';

Page(tabpage({
	index: 0
}));

这里使用tabpage进行封装内部逻辑,index:0为了区分是第几个tabPage。

firsttab.wxml 傀儡页示例 code1.2

<include wx:if='{{tabbarData.target == "teacher"}}' src='../../teacher/teach-lesson/teach-lesson.wxml' />
<include wx:elif='{{tabbarData.target == "student"}}' src='../../student/go-class/go-class.wxml' />

这里根据tabbarData.target判断哪个实际页的wxml。

firsttab.wxss 傀儡页示例 code1.3

@import '../../teacher/teach-lesson/teach-lesson.wxss';
@import '../../student/go-class/go-class.wxss';

这里导入需要显示的两个实际页的的wxss

实际页的json文件需要在傀儡页的json文件中进行相同的配置,例如实际页需要引用组件,若傀儡页json文件没有配置usingComponents参数则会导致傀儡页实例化出来的对象中没有selectComponent()方法

其他傀儡页同理

teacher/mine.js 实际页示例 code2.1

import coner from '../../../../coner/coner.js';

export default {
  createPage: createPage
}

function createPage() {
  var app = getApp();
  return coner.page({
    style: "outside",
    tabData: {
      selected: 3
    },

    datas() {
      return coner.extends({

      }, app.globalData);
    },

    onLoad: function (options) {

    }
  });
}

可以看到这里createPage方法返回的是一个page对象,所以实际页的开发还是类似于常规的page写法(我看到网上有大佬用的组件的方式写的,虽然功能确实实现了,但是对开发人员来说无疑是带来了麻烦,page的常规写法明显比组件的方式更亲切开发更快),这里我进行了page的封装写法是coner.page,将一些公共的方法封装进去了以及仿vue的写法datas方法,后面会讲到。以上代码可以简化成:

code2.2

export default {
  createPage: createPage
}

function createPage() {
  return {
    tabData: {
      selected: 3
    },

    data:{
    
    },

    onLoad: function (options) {

    }
  };
}

wxml文件,wxss文件,json文件则按常规方法写

实际页的样式文件wxss,需要注意选择器的名字,两个或多个实际页共用同一个傀儡页时,多个wxss文件的选择器应注意同名,并且注意选择器被重写等问题(参见code1.3 诸君就明白了)

/coner/tabbar.js code3.1

export default {
  getTabbarData: getTabbarData,
  tabbarData: {
    teacherLisr: {
      target: 'teacher',
      data: [{
          "mainPagePath": "/pages/tabbar/tabpage/firsttab/firsttab",
          "pagePath": "/pages/tabbar/teacher/teach-lesson/teach-lesson",
          "text": "教课",
          "iconPath": "/images/icon_teachclass_normal.png",
          "selectedIconPath": "/images/icon_teachclass_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/secondtab/secondtab",
          "pagePath": "/pages/tabbar/teacher/lesson-preparation/lesson-preparation",
          "text": "备课",
          "iconPath": "/images/icon_lessonpreparation_normal.png",
          "selectedIconPath": "/images/icon_lessonpreparation_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/thirdtab/thirdtab",
          "pagePath": "/pages/tabbar/teacher/level/level",
          "text": "教师职称",
          "iconPath": "/images/icon_level_normal.png",
          "selectedIconPath": "/images/icon_level_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/fourthtab/fourthtab",
          "pagePath": "/pages/tabbar/teacher/mine/mine",
          "text": "我的",
          "iconPath": "/images/icon_mine_normal.png",
          "selectedIconPath": "/images/icon_mine_select.png"
        }
      ]
    },
    studentLisr: {
      target: 'student',
      data: [{
          "mainPagePath": "/pages/tabbar/tabpage/firsttab/firsttab",
          "pagePath": "/pages/tabbar/student/go-class/go-class",
          "text": "上课",
          "iconPath": "/images/icon_goclass_normal.png",
          "selectedIconPath": "/images/icon_goclass_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/secondtab/secondtab",
          "pagePath": "/pages/tabbar/student/test/test",
          "text": "考试",
          "iconPath": "/images/icon_test_normal.png",
          "selectedIconPath": "/images/icon_test_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/thirdtab/thirdtab",
          "pagePath": "/pages/tabbar/student/homework/homework",
          "text": "课后作业",
          "iconPath": "/images/icon_homework_normal.png",
          "selectedIconPath": "/images/icon_homework_select.png"
        },
        {
          "mainPagePath": "/pages/tabbar/tabpage/fourthtab/fourthtab",
          "pagePath": "/pages/tabbar/student/mine/mine",
          "text": "我的",
          "iconPath": "/images/icon_mine_normal.png",
          "selectedIconPath": "/images/icon_mine_select.png"
        }
      ]
    },
  }
}

function getTabbarData() {
  let app = getApp();
  // 获取身份信息
  let status = app.getStorageValueSync({
    storage: 'userData',
    key: 'status'
  });
  let realList = {};
  // 根据身份显示不一样的title
  if (status != undefined && status != '') {
    let realList = [];
    if (status == '0') {
      // 身份为教师
      realList = this.tabbarData.teacherLisr;
    } else if (status == '1') {
      // 身份为学生
      realList = this.tabbarData.studentLisr;
    }
    return realList;
  } else {
    return this.tabbarData.teacherLisr;
  }
}

以上teacherLisr或studentLisr中的target用于code1.2的判断,mainPagePath参数为傀儡页的路径,pagePath参数为实际页的路径,getTabbarData方法是根据参数判断应该显示哪个target。

/coner/tabpage.js code3.2

import tabbar from '../coner/tabbar.js';
import teacherTabLessonPreparation from '../pages/tabbar/teacher/lesson-preparation/lesson-preparation.js';
import teacherTabLevel from '../pages/tabbar/teacher/level/level.js';
import teacherTabMine from '../pages/tabbar/teacher/mine/mine.js';
import teacherTabTeachLesson from '../pages/tabbar/teacher/teach-lesson/teach-lesson.js';
import studentTabGoClass from '../pages/tabbar/student/go-class/go-class.js';
import studentTabHomeWork from '../pages/tabbar/student/homework/homework.js';
import studentTabMine from '../pages/tabbar/student/mine/mine.js';
import studentTabTest from '../pages/tabbar/student/test/test.js';

/**
 * 获取tab页面数据
 */
function getCurrentAllTabs() {
  let currentAllTabs = [{
    name: '老师备课页面',
    path: '/pages/tabbar/teacher/lesson-preparation/lesson-preparation',
    object: teacherTabLessonPreparation
  }, {
    name: '老师职称页面',
    path: '/pages/tabbar/teacher/level/level',
    object: teacherTabLevel
  }, {
    name: '老师信息页面',
    path: '/pages/tabbar/teacher/mine/mine',
    object: teacherTabMine
  }, {
    name: '老师教课页面',
    path: '/pages/tabbar/teacher/teach-lesson/teach-lesson',
    object: teacherTabTeachLesson
  }, {
    name: '学生上课页面',
    path: '/pages/tabbar/student/go-class/go-class',
    object: studentTabGoClass
  }, {
    name: '学生课后作业页面',
    path: '/pages/tabbar/student/homework/homework',
    object: studentTabHomeWork
  }, {
    name: '学生信息页面',
    path: '/pages/tabbar/student/mine/mine',
    object: studentTabMine
  }, {
    name: '学生考试页面',
    path: '/pages/tabbar/student/test/test',
    object: studentTabTest
  }];
  return currentAllTabs;
}

这里是引入所有实际页的逻辑,注意:currentAllTabs数组中path的参数应该跟code3.1中pagePath相同。

以上关于动态改变pagePath的代码都介绍完了,还有不明白的请直接拉到最下面,github项目双手奉上。

开始埋坑

1、coner.page的写法,参见/coner/page.js文件,里面封装了jumpTo方法,以及各个生命周期执行完成监听listener。

2、datas方法,因为页面缓存之后data中的值就会固定死,打个比方

globalData: {
    state: 0
  }
data: {
    appState: app.globalData.state
}

当前page的appState值在页面初始化时会读取并赋值app.globalData.state,之后appState=0,当app的globalData中的state从0变成了1,这时候appState值会随着当前page的刷新而刷新值吗,答案是不会的!

因为我之前自学过一点VUE,我发现VUE的data是以方法的形式创建,值通过return返回出去,所以我想到通过下面的方式

datas() {
    return {
        appState: app.globalData.state
    };
}

datas是以方法的形式存在,我在coner.page的封装里有处理,在页面每一次onLoad之前我会调用一次datas,起到刷新值的作用。

以上我就是动态修改tabPage这个问题的解决办法,也因为着急上线,没有好好优化,如果各位看官有更好的解决办法或者对我的代码有优化意见的都欢迎留言。如果觉得我的项目对你有帮助,请怒点star,谢谢!最后留下github地址https://github.com/conerjoy/coner-github

---转载请标明出处 本文出自CONER的博客