feat: 添加强制密码重置功能及相关组件

This commit is contained in:
TsMask
2025-03-31 19:00:34 +08:00
parent c72f0290fd
commit d037a76856
4 changed files with 224 additions and 0 deletions

View File

@@ -37,3 +37,16 @@ export function updateUserPassword(oldPassword: string, newPassword: string) {
data: { oldPassword, newPassword },
});
}
/**
* 用户强制重置密码
* @param password 密码
* @returns object
*/
export function updateUserPasswordForce(password: string) {
return request({
url: '/system/user/profile/password-force',
method: 'PUT',
data: { password },
});
}

View File

@@ -0,0 +1,198 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { regExpPasswd, regExpUserName } from '@/utils/regular-utils';
import { Form, message, Modal } from 'ant-design-vue';
import useI18n from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user';
import { updateUserPasswordForce } from '@/api/profile';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { getConfigKey } from '@/api/system/config';
const userStore = useUserStore();
const { t } = useI18n();
/**对话框对象信息状态类型 */
type ModalStateType = {
/**重置密码框是否显示 */
open: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**密码策略 */
passwordPolicy: Record<string, any>;
/**密码有效期 */
passwdExpire: Record<string, any>;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
open: userStore.forcePasswdChange,
title: t('components.ForcePasswdChange.title'),
from: {
userId: userStore.userId,
userName: userStore.userName,
password: '',
},
confirmLoading: false,
passwordPolicy: { minLength: 8, specialChars: 2, uppercase: 1, lowercase: 1 },
passwdExpire: { expHours: 2, alertHours: 1 },
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
userName: [
{
required: true,
pattern: regExpUserName,
message: t('views.system.user.userNameTip'),
},
],
password: [
{
required: true,
pattern: regExpPasswd,
message: t('views.system.user.passwdTip'),
},
],
})
);
/**对话框提交确认 */
function fnModalOk() {
const { password } = modalState.from;
if (!password) {
message.error({
content: t('views.system.user.passwdTip'),
duration: 2,
});
return;
}
updateUserPasswordForce(password).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
userStore.fnLogOut();
Modal.success({
title: t('common.tipTitle'),
content: t('views.account.settings.submitOkTip', {
num: modalState.from.userName,
}),
okText: t('views.account.settings.submitOk'),
onOk() {
window.location.reload();
},
});
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
});
}
/**组件实例挂载之后调用 */
onMounted(() => {
Promise.all([
getConfigKey('sys.user.passwordPolicy'),
getConfigKey('sys.user.passwdExpire'),
]).then(resArr => {
if (resArr[0].code === RESULT_CODE_SUCCESS) {
try {
modalState.passwordPolicy = JSON.parse(resArr[0].data);
} catch (error) {
console.error('passwordPolicy', error);
}
}
if (resArr[1].code === RESULT_CODE_SUCCESS) {
try {
modalState.passwdExpire = JSON.parse(resArr[1].data);
} catch (error) {
console.error('passwdExpire', error);
}
}
});
});
/**组件实例被卸载之后调用 */
onUnmounted(() => {});
</script>
<template>
<a-modal
v-model:open="modalState.open"
get-container="#app"
:footer="null"
:zIndex="1008"
:closable="false"
:keyboard="false"
:destroyOnClose="true"
:mask-closable="false"
:title="modalState.title"
>
<a-form
name="modalStateFromByResetPwd"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-form-item
:label="t('views.system.user.account')"
name="userName"
v-bind="modalStateFrom.validateInfos.userName"
>
<a-input :value="modalState.from.userName" disabled :maxlength="30">
<template #prefix>
<UserOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item
:label="t('views.system.user.loginPwd')"
name="password"
v-bind="modalStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="modalState.from.password"
:maxlength="26"
>
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
<a-form-item name="ok" :wrapper-col="{ offset: 6 }">
<a-button type="primary" @click="fnModalOk()">
{{ t('common.ok') }}
</a-button>
</a-form-item>
<!-- 提示信息 -->
<a-form-item name="info" :label="t('components.ForcePasswdChange.desc')">
<div>
<p>
1. {{ t('components.ForcePasswdChange.passwordPolicy') }}<br />
{{
t(
'components.ForcePasswdChange.passwordPolicyMsg',
modalState.passwordPolicy
)
}}
</p>
<p>
2. {{ t('components.ForcePasswdChange.passwdExpire') }}<br />
{{
t('components.ForcePasswdChange.passwdExpireMsg', {
expDay: (modalState.passwdExpire.expHours / 24).toFixed(2),
alertDay: (modalState.passwdExpire.alertHours / 24).toFixed(2),
})
}}
</p>
</div>
</a-form-item>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

View File

@@ -8,6 +8,7 @@ import {
import RightContent from './components/RightContent.vue';
import Tabs from './components/Tabs.vue';
import GlobalMask from '@/components/GlobalMask/index.vue';
import ForcePasswdChange from '@/components/ForcePasswdChange/index.vue';
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
import {
computed,
@@ -362,6 +363,8 @@ onUnmounted(() => {
<!-- 全局遮罩 -->
<GlobalMask />
<!-- 强制密码修改 -->
<ForcePasswdChange />
</a-watermark>
</template>

View File

@@ -9,6 +9,8 @@ import { parseUrlPath } from '@/plugins/file-static-url';
/**用户信息类型 */
type UserInfo = {
/**用户ID */
forcePasswdChange: boolean;
/**用户ID */
userId: string;
/**登录账号 */
@@ -33,6 +35,7 @@ type UserInfo = {
const useUserStore = defineStore('user', {
state: (): UserInfo => ({
forcePasswdChange: false,
userId: '',
userName: '',
roles: [],
@@ -104,6 +107,9 @@ const useUserStore = defineStore('user', {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
const token = res.data[TOKEN_RESPONSE_FIELD];
setToken(token);
if (res.data?.forcePasswdChange) {
this.forcePasswdChange = true;
}
}
return res;
},
@@ -139,6 +145,10 @@ const useUserStore = defineStore('user', {
// }
// useLayoutStore().changeWaterMark(waterMarkContent);
useLayoutStore().changeWaterMark('');
// 强制修改密码
if (res.data?.forcePasswdChange) {
this.forcePasswdChange = true;
}
}
// 网络错误时退出登录状态
if (res.code === 0) {