Files
ApiServer-Web-admin_dashboa…/src/views/profile/UserInfo.vue
T
2025-07-15 18:02:29 +08:00

529 lines
13 KiB
Vue

<template>
<div class="user-info-container">
<div class="page-header">
<h2 class="page-title">个人信息</h2>
<div class="page-actions">
<el-button type="primary" @click="handleEdit" v-if="!isEditing">
<el-icon><edit /></el-icon>编辑资料
</el-button>
</div>
</div>
<div class="profile-layout">
<!-- 左侧用户基本信息卡片 -->
<div class="profile-sidebar">
<div class="profile-card">
<div class="profile-avatar-container">
<el-avatar
:size="120"
:src="userInfo.avatar || 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'"
class="profile-avatar"
/>
<div class="profile-avatar-edit" v-if="isEditing">
<el-upload
class="avatar-uploader"
action="/api/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<div class="upload-trigger">
<el-icon><upload-filled /></el-icon>
<span>更换头像</span>
</div>
</el-upload>
</div>
</div>
<h3 class="profile-name">{{ userInfo.realName }}</h3>
<div class="profile-title">{{ userInfo.position }}</div>
<div class="profile-role">
<el-tag type="success" effect="plain">{{ userInfo.role }}</el-tag>
</div>
<div class="profile-stats">
<div class="stat-item">
<div class="stat-value">{{ userInfo.department }}</div>
<div class="stat-label">部门</div>
</div>
<div class="divider"></div>
<div class="stat-item">
<div class="stat-value">{{ formatDate(userInfo.lastLogin) }}</div>
<div class="stat-label">最近登录</div>
</div>
</div>
</div>
</div>
<!-- 右侧详细信息内容 -->
<div class="profile-content">
<!-- 查看模式 -->
<div v-if="!isEditing" class="info-display">
<el-card shadow="hover" class="info-card">
<template #header>
<div class="card-header">
<el-icon><user /></el-icon>
<span>账号信息</span>
</div>
</template>
<div class="info-list">
<div class="info-item">
<span class="info-label">用户名</span>
<span class="info-value">{{ userInfo.username }}</span>
</div>
<div class="info-item">
<span class="info-label">真实姓名</span>
<span class="info-value">{{ userInfo.realName }}</span>
</div>
<div class="info-item">
<span class="info-label">邮箱</span>
<span class="info-value">{{ userInfo.email }}</span>
</div>
<div class="info-item">
<span class="info-label">手机号</span>
<span class="info-value">{{ userInfo.phone }}</span>
</div>
</div>
</el-card>
<el-card shadow="hover" class="info-card bio-card">
<template #header>
<div class="card-header">
<el-icon><document /></el-icon>
<span>个人简介</span>
</div>
</template>
<div class="bio-content">
{{ userInfo.bio || '暂无个人简介' }}
</div>
</el-card>
<el-card shadow="hover" class="info-card">
<template #header>
<div class="card-header">
<el-icon><timer /></el-icon>
<span>时间信息</span>
</div>
</template>
<div class="info-list">
<div class="info-item">
<span class="info-label">账号创建时间</span>
<span class="info-value">{{ formatDateFull(userInfo.createTime) }}</span>
</div>
<div class="info-item">
<span class="info-label">最后登录时间</span>
<span class="info-value">{{ formatDateFull(userInfo.lastLogin) }}</span>
</div>
</div>
</el-card>
</div>
<!-- 编辑表单部分 -->
<div v-else class="info-edit">
<el-card shadow="hover" class="edit-card">
<template #header>
<div class="card-header">
<el-icon><edit-pen /></el-icon>
<span>编辑个人资料</span>
</div>
</template>
<el-form ref="userFormRef" :model="userForm" :rules="rules" label-width="100px" class="edit-form">
<el-form-item label="用户名" prop="username">
<el-input v-model="userForm.username" disabled />
</el-form-item>
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="userForm.realName" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userForm.email">
<template #prefix>
<el-icon><message /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="userForm.phone">
<template #prefix>
<el-icon><phone /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="部门">
<el-input v-model="userForm.department">
<template #prefix>
<el-icon><office-building /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="职位">
<el-input v-model="userForm.position" />
</el-form-item>
<el-form-item label="个人简介">
<el-input v-model="userForm.bio" type="textarea" :rows="4" resize="none" maxlength="200" show-word-limit />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm" :loading="loading">保存</el-button>
<el-button @click="cancelEdit">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import {
User, Edit, Document, Timer, EditPen, Message,
Phone, OfficeBuilding, UploadFilled
} from '@element-plus/icons-vue'
// 是否处于编辑模式
const isEditing = ref(false)
const loading = ref(false)
// 用户信息数据
const userInfo = reactive({
username: 'admin',
realName: '管理员',
email: 'admin@example.com',
phone: '13800138000',
department: '技术部',
position: '系统管理员',
role: '超级管理员',
createTime: '2023-01-01 00:00:00',
lastLogin: '2023-06-15 10:30:45',
bio: '系统管理员,负责系统的日常维护和管理工作。拥有丰富的系统管理经验,精通Linux服务器配置和维护,熟悉网络安全,对系统性能优化有独到见解。',
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
})
// 表单数据
const userForm = reactive({...userInfo})
// 表单校验规则
const rules = {
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
]
}
const userFormRef = ref(null)
// 日期格式化函数
const formatDate = (dateStr) => {
const date = new Date(dateStr)
const now = new Date()
// 如果是今天,只显示时间
if (date.toDateString() === now.toDateString()) {
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
}
// 否则显示日期
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' })
}
// 完整日期格式化
const formatDateFull = (dateStr) => {
const date = new Date(dateStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
// 编辑按钮点击事件
const handleEdit = () => {
Object.assign(userForm, userInfo)
isEditing.value = true
}
// 取消编辑
const cancelEdit = () => {
isEditing.value = false
}
// 提交表单
const submitForm = async () => {
if (!userFormRef.value) return
await userFormRef.value.validate(async (valid) => {
if (valid) {
try {
loading.value = true
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 更新本地用户信息
Object.assign(userInfo, userForm)
ElMessage.success('个人信息更新成功')
isEditing.value = false
loading.value = false
} catch (error) {
loading.value = false
ElMessage.error('保存失败,请重试')
console.error(error)
}
}
})
}
// 头像上传成功回调
const handleAvatarSuccess = (res) => {
userForm.avatar = res.url
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 500))
// 实际项目中,应该从后端获取用户信息并更新userInfo
} catch (error) {
ElMessage.error('获取用户信息失败')
console.error(error)
}
}
onMounted(() => {
fetchUserInfo()
})
</script>
<style scoped>
.user-info-container {
padding: 24px;
min-height: 100%;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.page-title {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #1a1f36;
}
.profile-layout {
display: flex;
gap: 24px;
}
.profile-sidebar {
width: 300px;
flex-shrink: 0;
}
.profile-content {
flex: 1;
}
.profile-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
padding: 30px;
text-align: center;
margin-bottom: 24px;
}
.profile-avatar-container {
position: relative;
margin-bottom: 20px;
display: inline-block;
}
.profile-avatar {
border: 4px solid #fff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.profile-avatar-edit {
position: absolute;
bottom: 0;
right: 0;
cursor: pointer;
}
.upload-trigger {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #409eff;
color: white;
font-size: 12px;
line-height: 1;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.5);
}
.upload-trigger span {
display: none;
}
.upload-trigger:hover {
width: auto;
height: auto;
border-radius: 18px;
padding: 4px 12px;
}
.upload-trigger:hover span {
display: inline-block;
margin-left: 4px;
}
.profile-name {
font-size: 22px;
font-weight: 600;
margin: 0 0 8px 0;
color: #1a1f36;
}
.profile-title {
color: #667085;
font-size: 16px;
margin-bottom: 14px;
}
.profile-role {
margin-bottom: 24px;
}
.profile-stats {
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #edf2f7;
padding-top: 20px;
}
.stat-item {
flex: 1;
text-align: center;
}
.stat-value {
font-weight: 600;
color: #333;
font-size: 16px;
margin-bottom: 4px;
}
.stat-label {
color: #94a3b8;
font-size: 12px;
}
.divider {
width: 1px;
height: 30px;
background-color: #edf2f7;
margin: 0 15px;
}
.info-card {
margin-bottom: 24px;
border-radius: 8px;
overflow: hidden;
}
.bio-card {
position: relative;
}
.card-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #1a1f36;
}
.card-header .el-icon {
margin-right: 8px;
font-size: 18px;
color: #409eff;
}
.info-list {
display: grid;
gap: 16px;
}
.info-item {
display: flex;
align-items: center;
}
.info-label {
width: 120px;
color: #64748b;
font-size: 14px;
}
.info-value {
color: #334155;
font-weight: 500;
flex: 1;
}
.bio-content {
color: #475569;
line-height: 1.6;
font-size: 14px;
white-space: pre-line;
}
.edit-card {
border-radius: 8px;
overflow: hidden;
}
.edit-form {
padding: 16px 0;
}
@media (max-width: 992px) {
.profile-layout {
flex-direction: column;
}
.profile-sidebar {
width: 100%;
}
.profile-card {
padding: 20px;
}
}
</style>