1
This commit is contained in:
258
Design/frontend_Design/ProfilePage_Design.md
Normal file
258
Design/frontend_Design/ProfilePage_Design.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# 个人中心页面设计文档
|
||||
|
||||
## 1. 页面概述
|
||||
|
||||
个人中心页面允许当前登录的用户查看和修改自己的个人信息,以及更改密码。此页面旨在提供一个简单、安全的界面来管理个人账户资料。
|
||||
|
||||
## 2. 页面布局
|
||||
|
||||

|
||||
|
||||
### 2.1 布局结构
|
||||
|
||||
页面通常采用多标签页(Tabs)布局,将不同功能的表单分开,以保持界面整洁。
|
||||
- **左侧**: 用户头像和基本信息展示。
|
||||
- **右侧**: 包含两个标签页:
|
||||
- **基本资料**: 用于修改用户名、姓名、邮箱等信息。
|
||||
- **修改密码**: 用于更改当前用户的登录密码。
|
||||
|
||||
## 3. 组件结构
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="profile-page">
|
||||
<el-page-header title="个人中心" content="管理您的账户信息" />
|
||||
|
||||
<el-row :gutter="20" class="page-container">
|
||||
<el-col :span="8" :xs="24">
|
||||
<el-card class="user-card">
|
||||
<div class="user-avatar">
|
||||
<el-avatar :size="100" :src="user.avatarUrl">{{ user.name?.[0] }}</el-avatar>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<h2>{{ user.name }}</h2>
|
||||
<p>{{ user.role }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="16" :xs="24">
|
||||
<el-card>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 基本资料标签页 -->
|
||||
<el-tab-pane label="基本资料" name="profile">
|
||||
<el-form :model="profileForm" :rules="profileRules" ref="profileFormRef" label-width="80px">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="profileForm.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="profileForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="profileForm.email" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="updateProfile" :loading="profileLoading">
|
||||
保存更改
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 修改密码标签页 -->
|
||||
<el-tab-pane label="修改密码" name="password">
|
||||
<el-form :model="passwordForm" :rules="passwordRules" ref="passwordFormRef" label-width="80px">
|
||||
<el-form-item label="旧密码" prop="oldPassword">
|
||||
<el-input type="password" v-model="passwordForm.oldPassword" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input type="password" v-model="passwordForm.newPassword" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input type="password" v-model="passwordForm.confirmPassword" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="changePassword" :loading="passwordLoading">
|
||||
确认修改
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
```typescript
|
||||
// 用户信息 (来自 Store)
|
||||
interface UserProfile {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
// 修改密码表单
|
||||
interface PasswordForm {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 状态管理
|
||||
|
||||
```typescript
|
||||
// 组件内状态
|
||||
const activeTab = ref('profile');
|
||||
const profileForm = ref<Partial<UserProfile>>({});
|
||||
const passwordForm = ref<PasswordForm>({ oldPassword: '', newPassword: '', confirmPassword: '' });
|
||||
const profileLoading = ref(false);
|
||||
const passwordLoading = ref(false);
|
||||
const profileFormRef = ref<FormInstance>();
|
||||
const passwordFormRef = ref<FormInstance>();
|
||||
|
||||
// 全局状态 (Pinia Store)
|
||||
const authStore = useAuthStore();
|
||||
const { user } = storeToRefs(authStore);
|
||||
|
||||
// 当 user 数据从 store 加载后,填充表单
|
||||
watch(user, (currentUser) => {
|
||||
if (currentUser) {
|
||||
profileForm.value = { ...currentUser };
|
||||
}
|
||||
}, { immediate: true });
|
||||
```
|
||||
|
||||
## 6. 交互逻辑
|
||||
|
||||
### 6.1 更新个人资料
|
||||
|
||||
```typescript
|
||||
const updateProfile = async () => {
|
||||
await profileFormRef.value?.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
profileLoading.value = true;
|
||||
try {
|
||||
await authStore.updateProfile({
|
||||
name: profileForm.value.name,
|
||||
email: profileForm.value.email,
|
||||
});
|
||||
ElMessage.success('个人资料更新成功');
|
||||
} finally {
|
||||
profileLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 修改密码
|
||||
|
||||
```typescript
|
||||
const changePassword = async () => {
|
||||
await passwordFormRef.value?.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
passwordLoading.value = true;
|
||||
try {
|
||||
await authStore.changePassword(passwordForm.value);
|
||||
ElMessage.success('密码修改成功,请重新登录');
|
||||
// 密码修改成功后通常需要用户重新登录
|
||||
authStore.logout();
|
||||
router.push('/auth/login');
|
||||
} catch (error) {
|
||||
ElMessage.error(error.response?.data?.message || '密码修改失败');
|
||||
} finally {
|
||||
passwordLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 自定义密码验证规则
|
||||
const validateConfirmPassword = (rule: any, value: any, callback: any) => {
|
||||
if (value !== passwordForm.value.newPassword) {
|
||||
callback(new Error('两次输入的新密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const passwordRules = {
|
||||
// ... oldPassword 和 newPassword 的必填和长度规则
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 7. API 调用
|
||||
|
||||
```typescript
|
||||
// api/me.ts
|
||||
export const meApi = {
|
||||
getProfile: () => apiClient.get<UserProfile>('/me'),
|
||||
updateProfile: (data: Partial<UserProfile>) => apiClient.put('/me', data),
|
||||
changePassword: (data: PasswordForm) => apiClient.post('/me/change-password', data),
|
||||
};
|
||||
|
||||
// stores/auth.ts
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
// ... state, getters
|
||||
actions: {
|
||||
async fetchUserProfile() {
|
||||
// (在App启动或登录后调用)
|
||||
const { data } = await meApi.getProfile();
|
||||
this.user = data;
|
||||
},
|
||||
async updateProfile(data) {
|
||||
const { data: updatedUser } = await meApi.updateProfile(data);
|
||||
this.user = { ...this.user, ...updatedUser };
|
||||
},
|
||||
async changePassword(data) {
|
||||
await meApi.changePassword(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 8. 样式设计
|
||||
|
||||
```scss
|
||||
.profile-page {
|
||||
padding: 24px;
|
||||
|
||||
.page-container {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
text-align: center;
|
||||
.user-avatar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.user-info h2 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.user-info p {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 测试用例
|
||||
|
||||
1. **集成测试**:
|
||||
- 测试页面是否能正确显示当前用户的个人信息。
|
||||
- 测试更新个人资料功能是否成功,并验证 store 中的用户信息是否同步更新。
|
||||
- 测试修改密码的成功流程,验证是否提示成功并跳转到登录页。
|
||||
- 测试修改密码的失败流程(如旧密码错误),验证是否显示正确的错误提示。
|
||||
- 测试所有表单验证规则是否生效(如邮箱格式、密码长度、两次密码一致性等)。
|
||||
Reference in New Issue
Block a user