自定义组件
近期开发移动业务多一点,都是使用 uni-app
开发;开发中一些需要注意的地方和开发技巧记录下来,以便后续能有个地方查证。
自定义头部导航栏
自定义头部应该是最常见的一个需求,以前小程序都不能修改头部或者要么只能全局自定义,后来才逐渐放开可以单个页面自定义头部。
隐藏自带的导航栏
需要自定义头部,可以在项目根目录 pages.json
路由上设置 navigationStyle: custom
。
// pages.json
{
"path": "pages/test/test",
"style": {
"navigationStyle": "custom"
}
}
如果要全局隐藏
"globalStyle": {
"navigationStyle": "custom"
}
自定义导航
自定义头部需要计算导航栏高度
<!-- page-head -->
<template>
<view class="nav-box" :style="{height: navBarHeight + 'px'}">
<view class="capsule-box" :style="{height: menuHeight + 'px', 'line-height': menuHeight + 'px', bottom: menuBottom + 'px'}">
<view class="nav-title">
<view v-if="!!title" class="nav">{{title}}</view>
</view>
</view>
</view>
</template>
导航栏高度又由几个高度计算而来:状态栏到胶囊的间距(胶囊上坐标位置-状态栏高度) * 2 + 胶囊高度 + 状态栏高度
。
状态栏的高度:uni.getSystemInfoSync
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
// 状态栏
systemInfo.statusBarHeight
而胶囊信息可以通过:uni.getMenuButtonBoundingClientRect 获取
/**
* 获取微信小程序菜单栏(胶囊)信息
* 菜单按键宽度:width
* 菜单按键高度:height
* 菜单按键上边界坐标:top
* 菜单按键右边界坐标:right
* 菜单按键下边界坐标:bottom
* 菜单按键左边界坐标:left
*/
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
最后设置高度
props: {
title: String
},
data() {
return {
navBarHeight: 0,
menuBottom: 0,
menuHeight: 0
};
},
mounted() {
this.$nextTick(() => {
this.calcNavBarInfo();
})
},
methods: {
// 计算导航栏高度
calcNavBarInfo() {
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
const menuButtonInfo = this.getMenuButtonInfo(systemInfo);
// 胶囊按钮位置信息
// 导航栏高度 = 状态栏到胶囊的间距(胶囊上坐标位置-状态栏高度) * 2 + 胶囊高度 + 状态栏高度
const navBarHeight = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height + systemInfo.statusBarHeight;
// 状态栏和菜单按钮(标题栏)之间的间距
// 等同于菜单按钮(标题栏)到正文之间的间距(胶囊上坐标位置-状态栏高度)
const menuBottom = menuButtonInfo.top - systemInfo.statusBarHeight;
// 菜单按钮栏(标题栏)的高度
const menuHeight = menuButtonInfo.height;
this.navBarHeight = navBarHeight;
this.menuBottom = menuBottom;
this.menuHeight = menuHeight;
this.$emit('getnavinfo', {
navBarHeight,
menuBottom,
menuHeight,
systemInfo
});
},
// 判断是否为 IOS
checkSystem(res) {
const { system } = res;
const _sys = system.toLocaleUpperCase();
return _sys.includes('IOS');
},
getMenuButtonInfo(systemInfo) {
// fix: IOS 获取胶囊信息存在0值情况,由于 IOS 胶囊信息固定所以直接固定值
let menuButtonInfo = {
width: 87,
height: 32,
left: systemInfo.screenWidth - 7 - 87,
right: systemInfo.screenWidth - 7,
top: systemInfo.statusBarHeight + 4,
bottom: systemInfo.statusBarHeight + 4 + 32
};
const isIOS = this.checkSystem(systemInfo);
if (isIOS) {
// ios设备的胶囊按钮都是固定的
return menuButtonInfo;
} else {
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
}
return menuButtonInfo;
}
}
实现效果:
自定义内容区
当自定义头部导航栏后,如果页面存在滚动条时,会出现滚动条在导航栏上;对于 UI
是不能忍的,没办法随叫 UI
是个妹子呢😉
其实也简单,只要使用 scroll-view
组件包裹内容区就行,麻烦点的是需要设置 scroll-view
的高度。而它的高度其实就是
可视区域高度 - 导航栏高度
- 定义组件 page-layout
<template>
<view class="page-layout">
<page-head class="nav-ref" v-if="!!title" :title="title" @getnavinfo="getNavInfo"></page-head>
<scroll-view class="page-container" enable-back-to-top scroll-y
:style="{'padding-top': !!title ? containerStyle: '0', 'height': containerHeight}">
<slot></slot>
</scroll-view>
</view>
</template>
需要在 page-head
组件中发送出导航栏高度信息
calcNavBarInfo() {
// ...
this.$emit('getnavinfo', {
navBarHeight,
menuBottom,
menuHeight,
systemInfo
});
}
计算当前内容高度
const getNavInfo = (options: any) => {
const {
navBarHeight,
systemInfo
} = options;
containerStyle.value = (navBarHeight) + 'px'
containerHeight.value = (systemInfo.screenHeight - navBarHeight) + 'px'
}
自定义底部导航
底部导航也是 UI
妹子最喜欢弄得花里胡巧一个地方。幸好微信放开了底部导航的自定义,不然又要被妹子嫌弃😒
- 先配置导航
先要在 pages.json
中配置导航,注意导航最少2个;在配置 "custom": true
"tabBar": {
"custom": true,
"list": [
{
"pagePath": "pages/index/index"
},
{
"pagePath": "pages/user/user"
}
]
},
- 编写底部导航
自定义底部导航,需要按照官方文档进行,在项目根目录创建 custom-tab-bar
目录
custom-tab-bar/index.js
custom-tab-bar/index.json
custom-tab-bar/index.wxml
custom-tab-bar/index.wxss
注意:使用 uni-app
开发也需要同上述步骤一样,创建相关文件,不能使用 .vue 文件开发
导航的布局大同小异
<view class="tab-bar-box">
<view wx:for="{{list}}" wx:key="id" class="tab-item" data-id="{{item.id}}" bindtap="onChange">
<view class="icon">
<image
src="{{selected == item.id ? item.selectedIconPath : item.iconPath}}"
mode="scaleToFill"
alt="user"
/>
</view>
</view>
Component({
data: {
selected: 0,
list: [
{
id: 1,
iconPath: ICON_PREX + 'home.svg',
selectedIconPath: ICON_PREX + 'homeed.svg',
text: '首页',
path: '/pages/index/index',
},
{
id: 2,
iconPath: ICON_PREX + 'user.svg',
selectedIconPath: ICON_PREX + 'usered.svg',
text: '用户',
path: '/pages/user/user',
}
]
},
methods: {
// 标签切换
onChange: function (e) {
const { dataset: { id } } = e.currentTarget;
const row = this.data.list.find(v => v.id === id);
wx.switchTab({
url: row.path,
})
}
}
})
组件编写完成,需要注意的是每个 tabbar
模块处理。需要在 tabbar 模块各自处理
export function setTabBarIndex(index: number) {
const curPages = getCurrentPages()[0] as { getTabBar: Function }; // 获取当前页面实例
if (typeof curPages.getTabBar === 'function') {
curPages.getTabBar().setData({
selected: index
});
}
}
// index.vue
onMounted(() => {
setTabBarIndex(0);
})
上面是 Vue3 的写法,如果是 Vue2,则需要在 onShow
处理
// index.vue
onShow() {
this.$mp.page.getTabBar().setData({
selected: 0
});
}
效果如下:
动态去除 page-head
只针对 H5 平台
H5
平台的时候,微信浏览器需要去掉头部导航,但在手机浏览器又需要存在头部导航,这时只能动态设置 page-head。
- 先隐藏
先对 page-head
隐藏
/* #ifdef H5 */
uni-page-head {
display: none;
}
/* #endif */
- 判断平台与环境
Vue.mixin({
onShow() {
// #ifdef H5
if (isWeixin()) {
this.navTitle();
} else {
this.navTitle('block');
}
// #endif
},
methods: {
navTitle(show = 'none') {
const navTitle = document.getElementsByTagName('uni-page-head');
if (navTitle) {
navTitle.forEach((nav) => {
nav.style.display = show;
})
}
if (show === 'none') {
// sticky 全局设置,在微信环境下置顶
document.documentElement.style.setProperty('--sticky-top', '0');
document.documentElement.style.setProperty('--window-top', '0');
}
}
}
});
这里是 Vue 2 写法, Vue3 的写法也大致类似
export function createApp() {
const app = createSSRApp(App)
app.mixin({
// ...
})
return {
app
}
}
总结
对于前端开发来说,只要是熟悉 Vue 技术栈的话,完全可以快速上手实现业务需求;不过不得不说它的文档是普遍被吐槽的最多的,自己也是被误导多次。但是选用 uni-app
开发其实对于小公司或者是快速实现 mvp
是最划算最便捷。自己也用它自己开发了一个 IOS APP 和小程序欢迎体验使用与反馈。