Files
tms-erp-web/src/views/system/userinfo.vue
2026-07-08 18:00:33 +08:00

486 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>