WholeProcessPlatform/frontend-sjtb/src/layout/components/changePassword.vue

395 lines
9.5 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import type { FormInstance } from 'ant-design-vue';
import { useUserStore } from '@/store/modules/user';
import { encrypt } from '@/utils/rsaEncrypt';
import { updatePassword } from '@/api/changePassWord';
const router = useRouter();
const userStore = useUserStore();
const props = defineProps<{ open: boolean }>();
const emit = defineEmits<{ 'update:open': [value: boolean] }>();
const closeModal = () => emit('update:open', false);
// 表单引用
const formRef = ref<FormInstance>();
// 提交状态
const submitting = ref(false);
// 表单数据
interface ChangePasswordForm {
oldPassword: string;
newPassword: string;
confirmPassword: string;
}
const formState = reactive<ChangePasswordForm>({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
// ==================== 密码校验函数 ====================
/**
* 校验密码复杂度至少包含四类字符中的三类
*/
const checkComplexity = (
password: string
): { valid: boolean; message: string } => {
let count = 0;
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasNumber = /[0-9]/.test(password);
const hasSpecial = /[^A-Za-z0-9]/.test(password);
if (hasUpper) count++;
if (hasLower) count++;
if (hasNumber) count++;
if (hasSpecial) count++;
if (count >= 3) {
return { valid: true, message: '' };
}
return {
valid: false,
message: '密码必须包含大写字母、小写字母、数字、特殊字符中的至少三类'
};
};
/**
* 校验连续重复字符
*/
const checkConsecutiveRepeat = (
password: string
): { valid: boolean; message: string } => {
for (let i = 0; i < password.length - 1; i++) {
if (password[i] === password[i + 1]) {
return {
valid: false,
message: '密码不能包含2位及以上相同字符的连续重复如11、aa'
};
}
}
return { valid: true, message: '' };
};
/**
* 校验连续递增/递减数字
*/
const checkConsecutiveNumbers = (
password: string
): { valid: boolean; message: string } => {
for (let i = 0; i < password.length - 1; i++) {
const curr = password.charCodeAt(i);
const next = password.charCodeAt(i + 1);
// 检查是否都是数字
if (curr >= 48 && curr <= 57 && next >= 48 && next <= 57) {
if (Math.abs(next - curr) === 1) {
return {
valid: false,
message: '密码不能包含2位及以上连续递增或递减的数字如12、21'
};
}
}
}
return { valid: true, message: '' };
};
/**
* 校验连续递增/递减字母
*/
const checkConsecutiveLetters = (
password: string
): { valid: boolean; message: string } => {
for (let i = 0; i < password.length - 1; i++) {
const curr = password.charCodeAt(i);
const next = password.charCodeAt(i + 1);
// 检查是否都是字母(大写或小写)
const isCurrLetter =
(curr >= 65 && curr <= 90) || (curr >= 97 && curr <= 122);
const isNextLetter =
(next >= 65 && next <= 90) || (next >= 97 && next <= 122);
if (isCurrLetter && isNextLetter) {
if (Math.abs(next - curr) === 1) {
return {
valid: false,
message: '密码不能包含2位及以上连续递增或递减的字母如ab、ba'
};
}
}
}
return { valid: true, message: '' };
};
/**
* 校验用户名关联
*/
const checkUsernameRelation = (
password: string,
username: string
): { valid: boolean; message: string } => {
if (!username) {
return { valid: true, message: '' };
}
const lowerPassword = password.toLowerCase();
const lowerUsername = username.toLowerCase();
if (lowerPassword.includes(lowerUsername)) {
return {
valid: false,
message: '密码不能包含用户名或其核心部分'
};
}
return { valid: true, message: '' };
};
// ==================== 表单验证规则 ====================
const rules = {
oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 10, message: '密码长度不能少于10位', trigger: 'blur' },
{
validator: async (_rule: any, value: string) => {
if (!value) {
return Promise.resolve();
}
// 校验复杂度
const complexity = checkComplexity(value);
if (!complexity.valid) {
return Promise.reject(new Error(complexity.message));
}
// 校验连续重复
const repeat = checkConsecutiveRepeat(value);
if (!repeat.valid) {
return Promise.reject(new Error(repeat.message));
}
// 校验连续数字
const numbers = checkConsecutiveNumbers(value);
if (!numbers.valid) {
return Promise.reject(new Error(numbers.message));
}
// 校验连续字母
const letters = checkConsecutiveLetters(value);
if (!letters.valid) {
return Promise.reject(new Error(letters.message));
}
// 校验用户名关联
const username = userStore.username || '';
const usernameCheck = checkUsernameRelation(value, username);
if (!usernameCheck.valid) {
return Promise.reject(new Error(usernameCheck.message));
}
return Promise.resolve();
},
trigger: 'blur'
}
],
confirmPassword: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{
validator: async (_rule: any, value: string) => {
if (!value) {
return Promise.resolve();
}
if (value !== formState.newPassword) {
return Promise.reject(new Error('两次输入的密码不一致'));
}
return Promise.resolve();
},
trigger: 'blur'
}
]
};
/**
* 提交表单
*/
const handleSubmit = async () => {
try {
// 表单验证
await formRef.value?.validate();
submitting.value = true;
// RSA 加密密码
const encryptedOldPassword = encrypt(formState.oldPassword);
const encryptedNewPassword = encrypt(formState.newPassword);
if (!encryptedOldPassword || !encryptedNewPassword) {
message.error('密码加密失败');
return;
}
// 调用接口
const res: any = await updatePassword({
id: userStore.userid,
password: encryptedNewPassword,
oldPassword: encryptedOldPassword
});
// 兼容字符串和数字类型的状态码
if (res.code === 0 || res.code === '0') {
message.success('密码修改成功,请重新登录');
// 清空 token 和用户信息
userStore.resetToken();
closeModal();
// 跳转到登录页
setTimeout(() => {
router.push('/login');
}, 1500);
} else {
message.error(res.msg || '密码修改失败');
}
} catch (error: any) {
if (error.errorFields) {
// 表单验证错误
message.error('请检查表单填写是否正确');
} else {
// 其他错误
message.error(error.message || '密码修改失败,请稍后重试');
}
} finally {
submitting.value = false;
}
};
/**
* 重置表单
*/
const handleReset = () => {
formRef.value?.resetFields();
formState.oldPassword = '';
formState.newPassword = '';
formState.confirmPassword = '';
};
/**
* 关闭弹窗并重置表单
*/
const handleCancel = () => {
formRef.value?.resetFields();
formState.oldPassword = '';
formState.newPassword = '';
formState.confirmPassword = '';
closeModal();
};
</script>
<template>
<a-modal
:open="props.open"
title="修改密码"
:footer="null"
:mask-closable="false"
width="480px"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="formState"
:rules="rules"
layout="vertical"
class="password-form"
>
<a-form-item label="旧密码" name="oldPassword">
<a-input-password
v-model:value="formState.oldPassword"
placeholder="请输入旧密码"
allow-clear
/>
</a-form-item>
<a-form-item label="新密码" name="newPassword">
<a-input-password
v-model:value="formState.newPassword"
placeholder="请输入新密码"
allow-clear
/>
</a-form-item>
<a-form-item label="确认新密码" name="confirmPassword">
<a-input-password
v-model:value="formState.confirmPassword"
placeholder="请再次输入新密码"
allow-clear
/>
</a-form-item>
<a-form-item class="button-group">
<a-space>
<a-button
type="primary"
@click="handleSubmit"
:loading="submitting"
class="submit-btn"
>
提交
</a-button>
<a-button @click="handleReset">重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-modal>
</template>
<style scoped lang="scss">
.password-form {
:deep(.ant-form-item-label) {
label {
font-weight: 500;
color: #333;
}
}
:deep(.ant-input-password) {
border-radius: 4px;
&:hover {
border-color: #005293;
}
&:focus {
border-color: #005293;
box-shadow: 0 0 0 2px rgba(0, 82, 147, 0.2);
}
}
}
.button-group {
margin-top: 16px;
margin-bottom: 0;
.submit-btn {
width: 100px;
background-color: #005293;
border-color: #005293;
&:hover {
background-color: #003d6e;
border-color: #003d6e;
}
}
}
</style>