为什么需要封装自定义导航
- 首先如果开发的是微信小程序,那么在安卓环境下导航栏标题是
默认靠左对齐
的(虽然你在微信开发者工具上看到的依旧是居中展示),而在ios环境则是居中展示的。 - 很多时候我们需要对整个项目有一个
主题色或者公共的样式配置
,封装一个公共的自定义导航栏更易于实现与管理。 系统导航栏高度
根据机型会发生变化,大部分是44px,但是有时候也会是40px等高度,如果你需要针对导航栏和状态栏高度对页面做一些定制需求那会显得比较困难。(网上有一些根据胶囊按钮获取导航栏高度的方法实测后发现并不精准。)
当然原生导航也并不是一无是处的。原生导航的体验更好,渲染新页面时,原生导航栏的渲染无需等待新页面dom加载,可以在新页面进入动画开始时就渲染。原生导航还可以避免滚动条通顶,并方便的控制原生下拉刷新。
封装思路
这里我们借助uniapp官方提供的uni-nav-bar进行封装。(vue3 + ts + setup)
-
首先,我们希望可以自动获取每个页面的标题,并不是每次使用自定义组件再去设置,我们置顶
page.json
里面包含了我们所有的页面的标题,所以我们直接导入该文件,使用currentPage获取当前页面的路由,然后匹配page.json文件里面的page或者subPackages路由,需要注意的是subPackages里面的路由需要自己收到拼装。jsconst { title, ...$attrs } = useAttrs() const defaultTitle = '我是默认标题' // 通过page.json文件里面的pages与subPackages数组自动获取标题栏 const getCurrentPageTitle = () => { // 有传入的标题直接使用传入的标题 if (title) { return title } const { route } = currentPage() const page = pagesjson.pages.find((item) => item.path === route) if (page) { return page.style?.navigationBarTitleText || defaultTitle } for (const subPackage of pagesjson.subPackages) { const subPage = subPackage.pages?.find( (item) => route === `${subPackage.root}/${item.path}` ) if (subPage) { return subPage.style?.navigationBarTitleText || defaultTitle } } }
-
然后我们使用
getCurrentInstance
和``createSelectorQuery定义一个方法返回自定义导航栏的高度,便于我们需要的时候使用。jsconst pageInstace = getCurrentInstance() const emits = defineEmits(['getHeight']) onMounted(() => { const query = uni.createSelectorQuery().in(pageInstace?.proxy) query .select('.uniNavBar') .boundingClientRect((data: any) => { emits('getHeight', data.height + 'px') }) .exec() })
-
最后我们还需使用
$attrs
对组件的属性方法进行穿透,这在我们对组件进行二次封装的时候应该都是必须要做的操作。
完整代码
js
<template>
<uni-nav-bar
class="uniNavBar"
v-bind="$attrs"
:border="border"
:statusBar="statusBar"
:fixed="fixed"
:leftIcon="leftIcon"
:title="getCurrentPageTitle()"
@clickLeft="clickLeft"
></uni-nav-bar>
</template>
<script lang="ts" setup>
import { onMounted, getCurrentInstance } from 'vue'
import { useAttrs } from 'vue'
import pagesjson from '@/pages.json'
import { currentPage } from '@/utils/tools'
interface Props {
border?: boolean
statusBar?: boolean
fixed?: boolean
leftIcon?: string
}
const props = withDefaults(defineProps<Props>(), {
border: false,
statusBar: true,
fixed: true,
leftIcon: 'left',
})
const { title, ...$attrs } = useAttrs()
const pageInstace = getCurrentInstance()
const emits = defineEmits(['getHeight'])
onMounted(() => {
const query = uni.createSelectorQuery().in(pageInstace?.proxy)
query
.select('.uniNavBar')
.boundingClientRect((data: any) => {
emits('getHeight', data.height + 'px')
})
.exec()
})
const defaultTitle = '我是默认标题'
// 通过page.json文件里面的pages与subPackages数组自动获取标题栏
const getCurrentPageTitle = () => {
// 有传入的标题直接使用传入的标题
if (title) {
return title
}
const { route } = currentPage()
const page = pagesjson.pages.find((item) => item.path === route)
if (page) {
return page.style?.navigationBarTitleText || defaultTitle
}
for (const subPackage of pagesjson.subPackages) {
const subPage = subPackage.pages?.find(
(item) => route === `${subPackage.root}/${item.path}`
)
if (subPage) {
return subPage.style?.navigationBarTitleText || defaultTitle
}
}
}
// navbar返回按钮事件
const clickLeft = () => {
uni.navigateBack()
}
</script>