529 lines
13 KiB
Vue
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> |