hookehuyr

feat: 新增可复用底部导航栏组件并重构页面使用

- 新增 `TabBar.vue` 组件,统一底部导航栏样式与交互逻辑
- 在 `components.d.ts` 中注册 `TabBar` 和 `NutButton` 组件
- 重构首页、入职页、签单页,移除重复的导航栏代码,统一使用 `TabBar` 组件
- 替换首页自定义按钮为 NutUI 的 `nut-button` 组件,保持原有视觉样式
- 更新 CHANGELOG.md 记录相关变更
...@@ -8,11 +8,13 @@ export {} ...@@ -8,11 +8,13 @@ export {}
8 declare module 'vue' { 8 declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 IndexNav: typeof import('./src/components/indexNav.vue')['default'] 10 IndexNav: typeof import('./src/components/indexNav.vue')['default']
11 + NutButton: typeof import('@nutui/nutui-taro')['Button']
11 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 12 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
12 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 13 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
13 QrCode: typeof import('./src/components/qrCode.vue')['default'] 14 QrCode: typeof import('./src/components/qrCode.vue')['default']
14 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] 15 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default']
15 RouterLink: typeof import('vue-router')['RouterLink'] 16 RouterLink: typeof import('vue-router')['RouterLink']
16 RouterView: typeof import('vue-router')['RouterView'] 17 RouterView: typeof import('vue-router')['RouterView']
18 + TabBar: typeof import('./src/components/TabBar.vue')['default']
17 } 19 }
18 } 20 }
......
...@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. ...@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
14 - 注册新页面路由至 `src/app.config.js` 14 - 注册新页面路由至 `src/app.config.js`
15 - 新增 "签单相关" 页面 (`src/pages/signing`),复用 "入职相关" 页面布局 15 - 新增 "签单相关" 页面 (`src/pages/signing`),复用 "入职相关" 页面布局
16 - 为 "签单相关" 页面配置自定义导航栏与渐变色背景样式 16 - 为 "签单相关" 页面配置自定义导航栏与渐变色背景样式
17 +- 新增可复用的底部导航栏组件 (`src/components/TabBar.vue`),统一各页面的导航交互
17 18
18 ### Changed 19 ### Changed
19 - 暂时禁用授权模式功能 (`ENABLE_AUTH_MODE = false`) 20 - 暂时禁用授权模式功能 (`ENABLE_AUTH_MODE = false`)
...@@ -35,6 +36,11 @@ All notable changes to this project will be documented in this file. ...@@ -35,6 +36,11 @@ All notable changes to this project will be documented in this file.
35 - 替换 "入职相关" 页面图标为 NutUI 标准图标库,提升加载性能与清晰度 36 - 替换 "入职相关" 页面图标为 NutUI 标准图标库,提升加载性能与清晰度
36 - 优化 "入职相关" 与 "签单相关" 页面的视觉体验,引入渐变色背景系统(Header 及各板块标题) 37 - 优化 "入职相关" 与 "签单相关" 页面的视觉体验,引入渐变色背景系统(Header 及各板块标题)
37 - 修复 "入职相关" 页面首个板块与导航栏重叠的布局问题 38 - 修复 "入职相关" 页面首个板块与导航栏重叠的布局问题
39 +- 优化底部导航栏样式,移除 Home Indicator (底部灰条) 以符合设计稿
40 +- 重构 TabBar 布局,移除绝对定位与固定高度,改用 Flexbox + Padding 实现更自然的垂直居中与适配
41 +- 增加底部导航栏 `active` 属性,支持不同页面高亮状态切换
42 +- 重构首页、入职页、签单页,统一使用 `TabBar` 组件
43 +- 替换首页 (`src/pages/index`) 自定义按钮为 NutUI `nut-button` 组件,并保留原有视觉样式
38 44
39 ### Removed 45 ### Removed
40 - 删除项目所有离线功能相关逻辑 46 - 删除项目所有离线功能相关逻辑
......
1 +<!--
2 + * @Date: 2026-01-29 20:33:23
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-01-29 20:42:52
5 + * @FilePath: /manulife-weapp/src/components/TabBar.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="fixed bottom-0 left-0 w-full bg-white shadow-[0_-4rpx_16rpx_rgba(0,0,0,0.05)] pb-[env(safe-area-inset-bottom)] z-50">
10 + <view class="flex items-center justify-around py-[32rpx]">
11 + <view
12 + class="flex-1 flex flex-col items-center justify-center"
13 + v-for="(item, index) in tabs"
14 + :key="index"
15 + @tap="handleTabClick(item)"
16 + >
17 + <component
18 + :is="item.icon"
19 + :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']"
20 + size="24"
21 + />
22 + <text
23 + class="text-[20rpx] mt-[8rpx]"
24 + :class="[current === item.key ? 'text-blue-600' : 'text-gray-400']"
25 + >{{ item.label }}</text>
26 + </view>
27 + </view>
28 + </view>
29 +</template>
30 +
31 +<script setup>
32 +import { ref } from 'vue'
33 +import { Home, Service, My } from '@nutui/icons-vue-taro'
34 +import { useGo } from '@/hooks/useGo'
35 +import Taro from '@tarojs/taro'
36 +
37 +const props = defineProps({
38 + current: {
39 + type: String,
40 + default: 'home'
41 + }
42 +})
43 +
44 +const go = useGo()
45 +
46 +const tabs = ref([
47 + {
48 + key: 'home',
49 + label: '首页',
50 + icon: Home,
51 + path: '/pages/index/index'
52 + },
53 + {
54 + key: 'ai',
55 + label: 'AI答疑',
56 + icon: Service,
57 + path: '/pages/ai/index' // Placeholder path
58 + },
59 + {
60 + key: 'me',
61 + label: '我的',
62 + icon: My,
63 + path: '/pages/me/index' // Placeholder path
64 + }
65 +])
66 +
67 +const handleTabClick = (item) => {
68 + if (item.key === props.current) return
69 +
70 + if (item.key === 'home') {
71 + go(item.path)
72 + } else if (item.key === 'me') {
73 + // Check if 'me' page exists, otherwise show toast or just go
74 + // For now, assuming me page might not exist or we just navigate
75 + // go(item.path)
76 + // Based on existing logic in index.vue:
77 + // Taro.redirectTo({ url: '/pages/me/index' });
78 + // But wait, standard tab bar usually uses switchTab or redirectTo.
79 + // Since this is a custom tab bar, we use useGo (which uses navigateTo usually).
80 + // Ideally for tab switching we might want reLaunch or redirectTo to avoid stack buildup.
81 + // However, useGo defaults to navigateTo.
82 + // Let's use reLaunch to simulate tab switching behavior if it's a "tab bar"
83 +
84 + // Actually, let's look at index.vue logic:
85 + // if (item.key === 'me') Taro.redirectTo({ url: '/pages/me/index' });
86 + // if (item.key === 'ai') Taro.showToast({ title: '功能开发中', icon: 'none' });
87 +
88 + // I will replicate this logic for now but make it more generic if possible.
89 + // Since 'me' and 'ai' might not be fully implemented, I will stick to what I know.
90 + // But 'onboarding' and 'signing' pages use this tab bar too.
91 +
92 + // If we are on 'onboarding' (which is seemingly a sub-page but has a tab bar),
93 + // clicking 'Home' should probably go back to Home.
94 +
95 + Taro.reLaunch({ url: item.path })
96 + } else if (item.key === 'ai') {
97 + Taro.showToast({
98 + title: '功能开发中',
99 + icon: 'none'
100 + })
101 + }
102 +}
103 +</script>
...@@ -59,12 +59,8 @@ ...@@ -59,12 +59,8 @@
59 </view> 59 </view>
60 </view> 60 </view>
61 <view class="flex justify-between gap-[24rpx]"> 61 <view class="flex justify-between gap-[24rpx]">
62 - <view class="flex-1 h-[64rpx] flex items-center justify-center border border-blue-600 rounded-[16rpx] bg-white"> 62 + <nut-button plain color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600">产品资料</nut-button>
63 - <text class="text-blue-600 text-[26rpx]">产品资料</text> 63 + <nut-button color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0">计划书</nut-button>
64 - </view>
65 - <view class="flex-1 h-[64rpx] flex items-center justify-center bg-blue-600 rounded-[16rpx]">
66 - <text class="text-white text-[26rpx]">计划书</text>
67 - </view>
68 </view> 64 </view>
69 </view> 65 </view>
70 66
...@@ -83,12 +79,8 @@ ...@@ -83,12 +79,8 @@
83 </view> 79 </view>
84 </view> 80 </view>
85 <view class="flex justify-between gap-[24rpx]"> 81 <view class="flex justify-between gap-[24rpx]">
86 - <view class="flex-1 h-[64rpx] flex items-center justify-center border border-blue-600 rounded-[16rpx] bg-white"> 82 + <nut-button plain color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600">产品资料</nut-button>
87 - <text class="text-blue-600 text-[26rpx]">产品资料</text> 83 + <nut-button color="#2563EB" class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0">计划书</nut-button>
88 - </view>
89 - <view class="flex-1 h-[64rpx] flex items-center justify-center bg-blue-600 rounded-[16rpx]">
90 - <text class="text-white text-[26rpx]">计划书</text>
91 - </view>
92 </view> 84 </view>
93 </view> 85 </view>
94 </view> 86 </view>
...@@ -145,27 +137,7 @@ ...@@ -145,27 +137,7 @@
145 </view> 137 </view>
146 138
147 <!-- Bottom Tab Bar --> 139 <!-- Bottom Tab Bar -->
148 - <view class="fixed bottom-0 left-0 w-full bg-white shadow-[0_-4rpx_16rpx_rgba(0,0,0,0.05)] pb-safe z-50"> 140 + <TabBar current="home" />
149 - <view class="flex h-[110rpx] items-center">
150 - <view
151 - class="flex-1 flex flex-col items-center justify-center"
152 - v-for="(item, index) in loopData1"
153 - :key="index"
154 - @tap="handleTabClick(item)"
155 - >
156 - <component
157 - :is="item.icon"
158 - :class="[item.key === 'home' ? 'text-blue-600' : 'text-gray-400']"
159 - size="24"
160 - />
161 - <text
162 - class="text-[20rpx] mt-[8rpx]"
163 - :class="[item.key === 'home' ? 'text-blue-600' : 'text-gray-400']"
164 - >{{ item.lanhutext0 }}</text>
165 - </view>
166 - </view>
167 - <view class="w-[268rpx] h-[10rpx] mx-auto bg-gray-200 rounded-full mb-[10rpx]" />
168 - </view>
169 </view> 141 </view>
170 </template> 142 </template>
171 143
...@@ -173,6 +145,7 @@ ...@@ -173,6 +145,7 @@
173 import { ref } from 'vue'; 145 import { ref } from 'vue';
174 import Taro, { useShareAppMessage } from '@tarojs/taro'; 146 import Taro, { useShareAppMessage } from '@tarojs/taro';
175 import { useGo } from '@/hooks/useGo'; 147 import { useGo } from '@/hooks/useGo';
148 +import TabBar from '@/components/TabBar.vue';
176 import { 149 import {
177 Search, 150 Search,
178 RectRight, 151 RectRight,
...@@ -213,40 +186,9 @@ const loopData0 = ref([ ...@@ -213,40 +186,9 @@ const loopData0 = ref([
213 }, 186 },
214 ]); 187 ]);
215 188
216 -const loopData1 = ref([
217 - {
218 - icon: Home,
219 - lanhutext0: '首页',
220 - key: 'home'
221 - },
222 - {
223 - icon: Service,
224 - lanhutext0: 'AI答疑',
225 - key: 'ai'
226 - },
227 - {
228 - icon: My,
229 - lanhutext0: '我的',
230 - key: 'me'
231 - },
232 -]);
233 -
234 // Navigation 189 // Navigation
235 const go = useGo(); 190 const go = useGo();
236 191
237 -const handleTabClick = (item) => {
238 - if (item.key === 'me') {
239 - Taro.redirectTo({
240 - url: '/pages/me/index'
241 - });
242 - } else if (item.key === 'ai') {
243 - Taro.showToast({
244 - title: '功能开发中',
245 - icon: 'none'
246 - });
247 - }
248 -};
249 -
250 useShareAppMessage(() => { 192 useShareAppMessage(() => {
251 return { 193 return {
252 title: '臻奇智荟圈', 194 title: '臻奇智荟圈',
......
...@@ -36,28 +36,14 @@ ...@@ -36,28 +36,14 @@
36 </div> 36 </div>
37 37
38 <!-- Tab Bar --> 38 <!-- Tab Bar -->
39 - <div class="fixed bottom-0 left-0 w-full bg-white border-t border-gray-100 pb-[env(safe-area-inset-bottom)] z-50"> 39 + <TabBar current="me" />
40 - <div class="flex justify-around items-center h-[110rpx]">
41 - <div class="flex flex-col items-center justify-center w-1/3" @tap="switchTab('home')">
42 - <Home class="text-gray-400" size="24" />
43 - <span class="text-[#9ca3af] text-[24rpx] mt-[8rpx]">首页</span>
44 - </div>
45 - <div class="flex flex-col items-center justify-center w-1/3" @tap="switchTab('ai')">
46 - <Service class="text-gray-400" size="24" />
47 - <span class="text-[#9ca3af] text-[24rpx] mt-[8rpx]">AI答疑</span>
48 - </div>
49 - <div class="flex flex-col items-center justify-center w-1/3">
50 - <My class="text-[#007aff]" size="24" />
51 - <span class="text-[#007aff] text-[24rpx] mt-[8rpx]">我的</span>
52 - </div>
53 - </div>
54 - </div>
55 </div> 40 </div>
56 </template> 41 </template>
57 42
58 <script setup> 43 <script setup>
59 import { ref } from 'vue' 44 import { ref } from 'vue'
60 import { useGo } from '@/hooks/useGo' 45 import { useGo } from '@/hooks/useGo'
46 +import TabBar from '@/components/TabBar.vue'
61 import { 47 import {
62 Edit, 48 Edit,
63 Find, 49 Find,
...@@ -68,10 +54,7 @@ import { ...@@ -68,10 +54,7 @@ import {
68 Star, 54 Star,
69 Top, 55 Top,
70 PlayCircleFill, 56 PlayCircleFill,
71 - RectRight, 57 + RectRight
72 - Home,
73 - Service,
74 - My
75 } from '@nutui/icons-vue-taro' 58 } from '@nutui/icons-vue-taro'
76 59
77 const go = useGo() 60 const go = useGo()
...@@ -150,19 +133,6 @@ const handleItemClick = (item) => { ...@@ -150,19 +133,6 @@ const handleItemClick = (item) => {
150 console.log('Clicked:', item.title) 133 console.log('Clicked:', item.title)
151 // TODO: Navigate to respective page 134 // TODO: Navigate to respective page
152 } 135 }
153 -
154 -/**
155 - * Switch tab
156 - * @param {string} tab - Tab key
157 - */
158 -const switchTab = (tab) => {
159 - if (tab === 'home') {
160 - go('/pages/index/index')
161 - } else if (tab === 'ai') {
162 - // go('/pages/ai/index') // Assuming there is an AI page
163 - console.log('Switch to AI tab')
164 - }
165 -}
166 </script> 136 </script>
167 137
168 <script> 138 <script>
......
...@@ -36,28 +36,14 @@ ...@@ -36,28 +36,14 @@
36 </div> 36 </div>
37 37
38 <!-- Tab Bar --> 38 <!-- Tab Bar -->
39 - <div class="fixed bottom-0 left-0 w-full bg-white border-t border-gray-100 pb-[env(safe-area-inset-bottom)] z-50"> 39 + <TabBar current="me" />
40 - <div class="flex justify-around items-center h-[110rpx]">
41 - <div class="flex flex-col items-center justify-center w-1/3" @tap="switchTab('home')">
42 - <Home class="text-gray-400" size="24" />
43 - <span class="text-[#9ca3af] text-[24rpx] mt-[8rpx]">首页</span>
44 - </div>
45 - <div class="flex flex-col items-center justify-center w-1/3" @tap="switchTab('ai')">
46 - <Service class="text-gray-400" size="24" />
47 - <span class="text-[#9ca3af] text-[24rpx] mt-[8rpx]">AI答疑</span>
48 - </div>
49 - <div class="flex flex-col items-center justify-center w-1/3">
50 - <My class="text-[#007aff]" size="24" />
51 - <span class="text-[#007aff] text-[24rpx] mt-[8rpx]">我的</span>
52 - </div>
53 - </div>
54 - </div>
55 </div> 40 </div>
56 </template> 41 </template>
57 42
58 <script setup> 43 <script setup>
59 import { ref } from 'vue' 44 import { ref } from 'vue'
60 import { useGo } from '@/hooks/useGo' 45 import { useGo } from '@/hooks/useGo'
46 +import TabBar from '@/components/TabBar.vue'
61 import { 47 import {
62 Shop, 48 Shop,
63 Category, 49 Category,
...@@ -70,10 +56,7 @@ import { ...@@ -70,10 +56,7 @@ import {
70 Clock, 56 Clock,
71 Refresh, 57 Refresh,
72 Location, 58 Location,
73 - RectRight, 59 + RectRight
74 - Home,
75 - Service,
76 - My
77 } from '@nutui/icons-vue-taro' 60 } from '@nutui/icons-vue-taro'
78 61
79 const go = useGo() 62 const go = useGo()
...@@ -174,19 +157,6 @@ const handleItemClick = (item) => { ...@@ -174,19 +157,6 @@ const handleItemClick = (item) => {
174 console.log('Clicked:', item.title) 157 console.log('Clicked:', item.title)
175 // TODO: Navigate to respective page 158 // TODO: Navigate to respective page
176 } 159 }
177 -
178 -/**
179 - * Switch tab
180 - * @param {string} tab - Tab key
181 - */
182 -const switchTab = (tab) => {
183 - if (tab === 'home') {
184 - go('/pages/index/index')
185 - } else if (tab === 'ai') {
186 - // go('/pages/ai/index') // Assuming there is an AI page
187 - console.log('Switch to AI tab')
188 - }
189 -}
190 </script> 160 </script>
191 161
192 <script> 162 <script>
......