mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-23 23:39:47 +08:00
244 lines
8.5 KiB
Vue
244 lines
8.5 KiB
Vue
<script setup lang="ts">
|
|
import { reactive } from 'vue';
|
|
import { NButton } from 'naive-ui';
|
|
import { useLoading } from '@sa/hooks';
|
|
import { fetchUpdateUserPassword, fetchUpdateUserProfile } from '@/service/api/system';
|
|
import { useAuthStore } from '@/store/modules/auth';
|
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
|
import OnlineTable from './modules/online-table.vue';
|
|
import SocialCard from './modules/social-card.vue';
|
|
import UserAvatar from './modules/user-avatar.vue';
|
|
defineOptions({
|
|
name: 'UserCenter'
|
|
});
|
|
|
|
const authStore = useAuthStore();
|
|
const { userInfo } = authStore;
|
|
|
|
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
|
|
|
|
const {
|
|
formRef: profileFormRef,
|
|
validate: profileValidate,
|
|
restoreValidation: profileRestoreValidation
|
|
} = useNaiveForm();
|
|
const {
|
|
formRef: passwordFormRef,
|
|
validate: passwordValidate,
|
|
restoreValidation: passwordRestoreValidation
|
|
} = useNaiveForm();
|
|
const { createRequiredRule, patternRules } = useFormRules();
|
|
|
|
type ProfileModel = Api.System.UserProfileOperateParams;
|
|
type PasswordModel = Api.System.UserPasswordOperateParams & { confirmPassword: string };
|
|
|
|
const profileModel: ProfileModel = reactive(createDefaultProfileModel());
|
|
const passwordModel: PasswordModel = reactive(createDefaultPasswordModel());
|
|
|
|
function createDefaultProfileModel(): ProfileModel {
|
|
return {
|
|
nickName: userInfo.user?.nickName || '',
|
|
email: userInfo.user?.email || '',
|
|
phonenumber: userInfo.user?.phonenumber || '',
|
|
sex: userInfo.user?.sex || '0'
|
|
};
|
|
}
|
|
|
|
function createDefaultPasswordModel(): PasswordModel {
|
|
return {
|
|
oldPassword: '',
|
|
confirmPassword: '',
|
|
newPassword: ''
|
|
};
|
|
}
|
|
|
|
type ProfileRuleKey = Extract<keyof ProfileModel, 'nickName' | 'email' | 'phonenumber' | 'sex'>;
|
|
type PasswordRuleKey = Extract<keyof PasswordModel, 'oldPassword' | 'newPassword' | 'confirmPassword'>;
|
|
|
|
const profileRules: Record<ProfileRuleKey, App.Global.FormRule> = {
|
|
nickName: createRequiredRule('昵称不能为空'),
|
|
email: { ...patternRules.email, required: true },
|
|
phonenumber: { ...patternRules.phone, required: true },
|
|
sex: createRequiredRule('性别不能为空')
|
|
};
|
|
|
|
const passwordRules: Record<PasswordRuleKey, App.Global.FormRule> = {
|
|
oldPassword: createRequiredRule('旧密码不能为空'),
|
|
confirmPassword: createRequiredRule('确认密码不能为空'),
|
|
newPassword: createRequiredRule('新密码不能为空')
|
|
};
|
|
|
|
async function updateProfile() {
|
|
await profileValidate();
|
|
startBtnLoading();
|
|
const { error } = await fetchUpdateUserProfile(profileModel);
|
|
if (!error) {
|
|
window.$message?.success('更新成功');
|
|
// 更新本地用户信息
|
|
if (userInfo.user) {
|
|
Object.assign(userInfo.user, profileModel);
|
|
profileRestoreValidation();
|
|
}
|
|
}
|
|
endBtnLoading();
|
|
}
|
|
|
|
async function updatePassword() {
|
|
await passwordValidate();
|
|
if (passwordModel.newPassword !== passwordModel.confirmPassword) {
|
|
window.$message?.error('两次输入的密码不一致');
|
|
return;
|
|
}
|
|
startBtnLoading();
|
|
const { oldPassword, newPassword } = passwordModel;
|
|
const { error } = await fetchUpdateUserPassword({ oldPassword, newPassword });
|
|
if (!error) {
|
|
window.$message?.success('密码修改成功');
|
|
// 清空表单
|
|
Object.assign(passwordModel, createDefaultPasswordModel());
|
|
passwordRestoreValidation();
|
|
}
|
|
endBtnLoading();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex gap-16px">
|
|
<!-- 个人信息卡片 -->
|
|
<NCard title="个人信息" class="w-360px shadow-sm">
|
|
<div class="flex-x-center flex-wrap gap-24px">
|
|
<div class="flex-center flex-col gap-16px">
|
|
<div class="relative">
|
|
<UserAvatar />
|
|
</div>
|
|
<div class="text-18px font-medium">{{ userInfo.user?.nickName }}</div>
|
|
<div class="text-14px text-gray-500">{{ userInfo.user?.userName }}</div>
|
|
</div>
|
|
<NDescriptions :column="1" label-placement="left" label-width="120px">
|
|
<NDescriptionsItem label="手机号码">
|
|
<div class="text-14px">{{ userInfo.user?.phonenumber }}</div>
|
|
</NDescriptionsItem>
|
|
<NDescriptionsItem label="用户邮箱">
|
|
<div class="text-14px">{{ userInfo.user?.email }}</div>
|
|
</NDescriptionsItem>
|
|
<NDescriptionsItem label="所属部门">
|
|
<div class="text-14px">{{ userInfo.user?.deptName }}</div>
|
|
</NDescriptionsItem>
|
|
<NDescriptionsItem label="所属角色">
|
|
<NSpace>
|
|
<NTag v-for="role in userInfo.user?.roles" :key="role.roleId" type="primary" size="small">
|
|
{{ role.roleName }}
|
|
</NTag>
|
|
</NSpace>
|
|
</NDescriptionsItem>
|
|
<NDescriptionsItem label="创建日期">
|
|
<div class="text-14px">{{ userInfo.user?.createTime }}</div>
|
|
</NDescriptionsItem>
|
|
</NDescriptions>
|
|
</div>
|
|
</NCard>
|
|
|
|
<!-- 基本资料卡片 -->
|
|
<NCard title="基本资料" class="shadow-sm">
|
|
<NTabs type="line" animated class="h-full" s>
|
|
<NTabPane name="userInfo" tab="基本资料">
|
|
<NForm
|
|
ref="profileFormRef"
|
|
:model="profileModel"
|
|
:rules="profileRules"
|
|
label-placement="left"
|
|
label-width="100px"
|
|
class="mt-16px max-w-520px"
|
|
>
|
|
<NFormItem label="昵称" path="nickName">
|
|
<NInput v-model:value="profileModel.nickName" placeholder="请输入昵称" />
|
|
</NFormItem>
|
|
<NFormItem label="邮箱" path="email">
|
|
<NInput v-model:value="profileModel.email" placeholder="请输入邮箱" />
|
|
</NFormItem>
|
|
<NFormItem label="手机号" path="phonenumber">
|
|
<NInput v-model:value="profileModel.phonenumber" placeholder="请输入手机号" />
|
|
</NFormItem>
|
|
<NFormItem label="性别" path="sex">
|
|
<NRadioGroup v-model:value="profileModel.sex">
|
|
<NRadio value="0">男</NRadio>
|
|
<NRadio value="1">女</NRadio>
|
|
</NRadioGroup>
|
|
</NFormItem>
|
|
<NFormItem class="flex items-center justify-end">
|
|
<NButton class="ml-20px w-80px" type="primary" :loading="btnLoading" @click="updateProfile">
|
|
<template #icon>
|
|
<SvgIcon icon="ic:outline-save" class="size-24px" />
|
|
</template>
|
|
保存
|
|
</NButton>
|
|
</NFormItem>
|
|
</NForm>
|
|
</NTabPane>
|
|
<NTabPane name="updatePwd" tab="修改密码">
|
|
<NForm
|
|
ref="passwordFormRef"
|
|
:model="passwordModel"
|
|
:rules="passwordRules"
|
|
label-placement="left"
|
|
label-width="100px"
|
|
class="mt-16px max-w-520px"
|
|
>
|
|
<NFormItem label="旧密码" path="oldPassword">
|
|
<NInput
|
|
v-model:value="passwordModel.oldPassword"
|
|
type="password"
|
|
placeholder="请输入旧密码"
|
|
show-password-on="click"
|
|
/>
|
|
</NFormItem>
|
|
<NFormItem label="新密码" path="newPassword">
|
|
<NInput
|
|
v-model:value="passwordModel.newPassword"
|
|
type="password"
|
|
placeholder="请输入新密码"
|
|
show-password-on="click"
|
|
/>
|
|
</NFormItem>
|
|
<NFormItem label="确认密码" path="confirmPassword">
|
|
<NInput
|
|
v-model:value="passwordModel.confirmPassword"
|
|
type="password"
|
|
placeholder="请再次输入新密码"
|
|
show-password-on="click"
|
|
/>
|
|
</NFormItem>
|
|
<NFormItem class="flex items-center justify-end">
|
|
<NButton class="ml-20px w-120px" type="primary" :loading="btnLoading" @click="updatePassword">
|
|
<template #icon>
|
|
<SvgIcon icon="ic:outline-key" class="size-24px" />
|
|
</template>
|
|
修改密码
|
|
</NButton>
|
|
</NFormItem>
|
|
</NForm>
|
|
</NTabPane>
|
|
<NTabPane name="social" tab="第三方应用">
|
|
<SocialCard />
|
|
</NTabPane>
|
|
<NTabPane name="online" tab="在线设备">
|
|
<div class="h-full">
|
|
<OnlineTable />
|
|
</div>
|
|
</NTabPane>
|
|
</NTabs>
|
|
</NCard>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.shadow-sm {
|
|
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
}
|
|
|
|
:deep(.n-tabs-pane-wrapper),
|
|
:deep(.n-tab-pane) {
|
|
height: 100% !important;
|
|
}
|
|
</style>
|