feat: 添加强制密码重置功能及相关组件
This commit is contained in:
@@ -37,3 +37,16 @@ export function updateUserPassword(oldPassword: string, newPassword: string) {
|
|||||||
data: { oldPassword, newPassword },
|
data: { oldPassword, newPassword },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户强制重置密码
|
||||||
|
* @param password 密码
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function updateUserPasswordForce(password: string) {
|
||||||
|
return request({
|
||||||
|
url: '/system/user/profile/password-force',
|
||||||
|
method: 'PUT',
|
||||||
|
data: { password },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
198
src/components/ForcePasswdChange/index.vue
Normal file
198
src/components/ForcePasswdChange/index.vue
Normal 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>
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import RightContent from './components/RightContent.vue';
|
import RightContent from './components/RightContent.vue';
|
||||||
import Tabs from './components/Tabs.vue';
|
import Tabs from './components/Tabs.vue';
|
||||||
import GlobalMask from '@/components/GlobalMask/index.vue';
|
import GlobalMask from '@/components/GlobalMask/index.vue';
|
||||||
|
import ForcePasswdChange from '@/components/ForcePasswdChange/index.vue';
|
||||||
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
|
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
@@ -362,6 +363,8 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<!-- 全局遮罩 -->
|
<!-- 全局遮罩 -->
|
||||||
<GlobalMask />
|
<GlobalMask />
|
||||||
|
<!-- 强制密码修改 -->
|
||||||
|
<ForcePasswdChange />
|
||||||
</a-watermark>
|
</a-watermark>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { parseUrlPath } from '@/plugins/file-static-url';
|
|||||||
|
|
||||||
/**用户信息类型 */
|
/**用户信息类型 */
|
||||||
type UserInfo = {
|
type UserInfo = {
|
||||||
|
/**用户ID */
|
||||||
|
forcePasswdChange: boolean;
|
||||||
/**用户ID */
|
/**用户ID */
|
||||||
userId: string;
|
userId: string;
|
||||||
/**登录账号 */
|
/**登录账号 */
|
||||||
@@ -33,6 +35,7 @@ type UserInfo = {
|
|||||||
|
|
||||||
const useUserStore = defineStore('user', {
|
const useUserStore = defineStore('user', {
|
||||||
state: (): UserInfo => ({
|
state: (): UserInfo => ({
|
||||||
|
forcePasswdChange: false,
|
||||||
userId: '',
|
userId: '',
|
||||||
userName: '',
|
userName: '',
|
||||||
roles: [],
|
roles: [],
|
||||||
@@ -104,6 +107,9 @@ const useUserStore = defineStore('user', {
|
|||||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||||
const token = res.data[TOKEN_RESPONSE_FIELD];
|
const token = res.data[TOKEN_RESPONSE_FIELD];
|
||||||
setToken(token);
|
setToken(token);
|
||||||
|
if (res.data?.forcePasswdChange) {
|
||||||
|
this.forcePasswdChange = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
@@ -139,6 +145,10 @@ const useUserStore = defineStore('user', {
|
|||||||
// }
|
// }
|
||||||
// useLayoutStore().changeWaterMark(waterMarkContent);
|
// useLayoutStore().changeWaterMark(waterMarkContent);
|
||||||
useLayoutStore().changeWaterMark('');
|
useLayoutStore().changeWaterMark('');
|
||||||
|
// 强制修改密码
|
||||||
|
if (res.data?.forcePasswdChange) {
|
||||||
|
this.forcePasswdChange = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 网络错误时退出登录状态
|
// 网络错误时退出登录状态
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user