From 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 27 四月 2026 10:13:53 +0800
Subject: [PATCH] 小程序对接finish

---
 yuyueMiniProgram/src/pages/profile/index.vue |  204 ++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 161 insertions(+), 43 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/profile/index.vue b/yuyueMiniProgram/src/pages/profile/index.vue
index 8a72bfd..3dfaedf 100644
--- a/yuyueMiniProgram/src/pages/profile/index.vue
+++ b/yuyueMiniProgram/src/pages/profile/index.vue
@@ -1,5 +1,9 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { reactive, ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { fetchMiniMe, updateMiniProfile, uploadMiniProfileImage } from '@/api/auth'
+import { useMiniAuthStore } from '@/store/auth'
+import { normalizeMeProfile } from '@/types/auth'
 
 definePage({
   name: 'profile',
@@ -9,98 +13,212 @@
   },
 })
 
+type GenderValue = 'male' | 'female' | ''
+
 const router = useRouter()
+const authStore = useMiniAuthStore()
 const sysInfo = uni.getSystemInfoSync()
 const statusBarHeight = sysInfo.statusBarHeight || 0
 const navBarHeight = 44
+const saving = ref(false)
+const loading = ref(false)
+const uploadingAvatar = ref(false)
 
-const formData = ref({
-  name: '王明',
-  company: '成都xxxxx有限公司',
-  gender: 'female', // 'male' or 'female'
+const formData = reactive({
+  name: '',
+  company: '',
+  gender: '' as GenderValue,
+  avatarUrl: '',
 })
+
+function mapProfileGender(value?: string | null): GenderValue {
+  if (value === '男') {
+    return 'male'
+  }
+  if (value === '女') {
+    return 'female'
+  }
+  return ''
+}
+
+function mapSubmitGender(value: GenderValue): string | null {
+  if (value === 'male') {
+    return '男'
+  }
+  if (value === 'female') {
+    return '女'
+  }
+  return null
+}
+
+function applyProfileToForm() {
+  const profile = authStore.profile
+  formData.name = profile?.displayName || ''
+  formData.company = profile?.companyName || ''
+  formData.gender = mapProfileGender(profile?.gender)
+  formData.avatarUrl = profile?.avatarUrl || ''
+}
 
 function handleBack() {
   router.back()
 }
 
-function handleSave() {
-  uni.showToast({ title: '保存成功', icon: 'success' })
-  setTimeout(() => {
-    router.back()
-  }, 1000)
+async function loadProfile() {
+  applyProfileToForm()
+  loading.value = true
+  try {
+    const profile = await fetchMiniMe()
+    authStore.setProfile(normalizeMeProfile(profile))
+    applyProfileToForm()
+  }
+  catch (error) {
+    uni.showToast({
+      title: error instanceof Error ? error.message : '加载个人资料失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    loading.value = false
+  }
 }
+
+async function handleSave() {
+  const nickName = formData.name.trim()
+  if (!nickName) {
+    return uni.showToast({ title: '请输入姓名', icon: 'none' })
+  }
+  saving.value = true
+  try {
+    const profile = await updateMiniProfile({
+      nickName,
+      companyName: formData.company.trim() || null,
+      gender: mapSubmitGender(formData.gender),
+      avatarUrl: formData.avatarUrl || null,
+    })
+    authStore.setProfile(normalizeMeProfile(profile))
+    applyProfileToForm()
+    uni.showToast({ title: '保存成功', icon: 'success' })
+    setTimeout(() => {
+      router.back()
+    }, 600)
+  }
+  catch (error) {
+    uni.showToast({
+      title: error instanceof Error ? error.message : '保存失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    saving.value = false
+  }
+}
+
+async function handleChooseAvatar() {
+  if (uploadingAvatar.value) {
+    return
+  }
+  try {
+    const chooseResult = await uni.chooseImage({
+      count: 1,
+      sizeType: ['compressed'],
+      sourceType: ['album', 'camera'],
+    })
+    const filePath = chooseResult.tempFilePaths?.[0]
+    if (!filePath) {
+      return
+    }
+    uploadingAvatar.value = true
+    uni.showLoading({ title: '上传中...' })
+    const avatarUrl = await uploadMiniProfileImage(filePath)
+    formData.avatarUrl = avatarUrl
+    authStore.setProfile({
+      ...(authStore.profile || normalizeMeProfile(await fetchMiniMe())),
+      avatarUrl,
+    })
+    uni.showToast({ title: '头像上传成功', icon: 'success' })
+  }
+  catch (error) {
+    uni.showToast({
+      title: error instanceof Error ? error.message : '头像上传失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    uploadingAvatar.value = false
+    uni.hideLoading()
+  }
+}
+
+onShow(() => {
+  void loadProfile()
+})
 </script>
 
 <template>
   <view class="h-screen w-full relative box-border flex flex-col overflow-hidden m-0 p-0 bg-[#F6FBFF]">
-    <!-- 顶部渐变背景 -->
     <view class="fixed left-0 top-0 z-0 h-[500rpx] w-full pointer-events-none"
       style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);" />
 
-    <!-- 自定义导航栏 -->
     <view class="relative z-50 w-full shrink-0" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
       <view class="fixed left-0 top-0 w-full z-50" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
-        <!-- 固定的背景(限制在导航栏区域内) -->
         <view class="absolute left-0 top-0 z-[-1] w-full overflow-hidden" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
-          <view class="w-full h-[500rpx] absolute top-0 left-0 pointer-events-none" style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);"></view>
+          <view class="w-full h-[500rpx] absolute top-0 left-0 pointer-events-none" style="background: linear-gradient(180deg, #5398FF 0%, #A9D0FF 50%, #F6FBFF 100%);" />
         </view>
         <view class="flex items-center justify-between w-full absolute left-0 z-10"
           :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
           <view class="flex h-full w-[88rpx] items-center justify-center relative z-10" @click="handleBack">
             <wd-icon name="arrow-left" size="40rpx" color="#000000" />
           </view>
-          <view class="absolute left-0 w-full text-center  text-[36rpx] font-500 text-[#000000] z-0">
+          <view class="absolute left-0 w-full text-center text-[36rpx] font-500 text-[#000000] z-0">
             个人资料
           </view>
-          <view class="w-[88rpx]"></view>
+          <view class="w-[88rpx]" />
         </view>
       </view>
     </view>
 
-    <!-- 内容区 -->
     <view class="relative z-10 flex-1 flex flex-col items-center pt-[48rpx] px-[32rpx]">
-      
-      <!-- 头像 -->
-      <view class="w-[160rpx] h-[160rpx] rounded-full border-[4rpx] border-solid border-white shadow-[0_4rpx_12rpx_rgba(0,0,0,0.1)] overflow-hidden mb-[64rpx] shrink-0 relative bg-white">
-        <image src="/static/mine/header.png" class="w-full h-full" mode="aspectFill" />
+      <view class="mb-[64rpx] flex flex-col items-center">
+        <view class="w-[160rpx] h-[160rpx] rounded-full border-[4rpx] border-solid border-white shadow-[0_4rpx_12rpx_rgba(0,0,0,0.1)] overflow-hidden shrink-0 relative bg-white"
+          @click="handleChooseAvatar">
+          <image :src="formData.avatarUrl || authStore.profile?.avatarUrl || '/static/mine/header.png'" class="w-full h-full" mode="aspectFill" />
+        </view>
+        <text class="mt-[20rpx] text-[24rpx] text-[#666666]">
+          {{ uploadingAvatar ? '头像上传中...' : '点击更换头像' }}
+        </text>
       </view>
 
-      <!-- 表单卡片 -->
       <view class="w-full bg-white rounded-[16rpx] py-[36rpx] shadow-[0_4rpx_16rpx_rgba(0,0,0,0.05)] px-[24rpx] box-border mb-[120rpx]">
-        <!-- 姓名 -->
         <view class="h-[96rpx] pl-[40rpx] border-b1 mb-[24rpx] flex items-center">
-          <input 
-            v-model="formData.name" 
-            type="text" 
-            placeholder="请输入姓名" 
+          <input
+            v-model="formData.name"
+            type="text"
+            placeholder="请输入姓名"
             placeholder-style="color: #B3B3B3; font-size: 28rpx;"
-            class="flex-1 text-[28rpx] text-[#333333]" 
+            class="flex-1 text-[28rpx] text-[#333333]"
           />
         </view>
-        <!-- 公司 -->
         <view class="h-[96rpx] pl-[40rpx] border-b1 mb-[24rpx] flex items-center">
-          <input 
-            v-model="formData.company" 
-            type="text" 
-            placeholder="请输入公司名称" 
+          <input
+            v-model="formData.company"
+            type="text"
+            placeholder="请输入公司名称"
             placeholder-style="color: #B3B3B3; font-size: 28rpx;"
-            class="flex-1 text-[28rpx] text-[#333333]" 
+            class="flex-1 text-[28rpx] text-[#333333]"
           />
         </view>
-        <!-- 性别 -->
         <view class="h-[96rpx] pl-[40rpx] pr-[40rpx] border-b1 flex items-center justify-between">
           <text class="text-[28rpx] text-[#333333]">性别:</text>
           <view class="flex items-center gap-[40rpx]">
             <view class="flex items-center gap-[12rpx]" @click="formData.gender = 'female'">
               <view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center box-border" :class="formData.gender === 'female' ? 'border-[#0068FF]' : 'border-[#CCCCCC]'">
-                <view v-if="formData.gender === 'female'" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]"></view>
+                <view v-if="formData.gender === 'female'" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]" />
               </view>
               <image src="/static/appointment/woman.png" class="w-[32rpx] h-[32rpx]" />
             </view>
             <view class="flex items-center gap-[12rpx]" @click="formData.gender = 'male'">
               <view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center box-border" :class="formData.gender === 'male' ? 'border-[#0068FF]' : 'border-[#CCCCCC]'">
-                <view v-if="formData.gender === 'male'" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]"></view>
+                <view v-if="formData.gender === 'male'" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]" />
               </view>
               <image src="/static/appointment/man.png" class="w-[32rpx] h-[32rpx]" />
             </view>
@@ -108,17 +226,17 @@
         </view>
       </view>
 
-      <!-- 保存按钮 -->
-      <view class="w-[520rpx] h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] transition-opacity active:opacity-70" @click="handleSave">
-        <text class="text-[32rpx] text-white font-medium">保存</text>
+      <view class="w-[520rpx] h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] transition-opacity active:opacity-70"
+        :class="saving || loading ? 'opacity-70' : ''"
+        @click="handleSave">
+        <text class="text-[32rpx] text-white font-medium">{{ saving ? '保存中...' : '保存' }}</text>
       </view>
-      
     </view>
   </view>
 </template>
 
 <style scoped>
-.border-b1{
+.border-b1 {
   border: 2rpx solid #F5F5F5;
 }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.7.1