ACS
This commit is contained in:
@@ -0,0 +1,525 @@
|
||||
<template>
|
||||
<div class="change-password-container">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">修改密码</h2>
|
||||
</div>
|
||||
|
||||
<div class="password-layout">
|
||||
<!-- 修改密码表单 -->
|
||||
<div class="password-form-container">
|
||||
<el-card shadow="hover" class="password-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><lock /></el-icon>
|
||||
<span>密码修改</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="passwordFormRef"
|
||||
:model="passwordForm"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
class="password-form"
|
||||
status-icon
|
||||
>
|
||||
<el-form-item label="当前密码" prop="oldPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.oldPassword"
|
||||
type="password"
|
||||
placeholder="请输入当前密码"
|
||||
show-password
|
||||
prefix-icon="Key"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
show-password
|
||||
prefix-icon="Lock"
|
||||
/>
|
||||
|
||||
<!-- 密码强度指示器 -->
|
||||
<div class="password-strength" v-if="passwordForm.newPassword">
|
||||
<div class="strength-label">密码强度:</div>
|
||||
<div class="strength-bar">
|
||||
<div
|
||||
class="strength-indicator"
|
||||
:class="[
|
||||
passwordStrength === 'weak' ? 'weak' :
|
||||
passwordStrength === 'medium' ? 'medium' :
|
||||
passwordStrength === 'strong' ? 'strong' : ''
|
||||
]"
|
||||
:style="{ width: strengthPercentage + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<div class="strength-text" :class="passwordStrength">
|
||||
{{ passwordStrengthText }}
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认新密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
show-password
|
||||
prefix-icon="Check"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
:loading="loading"
|
||||
:disabled="submitDisabled"
|
||||
class="submit-btn"
|
||||
>
|
||||
保存修改
|
||||
</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 密码规则说明 -->
|
||||
<div class="password-tips-container">
|
||||
<el-card shadow="hover" class="tips-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>密码要求</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="tips-content">
|
||||
<p class="tips-title">为了保障您的账号安全,密码需满足以下要求:</p>
|
||||
|
||||
<ul class="tips-list">
|
||||
<li class="tips-item" :class="{ passed: hasMinLength }">
|
||||
<el-icon :class="{ 'is-passed': hasMinLength }">
|
||||
<component :is="hasMinLength ? 'CircleCheck' : 'CircleClose'" />
|
||||
</el-icon>
|
||||
<span>长度至少8个字符</span>
|
||||
</li>
|
||||
<li class="tips-item" :class="{ passed: hasUpperCase }">
|
||||
<el-icon :class="{ 'is-passed': hasUpperCase }">
|
||||
<component :is="hasUpperCase ? 'CircleCheck' : 'CircleClose'" />
|
||||
</el-icon>
|
||||
<span>包含至少一个大写字母 (A-Z)</span>
|
||||
</li>
|
||||
<li class="tips-item" :class="{ passed: hasLowerCase }">
|
||||
<el-icon :class="{ 'is-passed': hasLowerCase }">
|
||||
<component :is="hasLowerCase ? 'CircleCheck' : 'CircleClose'" />
|
||||
</el-icon>
|
||||
<span>包含至少一个小写字母 (a-z)</span>
|
||||
</li>
|
||||
<li class="tips-item" :class="{ passed: hasNumber }">
|
||||
<el-icon :class="{ 'is-passed': hasNumber }">
|
||||
<component :is="hasNumber ? 'CircleCheck' : 'CircleClose'" />
|
||||
</el-icon>
|
||||
<span>包含至少一个数字 (0-9)</span>
|
||||
</li>
|
||||
<li class="tips-item" :class="{ passed: hasSpecialChar }">
|
||||
<el-icon :class="{ 'is-passed': hasSpecialChar }">
|
||||
<component :is="hasSpecialChar ? 'CircleCheck' : 'CircleClose'" />
|
||||
</el-icon>
|
||||
<span>包含至少一个特殊字符 (@$!%*#?&)</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tips-note">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<p>为了您的账户安全,请不要使用与其他网站相同的密码,并定期更换密码。</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
Lock,
|
||||
Key,
|
||||
InfoFilled,
|
||||
CircleCheck,
|
||||
CircleClose,
|
||||
Warning,
|
||||
Check
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
// 表单数据
|
||||
const passwordForm = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
const passwordFormRef = ref(null)
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 密码强度计算
|
||||
const hasMinLength = computed(() =>
|
||||
passwordForm.newPassword.length >= 8
|
||||
)
|
||||
|
||||
const hasUpperCase = computed(() =>
|
||||
/[A-Z]/.test(passwordForm.newPassword)
|
||||
)
|
||||
|
||||
const hasLowerCase = computed(() =>
|
||||
/[a-z]/.test(passwordForm.newPassword)
|
||||
)
|
||||
|
||||
const hasNumber = computed(() =>
|
||||
/\d/.test(passwordForm.newPassword)
|
||||
)
|
||||
|
||||
const hasSpecialChar = computed(() =>
|
||||
/[@$!%*#?&]/.test(passwordForm.newPassword)
|
||||
)
|
||||
|
||||
// 密码强度计算
|
||||
const passwordStrength = computed(() => {
|
||||
let score = 0
|
||||
|
||||
// 最低长度要求
|
||||
if (hasMinLength.value) score++
|
||||
|
||||
// 大写字母
|
||||
if (hasUpperCase.value) score++
|
||||
|
||||
// 小写字母
|
||||
if (hasLowerCase.value) score++
|
||||
|
||||
// 数字
|
||||
if (hasNumber.value) score++
|
||||
|
||||
// 特殊字符
|
||||
if (hasSpecialChar.value) score++
|
||||
|
||||
if (score <= 2) return 'weak'
|
||||
if (score <= 4) return 'medium'
|
||||
return 'strong'
|
||||
})
|
||||
|
||||
// 密码强度百分比
|
||||
const strengthPercentage = computed(() => {
|
||||
if (passwordStrength.value === 'weak') return 30
|
||||
if (passwordStrength.value === 'medium') return 70
|
||||
return 100
|
||||
})
|
||||
|
||||
// 密码强度文字描述
|
||||
const passwordStrengthText = computed(() => {
|
||||
if (passwordStrength.value === 'weak') return '弱'
|
||||
if (passwordStrength.value === 'medium') return '中'
|
||||
return '强'
|
||||
})
|
||||
|
||||
// 提交按钮是否禁用
|
||||
const submitDisabled = computed(() => {
|
||||
return (
|
||||
!passwordForm.oldPassword ||
|
||||
!passwordForm.newPassword ||
|
||||
!passwordForm.confirmPassword ||
|
||||
passwordStrength.value === 'weak'
|
||||
)
|
||||
})
|
||||
|
||||
// 密码强度校验
|
||||
const validatePasswordStrength = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请输入密码'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasMinLength.value) {
|
||||
callback(new Error('密码长度至少为8个字符'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!(hasUpperCase.value && hasLowerCase.value && hasNumber.value && hasSpecialChar.value)) {
|
||||
callback(new Error('密码必须包含大小写字母、数字和特殊字符'))
|
||||
return
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
// 确认密码校验
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
return
|
||||
}
|
||||
|
||||
if (value !== passwordForm.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
return
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
oldPassword: [
|
||||
{ required: true, message: '请输入当前密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度至少为6个字符', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ validator: validatePasswordStrength, trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
|
||||
await passwordFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
// 这里应该调用API修改密码
|
||||
// const res = await api.changePassword(passwordForm)
|
||||
|
||||
ElMessage.success('密码修改成功,请重新登录')
|
||||
loading.value = false
|
||||
|
||||
// 清空表单
|
||||
resetForm()
|
||||
|
||||
// 实际项目中可能需要跳转到登录页
|
||||
// router.push('/login')
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
ElMessage.error('密码修改失败,请重试')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
if (passwordFormRef.value) {
|
||||
passwordFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.change-password-container {
|
||||
padding: 24px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1f36;
|
||||
}
|
||||
|
||||
.password-layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.password-form-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.password-tips-container {
|
||||
width: 380px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.password-card,
|
||||
.tips-card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.password-form {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.password-strength {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.strength-label {
|
||||
margin-right: 8px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.strength-bar {
|
||||
width: 120px;
|
||||
height: 4px;
|
||||
background-color: #e4e7ed;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.strength-indicator {
|
||||
height: 100%;
|
||||
transition: width 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.strength-indicator.weak {
|
||||
background-color: #f56c6c;
|
||||
}
|
||||
|
||||
.strength-indicator.medium {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.strength-indicator.strong {
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.strength-text {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.strength-text.weak {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.strength-text.medium {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.strength-text.strong {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
padding: 0 0 15px;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.tips-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.tips-item .el-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
color: #c0c4cc;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.tips-item .el-icon.is-passed {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.tips-item.passed {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.tips-note {
|
||||
background-color: #fdf6ec;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tips-note .el-icon {
|
||||
color: #e6a23c;
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
margin-top: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tips-note p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #af8741;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.password-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.password-tips-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,529 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user