小程序多用户底部导航栏

前面已经讲了微信小程序底部导航栏的使用,也涉及了简单的多用户登录问题,不过底部标签页最多五个的限制真是太头疼了,这就不得不再使用其他的办法自定义底部导航栏了,大致有两种思路。

自定义底部导航栏的两种思路

  • 将导航作为组件

这里的意思大概就是在每个界面都放一个底部导航栏,不同页面选中自己的标签,当点击其他标签的时候,通过跳转前往其他页面。因为每个页面都有底部导航栏,看起来底部导航栏好像是没变一样,但是通过跳转实现肯定是有一定卡顿的,特别是还会有页面闪烁。

所以这里不推荐使用这种方法,一是有页面闪烁,二是太笨不够优雅。

  • 将页面作为组件

微信官方是支持使用 component 构建页面的,作为一个安卓开发者,完全可以将 component 当成 fragment 来使用,所以这个方法用起来还是很熟悉的。

https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html

这里需要一个主界面用来放底部导航栏以及页面容器,底部导航栏用来切换容器内页面,页面容器里用来存放和底部导航栏标签对应的页面。虽然小程序没有安卓那样的 FramLayout,但是使用 wx:if 以及 display:none 还是可以凑成差不多的效果。

废话不多说,下面就详细介绍第二种思路的具体实现。

先看显示效果

在这里插入图片描述

这里将页面分成了两部分,底部导航栏部分以及中间容器部分

创建主界面

这里我直接使用 index 页面作为主页面,主页面功能就是放底部导航栏和中间页面的,需要在 app.json 中注册,但是不需要放在 pages 里面的第一个。

主界面代码

  • WXML
<!--pages/tabbar/index.wxml-->
<view class="page-container" style="height:{{containerHeight}}rpx">
    <!-- 使用wx:if 不会渲染,每次点击会重走组件生命周期 -->
    <!-- 使用display:none隐藏,仍然会渲染组件,只走一遍生命周期 -->
    <!-- 使用hidden隐藏,和display:none一样,但是受限于块级元素,无法使用 -->

    <!-- 用户 -->
    <block wx:if="{{roleType=='user'}}">
        <home id="id0" indexpage="{{true}}" style="display: {{currentPageIndex==0?'block':'none'}};" />
        <user id="id1" show='{{currentPageIndex==1}}' style="display: {{currentPageIndex==1?'block':'none'}};" />
    </block>

    <!-- 管理员 -->
    <block wx:elif="{{roleType=='manager'}}">
        <manager id="id0" style="display: {{currentPageIndex==0?'block':'none'}};" />
        <log id="id1" style="display: {{currentPageIndex==1?'block':'none'}};" />
    </block>
</view>

<!-- 底部切换菜单 -->
<view class="row-center tab-container" style="height:{{104 + bottomHeight}}rpx;padding-bottom:{{bottomHeight}}rpx">
    <view wx:for="{{tabBar}}" wx:key="index" class="flex1 center-both col"
        style="color:{{index==currentPageIndex?'#0B7BFBFF':'#616161FF'}}" data-index="{{index}}" bindtap="onSelectTap">
        <view class="{{item.iconClass}} icon"></view>
        <view class="text">{{item.text}}</view>
    </view>
</view>

这里和我们看到的页面样式一致,就是两部分内容。

页面容器里面,通过 wx:if 来渲染不同角色页面,只有本角色的页面才会被渲染,其他角色页面不会被渲染;而通过 display: none 来隐藏页面,不会触发页面的重新生成,保证页面的唯一性。通过两者的搭配,还是很合理的。

这里的 indexpage 和 show 是组件的两个属性,后续可能会用到,一个是标识页面在主界面被使用,一个可以使组件感知到页面的切换,即当该界面显示的时候 show 的值会被设置为 true,组件页面通过监听 show 值,可以在显示的时候做一些工作。

底部导航栏没什么说的,就是一个列表横向渲染,选中的标签会被设置成不同颜色。这里用到了 iconfont,实际上把 icon 当成文字来使用,能把文字和 icon 统一设置样式,还是很不错的。

至于 containerHeight 和 bottomHeight 这两个值是用来适配用的,后面详细讲解。主要就是确定了容器高度和底部导航栏的高度,这里底部还有一个安全高度,空出来就好。

  • WXSS
/* pages/tabbar/index.wxss */

.row-center{
  display:flex; 
  flex-direction:row;
  align-items: center;
}

.center-both{
  display:flex; 
  justify-content: center;
  align-items: center;
}

.page-container{
    position: absolute;
    width: 100%;
    box-sizing: border-box;
    overflow: scroll;
}

.tab-container{
    height: 104rpx;
    width: 100%;
    position: fixed;
    bottom: 0rpx;
    border-top: 2rpx solid #E5E5E5FF;

    box-sizing: border-box;
    background: #FFFFFFFF;
    overflow: hidden;
}

.active{
    color: #0B7BFBFF;
}

.unactive{
    color: #616161FF;
}

.icon{
    font-size: 40rpx;
}

.text{
    font-size: 22rpx;
    font-family: PingFangSC-Regular, PingFang SC;
    font-weight: 400;
    line-height: 32rpx;
}

这里稍微注意下 104 这个值是我们底部导航栏的高度,而且是 rpx 值。

  • JSON
"usingComponents": {
    "home": "../home/home",
    "user": "../user/user",

    "manager": "../manager/manager",
    "log": "../log/log",
  },

这里引入中间容器的页面,页面作为组件引入,页面的路径写成了相对路径。

  • Page.js
// pages/index/index.js
var app = getApp()

Page({

    data: {
        //底部高度
        bottomHeight: app.globalData.bottomHeight,
        //中间内容容器高度 = 屏幕高度 - 底部安全高度 - 底部导航栏高度
        containerHeight: app.globalData.screenHeight - app.globalData.bottomHeight - 104,

        currentPageIndex: 0,
        roleType: "",
        tabBar: [],

        userTabBar: [{
                "iconClass": "iconfont home",
                "text": "首页",
            },
            {
                "iconClass": "iconfont user",
                "text": "个人中心",
            },
        ],

        managerTabBar: [{
                "iconClass": "iconfont manager",
                "text": "管理",
            },
            {
                "iconClass": "iconfont log",
                "text": "日志",
            },
        ]
    },

    onLoad: function (options) {
        let tabBar = this.data.tabBar
        if (options.roleType == 'user') {
            tabBar = this.data.userTabBar
        } else if (options.roleType == 'manager') {
            tabBar = this.data.managerTabBar
        }
		
        // 在此加载数据
        this.setData({
            tabBar: tabBar,
            roleType: options.roleType,
        })
    },
    
    // 下拉加载
    onPullDownRefresh() {
        this.selectComponent("#id" + this.data.currentPageIndex).onPullDownRefresh()
	},

    onSelectTap(event) {
        let index = event.currentTarget.dataset.index;
        //选中页面更新
        this.setData({
            currentPageIndex: index,
        })
        
        //标题更新
      	wx.setNavigationBarTitle({
        	title: this.data.tabBar[index].text,
      	})
    },
})

这里和前面简单的多用户登录类似,只是规划了一个专门的主界面来控制导航页面,在页面加载的时候通过传参判断角色类型,加载不同的底部导航数据,并刷新页面,这时候就会把各个标签页面加载进来。切换也比较简单,主要就是更新 currentPageIndex 的值,刷新这个值会使页面切换,同时还要修改的就是标题的内容。

高度适配值

上面用到的 containerHeight 和 bottomHeight 这两个值是在 app 内配置的全局变量,也具体看看如何得到的吧,这里也把其他一些值写出来,后面博客可能会用到。

globalData: {
    //系统信息
    systemInfo: {},
    //1px像素值 对应 rpx
    pixelRatio1: 2,
    //胶囊信息
    menuInfo: {},
    //屏幕高度
    screenHeight: 2000,
    //顶部高度 = 状态栏高度 + 导航栏高度
    topHeight: 0,
    //状态栏高度
    statusHeight: 0,
    //导航栏高度
    naviHeight: 0,
    //底部安全高度
    bottomHeight: 0,
},
      
onLaunch: function () {
    var that = this;

    //获取设备信息
    let systemInfo = wx.getSystemInfoSync()
    that.globalData.systemInfo = systemInfo

    //1rpx 像素值
    let pixelRatio1 = 750 / systemInfo.windowWidth;
    that.globalData.pixelRatio1 = pixelRatio1

    //胶囊信息
    let menu = wx.getMenuButtonBoundingClientRect()
    that.globalData.menuInfo = menu

    //状态栏高度
    let statusHeight = systemInfo.statusBarHeight
    that.globalData.statusHeight = statusHeight * pixelRatio1

    //导航栏高度
    let naviHeight = (menu.top - statusHeight) * 2 + menu.height
    that.globalData.naviHeight = naviHeight * pixelRatio1

    //顶部高度 = 状态栏高度 + 导航栏高度
    that.globalData.topHeight = (statusHeight + naviHeight) * pixelRatio1

    //屏幕高度
    let screenHeight = systemInfo.screenHeight
    that.globalData.screenHeight = screenHeight * pixelRatio1

    //底部高度 = 屏幕高度 - 安全区域bottom
    let bottom = systemInfo.safeArea.bottom
    that.globalData.bottomHeight = (screenHeight - bottom) * pixelRatio1
}

这里导航栏高度要说明一下,具体看下图,用胶囊顶部绝对高度减去状态栏高度得到一个小差值,这个差值的两倍加上胶囊的高度就是导航栏的高度了,应该不难,自定义导航栏的时候还是可以用到的。

在这里插入图片描述


主界面到这就讲的差不多了,下面看看具体的中间组件界面。

  • WXML

自己随便写,和 page 一样,没什么要求。

  • WXSS

这里有问题了,一个是样式隔离问题,一个是选择器问题,选择器问题就是要遵守组件样式要求,具体看下面的官方文档(组件样式部分)。

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html

样式隔离问题,下面另开一节。

@import '/assets/fonts/iconfont.wxss';

...

这里记得重新引入 iconfont,不然无法使用,很奇怪啊,后面设置了可以使用全局 css 啊。。。

  • JSON
{
  "usingComponents": {},
  "component": true
}

这里声明页面是组件,好像不写也可以,还是写上吧,不然得去 app.json 注册页面。

  • JS
Component({

    //解决样式隔离
    options: {
        styleIsolation: 'apply-shared'
    },

    // 属性值,可以和 data 一样在页面绑定
    properties: {
        
        // 在首页
        indexpage: {
            type: Boolean,
            value: false,
        },
        
        show: {
            type: Boolean,
            value: false,
            observer: function (newVal, oldVal) {
				// 监听页面被显示
                if(newVal) {
                    ...
                }
            }
        }
    },

    // 私有数据
    data: {
        
    },

    lifetimes: {
        attached: function () {
            // 在这初始化数据
            
        },
    },

    // 方法要写在里面
    methods: {

   
    },
})

这里其实可以不写成 Component 的,直接用Page,大家可以试试,问题就是写成 Page 的话初始方法到底写在哪呢?写在 onLoad 里面是不会生效的,写在 attached 里面好像 Page 不是 Component 吧,试过了也不会生效,所以除非是静态页面,还是乖乖用 Component 吧。

组件样式隔离

写在页面容器里面的页面,样式会互相冲突,所以写 WXSS 的时候尽量使用不同的命名,不然样式不对了,找起来真的头疼。在小程序组件中,自定义组件的样式默认只受到自定义组件 WXSS 的影响,不会受到全局的影响,下面几种方法可以突破这种限制:

  1. 在 app.wxss 或页面的 wxss 中使用标签名选择器直接指定样式

    //app.wxss
    text{
        color:red;
    }
    
  2. 指定特殊的样式隔离选项 styleIsolation

    Component({
        options: {
            styleIsolation: 'isolated'
        }
    })
    
  3. 启用全局样式选项 addGlobalClass,等价于设置 styleIsolation: apply-shared

    /* 组件 custom-component.js */
    Component({
        options: {
            addGlobalClass: true,
        }
    })
    

styleIsolation 选项从基础库版本 2.6.5 开始支持。它支持以下取值:

  • isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);

  • apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;

  • shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。(这个选项在插件中不可用。)

使用后两者时,请务必注意组件间样式的相互影响。

如果这个 Component 构造器用于构造页面 ,则默认值为 shared ,且还有以下几个额外的样式隔离选项可用:

  • page-isolated 表示在这个页面禁用 app.wxss ,同时,页面的 wxss 不会影响到其他自定义组件;
  • page-apply-shared 表示在这个页面禁用 app.wxss ,同时,页面 wxss 样式不会影响到其他自定义组件,但设为 shared 的自定义组件会影响到页面;
  • page-shared 表示在这个页面禁用 app.wxss ,同时,页面 wxss 样式会影响到其他设为 apply-shared 或 shared 的自定义组件,也会受到设为 shared 的自定义组件的影响。

自动加载数据

在这里有个很傻的问题,就是在首页的自动加载的问题。我们这里将底部导航栏抽象在一起,把功能解耦开来了,如果中间的页面需要传值进去,那怎么办?如果只是在首页使用,那直接在 attached 中用默认值加载就行,但是如果我们的中间容器的页面如果在多个地方使用到的话,比如我在组件页面外包裹一层 page,使传递的参数能被组件页面使用,这时候自动加载就会触发一次无效的加载。

说的很复杂,看看具体代码吧。

在首页使用:

    lifetimes: {
        attached: function () {
            //在首页要加载
            if (this.properties.indexpage) {
				// 请求初始数据
                xx.request(-1)
                ...
            }
        },
    },

在非首页通过传值加载数据:

<!-- 包裹page层wxml -->
<user config="config"></user>
// 包裹page层
Page({
	onLoad: function (options) {
		this.setData({
        	config: options.config
    	})
	},
})

页面属性值监听

    properties: {
        config: {
            type: Number,
            value: -1,
            observer: function (newVal, oldVal) {
                // 切换传入值触发
                if(newVal != -1) {
                   	// 请求初始数据
                    xx.request(newVal)
                	... 
                }
            }
        }
    },

如果不对是否在首页进行判断,那么不在首页时两个请求数据会同时触发,收到结果的顺序会有问题,造成错误,不太优雅。

下拉刷新

可以在 app.json 中设置全局下拉加载,也可以在 index.json 中加入下拉选项使页面能够下拉加载,这里最好把 backgroundTextStyle 设置成黑色,不然下拉的三个点看不到。

	"window": {
		...
		"backgroundTextStyle": "dark",
		"enablePullDownRefresh": true
	},

在 index.js 中重写 onPullDownRefresh,选择对应的组件页面调用它的 onPullDownRefresh 方法

    onPullDownRefresh() {
        this.selectComponent("#id" + this.data.currentPageIndex).onPullDownRefresh()
	},

在组件页面中实际是没有 onPullDownRefresh 方法的,我们可以按需增加这么一个方法,使之生效

onPullDownRefresh() {
    this.setData({
        currentPage: 1,
        isLoadEnd: false,
        isLoadComplete: false,
    })
    xx.request(...)
},
        
// 请求成功失败都应该关闭下拉
requestFinish() {
    wx.stopPullDownRefresh();
}

结语

这样我们这个自定义底部导航栏的功能就挺完善了,下一节我们在这基础上再增加自定义导航栏,并加上沉浸状态栏的效果。

end

完美撒花

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于微信小程序的在线阅读系统是一款功能强大且易于使用的应用程序,专为微信用户设计。该应用提供了丰富的图书资源,包括小说、散文、诗歌等各类文学作品,满足了不同用户的阅读需求。这款在线阅读系统采用了先进的技术架构,确保了用户在浏览和搜索图书时能够获得流畅的体验。同时,它还支持多种语言,方便全球范围内的用户使用。在内容方面,该应用拥有庞大的图书库,涵盖了各个领域的经典作品。此外,开发者还会定期更新图书资源,以便为用户提供最新的阅读材料。用户可以轻松地通过搜索功能找到感兴趣的书籍,并在线阅读或下载到本地进行离线阅读。为了提高用户体验,该应用还提供了个性化推荐功能。根据用户的阅读历史和喜好,系统会智能推荐适合他们的图书。此外,用户还可以关注自己喜欢的作者或主题,获取更多相关作品的信息。在线阅读系统的界面设计简洁大方,操作简便。用户可以通过底部导航栏快速切换不同的功能模块,如书架、我的、设置等。同时,系统还提供了夜间模式和字体调整功能,让用户在各种环境下都能舒适地阅读。总之,基于微信小程序的在线阅读系统是一款非常实用的应用程序,为微信用户提供了便捷的图书阅读体验。无论是喜欢小说、散文还是诗歌的用户,都可以在这里找到自己喜欢的作品。
Himall3.0 操作手册.......................................................................................................................... 1 一、名词解释..................................................................................................................................... 5 1、角色....................................................................................................................................... 5 1.1 平台............................................................................................................................... 5 1.2 商家............................................................................................................................... 6 1.3 自营店........................................................................................................................... 6 1.4 门店............................................................................................................................... 7 1.5 会员............................................................................................................................... 9 1.6 推广员........................................................................................................................... 9 1.7 销售员........................................................................................................................... 9 2. 商品.....................................................................................................................................10 2.1 商品分类.....................................................................................................................10 2.2 商品类型.....................................................................................................................12 2.3 商品品牌.....................................................................................................................12 2.4 商品属性.....................................................................................................

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值