微信小程序自带tabbar样式固定,无法满足业务上一些个性化诉求如tabbar中间位置凸起显示,这个时候就需要自定义tabbar出场了。自定义tabbar其实也有很多方法,比如引入第三方包就可以相对完美且高效的“解决问题”,那么手写怎么实现呢?以下为手动实现自定义tabbar的一些笔记总结(仍有诸多不足,仅供学习及笔记记录,如有问题或建议,欢迎指正交流),给有需要的小伙伴参考:
项目准备
vue3+typescript+vite+微信小程序+pinia
基础配置
- cd到工程目录
- npx degit dcloudio/uni-preset-vue#vite-ts [你的项目名称],此处注意创建的是uni-app默认的vite+typescript模板项目,需要其他模板,可以访问官网查询
- 配置mainfest.json,找到
mp-weixin项,配置所需的关键信息,示例:
"mp-weixin" : {
"appid" : "你的appid",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
- npm i初始化项目依赖(喜欢用yarn可以自由切换)
- 项目运行
npm run dev:mp-weixin
- 可视化调测:打开
微信小程序开发者工具,选择导入项目,选择工程目录下的dist/dev/mp-weixin导入即可查看微信小程序效果,不赘述。
集成pinia
- 安装
npm i pinia -S
- 挂载
import { createSSRApp } from "vue";
import { createPinia } from 'pinia'
import App from "./App.vue";
export function createApp() {
const app = createSSRApp(App)
.use(createPinia())
return {
app,
};
}
拓展配置
个人开发习惯,不需要集成这些的可以跳过
集成sass
- 注意此处有坑:安装sass-loader,注意指定版本10,否则可能会出现vue与sass的兼容导致报错
npm i sass -D
npm i sass-loader@10 -D
集成unplugin-auto-import
作用:配置后可以自动导入
ref、reactive...这些,提升开发效率
- 安装
npm i unplugin-auto-import -D
- 找到
tsconfig.json文件,在include选项中增加auto-imports.d.ts类型声明文件,示例
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"./auto-imports.d.ts",
],
- 找到vite.config.ts,配置自动导入配置项
AutoImport({imports: ['vue']})
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from 'unplugin-auto-import/vite'
const path = require('path');
// https://vitejs.dev/config/
export default defineConfig({
plugins: [uni(), AutoImport({
imports: ['vue']
})],
});
设置@路径引用
- 找到vite.config.ts配置@路径
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from 'unplugin-auto-import/vite'
const path = require('path');
// https://vitejs.dev/config/
export default defineConfig({
plugins: [uni(), AutoImport({
imports: ['vue']
})],
resolve: {
// 配置路径别名
alias: {
'@': path.resolve(__dirname, './src')
}
}
});
全局样式配置
- 项目根目录创建wx_theme.json
{
"light": {
"navBgColor": "#0165FF",
"navTxtStyle": "white",
"pullBgColor":"#FFFFFF"
},
"dark": {
"navBgColor": "#292929",
"navTxtStyle": "white",
"pullBgColor":"#FFFFFF"
}
}
mainfest.json找到mp-weixin,挂载wx_theme.json
"mp-weixin" : {
/* 小程序特有相关 */
"appid" : "wx5aac7e8c80869c9a",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"darkmode" : false,
"themeLocation":"wx_theme.json"
},
- 在package.json中使用,如:
"globalStyle": {
"navigationBarTextStyle": "@navTxtStyle",
"navigationBarTitleText": "移山·获取答案的快乐",
"navigationBarBackgroundColor": "@navBgColor",
"backgroundColor": "@pullBgColor",
"app-plus": {
"background": "#0165FF"
}
},
过程演示(自定义中间凸起的tabbar并应用)
定义自定义tabbar组件
- 在src\components新建custom-tabbar(也即src\components\custom-tabbar),在custom-tabbar文件夹下新建index.vue
uni-app自带的easycom模式,components符合如下规范的会自动导入(无需手工引入):
- 组件命名必须是小写字母,使用短横线连接单词。例如:
my-component- 以
组件名/组件名.vue形式命名显然此处src\components\custom-tabbar\index.vue无法自动导入,后文会交代如何导入或者挂载,在这里先买个彩蛋
<template>
<view class="tabbar">
<view
class="tabbar-item"
v-for="tabbarItem in tabbarStore.tabbarList"
:key="tabbarItem.id"
@click="changeTab(tabbarItem.id)"
:class="
tabbarStore.currentTabId === 2 &&
tabbarStore.currentTabId === tabbarItem.id
? 'center-bottom'
: ''
"
:style="
tabbarStore.currentTabId === 2 &&
tabbarStore.currentTabId === tabbarItem.id
? { borderBottom: '1px solid #0165FF' }
: ''
"
>
<block v-if="tabbarItem.id !== 2">
<image
:src="
tabbarItem.id === tabbarStore.currentTabId
? tabbarItem.selectedIconPath
: tabbarItem.iconPath
"
class="sides"
></image>
<text
class="tabbar-item-text"
:style="{
color:
tabbarItem.id === tabbarStore.currentTabId
? props.activeColor
: ''
}"
>{{
tabbarItem.id === tabbarStore.currentTabId
? selectedText
: tabbarItem.text
}}</text
>
</block>
<block v-else>
<view class="center-top"></view>
<image :src="tabbarItem.selectedIconPath" class="center"></image>
</block>
</view>
</view>
</template>
<script setup lang="ts">
import useTabbarStore from '@/store/tabbarStore'
const tabbarStore = useTabbarStore()
tabbarStore._loadTabbar()
const props = withDefaults(
defineProps<{
defaultColor: string
activeColor: string
currentId: number
}>(),
{
defaultColor: () => '',
activeColor: () => '#0165FF',
currentId: () => -1
}
)
const selectedText = ref<string>('•')
const changeTab = (id: number) => {
const prevTabId = tabbarStore.currentTabId
const url = tabbarStore.changeTabbar(id)
// uni.switchTab({ url })
if (prevTabId === id) {
console.log('不需要切换但是需要刷新')
}
}
if (props.currentId * 1 >= 0) {
changeTab(props.currentId * 1)
}
</script>
<style lang="scss" scoped>
$border-color: #f2f2f2;
$bgc-color: #ffffff;
$selected-main-color: #0165ff;
$global-height: 50px;
.tabbar {
position: fixed;
margin-top: 6rpx;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
width: 100%;
height: $global-height;
border-top: 1px solid $border-color;
&-item {
flex: 1;
box-sizing: border-box;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.sides {
width: 24px;
height: 24px;
}
&-text {
font-size: 10px;
}
}
}
.center {
width: 48px;
height: ($global-height - 2);
float: right;
position: absolute;
bottom: 7px;
}
.center-top {
float: right;
position: absolute;
bottom: 30px;
box-sizing: border-box;
width: 70px;
height: ($global-height + 10);
background-color: $bgc-color;
border-radius: 50%;
border-top: 1px solid $border-color;
margin-bottom: -25px;
}
.center-bottom {
border-bottom: 2px solid $selected-main-color;
}
</style>
定义tabbarList状态管理
- 根目录新建store文件夹,新建tabbarStore.ts,对外暴露state、actions等(路径:
src\store\tabbarStore.ts)
import { defineStore } from 'pinia'
import type { TabbarItem, TabbrList } from '@/typings'
import tabbarRawData from '@/data/tabbar'
import useMate from '@/hooks/useMate'
const useTabbarStore = defineStore('tabbarStore', {
state() {
const tabbarList: TabbrList = []
const currentTabId: number = Number(uni.getStorageSync('currentTabId')) || 0
const _isloaded: boolean = false
return {
tabbarList,
currentTabId,
_isloaded
}
},
actions: {
_loadTabbar() {
if (this._isloaded) return
console.log('_loadTabbar is working')
this.tabbarList = tabbarRawData
this._isloaded = true
uni.setStorageSync('tabbarList', JSON.stringify(this.tabbarList))
},
changeTabbar(id: number): string {
const foundedItem = useMate('id', id, this.tabbarList)
this.currentTabId = foundedItem.id
uni.setStorageSync('currentTabId', this.currentTabId + '')
return `/${foundedItem.pagePath}`
}
},
})
export default useTabbarStore
自定义hook对数组find查询进行简单封装
- 根目录新建hooks目录,新增useMate.ts文件(路径:
src\hooks\useMate.ts)
const mate = <K extends keyof T, T>(key: K, val: T[keyof T], rawData: T[]): T => {
return rawData.find(item => {
return item[key] === val
}) as T
}
export default mate
自定义tabbar自动导入或挂载
根据需要选择合适的挂载方式,建议二者选其一,笔者选择的是全局挂载
自动导入配置
- 找到
pages.json,easycom新增如下配置
"easycom": {
"autoscan": true,
"custom": {
"^custom-(.*)": "@/components/custom-$1/index.vue"
}
},
全局挂载
- 找到
main.ts,设置自定义tabbar全局挂载
import { createSSRApp } from "vue";
import { createPinia } from 'pinia'
import tabBar from '@/components/custom-tabbar/index.vue'
import App from "./App.vue";
export function createApp() {
const app = createSSRApp(App)
.use(createPinia())
.component('customTabBar', tabBar)
return {
app,
};
}
pages页面简单定义
因为主要作测试用,页面都非常简单(寒颤),敬请见谅
- 太过简单,就不一一列举了,总之有5个页面,分别为
首页、消息、添加、喜欢、我的
<!-- src\pages\home\index.vue -->
<template>
<view>我是首页</view>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
<!-- src\pages\message\index.vue -->
<template>
<view>我是消息页面</view>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
<!-- src\pages\add\index.vue -->
<template>
<view>我是添加页面</view>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
<!-- src\pages\follow\index.vue -->
<template>
<view>我是关注页面</view>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
<!-- src\pages\my\index.vue -->
<template>
<view>我是我的页面</view>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
修改pages.json生效自定义tabbar组件
"custom": true,iconPath、selectedIconPath根据需要自行设置
{
"pages": [
{
"path": "pages/home/index",
"style": {
"enablePullDownRefresh": false
}
},
{
"path": "pages/message/index",
"style": {
"enablePullDownRefresh": false
}
},
{
"path": "pages/add/index",
"style": {
"enablePullDownRefresh": false
}
},
{
"path": "pages/follow/index",
"style": {
"enablePullDownRefresh": false
}
},
{
"path": "pages/my/index",
"style": {
"enablePullDownRefresh": false
}
}
],
"tabBar": {
"custom": true,
"selectedColor": "#0165FF",
"list": [
{
"pagePath": "pages/home/index",
"text": "首页",
"iconPath": "static/tabIcons/shouye.png",
"selectedIconPath": "static/tabIcons/shouye-selected.png"
},
{
"pagePath": "pages/message/index",
"text": "消息",
"iconPath": "static/tabIcons/tongzhi.png",
"selectedIconPath": "static/tabIcons/tongzhi-selected.png"
},
{
"pagePath": "pages/add/index",
"text": "",
"iconPath": "static/tabIcons/add.png",
"selectedIconPath": "static/tabIcons/add.png"
},
{
"pagePath": "pages/follow/index",
"text": "关注",
"iconPath": "static/tabIcons/xihuan1.png",
"selectedIconPath": "static/tabIcons/xihuan1-selected.png"
},
{
"pagePath": "pages/my/index",
"text": "我的",
"iconPath": "static/tabIcons/wode.png",
"selectedIconPath": "static/tabIcons/wode-selected.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "@navTxtStyle",
"navigationBarTitleText": "移山·获取答案的快乐",
"navigationBarBackgroundColor": "@navBgColor",
"backgroundColor": "@pullBgColor",
"app-plus": {
"background": "#0165FF"
}
},
"lazyCodeLoading": "requiredComponents"
}
pages目录下新建tab聚合页面
由于uni-app暂时不支持component :is动态绑定组件,暂时只能使用v-if这种非常不优雅的方式实现,如uni-app支持动态组件了,还请告知)
- pages目录下创建组合页面src\pages\combination-page\index.vue
<template>
<Home v-if="tabbarStore.currentTabId === 0"></Home>
<Message v-if="tabbarStore.currentTabId === 1"></Message>
<Add v-if="tabbarStore.currentTabId === 2"></Add>
<Follow v-if="tabbarStore.currentTabId === 3"></Follow>
<My v-if="tabbarStore.currentTabId === 4"></My>
<customTabBar :currentId="0"></customTabBar>
</template>
<script setup lang="ts">
import useTabbarStore from '@/store/tabbarStore'
const tabbarStore = useTabbarStore()
import Home from '@/pages/home/index.vue'
import Message from '@/pages/message/index.vue'
import Add from '@/pages/add/index.vue'
import Follow from '@/pages/follow/index.vue'
import My from '@/pages/my/index.vue'
</script>
<style lang="scss" scoped></style>
- 在pages.json中注册src\pages\combination-page\index.vue,强烈建议设置在第一个索引位置,因为会默认展示
"pages": [
{
"path": "pages/combination-page/index",
"style": {
"enablePullDownRefresh": false
}
},...]
效果展示
以上就是手动实现自定义tabbar的一些笔记总结,仍有诸多不足,仅供学习及笔记记录,如有问题或建议,欢迎指正交流,后续有迭代优化会持续更新,敬请期待~