Bläddra i källkod

feat(index): 添加用户信息展示和功能模块入口

- 新增用户头像、昵称、年龄、ID展示区域
- 实现健康数据、提醒管理、我的医生、个人中心四个功能模块入口
- 添加用户信息获取逻辑,支持从本地存储或网络请求加载
- 实现头像地址合法性校验及默认头像fallback机制
- 增加页面显示时的用户认证检查和自动跳转逻辑
- 设计并实现新的UI样式布局,包括头像圆角、功能卡片等视觉元素
mcbaiyun 1 månad sedan
förälder
incheckning
45db45140b
1 ändrade filer med 264 tillägg och 3 borttagningar
  1. 264 3
      src/pages/public/index/index.vue

+ 264 - 3
src/pages/public/index/index.vue

@@ -3,7 +3,61 @@
   <view class="page-container">
     <view class="content">
       <view class="user-info">
-        
+        <view class="avatar-section">
+          <view class="avatar">
+            <view class="avatar-frame">
+              <image class="avatar-img" :src="avatarSrc" mode="aspectFill" />
+            </view>
+          </view>
+          <view class="user-details">
+            <text class="username">{{ user.nickname || '慢病患者' }}</text>
+            <text class="user-age" v-if="user.age">年龄: {{ user.age }}</text>
+            <text class="user-id" v-if="user.openid">ID: {{ user.openid }}</text>
+          </view>
+        </view>
+      </view>
+
+      <view class="function-container">
+        <view class="function-row">
+          <view class="function-item health-data">
+            <view class="item-content">
+              <view class="title-row">
+                <view class="item-line"></view>
+                <text class="item-title">健康数据</text>
+              </view>
+              <text class="item-desc">查看您的健康数据</text>
+            </view>
+          </view>
+          <view class="function-item reminder">
+            <view class="item-content">
+              <view class="title-row">
+                <view class="item-line"></view>
+                <text class="item-title">提醒管理</text>
+              </view>
+              <text class="item-desc">管理健康提醒</text>
+            </view>
+          </view>
+        </view>
+        <view class="function-row">
+          <view class="function-item doctor">
+            <view class="item-content">
+              <view class="title-row">
+                <view class="item-line"></view>
+                <text class="item-title">我的医生</text>
+              </view>
+              <text class="item-desc">联系您的医生</text>
+            </view>
+          </view>
+          <view class="function-item profile">
+            <view class="item-content">
+              <view class="title-row">
+                <view class="item-line"></view>
+                <text class="item-title">个人中心</text>
+              </view>
+              <text class="item-desc">管理个人信息</text>
+            </view>
+          </view>
+        </view>
       </view>
 
     </view>
@@ -12,10 +66,87 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
 
+const user = ref<{ avatar?: string; nickname?: string; openid?: string; age?: number }>({})
+
+const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
+
+const avatarSrc = computed(() => {
+  const a = user.value?.avatar
+  if (!a) return defaultAvatarUrl
+  try {
+    const s = String(a)
+    if (/^(https?:\/\/|data:|wxfile:\/\/|file:\/\/|\/static\/)/i.test(s)) {
+      return s
+    }
+    if (/^(\.|\/|temp)/i.test(s)) return s
+  } catch (e) {
+    // fallback
+  }
+  return defaultAvatarUrl
+})
+
+const loadUser = () => {
+  try {
+    const u = uni.getStorageSync('user_info')
+    if (u) {
+      user.value = u
+    }
+  } catch (e) {
+    // ignore
+  }
+}
+
+const fetchUserInfo = async () => {
+  try {
+    const token = uni.getStorageSync('token')
+    if (!token) return
+    uni.showLoading({ title: '加载中...' })
+    const response = await uni.request({
+      url: 'https://wx.baiyun.work/user_info',
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json',
+        'Authorization': `Bearer ${token}`
+      },
+      data: {}
+    })
+    uni.hideLoading()
+    console.log('User info response:', response)
+    const resp = response.data as any
+    if (response.statusCode === 401) {
+      uni.removeStorageSync('token')
+      uni.removeStorageSync('role')
+      user.value = {}
+      uni.reLaunch({ url: '/pages/public/login/index' })
+      return
+    }
+    if (resp && resp.code === 200 && resp.data) {
+      user.value = resp.data
+      uni.setStorageSync('user_info', resp.data)
+      if (!resp.data.nickname || !resp.data.avatar) {
+        uni.navigateTo({ url: '/pages/public/profile/infos/base-info' })
+      }
+    }
+  } catch (err) {
+    uni.hideLoading()
+    console.error('Fetch user info error:', err)
+  }
+}
+
+onShow(() => {
+  const token = uni.getStorageSync('token')
+  if (!token) {
+    uni.reLaunch({ url: '/pages/public/login/index' })
+  } else {
+    fetchUserInfo()
+  }
+})
+
 function handleScan(res: any) {
   console.log('[index] scan result', res)
   const resultText = res?.result || ''
@@ -42,7 +173,137 @@ function handleScan(res: any) {
 }
 
 .user-info {
+  /* background-color: #fff; */
+  padding: 40rpx;
+  margin-top: 10rpx;
+}
+
+.avatar-section {
+  display: flex;
+  align-items: center;
+}
+
+.avatar {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 50%;
+  border: 1px solid rgba(128, 128, 128, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 30rpx;
+}
+
+.avatar-frame {
   width: 100%;
-  height: 100px;
+  height: 100%;
+  border-radius: 50%;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.avatar-img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.user-details {
+  flex: 1;
+}
+
+.username {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.user-age {
+  font-size: 28rpx;
+  color: #666;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.user-id {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.function-container {
+  padding-inline: 20rpx;
+}
+
+.function-row {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
+}
+
+.function-row:last-child {
+  margin-bottom: 0;
+}
+
+.function-item {
+  flex: 1;
+  height: 160rpx;
+  background-color: #fff;
+  border-radius: 20rpx;
+  margin: 0 10rpx;
+  position: relative;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.item-content {
+  position: absolute;
+  top: 20rpx;
+  left: 20rpx;
+  display: flex;
+  flex-direction: column;
+}
+
+.title-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10rpx;
+}
+
+.item-line {
+  width: 6rpx;
+  height: 48rpx;
+  margin-right: 15rpx;
+  border-radius: 10rpx;
+}
+
+.health-data .item-line {
+  background-color: #2ed573;
+}
+
+.reminder .item-line {
+  background-color: #3742fa;
+}
+
+.doctor .item-line {
+  background-color: #ffa502;
+}
+
+.profile .item-line {
+  background-color: #9c88ff;
+}
+
+.item-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.item-desc {
+  font-size: 28rpx;
+  color: #666;
 }
 </style>