初始化=商城+金融,用于演示.

This commit is contained in:
2025-11-28 16:43:16 +08:00
commit 08580b07ad
117 changed files with 20012 additions and 0 deletions

84
src/tabbar/README.md Normal file
View File

@@ -0,0 +1,84 @@
# 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`,切换字体颜色方便。有缓存。可以实现各种花里胡哨的动效等。
- 劣势:首次点击 `tabbar` 会闪烁。
- 3 `无缓存自定义 tabbar`,使用 `navigateTo` 切换 `tabbar``tabbar` 页面无缓存。使用了第三方 UI 库的 `tabbar` 组件。
- 优势:可以随意配置自己想要的 svg icon切换字体颜色方便。可以实现各种花里胡哨的动效等。
- 劣势:首次点击 `tababr` 会闪烁,无缓存。
> 注意:花里胡哨的效果需要自己实现,本模版不提供。
## tabbar 配置说明
- 如果使用的是 `原生tabbar`,需要配置 `nativeTabbarList`,每个 `item` 需要配置 `path``text``iconPath``selectedIconPath` 等属性。
- 如果使用的是 `自定义tabbar`,需要配置 `customTabbarList`,每个 `item` 需要配置 `path``text``icon``iconType` 等属性(如果是 `image` 图片还需要配置2种图片
## 文件说明
`config.ts` 专门配置 `nativeTabbarList``customTabbarList` 的相关信息,请按照文件里面的注释配置相关项。
使用 `原生tabbar`不需要关心下面2个文件
- `store.ts` ,专门给 `自定义 tabbar` 提供状态管理,代码几乎不需要修改。
- `index.vue` ,专门给 `自定义 tabbar` 提供渲染逻辑,代码可以稍微修改,以符合自己的需求。
## 自定义tabbar的不同类型的配置
- uniUi 图标
```js
{
// ... 其他配置
"iconType": "uniUi",
"icon": "home",
}
```
- unocss 图标
```js
{
// ... 其他配置
// 注意 unocss 图标需要如下处理:(二选一)
// 1在fg-tabbar.vue页面上引入一下并注释掉见tabbar/index.vue代码第2行
// 2配置到 unocss.config.ts 的 safelist 中
iconType: 'unocss',
icon: 'i-carbon-code',
}
```
- iconfont 图标
```js
{
// ... 其他配置
// 注意 iconfont 图标需要额外加上 'iconfont',如下
iconType: 'iconfont',
icon: 'iconfont icon-my',
}
```
- image 本地图片
```js
{
// ... 其他配置
// 使用 image需要配置 icon + iconActive 2张图片不推荐
// 既然已经用了自定义tabbar了就不建议用图片了所以不推荐
iconType: 'image',
icon: '/static/tabbar/home.png',
iconActive: '/static/tabbar/homeHL.png',
}
```

128
src/tabbar/config.ts Normal file
View File

@@ -0,0 +1,128 @@
import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
import type { CustomTabBarItem, NativeTabBarItem } from './types'
/**
* 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_STRATEGY_MAP = {
NO_TABBAR: 0,
NATIVE_TABBAR: 1,
CUSTOM_TABBAR_WITH_CACHE: 2,
CUSTOM_TABBAR_WITHOUT_CACHE: 3,
}
// TODO: 1/3. 通过这里切换使用tabbar的策略
// 如果是使用 NO_TABBAR(0)nativeTabbarList 和 customTabbarList 都不生效(里面的配置不用管)
// 如果是使用 NATIVE_TABBAR(1),只需要配置 nativeTabbarListcustomTabbarList 不生效
// 如果是使用 CUSTOM_TABBAR(2,3),只需要配置 customTabbarListnativeTabbarList 不生效
export const selectedTabbarStrategy = TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE
// TODO: 2/3. 使用 NATIVE_TABBAR 时,更新下面的 tabbar 配置
export const nativeTabbarList: NativeTabBarItem[] = [
{
iconPath: 'static/tabbar/home.png',
selectedIconPath: 'static/tabbar/homeHL.png',
pagePath: 'pages/index/index',
text: '首页',
},
{
iconPath: 'static/tabbar/personal.png',
selectedIconPath: 'static/tabbar/personalHL.png',
pagePath: 'pages/me/me',
text: '个人',
},
]
// TODO: 3/3. 使用 CUSTOM_TABBAR(2,3) 时,更新下面的 tabbar 配置
// 如果需要配置鼓包,需要在 'tabbar/store.ts' 里面设置,最后在 `tabbar/index.vue` 里面更改鼓包的图片
export const customTabbarList: CustomTabBarItem[] = [
{
text: '首页',
pagePath: 'pages/index/index',
// 注意 unocss 图标需要如下处理:(二选一)
// 1在fg-tabbar.vue页面上引入一下并注释掉见tabbar/index.vue代码第2行
// 2配置到 unocss.config.ts 的 safelist 中
iconType: 'unocss',
icon: 'i-carbon-home',
// badge: 'dot',
},
{
pagePath: 'pages/me/me',
text: '我的',
// 1在fg-tabbar.vue页面上引入一下并注释掉见tabbar/index.vue代码第2行
// 2配置到 unocss.config.ts 的 safelist 中
iconType: 'unocss',
icon: 'i-carbon-user',
// badge: 10,
},
// 其他类型演示
// 1、uiLib
// {
// pagePath: 'pages/index/index',
// text: '首页',
// iconType: 'uiLib',
// icon: 'home',
// },
// 2、iconfont
// {
// pagePath: 'pages/index/index',
// text: '首页',
// // 注意 iconfont 图标需要额外加上 'iconfont',如下
// iconType: 'iconfont',
// icon: 'iconfont icon-my',
// },
// 3、image
// {
// pagePath: 'pages/index/index',
// text: '首页',
// // 使用 image需要配置 icon + iconActive 2张图片
// iconType: 'image',
// icon: '/static/tabbar/home.png',
// iconActive: '/static/tabbar/homeHL.png',
// },
]
/**
* 是否启用 tabbar 缓存
* NATIVE_TABBAR(1) 和 CUSTOM_TABBAR_WITH_CACHE(2) 时需要tabbar缓存
*/
export const tabbarCacheEnable
= [TABBAR_STRATEGY_MAP.NATIVE_TABBAR, TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE].includes(selectedTabbarStrategy)
/**
* 是否启用自定义 tabbar
* CUSTOM_TABBAR(2,3) 时启用自定义tabbar
*/
export const customTabbarEnable
= [TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE, TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITHOUT_CACHE].includes(selectedTabbarStrategy)
/**
* 是否需要隐藏原生 tabbar
* CUSTOM_TABBAR_WITH_CACHE(2) 时需要隐藏原生tabbar
*/
export const needHideNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE
const _tabbarList = customTabbarEnable ? customTabbarList.map(item => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList
export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList
const _tabbar: TabBar = {
// 只有微信小程序支持 custom。App 和 H5 不生效
custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE,
color: '#999999',
selectedColor: '#018d71',
backgroundColor: '#F8F8F8',
borderStyle: 'black',
height: '50px',
fontSize: '10px',
iconWidth: '24px',
spacing: '3px',
list: _tabbarList as unknown as TabBar['list'],
}
export const tabBar = tabbarCacheEnable ? _tabbar : undefined

172
src/tabbar/index.vue Normal file
View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
// i-carbon-code
import type { CustomTabBarItem } from './types'
import { customTabbarEnable, needHideNativeTabbar, tabbarCacheEnable } from './config'
import { tabbarList, tabbarStore } from './store'
// #ifdef MP-WEIXIN
// 将自定义节点设置成虚拟的去掉自定义组件包裹层更加接近Vue组件的表现能更好的使用flex属性
defineOptions({
virtualHost: true,
})
// #endif
/**
* 中间的鼓包tabbarItem的点击事件
*/
function handleClickBulge() {
uni.showToast({
title: '点击了中间的鼓包tabbarItem',
icon: 'none',
})
}
function handleClick(index: number) {
// 点击原来的不做操作
if (index === tabbarStore.curIdx) {
return
}
if (tabbarList[index].isBulge) {
handleClickBulge()
return
}
const url = tabbarList[index].pagePath
tabbarStore.setCurIdx(index)
if (tabbarCacheEnable) {
uni.switchTab({ url })
}
else {
uni.navigateTo({ url })
}
}
// #ifndef MP-WEIXIN || MP-ALIPAY
// 因为有了 custom:true 微信里面不需要多余的hide操作
onLoad(() => {
// 解决原生 tabBar 未隐藏导致有2个 tabBar 的问题
needHideNativeTabbar
&& uni.hideTabBar({
fail(err) {
console.log('hideTabBar fail: ', err)
},
success(res) {
// console.log('hideTabBar success: ', res)
},
})
})
// #endif
// #ifdef MP-ALIPAY
onMounted(() => {
// 解决支付宝自定义tabbar 未隐藏导致有2个 tabBar 的问题; 注意支付宝很特别,需要在 onMounted 钩子调用
customTabbarEnable // 另外,支付宝里面,只要是 customTabbar 都需要隐藏
&& uni.hideTabBar({
fail(err) {
console.log('hideTabBar fail: ', err)
},
success(res) {
// console.log('hideTabBar success: ', res)
},
})
})
// #endif
const activeColor = 'var(--wot-color-theme, #1890ff)'
const inactiveColor = '#666'
function getColorByIndex(index: number) {
return tabbarStore.curIdx === index ? activeColor : inactiveColor
}
function getImageByIndex(index: number, item: CustomTabBarItem) {
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: 中间鼓包tabbarItem配置通常是一个图片或者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 === '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 top-0 box-border h-5 min-w-5 center rounded-full bg-#f56c6c px-1 text-center text-xs text-white -right-3">
{{ item.badge > 99 ? '99+' : 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>

78
src/tabbar/store.ts Normal file
View File

@@ -0,0 +1,78 @@
import type { CustomTabBarItem, CustomTabBarItemBadge } from './types'
import { reactive } from 'vue'
import { tabbarList as _tabbarList, customTabbarEnable, selectedTabbarStrategy, TABBAR_STRATEGY_MAP } from './config'
// TODO 1/2: 中间的鼓包tabbarItem的开关
const BULGE_ENABLE = false
/** tabbarList 里面的 path 从 pages.config.ts 得到 */
const tabbarList = reactive<CustomTabBarItem[]>(_tabbarList.map(item => ({
...item,
pagePath: item.pagePath.startsWith('/') ? item.pagePath : `/${item.pagePath}`,
})))
if (customTabbarEnable && BULGE_ENABLE) {
if (tabbarList.length % 2) {
console.error('有鼓包时 tabbar 数量必须是偶数,否则样式很奇怪!!')
}
tabbarList.splice(tabbarList.length / 2, 0, {
isBulge: true,
} as CustomTabBarItem)
}
export function isPageTabbar(path: string) {
if (selectedTabbarStrategy === TABBAR_STRATEGY_MAP.NO_TABBAR) {
return false
}
const _path = path.split('?')[0]
return tabbarList.some(item => item.pagePath === _path)
}
/**
* 自定义 tabbar 的状态管理,原生 tabbar 无需关注本文件
* tabbar 状态,增加 storageSync 保证刷新浏览器时在正确的 tabbar 页面
* 使用reactive简单状态而不是 pinia 全局状态
*/
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)
},
setTabbarItemBadge(idx: number, badge: CustomTabBarItemBadge) {
if (tabbarList[idx]) {
tabbarList[idx].badge = badge
}
},
setAutoCurIdx(path: string) {
// '/' 当做首页
if (path === '/') {
this.setCurIdx(0)
return
}
const index = tabbarList.findIndex(item => item.pagePath === path)
// console.log('tabbarList:', tabbarList)
if (index === -1) {
const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)
// console.log(pagesPathList)
const flag = tabbarList.some(item => pagesPathList.includes(item.pagePath))
if (!flag) {
this.setCurIdx(0)
return
}
}
else {
this.setCurIdx(index)
}
},
restorePrevIdx() {
if (this.prevIdx === this.curIdx)
return
this.setCurIdx(this.prevIdx)
this.prevIdx = uni.getStorageSync('app-tabbar-index') || 0
},
})
export { tabbarList, tabbarStore }

34
src/tabbar/types.ts Normal file
View File

@@ -0,0 +1,34 @@
import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
import type { RemoveLeadingSlashFromUnion } from '@/typings'
/**
* 原生 tabbar 的单个选项配置
*/
export type NativeTabBarItem = TabBar['list'][number] & {
pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>
}
/** badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改) */
export type CustomTabBarItemBadge = number | 'dot'
/** 自定义 tabbar 的单个选项配置 */
export interface CustomTabBarItem {
text: string
pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>
/** 图标类型,不建议用 image 模式,因为需要配置 2 张图,更麻烦 */
iconType: 'uiLib' | 'unocss' | 'iconfont' | 'image'
/**
* icon 的路径
* - uiLib: wot-design-uni 图标的 icon prop
* - unocss: unocss 图标的类名
* - iconfont: iconfont 图标的类名
* - image: 图片的路径
*/
icon: string
/** 只有在 image 模式下才需要传递的是高亮的图片PS 不建议用 image 模式) */
iconActive?: string
/** badge 显示一个数字或 小红点 */
badge?: CustomTabBarItemBadge
/** 是否是中间的鼓包tabbarItem */
isBulge?: boolean
}