feat(personal): 全面重构个人中心页面UI和功能
- 重新设计页面布局,移除顶部标题区域 - 优化个人信息展示,调整字段排序和显示位置 - 新增角色信息显示,支持多角色标签展示 - 修复状态标签颜色显示逻辑,启用状态使用主题色 - 使用n-tabs组件重新设计右侧操作区域 - 集成基本资料编辑和密码修改为标签页形式 - 优化日期格式显示为YYYY-MM-DD HH:mm:ss格式 - 改进表单验证错误处理机制 - 调整操作按钮位置到表单下方左侧 - 移除弹框设计,改为内联表单编辑 主要改进: - 统一视觉体验和交互逻辑 - 更好的空间利用和布局优化 - 增强的表单验证用户体验 - 清晰的功能分组和操作流程
This commit is contained in:
parent
b8ee337a75
commit
8f8a416e88
@ -2,16 +2,6 @@
|
|||||||
<div class="p-1 bg-gray-50 h-screen flex flex-col">
|
<div class="p-1 bg-gray-50 h-screen flex flex-col">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-100 flex-1 overflow-auto">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-100 flex-1 overflow-auto">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<!-- 页面标题 -->
|
|
||||||
<div class="mb-6">
|
|
||||||
<h1 class="text-2xl font-bold text-gray-900">
|
|
||||||
个人中心
|
|
||||||
</h1>
|
|
||||||
<p class="text-gray-600 mt-1">
|
|
||||||
管理您的个人信息和账户设置
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- 左侧个人信息卡片 -->
|
<!-- 左侧个人信息卡片 -->
|
||||||
<div class="lg:col-span-1">
|
<div class="lg:col-span-1">
|
||||||
@ -56,48 +46,68 @@
|
|||||||
@change="handleAvatarChange"
|
@change="handleAvatarChange"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- 用户名 -->
|
|
||||||
<h3 class="text-xl font-semibold mt-4 text-gray-900">
|
|
||||||
{{ personalData.userName || '未设置' }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- 登录名 -->
|
|
||||||
<p class="text-gray-500 text-sm">
|
|
||||||
@{{ personalData.loginName }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- 头像上传提示 -->
|
<!-- 头像上传提示 -->
|
||||||
<p class="text-xs text-gray-400 mt-2">
|
<p class="text-xs text-gray-400 mt-4">
|
||||||
点击头像上传新头像
|
点击头像上传新头像
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100">
|
<div class="flex justify-between items-center py-2">
|
||||||
|
<span class="text-gray-600">登录账号</span>
|
||||||
|
<span class="text-gray-900 text-sm">{{ personalData.loginName || '未设置' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center py-2">
|
||||||
|
<span class="text-gray-600">用户姓名</span>
|
||||||
|
<span class="text-gray-900 text-sm">{{ personalData.userName || '未设置' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center py-2">
|
||||||
|
<span class="text-gray-600">用户角色</span>
|
||||||
|
<div>
|
||||||
|
<n-tag
|
||||||
|
v-for="(roleName, index) in getRoleNames()"
|
||||||
|
:key="index"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mr-1"
|
||||||
|
>
|
||||||
|
{{ roleName }}
|
||||||
|
</n-tag>
|
||||||
|
<span v-if="getRoleNames().length === 0" class="text-gray-500">未分配角色</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-gray-600">邮箱</span>
|
<span class="text-gray-600">邮箱</span>
|
||||||
<span class="text-gray-900">{{ personalData.email || '未设置' }}</span>
|
<span class="text-gray-900">{{ personalData.email || '未设置' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100">
|
<div class="flex justify-between items-center py-2">
|
||||||
<span class="text-gray-600">手机号</span>
|
<span class="text-gray-600">手机号</span>
|
||||||
<span class="text-gray-900">{{ personalData.phone || '未设置' }}</span>
|
<span class="text-gray-900">{{ personalData.phone || '未设置' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100">
|
<div class="flex justify-between items-center py-2">
|
||||||
<span class="text-gray-600">性别</span>
|
<span class="text-gray-600">性别</span>
|
||||||
<span class="text-gray-900">{{ getGenderText(personalData.sex || '3') }}</span>
|
<span class="text-gray-900">{{ getGenderText(personalData.sex || '3') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center py-2 border-b border-gray-100">
|
<div class="flex justify-between items-center py-2">
|
||||||
<span class="text-gray-600">状态</span>
|
<span class="text-gray-600">状态</span>
|
||||||
<n-tag :type="personalData.userStatus === '0' ? 'success' : 'error'" size="small">
|
<n-tag
|
||||||
|
:type="(personalData.userStatus || '0') === '0' ? 'info' : 'error'"
|
||||||
|
:style="(personalData.userStatus || '0') === '0' ? { backgroundColor: '#6366f1', color: 'white', border: 'none' } : {}"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
{{ getStatusText(personalData.userStatus || '0') }}
|
{{ getStatusText(personalData.userStatus || '0') }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center py-2">
|
<div class="flex justify-between items-center py-2">
|
||||||
<span class="text-gray-600">注册时间</span>
|
<span class="text-gray-600">创建日期</span>
|
||||||
<span class="text-gray-900 text-sm">{{ formatDate(personalData.createTime || '') }}</span>
|
<span class="text-gray-900 text-sm">{{ formatDate(personalData.createTime || '') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -106,26 +116,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧操作区域 -->
|
<!-- 右侧操作区域 -->
|
||||||
<div class="lg:col-span-2 space-y-6">
|
<div class="lg:col-span-2">
|
||||||
<!-- 基本资料编辑 -->
|
<n-card>
|
||||||
<n-card title="基本资料编辑">
|
<n-tabs type="line" animated>
|
||||||
<template #header-extra>
|
<!-- 基本资料 -->
|
||||||
<n-space>
|
<n-tab-pane name="basic" tab="基本资料">
|
||||||
<n-button :loading="loading" @click="handleBasicReset">
|
<div class="pt-4">
|
||||||
<template #icon>
|
<!-- 基本资料表单 -->
|
||||||
<icon-park-outline-refresh />
|
|
||||||
</template>
|
|
||||||
重置
|
|
||||||
</n-button>
|
|
||||||
<n-button type="primary" :loading="loading" @click="handleBasicSave">
|
|
||||||
<template #icon>
|
|
||||||
<icon-park-outline-save />
|
|
||||||
</template>
|
|
||||||
保存
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<n-form
|
<n-form
|
||||||
ref="basicFormRef"
|
ref="basicFormRef"
|
||||||
:model="basicForm"
|
:model="basicForm"
|
||||||
@ -156,20 +153,32 @@
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</div>
|
</div>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 密码管理 -->
|
<!-- 操作按钮 -->
|
||||||
<n-card title="密码管理">
|
<div class="flex justify-start mt-6">
|
||||||
<template #header-extra>
|
<n-space>
|
||||||
<n-button type="warning" @click="openPasswordDialog">
|
<n-button :loading="loading" @click="handleBasicReset">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-park-outline-lock />
|
<icon-park-outline-refresh />
|
||||||
</template>
|
</template>
|
||||||
修改密码
|
重置
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<n-button type="primary" :loading="loading" @click="handleBasicSave">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-save />
|
||||||
</template>
|
</template>
|
||||||
|
保存
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
<!-- 修改密码 -->
|
||||||
|
<n-tab-pane name="password" tab="修改密码">
|
||||||
|
<div class="pt-4">
|
||||||
|
<!-- 密码安全提示 -->
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<icon-park-outline-info class="text-yellow-600 text-lg mt-0.5 mr-3" />
|
<icon-park-outline-info class="text-yellow-600 text-lg mt-0.5 mr-3" />
|
||||||
<div>
|
<div>
|
||||||
@ -182,32 +191,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 修改密码弹框 -->
|
<!-- 密码修改表单 -->
|
||||||
<NovaDialog
|
|
||||||
ref="passwordDialogRef"
|
|
||||||
title="修改密码"
|
|
||||||
:width="500"
|
|
||||||
height="auto"
|
|
||||||
confirm-text="确认修改"
|
|
||||||
cancel-text="取消"
|
|
||||||
@nova-confirm="handlePasswordSubmit"
|
|
||||||
@nova-cancel="handlePasswordCancel"
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div class="px-3 py-2">
|
|
||||||
<n-form
|
<n-form
|
||||||
ref="passwordFormRef"
|
ref="passwordFormRef"
|
||||||
:model="passwordForm"
|
:model="passwordForm"
|
||||||
:rules="passwordRules"
|
:rules="passwordRules"
|
||||||
label-placement="top"
|
label-placement="left"
|
||||||
|
label-width="100px"
|
||||||
require-mark-placement="right-hanging"
|
require-mark-placement="right-hanging"
|
||||||
>
|
>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<n-form-item label="当前密码" path="password">
|
<n-form-item label="当前密码" path="password">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="passwordForm.password"
|
v-model:value="passwordForm.password"
|
||||||
@ -226,7 +220,7 @@
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item label="确认新密码" path="confirmPassword">
|
<n-form-item label="确认新密码" path="confirmPassword" class="md:col-span-2">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="passwordForm.confirmPassword"
|
v-model:value="passwordForm.confirmPassword"
|
||||||
type="password"
|
type="password"
|
||||||
@ -234,20 +228,43 @@
|
|||||||
placeholder="请再次输入新密码"
|
placeholder="请再次输入新密码"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
|
||||||
</div>
|
</div>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex justify-start mt-6">
|
||||||
|
<n-space>
|
||||||
|
<n-button @click="handlePasswordReset">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-refresh />
|
||||||
</template>
|
</template>
|
||||||
</NovaDialog>
|
重置
|
||||||
|
</n-button>
|
||||||
|
<n-button type="warning" :loading="loading" @click="handlePasswordSubmit">
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-lock />
|
||||||
|
</template>
|
||||||
|
修改密码
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useAuthStore } from '@/store/auth'
|
import { useAuthStore } from '@/store/auth'
|
||||||
import { coiMsgError, coiMsgSuccess } from '@/utils/coi'
|
import { coiMsgError, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||||
import { getPersonalData, updateBasicData, updatePassword, uploadAvatar } from '@/service/api/personal'
|
import { getPersonalData, updateBasicData, updatePassword, uploadAvatar } from '@/service/api/personal'
|
||||||
import type { PersonalDataVo, UpdatePasswordBo, UpdatePersonalBo } from '@/service/api/personal'
|
import type { PersonalDataVo, UpdatePasswordBo, UpdatePersonalBo } from '@/service/api/personal'
|
||||||
import NovaDialog from '@/components/common/NovaDialog.vue'
|
|
||||||
import { serviceConfig } from '@/../service.config'
|
import { serviceConfig } from '@/../service.config'
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@ -266,6 +283,8 @@ const personalData = ref<PersonalDataVo>({
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
userStatus: '0',
|
userStatus: '0',
|
||||||
createTime: '',
|
createTime: '',
|
||||||
|
roleNames: [],
|
||||||
|
roleName: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
@ -289,9 +308,6 @@ const passwordForm = ref<UpdatePasswordBo>({
|
|||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹框控制
|
|
||||||
const passwordDialogRef = ref()
|
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const basicRules = {
|
const basicRules = {
|
||||||
userName: [
|
userName: [
|
||||||
@ -352,6 +368,17 @@ function getStatusText(status: string) {
|
|||||||
return option?.label || '未知'
|
return option?.label || '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取角色名称列表
|
||||||
|
function getRoleNames(): string[] {
|
||||||
|
if (personalData.value.roleNames && personalData.value.roleNames.length > 0) {
|
||||||
|
return personalData.value.roleNames
|
||||||
|
}
|
||||||
|
if (personalData.value.roleName) {
|
||||||
|
return [personalData.value.roleName]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
// 获取个人资料
|
// 获取个人资料
|
||||||
async function fetchPersonalData() {
|
async function fetchPersonalData() {
|
||||||
try {
|
try {
|
||||||
@ -451,8 +478,21 @@ async function handleAvatarSave() {
|
|||||||
|
|
||||||
// 保存基本信息
|
// 保存基本信息
|
||||||
async function handleBasicSave() {
|
async function handleBasicSave() {
|
||||||
|
if (!basicFormRef.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先进行表单验证
|
||||||
|
await basicFormRef.value.validate()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 表单验证失败,提示用户检查填写内容
|
||||||
|
coiMsgWarning('验证失败,请检查填写内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单验证通过,执行API调用
|
||||||
try {
|
try {
|
||||||
await basicFormRef.value?.validate()
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const result = await updateBasicData(basicForm.value)
|
const result = await updateBasicData(basicForm.value)
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
@ -487,25 +527,38 @@ function handleBasicReset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开修改密码弹框
|
// 重置密码表单
|
||||||
function openPasswordDialog() {
|
function handlePasswordReset() {
|
||||||
passwordForm.value = {
|
passwordForm.value = {
|
||||||
password: '',
|
password: '',
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
}
|
}
|
||||||
passwordDialogRef.value?.novaOpen()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交密码修改
|
// 提交密码修改
|
||||||
async function handlePasswordSubmit() {
|
async function handlePasswordSubmit() {
|
||||||
|
if (!passwordFormRef.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先进行表单验证
|
||||||
|
await passwordFormRef.value.validate()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 表单验证失败,提示用户检查填写内容
|
||||||
|
coiMsgWarning('验证失败,请检查填写内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单验证通过,执行API调用
|
||||||
try {
|
try {
|
||||||
await passwordFormRef.value?.validate()
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const result = await updatePassword(passwordForm.value)
|
const result = await updatePassword(passwordForm.value)
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
coiMsgSuccess('密码修改成功')
|
coiMsgSuccess('密码修改成功')
|
||||||
passwordDialogRef.value?.novaClose()
|
// 重置表单
|
||||||
|
handlePasswordReset()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
coiMsgError('密码修改失败')
|
coiMsgError('密码修改失败')
|
||||||
@ -519,16 +572,20 @@ async function handlePasswordSubmit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消密码修改
|
|
||||||
function handlePasswordCancel() {
|
|
||||||
passwordDialogRef.value?.novaClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
function formatDate(dateStr: string) {
|
function formatDate(dateStr: string) {
|
||||||
if (!dateStr)
|
if (!dateStr)
|
||||||
return '-'
|
return '-'
|
||||||
return new Date(dateStr).toLocaleString('zh-CN')
|
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面初始化
|
// 页面初始化
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user