This commit is contained in:
kk
2026-07-08 18:00:33 +08:00
commit e66b4a3680
312 changed files with 54718 additions and 0 deletions

View File

@@ -0,0 +1,485 @@
<template>
<div>
<basic-container>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<!-- 个人信息 Tab -->
<el-tab-pane label="个人信息" name="info">
<el-form ref="infoForm" :model="infoForm" label-width="100px">
<el-form-item label="头像">
<div class="avatar-upload-container">
<div class="avatar-wrapper" v-if="infoForm.avatar">
<img :src="infoForm.avatar" alt="用户头像" class="avatar" />
<div class="avatar-overlay">
<el-icon class="avatar-action" @click="previewAvatar"><View /></el-icon>
<el-icon class="avatar-action" @click="deleteAvatar"><Delete /></el-icon>
</div>
</div>
<el-upload
v-else
class="avatar-uploader"
action="/api/blade-resource/oss/endpoint/put-file"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
accept=".jpg,.jpeg,.png,.gif"
>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="el-upload__tip">支持 JPG/PNG/GIF 格式文件大小不超过 1MB</div>
</div>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="infoForm.realName" placeholder="请输入姓名">
<template #prefix>
<el-icon><Bell /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="用户名">
<el-input v-model="infoForm.name" placeholder="请输入用户名">
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="infoForm.phone" placeholder="请输入手机号">
<template #prefix>
<el-icon><Iphone /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="infoForm.email" placeholder="请输入邮箱">
<template #prefix>
<el-icon><Message /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="button-group">
<el-button type="primary" @click="submitInfo" :loading="loading">
<el-icon><Check /></el-icon>
<span>提交</span>
</el-button>
<el-button @click="resetForm">
<el-icon><RefreshRight /></el-icon>
<span>清空</span>
</el-button>
</div>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 修改密码 Tab -->
<el-tab-pane label="修改密码" name="password">
<el-form ref="passwordForm" :model="passwordForm" label-width="100px">
<el-form-item label="原密码">
<el-input
v-model="passwordForm.oldPassword"
type="password"
placeholder="请输入原密码"
show-password
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="新密码">
<el-input
v-model="passwordForm.newPassword"
type="password"
placeholder="请输入新密码"
show-password
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="确认密码">
<el-input
v-model="passwordForm.newPassword1"
type="password"
placeholder="请确认新密码"
show-password
>
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="button-group">
<el-button type="primary" @click="submitPassword" :loading="loading">
<el-icon><Check /></el-icon>
<span>提交</span>
</el-button>
<el-button @click="resetForm">
<el-icon><RefreshRight /></el-icon>
<span>清空</span>
</el-button>
</div>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</basic-container>
<!-- 头像预览组件 -->
<el-image-viewer
v-if="showAvatarPreview"
:url-list="avatarPreviewList"
:initial-index="0"
:hide-on-click-modal="true"
:teleported="true"
@close="showAvatarPreview = false"
/>
</div>
</template>
<script>
import { getUserInfo, updateInfo, updatePassword } from '@/api/system/user';
import md5 from 'js-md5';
import { sensitive } from '@/utils/sensitive';
import {
Plus,
Bell,
User,
Iphone,
Message,
Lock,
Check,
RefreshRight,
View,
Delete,
} from '@element-plus/icons-vue';
import { ElImageViewer } from 'element-plus';
import { getToken } from '@/utils/auth';
export default {
components: {
ElImageViewer,
Plus,
Bell,
User,
Iphone,
Message,
Lock,
Check,
RefreshRight,
View,
Delete,
},
data() {
return {
activeTab: 'info',
loading: false,
infoForm: {
id: '',
avatar: '',
name: '',
realName: '',
phone: '',
email: '',
},
passwordForm: {
oldPassword: '',
newPassword: '',
newPassword1: '',
},
sensitiveManager: null,
showAvatarPreview: false,
avatarPreviewList: [],
uploadHeaders: {
'Blade-Auth': `bearer ${getToken()}`,
'Blade-Requested-With': 'BladeHttpRequest',
},
};
},
created() {
// 初始化脱敏工具
this.sensitiveManager = sensitive.create({
fields: ['phone', 'email'],
});
// 初始化时根据默认tab加载数据
if (this.activeTab === 'info') {
this.loadUserInfo();
}
},
methods: {
// Tab切换处理
handleTabChange(name) {
if (name === 'info') {
this.loadUserInfo();
}
},
// 加载用户信息
loadUserInfo() {
getUserInfo().then(res => {
const user = res.data.data;
this.infoForm = {
id: user.id,
avatar: user.avatar,
name: user.name,
realName: user.realName,
phone: user.phone,
email: user.email,
};
// 保存初始脱敏数据
this.sensitiveManager.saveInitialData(this.infoForm);
});
},
// 提交个人信息
submitInfo() {
this.loading = true;
const submitData = this.sensitiveManager.getSubmitData(this.infoForm);
updateInfo(submitData).then(
res => {
if (res.data.success) {
this.$message({
type: 'success',
message: '修改信息成功!',
});
} else {
this.$message({
type: 'error',
message: res.data.msg,
});
}
this.loading = false;
},
error => {
window.console.log(error);
this.loading = false;
}
);
},
// 提交密码修改
submitPassword() {
this.loading = true;
updatePassword(
md5(this.passwordForm.oldPassword),
md5(this.passwordForm.newPassword),
md5(this.passwordForm.newPassword1)
).then(
res => {
if (res.data.success) {
this.$message({
type: 'success',
message: '修改密码成功!',
});
// 清空密码表单
this.passwordForm = {
oldPassword: '',
newPassword: '',
newPassword1: '',
};
} else {
this.$message({
type: 'error',
message: res.data.msg,
});
}
this.loading = false;
},
error => {
window.console.log(error);
this.loading = false;
}
);
},
// 头像上传成功处理
handleAvatarSuccess(response) {
if (response && response.data) {
this.infoForm.avatar = response.data.link || response.data;
}
},
// 头像上传前验证
beforeAvatarUpload(file) {
const isValidType = ['image/jpeg', 'image/png', 'image/gif'].includes(file.type);
const isLt1M = file.size / 1024 < 1024;
if (!isValidType) {
this.$message.error('上传头像只支持 JPG/PNG/GIF 格式!');
}
if (!isLt1M) {
this.$message.error('上传头像大小不能超过 1MB!');
}
return isValidType && isLt1M;
},
// 重置表单
resetForm() {
if (this.activeTab === 'info') {
// 清空个人信息表单但保留id
const id = this.infoForm.id;
this.infoForm = {
id: id,
avatar: '',
name: '',
realName: '',
phone: '',
email: '',
};
} else if (this.activeTab === 'password') {
// 清空密码表单
this.passwordForm = {
oldPassword: '',
newPassword: '',
newPassword1: '',
};
}
},
// 预览头像
previewAvatar() {
if (this.infoForm.avatar) {
this.avatarPreviewList = [this.infoForm.avatar];
this.showAvatarPreview = true;
} else {
this.$message.warning('暂无头像可预览');
}
},
// 删除头像
deleteAvatar() {
this.$confirm('确定要删除头像吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.infoForm.avatar = '';
this.$message.success('头像已删除');
})
.catch(() => {});
},
},
};
</script>
<style scoped>
.avatar-upload-container {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.avatar-wrapper {
position: relative;
width: 200px;
height: 200px;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.avatar-wrapper:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.avatar-wrapper .avatar {
width: 200px;
height: 200px;
display: block;
border-radius: 12px;
object-fit: cover;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.6) 100%);
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
opacity: 0;
transition: opacity 0.3s ease;
backdrop-filter: blur(2px);
}
.avatar-wrapper:hover .avatar-overlay {
opacity: 1;
}
.avatar-action {
font-size: 20px;
color: #fff;
cursor: pointer;
padding: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-action:hover {
background-color: rgba(255, 255, 255, 0.3);
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.avatar-uploader :deep(.el-upload) {
border: 2px dashed #d9d9d9;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
width: 200px;
height: 200px;
background-color: #fafafa;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-uploader :deep(.el-upload:hover) {
border-color: var(--el-color-primary);
background-color: #f5f5f5;
}
.avatar-uploader-icon {
font-size: 40px;
color: #8c939d;
transition: color 0.3s ease;
}
.avatar-uploader :deep(.el-upload:hover) .avatar-uploader-icon {
color: var(--el-color-primary);
}
.el-upload__tip {
font-size: 12px;
color: #969799;
margin-top: 10px;
line-height: 1.5;
padding-left: 4px;
}
.button-group {
display: flex;
justify-content: center;
gap: 16px;
width: 100%;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>