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

395 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>