Ver Fonte

fix:修复个人信息页面展示信息不完整的问题;编辑个人信息表单字段不完整的问题;

yangyi há 4 meses atrás
pai
commit
7be734b9c9

+ 4 - 0
src/type/index.ts

@@ -3,6 +3,10 @@ export interface User {
     name: string
     avatar?: string
     role: 'admin' | 'user' | 'guest'
+    email?: string
+    phone?: string
+    account?: string
+    enable?: number
 }
 
 export interface LoginUser {

+ 6 - 1
src/view/LoginView.vue

@@ -208,7 +208,12 @@ const handleLogin = async () => {
       loginUserStore.loginSuccess({
         id: String(userData.userId ?? userData.id ?? ''),
         name: userData.username || userData.account || '用户',
-        role: (userData.role as UserType['role']) || 'user'
+        role: (userData.role as UserType['role']) || 'user',
+        avatar: userData.avatar,
+        email: userData.email,
+        phone: userData.phone,
+        account: userData.account,
+        enable: userData.enable
       })
       ElMessage.success('登录成功')
       router.push((route.query.redirect as string) || '/booking')

+ 118 - 14
src/view/profile/ProfileEditView.vue

@@ -2,58 +2,132 @@
   <div class="profile-edit">
     <el-button text @click="router.back()">← 返回</el-button>
     <h1>修改个人资料</h1>
-    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="form">
-      <el-form-item label="昵称" prop="name">
-        <el-input v-model="form.name" />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="handleSubmit">保存</el-button>
-        <el-button @click="router.back()">取消</el-button>
-      </el-form-item>
-    </el-form>
+    <el-card v-loading="loading">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="form">
+        <el-form-item label="头像">
+          <div class="avatar-upload">
+            <el-avatar :size="80" v-if="form.avatar">
+              <img :src="form.avatar" alt="avatar" />
+            </el-avatar>
+            <el-avatar :size="80" v-else>{{ form.name?.[0] }}</el-avatar>
+            <el-button size="small" class="upload-btn" @click="triggerUpload">更换头像</el-button>
+            <input
+              ref="fileInputRef"
+              type="file"
+              accept="image/*"
+              style="display: none"
+              @change="handleFileChange"
+            />
+          </div>
+        </el-form-item>
+        <el-form-item label="昵称" prop="name">
+          <el-input v-model="form.name" />
+        </el-form-item>
+        <el-form-item label="邮箱" prop="email">
+          <el-input v-model="form.email" />
+        </el-form-item>
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSubmit" :loading="submitting">保存</el-button>
+          <el-button @click="router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
+import { reactive, ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { useLoginUserStore } from '@/store'
 import { getUserController } from '@/api/user-controller'
+import { getImageController } from '@/api/image-controller'
 import type { UserDto } from '@/api/models'
 
 const router = useRouter()
 const loginUserStore = useLoginUserStore()
 
 const formRef = ref<FormInstance>()
+const fileInputRef = ref<HTMLInputElement>()
+const loading = ref(false)
+const submitting = ref(false)
 
 const form = reactive({
-  name: loginUserStore.loginUser.user?.name ?? ''
+  name: loginUserStore.loginUser.user?.name ?? '',
+  email: loginUserStore.loginUser.user?.email ?? '',
+  phone: loginUserStore.loginUser.user?.phone ?? '',
+  avatar: loginUserStore.loginUser.user?.avatar ?? ''
 })
 
 const rules: FormRules = {
   name: [
     { required: true, message: '请输入昵称', trigger: 'blur' },
     { min: 2, message: '昵称至少 2 个字符', trigger: 'blur' }
+  ],
+  email: [
+    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
+  ],
+  phone: [
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
   ]
 }
 
+const triggerUpload = () => {
+  fileInputRef.value?.click()
+}
+
+const handleFileChange = async (e: Event) => {
+  const input = e.target as HTMLInputElement
+  const file = input.files?.[0]
+  if (!file) return
+
+  submitting.value = true
+  try {
+    const api = getImageController()
+    const res = await api.upload({ imageFile: file as Blob })
+    if (res.code === 200 && res.data) {
+      form.avatar = res.data
+      ElMessage.success('头像上传成功')
+    } else {
+      ElMessage.error(res.message || '头像上传失败')
+    }
+  } catch (err: any) {
+    ElMessage.error(err.message || '头像上传失败')
+  } finally {
+    submitting.value = false
+    input.value = ''
+  }
+}
+
 const handleSubmit = async () => {
   const valid = await formRef.value?.validate().catch(() => false)
   if (!valid) return
+
+  submitting.value = true
   try {
     const api = getUserController()
     const dto: UserDto = {
       id: Number(loginUserStore.userId),
-      username: form.name
+      username: form.name,
+      email: form.email || undefined,
+      phone: form.phone || undefined,
+      avatar: form.avatar || undefined
     }
     const res = await api.edit(dto)
     if (res.code === 200) {
       loginUserStore.updateUser({
         id: loginUserStore.loginUser.user?.id ?? '',
         name: form.name,
-        role: loginUserStore.loginUser.user?.role ?? 'user'
+        role: loginUserStore.loginUser.user?.role ?? 'user',
+        avatar: form.avatar,
+        email: form.email,
+        phone: form.phone,
+        account: loginUserStore.loginUser.user?.account,
+        enable: loginUserStore.loginUser.user?.enable
       })
       ElMessage.success('保存成功')
       router.push('/profile')
@@ -62,8 +136,32 @@ const handleSubmit = async () => {
     }
   } catch (err: any) {
     ElMessage.error(err.message || '保存失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+const fetchUserInfo = async () => {
+  if (!loginUserStore.userId) return
+  loading.value = true
+  try {
+    const api = getUserController()
+    const res = await api.queryById(Number(loginUserStore.userId))
+    if (res.code === 200 && res.data) {
+      const u = res.data
+      form.name = u.username ?? form.name
+      form.email = u.email ?? form.email
+      form.phone = u.phone ?? form.phone
+      form.avatar = u.avatar ?? form.avatar
+    }
+  } catch {
+    // use cached data
+  } finally {
+    loading.value = false
   }
 }
+
+onMounted(fetchUserInfo)
 </script>
 
 <style scoped>
@@ -76,4 +174,10 @@ const handleSubmit = async () => {
 .form {
   margin-top: 20px;
 }
-</style>
+
+.avatar-upload {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+</style>

+ 48 - 9
src/view/profile/ProfileView.vue

@@ -1,20 +1,24 @@
 <template>
   <div class="profile">
     <h1>个人中心</h1>
-    <el-card class="profile-card">
+    <el-card class="profile-card" v-loading="loading">
       <div class="avatar-section">
-        <el-avatar :size="80">{{ user.name?.[0] }}</el-avatar>
+        <el-avatar :size="80" v-if="user.avatar">
+          <img :src="user.avatar" alt="avatar" />
+        </el-avatar>
+        <el-avatar :size="80" v-else>{{ user.name?.[0] }}</el-avatar>
         <h2>{{ user.name }}</h2>
         <el-tag>{{ user.role }}</el-tag>
       </div>
       <el-descriptions :column="1" border class="info">
         <el-descriptions-item label="ID">{{ user.id || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="昵称">
-          {{ user.name }}
-          <el-button text type="primary" @click="router.push('/profile/edit')">编辑</el-button>
-        </el-descriptions-item>
+        <el-descriptions-item label="昵称">{{ user.name }}</el-descriptions-item>
+        <el-descriptions-item label="账号">{{ user.account || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="邮箱">{{ user.email || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ user.phone || '-' }}</el-descriptions-item>
       </el-descriptions>
       <div class="actions">
+        <el-button type="primary" @click="router.push('/profile/edit')">编辑资料</el-button>
         <el-button @click="router.push('/profile/password')">修改密码</el-button>
       </div>
     </el-card>
@@ -22,11 +26,46 @@
 </template>
 
 <script setup lang="ts">
-import { useLoginUserStore } from '@/store'
+import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
+import { useLoginUserStore } from '@/store'
+import { getUserController } from '@/api/user-controller'
+
 const router = useRouter()
 const loginUserStore = useLoginUserStore()
-const user = loginUserStore.loginUser.user || { id: '', name: '未登录', role: 'guest' as const }
+const loading = ref(false)
+
+const user = ref(loginUserStore.loginUser.user ? { ...loginUserStore.loginUser.user } : { id: '', name: '未登录', role: 'guest' as const })
+
+const fetchUserInfo = async () => {
+  if (!loginUserStore.userId) return
+  loading.value = true
+  try {
+    const api = getUserController()
+    const res = await api.queryById(Number(loginUserStore.userId))
+    if (res.code === 200 && res.data) {
+      const u = res.data
+      const updated = {
+        id: String(u.id ?? ''),
+        name: u.username ?? '',
+        role: (u.role as 'admin' | 'user' | 'guest') || 'user',
+        avatar: u.avatar,
+        email: u.email,
+        phone: u.phone,
+        account: u.account,
+        enable: u.enable
+      }
+      user.value = updated
+      loginUserStore.updateUser(updated)
+    }
+  } catch {
+    // use cached data
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(fetchUserInfo)
 </script>
 
 <style scoped>
@@ -52,4 +91,4 @@ const user = loginUserStore.loginUser.user || { id: '', name: '未登录', role:
 .actions {
   text-align: center;
 }
-</style>
+</style>