浏览代码

feat(tab-bar): 重构底部导航栏支持多角色切换

- 新增医生和患者家属角色的 tab 配置
- 使用 v-for 动态渲染 tab 项,替代原有硬编码方式
- 根据用户角色动态切换 tab 内容和跳转路径
- 添加了角色为 2(医生)和 4(患者家属)的页面路径配置
- 优化 tab 跳转逻辑,统一通过 url 配置进行页面切换
- 增加了医生首页和家属首页的页面文件
- 更新 pages.json 添加新页面路径和 tabbar 配置
- 修复退出登录后未正确跳转至公共首页的问题
- 引入新的图标资源:设置和二维码扫描图标
- 移除了 tab 文本"用户"改为更具语义的"我的"
mcbaiyun 1 月之前
父节点
当前提交
5b2069822c

+ 108 - 0
src/components/tab-bar.unuse

@@ -0,0 +1,108 @@
+<template>
+  <view class="tab-bar">
+    <view class="tab-item" @click="onTabClick(0)">
+      <!-- <uni-icons type="home" size="20" color="#666"></uni-icons> -->
+       <image src="/static/icons/remixicon/home-3-line.svg" class="icon" mode="widthFix" />
+      <text class="tab-text">首页</text>
+    </view>
+    <view class="tab-item" @click="onTabClick(1)">
+      <image src="/static/icons/remixicon/line-chart-line.svg" class="icon" mode="widthFix" />
+      <!-- <uni-icons type="bars" size="20" color="#666"></uni-icons> -->
+      <text class="tab-text">健康</text>
+    </view>
+    <view class="tab-item" @click="onTabClick(2)">
+      <image src="/static/icons/remixicon/account-circle-line.svg" class="icon" mode="widthFix" />
+      <!-- <uni-icons type="person" size="20" color="#666"></uni-icons> -->
+      <text class="tab-text">我的</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { isLoggedIn as checkLogin, getRole } from '../composables/useAuth'
+const onTabClick = (index: number) => {
+  console.log('Tab clicked:', index)
+
+  switch (index) {
+    case 0: // 慢病首页
+      try {
+        const logged = checkLogin()
+        const role = logged ? getRole() : null
+        if (logged && role === 3) {
+          uni.switchTab({ url: '/pages/patient/index/index' })
+        } else {
+          uni.switchTab({ url: '/pages/public/index/index' })
+        }
+      } catch (err) {
+        console.error('tab click index redirect error', err)
+        uni.switchTab({ url: '/pages/public/index/index' })
+      }
+      break
+    case 1: // 健康数据
+      try {
+        // 本地判断:已登录且 role===3 则跳转到患者端健康页(使用 switchTab,因为该页属于 tab)
+        // 未登录或非患者则降级到公共健康页
+        // TODO: 后续支持根据其他角色(医生/患者家属等)跳转到各自的健康页
+        const logged = checkLogin()
+        const role = logged ? getRole() : null
+        if (logged && role === 3) {
+          uni.switchTab({ url: '/pages/patient/health/index' })
+        } else {
+          uni.switchTab({ url: '/pages/public/health/index' })
+        }
+      } catch (err) {
+        console.error('tab click health redirect error', err)
+        uni.switchTab({ url: '/pages/public/health/index' })
+      }
+      break
+    case 2: // 个人中心
+      try {
+        const logged = checkLogin()
+        const role = logged ? getRole() : null
+        if (logged && role === 3) {
+          uni.switchTab({ url: '/pages/patient/profile/index' })
+        } else {
+          uni.switchTab({ url: '/pages/public/profile/index' })
+        }
+      } catch (err) {
+        console.error('tab click profile redirect error', err)
+        uni.switchTab({ url: '/pages/public/profile/index' })
+      }
+      break
+  }
+}
+</script>
+
+<style>
+.icon {
+  width: 45rpx;
+  height: 45rpx;
+  display: inline-block;
+}
+.tab-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 100rpx;
+  background-color: #ffffffe7;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  border-top: 1rpx solid #eee;
+}
+
+.tab-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+  height: 100%;
+}
+
+.tab-text {
+  font-size: 24rpx;
+  color: #666;
+}
+</style>

+ 59 - 62
src/components/tab-bar.vue

@@ -1,74 +1,71 @@
 <template>
   <view class="tab-bar">
-    <view class="tab-item" @click="onTabClick(0)">
-      <!-- <uni-icons type="home" size="20" color="#666"></uni-icons> -->
-       <image src="/static/icons/remixicon/home-3-line.svg" class="icon" mode="widthFix" />
-      <text class="tab-text">首页</text>
-    </view>
-    <view class="tab-item" @click="onTabClick(1)">
-      <image src="/static/icons/remixicon/line-chart-line.svg" class="icon" mode="widthFix" />
-      <!-- <uni-icons type="bars" size="20" color="#666"></uni-icons> -->
-      <text class="tab-text">健康</text>
-    </view>
-    <view class="tab-item" @click="onTabClick(2)">
-      <image src="/static/icons/remixicon/account-circle-line.svg" class="icon" mode="widthFix" />
-      <!-- <uni-icons type="person" size="20" color="#666"></uni-icons> -->
-      <text class="tab-text">用户</text>
+    <view v-for="(tab, index) in currentTabs" :key="index" class="tab-item" @click="onTabClick(index)">
+      <image :src="tab.icon" class="icon" mode="widthFix" />
+      <text class="tab-text">{{ tab.text }}</text>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
+import { computed } from 'vue'
 import { isLoggedIn as checkLogin, getRole } from '../composables/useAuth'
-const onTabClick = (index: number) => {
-  console.log('Tab clicked:', index)
 
-  switch (index) {
-    case 0: // 慢病首页
-      try {
-        const logged = checkLogin()
-        const role = logged ? getRole() : null
-        if (logged && role === 3) {
-          uni.switchTab({ url: '/pages/patient/index/index' })
-        } else {
-          uni.switchTab({ url: '/pages/public/index/index' })
-        }
-      } catch (err) {
-        console.error('tab click index redirect error', err)
-        uni.switchTab({ url: '/pages/public/index/index' })
-      }
-      break
-    case 1: // 健康数据
-      try {
-        // 本地判断:已登录且 role===3 则跳转到患者端健康页(使用 switchTab,因为该页属于 tab)
-        // 未登录或非患者则降级到公共健康页
-        // TODO: 后续支持根据其他角色(医生/患者家属等)跳转到各自的健康页
-        const logged = checkLogin()
-        const role = logged ? getRole() : null
-        if (logged && role === 3) {
-          uni.switchTab({ url: '/pages/patient/health/index' })
-        } else {
-          uni.switchTab({ url: '/pages/public/health/index' })
-        }
-      } catch (err) {
-        console.error('tab click health redirect error', err)
-        uni.switchTab({ url: '/pages/public/health/index' })
-      }
-      break
-    case 2: // 个人中心
-      try {
-        const logged = checkLogin()
-        const role = logged ? getRole() : null
-        if (logged && role === 3) {
-          uni.switchTab({ url: '/pages/patient/profile/index' })
-        } else {
-          uni.switchTab({ url: '/pages/public/profile/index' })
-        }
-      } catch (err) {
-        console.error('tab click profile redirect error', err)
-        uni.switchTab({ url: '/pages/public/profile/index' })
-      }
-      break
+type TabConfig = {
+  icon: string
+  text: string
+  url: string
+}
+
+type RoleTabs = {
+  [key: number | string]: TabConfig[]
+}
+
+// 根据角色定义不同的tab配置
+const roleTabs: RoleTabs = {
+  // 未登录
+  null: [
+    { icon: '/static/icons/remixicon/home-3-line.svg', text: '首页', url: '/pages/public/index/index' },
+    { icon: '/static/icons/remixicon/line-chart-line.svg', text: '健康', url: '/pages/public/health/index' },
+    { icon: '/static/icons/remixicon/account-circle-line.svg', text: '我的', url: '/pages/public/profile/index' }
+  ],
+  // 医生
+  2: [
+    { icon: '/static/icons/remixicon/home-3-line.svg', text: '首页', url: '/pages/doctor/index/index' },
+    { icon: '/static/icons/remixicon/settings-line.svg', text: '后台', url: '/pages/public/health/index' }, // 暂时使用公共健康页,后续可创建医生患者管理页
+    { icon: '/static/icons/remixicon/account-circle-line.svg', text: '我的', url: '/pages/public/profile/index' } // 暂时使用公共profile,后续可创建医生profile
+  ],
+  // 患者
+  3: [
+    { icon: '/static/icons/remixicon/home-3-line.svg', text: '首页', url: '/pages/patient/index/index' },
+    { icon: '/static/icons/remixicon/line-chart-line.svg', text: '健康', url: '/pages/patient/health/index' },
+    { icon: '/static/icons/remixicon/account-circle-line.svg', text: '我的', url: '/pages/patient/profile/index' }
+  ],
+  // 患者家属
+  4: [
+    { icon: '/static/icons/remixicon/home-3-line.svg', text: '首页', url: '/pages/patient-family/index/index' },
+    { icon: '/static/icons/remixicon/line-chart-line.svg', text: '健康', url: '/pages/public/health/index' }, // 暂时使用公共健康页,后续可创建家属健康页
+    { icon: '/static/icons/remixicon/account-circle-line.svg', text: '我的', url: '/pages/public/profile/index' } // 暂时使用公共profile,后续可创建家属profile
+  ]
+}
+
+// 获取当前用户的tabs配置
+const currentTabs = computed(() => {
+  const logged = checkLogin()
+  const role = logged ? getRole() : null
+  return roleTabs[role as keyof RoleTabs] || roleTabs.null
+})
+
+const onTabClick = (index: number) => {
+  const tab = currentTabs.value[index]
+  if (tab) {
+    try {
+      uni.switchTab({ url: tab.url })
+    } catch (err) {
+      console.error('tab click redirect error', err)
+      // fallback到公共首页
+      uni.switchTab({ url: '/pages/public/index/index' })
+    }
   }
 }
 </script>

+ 20 - 0
src/pages.json

@@ -78,6 +78,18 @@
 			"style": {
 				"navigationBarTitleText": "建档信息设定"
 			}
+		},
+		{
+			"path": "pages/doctor/index/index",
+			"style": {
+				"navigationBarTitleText": "医生首页"
+			}
+		},
+		{
+			"path": "pages/patient-family/index/index",
+			"style": {
+				"navigationBarTitleText": "家属首页"
+			}
 		}
 	],
 	"globalStyle": {
@@ -120,6 +132,14 @@
 			{
 				"pagePath": "pages/patient/profile/index",
 				"text": "个人中心-患者端"
+			},
+			{
+				"pagePath": "pages/doctor/index/index",
+				"text": "医生首页"
+			},
+			{
+				"pagePath": "pages/patient-family/index/index",
+				"text": "家属首页"
 			}
 		]
 	}

+ 139 - 0
src/pages/doctor/index/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <CustomNav title="首页" leftType="scan" @scan="handleScan" />
+  <view class="content">
+    <!-- 原始页面已移至此文件,保留以备参考 -->
+    <swiper class="banner-swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :circular="true">
+      <swiper-item v-for="(img, idx) in bannerImages" :key="idx">
+        <image :src="img" class="banner-img" mode="aspectFill" />
+      </swiper-item>
+    </swiper>
+    <view class="card-list">
+      <view class="card" v-for="(card, idx) in cards" :key="idx">
+        <view class="card-title">{{ card.title }}</view>
+        <view class="card-desc">{{ card.desc }}</view>
+      </view>
+    </view>
+  </view>
+  <TabBar />
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import CustomNav from '@/components/custom-nav.vue'
+import TabBar from '@/components/tab-bar.vue'
+import { isLoggedIn as checkLogin, getRole } from '@/composables/useAuth'
+
+const title = ref('Hello')
+
+const bannerImages = [
+  '/static/carousel/BHFIIABBCDJII-5kCEkD6zh9.png',
+  '/static/carousel/BHFIIABBDGHEA-wtWLrLS75o.png',
+  '/static/carousel/BHFIIABBHJBAH-yDeckRiiQP.png'
+]
+
+const cards = [
+  { title: '健康档案', desc: '管理您的健康信息' },
+  { title: '慢病管理', desc: '查看慢病相关数据' },
+  { title: '健康咨询', desc: '在线咨询医生' },
+  { title: '用药提醒', desc: '设置用药提醒' }
+]
+
+onShow(() => {
+  try {
+    const logged = checkLogin()
+    if (!logged) {
+      return
+    }
+
+    const r = getRole()
+    if (r === 3) {
+      console.log('[index] redirecting to patient index, role=', r)
+      uni.reLaunch({ url: '/pages/patient/index/index' })
+      return
+    }
+  } catch (err) {
+    console.error('检查登录态时出错:', err)
+  }
+})
+
+function handleScan(res: any) {
+  console.log('[index] scan result', res)
+  const resultText = res?.result || ''
+  if (resultText) {
+    uni.showToast({ title: String(resultText), icon: 'none', duration: 2000 })
+  } else {
+    uni.showToast({ title: '未识别到有效内容', icon: 'none' })
+  }
+}
+</script>
+
+<style>
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: flex-start;
+  padding-top: calc(var(--status-bar-height) + 44px);
+  height: calc(100vh - var(--status-bar-height) - 44px);
+  background: #f7f8fa;
+}
+
+.banner-swiper {
+  width: 670rpx;
+  max-width: 100vw;
+  height: 400rpx;
+  margin: 20rpx auto 30rpx auto;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
+  background: #fff;
+}
+
+.banner-img {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.card-list {
+  width: 90%;
+  display: flex;
+  flex-direction: column;
+  gap: 32rpx;
+  padding: 0 40rpx;
+  margin: 0 auto;
+}
+
+.card {
+  background: #fff;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+  padding: 40rpx 32rpx;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+}
+
+.card-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+
+.card-desc {
+  font-size: 28rpx;
+  color: #888;
+}
+
+.text-area {
+  display: flex;
+  justify-content: center;
+}
+
+.title {
+  font-size: 36rpx;
+  color: #8f8f94;
+}
+</style>

+ 39 - 0
src/pages/patient-family/index/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <CustomNav title="家属首页" leftType="back" />
+  <view class="page">
+    <view class="content">
+      <text class="title">患者家属</text>
+      <text class="desc">这里是患者家属的管理页面</text>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import CustomNav from '@/components/custom-nav.vue'
+</script>
+
+<style scoped>
+.page {
+  min-height: 100vh;
+  background: #f5f6f8;
+  padding-top: calc(var(--status-bar-height) + 44px);
+}
+
+.content {
+  padding: 40rpx;
+  text-align: center;
+}
+
+.title {
+  font-size: 48rpx;
+  color: #333;
+  margin-bottom: 20rpx;
+  display: block;
+}
+
+.desc {
+  font-size: 28rpx;
+  color: #666;
+  display: block;
+}
+</style>

+ 1 - 0
src/pages/patient/profile/index.vue

@@ -164,6 +164,7 @@ const onLogout = () => {
         uni.removeStorageSync('role')
         user.value = {}
         uni.showToast({ title: '已退出登录', icon: 'none' })
+        uni.reLaunch({ url: '/pages/public/index/index' })
       }
     }
   })

+ 1 - 0
src/pages/public/profile/index.vue

@@ -154,6 +154,7 @@ const onLogout = () => {
         uni.removeStorageSync('role')
         user.value = {}
         uni.showToast({ title: '已退出登录', icon: 'none' })
+        uni.reLaunch({ url: '/pages/public/index/index' })
       }
     }
   })

+ 1 - 0
src/static/icons/remixicon/qr-code-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32" fill="rgba(116,116,116,1)"><path d="M16 17V16H13V13H16V15H18V17H17V19H15V21H13V18H15V17H16ZM21 21H17V19H19V17H21V21ZM3 3H11V11H3V3ZM5 5V9H9V5H5ZM13 3H21V11H13V3ZM15 5V9H19V5H15ZM3 13H11V21H3V13ZM5 15V19H9V15H5ZM18 13H21V15H18V13ZM6 6H8V8H6V6ZM6 16H8V18H6V16ZM16 6H18V8H16V6Z"></path></svg>

+ 1 - 0
src/static/icons/remixicon/settings-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32" fill="rgba(116,116,116,1)"><path d="M12 1L21.5 6.5V17.5L12 23L2.5 17.5V6.5L12 1ZM12 3.311L4.5 7.65311V16.3469L12 20.689L19.5 16.3469V7.65311L12 3.311ZM12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12C16 14.2091 14.2091 16 12 16ZM12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14Z"></path></svg>