UNIVPLMDataIntegration/Web/src/views/system/user/component/userCenter.vue

402 lines
14 KiB
Vue
Raw Normal View History

2024-06-15 13:02:35 +08:00
<template>
<div class="sys-userCenter-container">
<el-row :gutter="5" style="width: 100%">
<el-col :span="8" :xs="24">
<el-card shadow="hover">
<div class="account-center-avatarHolder">
<!-- <el-upload class="h100" ref="uploadAvatarRef" action="" :limit="1" :show-file-list="false" :auto-upload="false" :on-change="uploadAvatarFile" accept=".jpg,.png,.bmp,.gif">
<el-avatar :size="100" :src="userInfos.avatar" />
</el-upload> -->
<el-avatar
:size="100"
:src="userInfos.avatar"
@click="openCropperDialog"
v-loading="state.avatarLoading"
element-loading-spinner="el-icon-Upload"
element-loading-background="rgba(0, 0, 0, 0.2)"
@mouseenter="mouseEnterAvatar"
@mouseleave="mouseLeaveAvatar"
/>
<div class="username">{{ userInfos.realName }}</div>
</div>
<div class="account-center-org">
<p>
<el-icon><ele-School /></el-icon> <span>{{ userInfos.orgName ?? '' }}</span>
</p>
<p>
<el-icon><ele-Mug /></el-icon> <span>{{ userInfos.posName ?? '' }}</span>
</p>
<p>
<el-icon><ele-LocationInformation /></el-icon> <span>{{ userInfos.address ?? '' }}</span>
</p>
</div>
<div class="image-signature">
<el-image :src="userInfos.signature" fit="contain" alt="电子签名" loading="lazy" style="width: 100%; height: 100%"> </el-image>
</div>
2024-07-22 02:54:41 +08:00
<el-button icon="ele-Edit" type="primary" @click="openSignDialog" v-auth="'sysFile/uploadSignature'"> 电子签名 </el-button>
2024-06-15 13:02:35 +08:00
<el-upload
ref="uploadSignRef"
action=""
accept=".png"
:limit="1"
:show-file-list="false"
:auto-upload="false"
:on-change="uploadSignFile"
:on-exceed="uploadSignFileExceed"
style="display: inline-block; margin-left: 12px; position: absolute"
>
2024-07-22 02:54:41 +08:00
<el-button icon="ele-UploadFilled" v-auth="'sysFile/uploadSignature'">上传手写签名</el-button>
2024-06-15 13:02:35 +08:00
</el-upload>
</el-card>
</el-col>
<el-col :span="16" :xs="24">
<el-card shadow="hover">
<el-tabs>
<el-tab-pane label="基础信息" v-loading="state.loading">
<el-form :model="state.ruleFormBase" ref="ruleFormBaseRef" label-width="auto">
2024-07-01 01:40:37 +08:00
<el-row :gutter="10">
2024-06-15 13:02:35 +08:00
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="真实姓名" prop="realName" :rules="[{ required: true, message: '真实姓名不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleFormBase.realName" placeholder="真实姓名" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="昵称">
<el-input v-model="state.ruleFormBase.nickName" placeholder="昵称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号码" prop="phone" :rules="[{ required: true, message: '手机号码不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleFormBase.phone" placeholder="手机号码" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="state.ruleFormBase.email" placeholder="邮箱" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="出生日期" prop="birthday" :rules="[{ required: true, message: '出生日期不能为空', trigger: 'blur' }]">
<el-date-picker v-model="state.ruleFormBase.birthday" type="date" placeholder="出生日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="性别">
<el-radio-group v-model="state.ruleFormBase.sex">
<el-radio :value="1"></el-radio>
<el-radio :value="2"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="地址">
<el-input v-model="state.ruleFormBase.address" placeholder="地址" clearable type="textarea" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="备注">
<el-input v-model="state.ruleFormBase.remark" placeholder="备注" clearable type="textarea" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item>
2024-07-22 02:54:41 +08:00
<el-button icon="ele-SuccessFilled" type="primary" @click="submitUserBase" v-auth="'sysUser/baseInfo'"> 保存基本信息 </el-button>
2024-06-15 13:02:35 +08:00
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
<el-tab-pane label="组织机构">
<OrgTree ref="orgTreeRef" />
</el-tab-pane>
<el-tab-pane label="修改密码">
<el-form ref="ruleFormPasswordRef" :model="state.ruleFormPassword" label-width="auto">
<el-form-item label="当前密码" prop="passwordOld" :rules="[{ required: true, message: '当前密码不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleFormPassword.passwordOld" type="password" autocomplete="off" show-password />
2024-06-15 13:02:35 +08:00
</el-form-item>
<el-form-item label="新密码" prop="passwordNew" :rules="[{ required: true, message: '新密码不能为空', trigger: 'blur' }]">
<el-input v-model="state.ruleFormPassword.passwordNew" type="password" autocomplete="off" show-password />
2024-06-15 13:02:35 +08:00
</el-form-item>
<el-form-item label="确认密码" prop="passwordNew2" :rules="[{ validator: validatePassword, required: true, trigger: 'blur' }]">
<el-input v-model="state.passwordNew2" type="password" autocomplete="off" show-password />
2024-06-15 13:02:35 +08:00
</el-form-item>
<el-form-item>
<el-button icon="ele-Refresh" @click="resetPassword"> </el-button>
2024-07-22 02:54:41 +08:00
<el-button icon="ele-SuccessFilled" type="primary" @click="submitPassword" v-auth="'sysUser/changePwd'"> </el-button>
2024-06-15 13:02:35 +08:00
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
2024-07-01 01:40:37 +08:00
<el-dialog v-model="state.signDialogVisible" draggable :close-on-click-modal="false" width="600px">
2024-06-15 13:02:35 +08:00
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-EditPen /> </el-icon>
<span> 电子签名 </span>
</div>
</template>
<div style="border: 1px dashed gray; width: 100%; height: 250px">
<VueSignaturePad ref="signaturePadRef" :options="state.signOptions" style="background-color: #fff" />
</div>
<div style="margin-top: 10px">
<div style="display: inline">画笔粗细<el-input-number v-model="state.signOptions.minWidth" :min="0.5" :max="2.5" :step="0.1" size="small" /></div>
<div style="display: inline; margin-left: 30px">画笔颜色<el-color-picker v-model="state.signOptions.penColor" color-format="hex" size="default"> </el-color-picker></div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="unDoSign">撤销</el-button>
<el-button @click="clearSign">清屏</el-button>
<el-button type="primary" @click="saveUploadSign">保存</el-button>
</span>
</template>
</el-dialog>
<CropperDialog ref="cropperDialogRef" :title="state.cropperTitle" @uploadCropperImg="uploadCropperImg" />
</div>
</template>
<script lang="ts" setup name="sysUserCenter">
import { onMounted, watch, reactive, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElForm, ElMessageBox, genFileId } from 'element-plus';
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
import { useUserInfo } from '/@/stores/userInfo';
import { base64ToFile, blobToFile } from '/@/utils/base64Conver';
2024-06-15 13:02:35 +08:00
import OrgTree from '/@/views/system/user/component/orgTree.vue';
import CropperDialog from '/@/components/cropper/index.vue';
import VueGridLayout from 'vue-grid-layout';
import { sm2 } from 'sm-crypto-v2';
import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
2024-07-01 01:53:41 +08:00
2024-06-15 13:02:35 +08:00
import { SysFileApi, SysUserApi } from '/@/api-services/api';
import { ChangePwdInput, SysUser, SysFile } from '/@/api-services/models';
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
const uploadSignRef = ref<UploadInstance>();
//const uploadAvatarRef = ref<UploadInstance>();
const signaturePadRef = ref<InstanceType<typeof VueGridLayout>>();
const ruleFormBaseRef = ref<InstanceType<typeof ElForm>>();
const ruleFormPasswordRef = ref<InstanceType<typeof ElForm>>();
const cropperDialogRef = ref<InstanceType<typeof CropperDialog>>();
const state = reactive({
loading: false,
avatarLoading: false,
signDialogVisible: false,
ruleFormBase: {} as SysUser,
ruleFormPassword: {} as ChangePwdInput,
signOptions: {
penColor: '#000000',
minWidth: 1.0,
onBegin: () => {
signaturePadRef.value.resizeCanvas();
},
},
signFileList: [] as any,
passwordNew2: '',
cropperTitle: '',
});
2024-07-01 01:53:41 +08:00
// 页面初始化
2024-06-15 13:02:35 +08:00
onMounted(async () => {
state.loading = true;
var res = await getAPI(SysUserApi).apiSysUserBaseInfoGet();
state.ruleFormBase = res.data.result ?? { account: '' };
state.loading = false;
});
2024-07-01 01:53:41 +08:00
// 签名监听
2024-06-15 13:02:35 +08:00
watch(state.signOptions, () => {
signaturePadRef.value.signaturePad.penColor = state.signOptions.penColor;
signaturePadRef.value.signaturePad.minWidth = state.signOptions.minWidth;
});
// 上传头像图片
const uploadCropperImg = async (e: any) => {
var res = await getAPI(SysFileApi).apiSysFileUploadAvatarPostForm(blobToFile(e.img, userInfos.value.account + '.png'));
2024-06-15 13:02:35 +08:00
userInfos.value.avatar = getFileUrl(res.data.result!);
2024-09-04 11:02:10 +08:00
state.ruleFormBase.avatar = userInfos.value.avatar;
2024-06-15 13:02:35 +08:00
};
// 打开电子签名页面
const openSignDialog = () => {
state.signDialogVisible = true;
};
// 保存并上传电子签名
const saveUploadSign = async () => {
const { isEmpty, data } = signaturePadRef.value.saveSignature();
2024-09-04 11:02:10 +08:00
if (isEmpty) {
userInfos.value.signature = null;
state.ruleFormBase.signature = null;
} else {
var res = await getAPI(SysFileApi).apiSysFileUploadSignaturePostForm(base64ToFile(data, userInfos.value.account + '.png'));
userInfos.value.signature = getFileUrl(res.data.result!);
state.ruleFormBase.signature = userInfos.value.signature;
}
2024-06-15 13:02:35 +08:00
clearSign();
state.signDialogVisible = false;
};
// 撤销电子签名
const unDoSign = () => {
signaturePadRef.value.undoSignature();
};
// 清空电子签名
const clearSign = () => {
signaturePadRef.value.clearSignature();
};
// 上传手写电子签名
const uploadSignFile = async (file: any) => {
var res = await getAPI(SysFileApi).apiSysFileUploadSignaturePostForm(file.raw);
userInfos.value.signature = res.data.result?.url;
2024-09-04 11:02:10 +08:00
state.ruleFormBase.signature = userInfos.value.signature;
2024-06-15 13:02:35 +08:00
};
// 获得电子签名文件列表
const handleChangeSignFile = (_file: any, fileList: []) => {
state.signFileList = fileList;
};
// 修改个人信息
const submitUserBase = () => {
ruleFormBaseRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
ElMessageBox.confirm('确定修改个人基础信息?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await getAPI(SysUserApi).apiSysUserBaseInfoPost(state.ruleFormBase);
});
});
};
// 密码验证
const validatePassword = (_rule: any, value: any, callback: any) => {
if (state.passwordNew2 != state.ruleFormPassword.passwordNew) {
callback(new Error('两次密码不一致!'));
} else {
callback();
}
};
// 密码重置
const resetPassword = () => {
state.ruleFormPassword.passwordOld = '';
state.ruleFormPassword.passwordNew = '';
state.passwordNew2 = '';
};
// 密码提交
const submitPassword = () => {
ruleFormPasswordRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
// SM2加密密码
const cpwd: ChangePwdInput = { passwordOld: '', passwordNew: '' };
const publicKey = window.__env__.VITE_SM_PUBLIC_KEY;
cpwd.passwordOld = sm2.doEncrypt(state.ruleFormPassword.passwordOld, publicKey, 1);
cpwd.passwordNew = sm2.doEncrypt(state.ruleFormPassword.passwordNew, publicKey, 1);
await getAPI(SysUserApi).apiSysUserChangePwdPost(cpwd);
// 退出系统
ElMessageBox.confirm('密码已修改,是否重新登录系统?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
clearAccessTokens();
});
});
};
// 打开裁剪弹窗
const openCropperDialog = () => {
state.cropperTitle = '更换头像';
cropperDialogRef.value?.openDialog(userInfos.value.avatar);
};
// 鼠标进入和离开头像时
const mouseEnterAvatar = () => {
state.avatarLoading = true;
};
const mouseLeaveAvatar = () => {
state.avatarLoading = false;
};
// 上传签名超出数量限制时执行
const uploadSignFileExceed: UploadProps['onExceed'] = (files) => {
uploadSignRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadSignRef.value!.handleStart(file);
};
// 获取文件地址
const getFileUrl = (row: SysFile): string => {
if (row.bucketName == 'Local') {
return `/${row.filePath}/${row.id}${row.suffix}`;
} else {
return row.url!;
}
};
// 导出对象
defineExpose({ handleChangeSignFile });
</script>
<style lang="scss" scoped>
.account-center-avatarHolder {
text-align: center;
margin-bottom: 24px;
.username {
font-size: 20px;
line-height: 28px;
font-weight: 500;
margin-bottom: 4px;
}
}
.account-center-org {
margin-bottom: 8px;
position: relative;
p {
margin-top: 10px;
}
span {
padding-left: 10px;
}
}
.avatar {
margin: 0 auto;
width: 104px;
height: 104px;
margin-bottom: 20px;
border-radius: 50%;
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
.image-signature {
margin-top: 20px;
margin-bottom: 10px;
width: 100%;
height: 150px;
background-color: #fff;
text-align: center;
vertical-align: middle;
border: solid 1px var(--el-border-color);
}
</style>