初始化仓库
This commit is contained in:
32
src/tabbar/README.md
Normal file
32
src/tabbar/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# tabbar 说明
|
||||
|
||||
## tabbar 4种策略
|
||||
|
||||
`tabbar` 分为 `4 种` 情况:
|
||||
|
||||
- 0 `无 tabbar`,只有一个页面入口,底部无 `tabbar` 显示;常用语临时活动页。
|
||||
- 1 `原生 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。
|
||||
- 优势:原生自带的 tabbar,最先渲染,有缓存。
|
||||
- 劣势:只能使用 2 组图片来切换选中和非选中状态,修改颜色只能重新换图片(或者用 iconfont)。
|
||||
- 2 `有缓存自定义 tabbar`,使用 `switchTab` 切换 tabbar,`tabbar` 页面有缓存。使用了第三方 UI 库的 `tabbar` 组件,并隐藏了原生 `tabbar` 的显示。
|
||||
- 优势:可以随意配置自己想要的 `svg icon`,切换字体颜色方便。有缓存。可以实现各种花里胡哨的动效等。
|
||||
- 劣势:首次点击 tababr 会闪烁。
|
||||
- 3 `无缓存自定义 tabbar`,使用 `navigateTo` 切换 `tabbar`,`tabbar` 页面无缓存。使用了第三方 UI 库的 `tabbar` 组件。
|
||||
- 优势:可以随意配置自己想要的 svg icon,切换字体颜色方便。可以实现各种花里胡哨的动效等。
|
||||
- 劣势:首次点击 `tababr` 会闪烁,无缓存。
|
||||
|
||||
|
||||
> 注意:花里胡哨的效果需要自己实现,本模版不提供。
|
||||
|
||||
## tabbar 配置说明
|
||||
|
||||
- 如果使用的是原生tabbar, 则每个 `item` 需要配置 `path`、`text`、`iconPath`、`selectedIconPath` 等属性。
|
||||
- 如果使用的是自定义tabbar, 则每个 `item` 需要配置 `path`、`text`、`icon` 、`iconType` 等属性(如果是local还需要配置2种图片)。
|
||||
|
||||
## 接口拿到tabbar列表怎么处理?
|
||||
|
||||
首先,接口的配置需要跟原生tabbar的 `path` 对应上。
|
||||
|
||||
然后,可以直接在 `index.vue` 文件请求接口拿到 `tabbarList`,然后赋值给 `tabbarList` 即可。
|
||||
|
||||
最后,如果用的是 `unocss` 图标,还需要在 `uno.config.ts` 的 `safelist` 中添加图标名称。
|
||||
139
src/tabbar/config.ts
Normal file
139
src/tabbar/config.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
|
||||
|
||||
type NativeTabBarItem = TabBar['list'][0]
|
||||
|
||||
type CustomTabBarItem = (Pick<NativeTabBarItem, 'text' | 'pagePath'> & {
|
||||
iconType: 'uniUi' | 'uiLib' | 'unocss' | 'iconfont' | 'image' // 不建议用 image 模式,需要配置2张图
|
||||
icon: any // 其实是 string 类型,这里是为了避免 ts 报错 (tabbar/index.vue 里面 uni-icons 那行)
|
||||
activeIcon?: string // 只有在 image 模式下才需要,传递的是高亮的图片(PS: 不建议用 image 模式)
|
||||
badge?: number | 'dot' // badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改)
|
||||
isBulge?: boolean // 是否是中间的鼓包tabbarItem
|
||||
})
|
||||
|
||||
/**
|
||||
* tabbar 选择的策略,更详细的介绍见 tabbar.md 文件
|
||||
* 0: 'NO_TABBAR' `无 tabbar`
|
||||
* 1: 'NATIVE_TABBAR' `完全原生 tabbar`
|
||||
* 2: 'CUSTOM_TABBAR_WITH_CACHE' `有缓存自定义 tabbar`
|
||||
* 3: 'CUSTOM_TABBAR_WITHOUT_CACHE' `无缓存自定义 tabbar`
|
||||
*
|
||||
* 温馨提示:本文件的任何代码更改了之后,都需要重新运行,否则 pages.json 不会更新导致配置不生效
|
||||
*/
|
||||
export const TABBAR_MAP = {
|
||||
NO_TABBAR: 0,
|
||||
NATIVE_TABBAR: 1,
|
||||
CUSTOM_TABBAR_WITH_CACHE: 2,
|
||||
CUSTOM_TABBAR_WITHOUT_CACHE: 3,
|
||||
}
|
||||
|
||||
// TODO: 1/3. 通过这里切换使用tabbar的策略
|
||||
export const selectedTabbarStrategy = TABBAR_MAP.NATIVE_TABBAR
|
||||
|
||||
// TODO: 2/3. 更新下面的 tabbar 配置
|
||||
// 如果是使用 NO_TABBAR(0),nativeTabbarList 和 customTabbarList 都不生效(里面的配置不用管)
|
||||
// 如果是使用 NATIVE_TABBAR(1),customTabbarList 不生效(里面的配置不用管)
|
||||
// 如果是使用 CUSTOM_TABBAR(2,3),nativeTabbarList 不生效(里面的配置不用管)
|
||||
// pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!!
|
||||
export const nativeTabbarList: NativeTabBarItem[] = [
|
||||
{
|
||||
iconPath: 'static/tabbar/home.png',
|
||||
selectedIconPath: 'static/tabbar/home_s.png',
|
||||
pagePath: 'pages/index/index',
|
||||
text: '首页',
|
||||
},
|
||||
{
|
||||
iconPath: 'static/tabbar/reserve.png',
|
||||
selectedIconPath: 'static/tabbar/reserve_s.png',
|
||||
pagePath: 'pages/reserve/reserve',
|
||||
text: '预约',
|
||||
},
|
||||
{
|
||||
iconPath: 'static/tabbar/my.png',
|
||||
selectedIconPath: 'static/tabbar/my_s.png',
|
||||
pagePath: 'pages/my/my',
|
||||
text: '我的',
|
||||
},
|
||||
// {
|
||||
// iconPath: 'static/tabbar/example.png',
|
||||
// selectedIconPath: 'static/tabbar/exampleHL.png',
|
||||
// pagePath: 'pages/about/about',
|
||||
// text: '关于',
|
||||
// },
|
||||
]
|
||||
|
||||
// pagePath 是 nativeTabbarList 和 customTabbarList 的关联点,如果没有对应上,会有问题!!
|
||||
// 如果希望通过接口调用 customTabbarList,可以在 tabbar/index.vue 文件里面调用接口
|
||||
// 本文件因为需要提前编译生成 pages.json, 接口拦截还不生效,无法正常调用接口
|
||||
export const customTabbarList: CustomTabBarItem[] = [
|
||||
{
|
||||
// text 和 pagePath 可以自己直接写,也可以通过索引从 nativeTabbarList 中获取
|
||||
text: '首页',
|
||||
pagePath: 'pages/index/index', // pagePath 是两者的关联点
|
||||
// 本框架内置了 uniapp 官方UI库 (uni-ui)的图标库
|
||||
// 使用方式如:<uni-icons type="home" size="30"/>
|
||||
// 图标列表地址:https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html
|
||||
iconType: 'uniUi',
|
||||
icon: 'home',
|
||||
// badge: 'dot',
|
||||
},
|
||||
{
|
||||
text: nativeTabbarList[1].text,
|
||||
pagePath: nativeTabbarList[1].pagePath, // pagePath 是两者的关联点
|
||||
// 注意 unocss 图标需要如下处理:(二选一)
|
||||
// 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
|
||||
// 2)配置到 unocss.config.ts 的 safelist 中
|
||||
iconType: 'unocss',
|
||||
icon: 'i-carbon-code',
|
||||
// badge: 10,
|
||||
},
|
||||
|
||||
// {
|
||||
// pagePath: 'pages/mine/index',
|
||||
// text: '我的',
|
||||
// // 注意 iconfont 图标需要额外加上 'iconfont',如下
|
||||
// iconType: 'iconfont',
|
||||
// icon: 'iconfont icon-my',
|
||||
// },
|
||||
// {
|
||||
// pagePath: 'pages/index/index',
|
||||
// text: '首页',
|
||||
// // 使用 ‘image’时,需要配置 icon + iconActive 2张图片(不推荐)
|
||||
// // 既然已经用了自定义tabbar了,就不建议用图片了,所以不推荐
|
||||
// iconType: 'image',
|
||||
// icon: '/static/tabbar/home.png',
|
||||
// iconActive: '/static/tabbar/homeHL.png',
|
||||
// },
|
||||
]
|
||||
|
||||
// NATIVE_TABBAR(1) 和 CUSTOM_TABBAR_WITH_CACHE(2) 时,需要tabbar缓存
|
||||
/** 是否启用 tabbar 缓存 */
|
||||
export const tabbarCacheEnable
|
||||
= [TABBAR_MAP.NATIVE_TABBAR, TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE].includes(selectedTabbarStrategy)
|
||||
|
||||
// CUSTOM_TABBAR_WITH_CACHE(2) 和 CUSTOM_TABBAR_WITHOUT_CACHE(3) 时,启用自定义tabbar
|
||||
/** 是否启用自定义 tabbar */
|
||||
export const customTabbarEnable
|
||||
= [TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE, TABBAR_MAP.CUSTOM_TABBAR_WITHOUT_CACHE].includes(selectedTabbarStrategy)
|
||||
|
||||
// CUSTOM_TABBAR_WITH_CACHE(2)时,需要隐藏原生tabbar
|
||||
/** 是否需要隐藏原生 tabbar */
|
||||
export const nativeTabbarNeedHide = selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE
|
||||
|
||||
const _tabbar: TabBar = {
|
||||
// 只有微信小程序支持 custom。App 和 H5 不生效
|
||||
custom: selectedTabbarStrategy === TABBAR_MAP.CUSTOM_TABBAR_WITH_CACHE,
|
||||
color: '#999999',
|
||||
selectedColor: '#018d71',
|
||||
backgroundColor: '#FFF',
|
||||
borderStyle: 'black',
|
||||
height: '50px',
|
||||
fontSize: '10px',
|
||||
iconWidth: '24px',
|
||||
spacing: '3px',
|
||||
list: nativeTabbarList as unknown as TabBar['list'],
|
||||
}
|
||||
|
||||
export const tabbarList = nativeTabbarList
|
||||
|
||||
// 0和1 需要显示底部的tabbar的各种配置,以利用缓存
|
||||
export const tabBar = tabbarCacheEnable ? _tabbar : undefined
|
||||
165
src/tabbar/index.vue
Normal file
165
src/tabbar/index.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
// 'i-carbon-code',
|
||||
import { customTabbarList as _tabBarList, customTabbarEnable, nativeTabbarNeedHide, tabbarCacheEnable } from './config'
|
||||
import { tabbarStore } from './store'
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 将自定义节点设置成虚拟的(去掉自定义组件包裹层),更加接近Vue组件的表现,能更好的使用flex属性
|
||||
defineOptions({
|
||||
virtualHost: true,
|
||||
})
|
||||
// #endif
|
||||
|
||||
// TODO 1/2: 中间的鼓包tabbarItem的开关
|
||||
const BULGE_ENABLE = false
|
||||
function handleClickBulge() {
|
||||
uni.showToast({
|
||||
title: '点击了中间的鼓包tabbarItem',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
/** tabbarList 里面的 path 从 pages.config.ts 得到 */
|
||||
const tabbarList = _tabBarList.map(item => ({ ...item, path: `/${item.pagePath}` }))
|
||||
if (BULGE_ENABLE) {
|
||||
if (tabbarList.length % 2 === 1) {
|
||||
console.error('tabbar 数量必须是偶数,否则样式很奇怪!!')
|
||||
}
|
||||
tabbarList.splice(tabbarList.length / 2, 0, {
|
||||
isBulge: true,
|
||||
} as any)
|
||||
}
|
||||
function handleClick(index: number) {
|
||||
// 点击原来的不做操作
|
||||
if (index === tabbarStore.curIdx) {
|
||||
return
|
||||
}
|
||||
if (tabbarList[index].isBulge) {
|
||||
handleClickBulge()
|
||||
return
|
||||
}
|
||||
const url = tabbarList[index].path
|
||||
tabbarStore.setCurIdx(index)
|
||||
if (tabbarCacheEnable) {
|
||||
uni.switchTab({ url })
|
||||
}
|
||||
else {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
}
|
||||
onLoad(() => {
|
||||
// 解决原生 tabBar 未隐藏导致有2个 tabBar 的问题
|
||||
nativeTabbarNeedHide
|
||||
&& uni.hideTabBar({
|
||||
fail(err) {
|
||||
console.log('hideTabBar fail: ', err)
|
||||
},
|
||||
success(res) {
|
||||
// console.log('hideTabBar success: ', res)
|
||||
},
|
||||
})
|
||||
})
|
||||
const activeColor = '#1890ff'
|
||||
const inactiveColor = '#666'
|
||||
function getColorByIndex(index: number) {
|
||||
return tabbarStore.curIdx === index ? activeColor : inactiveColor
|
||||
}
|
||||
|
||||
function getImageByIndex(index: number, item: { iconActive?: string, icon: string }) {
|
||||
if (!item.iconActive) {
|
||||
console.warn('image 模式下,需要配置 iconActive (高亮时的图片),否则无法切换高亮图片')
|
||||
return item.icon
|
||||
}
|
||||
return tabbarStore.curIdx === index ? item.iconActive : item.icon
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view v-if="customTabbarEnable" class="h-50px pb-safe">
|
||||
<view class="border-and-fixed bg-white" @touchmove.stop.prevent>
|
||||
<view class="h-50px flex items-center">
|
||||
<view
|
||||
v-for="(item, index) in tabbarList" :key="index"
|
||||
class="flex flex-1 flex-col items-center justify-center"
|
||||
:style="{ color: getColorByIndex(index) }"
|
||||
@click="handleClick(index)"
|
||||
>
|
||||
<view v-if="item.isBulge" class="relative">
|
||||
<!-- 中间一个鼓包tabbarItem的处理 -->
|
||||
<view class="bulge">
|
||||
<!-- TODO 2/2: 通常是一个图片,或者icon,点击触发业务逻辑 -->
|
||||
<!-- 常见的是:扫描按钮、发布按钮、更多按钮等 -->
|
||||
<image class="mt-6rpx h-200rpx w-200rpx" src="/static/tabbar/scan.png" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="relative px-3 text-center">
|
||||
<template v-if="item.iconType === 'uniUi'">
|
||||
<uni-icons :type="item.icon" size="20" :color="getColorByIndex(index)" />
|
||||
</template>
|
||||
<template v-if="item.iconType === 'uiLib'">
|
||||
<!-- TODO: 以下内容请根据选择的UI库自行替换 -->
|
||||
<!-- 如:<wd-icon name="home" /> (https://wot-design-uni.cn/component/icon.html) -->
|
||||
<!-- 如:<uv-icon name="home" /> (https://www.uvui.cn/components/icon.html) -->
|
||||
<!-- 如:<sar-icon name="image" /> (https://sard.wzt.zone/sard-uniapp-docs/components/icon)(sar没有home图标^_^) -->
|
||||
<wd-icon :name="item.icon" size="20" />
|
||||
</template>
|
||||
<template v-if="item.iconType === 'unocss' || item.iconType === 'iconfont'">
|
||||
<view :class="item.icon" class="text-20px" />
|
||||
</template>
|
||||
<template v-if="item.iconType === 'image'">
|
||||
<image :src="getImageByIndex(index, item)" mode="scaleToFill" class="h-20px w-20px" />
|
||||
</template>
|
||||
<view class="mt-2px text-12px">
|
||||
{{ item.text }}
|
||||
</view>
|
||||
<!-- 角标显示 -->
|
||||
<view v-if="item.badge">
|
||||
<template v-if="item.badge === 'dot'">
|
||||
<view class="absolute right-0 top-0 h-2 w-2 rounded-full bg-#f56c6c" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="absolute right-0 top-0 h-4 w-4 center rounded-full bg-#f56c6c text-center text-xs text-white">
|
||||
{{ item.badge }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pb-safe" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.border-and-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
border-top: 1px solid #eee;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
// 中间鼓包的样式
|
||||
.bulge {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform-origin: top center;
|
||||
transform: translateX(-50%) scale(0.5) translateY(-33%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
box-shadow: inset 0 0 0 1px #fefefe;
|
||||
|
||||
&:active {
|
||||
// opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
19
src/tabbar/store.ts
Normal file
19
src/tabbar/store.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* tabbar 状态,增加 storageSync 保证刷新浏览器时在正确的 tabbar 页面
|
||||
* 使用reactive简单状态,而不是 pinia 全局状态
|
||||
*/
|
||||
export const tabbarStore = reactive({
|
||||
curIdx: uni.getStorageSync('app-tabbar-index') || 0,
|
||||
prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
|
||||
setCurIdx(idx: number) {
|
||||
this.curIdx = idx
|
||||
uni.setStorageSync('app-tabbar-index', idx)
|
||||
},
|
||||
|
||||
restorePrevIdx() {
|
||||
if (this.prevIdx === this.curIdx)
|
||||
return
|
||||
this.setCurIdx(this.prevIdx)
|
||||
this.prevIdx = uni.getStorageSync('app-tabbar-index') || 0
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user