|
|
|
|
@ -0,0 +1,266 @@
|
|
|
|
|
<!--
|
|
|
|
|
修改密码弹窗
|
|
|
|
|
- 接收父组件的 open()/close() 控制
|
|
|
|
|
- 调用 updatePassword 接口
|
|
|
|
|
- 密码用 jsencrypt 的 RSA 加密(与登录一致)
|
|
|
|
|
-->
|
|
|
|
|
<template>
|
|
|
|
|
<el-dialog
|
|
|
|
|
:model-value="modelValue"
|
|
|
|
|
title="修改密码"
|
|
|
|
|
width="480px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
:close-on-press-escape="false"
|
|
|
|
|
:show-close="!submitting"
|
|
|
|
|
:before-close="handleClose"
|
|
|
|
|
class="change-password-dialog"
|
|
|
|
|
@update:model-value="onModelUpdate"
|
|
|
|
|
>
|
|
|
|
|
<el-form
|
|
|
|
|
ref="formRef"
|
|
|
|
|
:model="form"
|
|
|
|
|
:rules="rules"
|
|
|
|
|
label-position="top"
|
|
|
|
|
class="change-password-form"
|
|
|
|
|
@submit.prevent
|
|
|
|
|
>
|
|
|
|
|
<el-form-item label="账号">
|
|
|
|
|
<el-input :model-value="accountDisplay" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="原密码" prop="oldPassword">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.oldPassword"
|
|
|
|
|
type="password"
|
|
|
|
|
show-password
|
|
|
|
|
clearable
|
|
|
|
|
placeholder="请输入当前密码"
|
|
|
|
|
autocomplete="current-password"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="新密码" prop="newPassword">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.newPassword"
|
|
|
|
|
type="password"
|
|
|
|
|
show-password
|
|
|
|
|
clearable
|
|
|
|
|
placeholder="请输入新密码(6-20 位)"
|
|
|
|
|
autocomplete="new-password"
|
|
|
|
|
/>
|
|
|
|
|
<div class="pwd-strength" :class="`pwd-strength-${strength.level}`">
|
|
|
|
|
<span class="pwd-strength-bar"></span>
|
|
|
|
|
<span class="pwd-strength-bar"></span>
|
|
|
|
|
<span class="pwd-strength-bar"></span>
|
|
|
|
|
<span class="pwd-strength-text">强度:{{ strength.label }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="确认新密码" prop="confirmPassword">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.confirmPassword"
|
|
|
|
|
type="password"
|
|
|
|
|
show-password
|
|
|
|
|
clearable
|
|
|
|
|
placeholder="请再次输入新密码"
|
|
|
|
|
autocomplete="new-password"
|
|
|
|
|
@keyup.enter="submit"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="handleClose" :disabled="submitting">取消</el-button>
|
|
|
|
|
<el-button type="primary" :loading="submitting" @click="submit">确认修改</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { computed, reactive, ref, watch } from "vue";
|
|
|
|
|
import { ElMessage } from "element-plus";
|
|
|
|
|
import { updatePassword } from "@/api/teacher";
|
|
|
|
|
import { encrypt } from "@/utils/jsencrypt";
|
|
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
modelValue: { type: Boolean, default: false },
|
|
|
|
|
});
|
|
|
|
|
const emit = defineEmits(["update:modelValue", "success"]);
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
|
|
|
|
const accountDisplay = computed(() => {
|
|
|
|
|
const info = userStore.userInfo || {};
|
|
|
|
|
return info.userName || info.name || info.username || "-";
|
|
|
|
|
});
|
|
|
|
|
const currentUserId = computed(() => userStore.userInfo?.userId || userStore.userInfo?.id);
|
|
|
|
|
|
|
|
|
|
const initialForm = () => ({
|
|
|
|
|
oldPassword: "",
|
|
|
|
|
newPassword: "",
|
|
|
|
|
confirmPassword: "",
|
|
|
|
|
});
|
|
|
|
|
const form = reactive(initialForm());
|
|
|
|
|
const formRef = ref(null);
|
|
|
|
|
const submitting = ref(false);
|
|
|
|
|
|
|
|
|
|
const validateNewPassword = (rule, value, cb) => {
|
|
|
|
|
if (value === "") {
|
|
|
|
|
cb(new Error("请输入新密码"));
|
|
|
|
|
} else if (value.length < 6 || value.length > 20) {
|
|
|
|
|
cb(new Error("新密码长度应为 6-20 位"));
|
|
|
|
|
} else if (/\s/.test(value)) {
|
|
|
|
|
cb(new Error("新密码不能包含空格"));
|
|
|
|
|
} else if (form.oldPassword && value === form.oldPassword) {
|
|
|
|
|
cb(new Error("新密码不能与原密码相同"));
|
|
|
|
|
} else {
|
|
|
|
|
cb();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const validateConfirm = (rule, value, cb) => {
|
|
|
|
|
if (value === "") {
|
|
|
|
|
cb(new Error("请再次输入新密码"));
|
|
|
|
|
} else if (value !== form.newPassword) {
|
|
|
|
|
cb(new Error("两次输入的新密码不一致"));
|
|
|
|
|
} else {
|
|
|
|
|
cb();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rules = {
|
|
|
|
|
oldPassword: [
|
|
|
|
|
{ required: true, message: "请输入原密码", trigger: "blur" },
|
|
|
|
|
{ min: 1, max: 50, message: "原密码长度不正确", trigger: "blur" },
|
|
|
|
|
],
|
|
|
|
|
newPassword: [
|
|
|
|
|
{ required: true, validator: validateNewPassword, trigger: "blur" },
|
|
|
|
|
{ required: true, validator: validateNewPassword, trigger: "change" },
|
|
|
|
|
],
|
|
|
|
|
confirmPassword: [
|
|
|
|
|
{ required: true, validator: validateConfirm, trigger: "blur" },
|
|
|
|
|
{ required: true, validator: validateConfirm, trigger: "change" },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 简单密码强度评估:仅 UI 提示,不参与校验
|
|
|
|
|
const strength = computed(() => {
|
|
|
|
|
const pwd = form.newPassword || "";
|
|
|
|
|
if (!pwd) return { level: 0, label: "-" };
|
|
|
|
|
let score = 0;
|
|
|
|
|
if (pwd.length >= 8) score += 1;
|
|
|
|
|
if (/[A-Z]/.test(pwd)) score += 1;
|
|
|
|
|
if (/[a-z]/.test(pwd)) score += 1;
|
|
|
|
|
if (/[0-9]/.test(pwd)) score += 1;
|
|
|
|
|
if (/[^A-Za-z0-9]/.test(pwd)) score += 1;
|
|
|
|
|
// 把分值收敛到 0/1/2 三档
|
|
|
|
|
if (score <= 2) return { level: 1, label: "弱" };
|
|
|
|
|
if (score <= 3) return { level: 2, label: "中" };
|
|
|
|
|
return { level: 3, label: "强" };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => props.modelValue,
|
|
|
|
|
(val) => {
|
|
|
|
|
if (val) {
|
|
|
|
|
// 每次打开都重置表单,避免上一次的输入残留
|
|
|
|
|
Object.assign(form, initialForm());
|
|
|
|
|
submitting.value = false;
|
|
|
|
|
formRef.value?.clearValidate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function onModelUpdate(val) {
|
|
|
|
|
emit("update:modelValue", val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleClose() {
|
|
|
|
|
if (submitting.value) return;
|
|
|
|
|
emit("update:modelValue", false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submit() {
|
|
|
|
|
if (!currentUserId.value) {
|
|
|
|
|
ElMessage.error("未获取到当前用户信息,请重新登录后再试");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await formRef.value?.validate();
|
|
|
|
|
} catch {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
submitting.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
userId: currentUserId.value,
|
|
|
|
|
oldPasswordEncode: encrypt(form.oldPassword),
|
|
|
|
|
newPasswordEncode: encrypt(form.newPassword),
|
|
|
|
|
};
|
|
|
|
|
await updatePassword(params);
|
|
|
|
|
ElMessage.success("密码修改成功,请使用新密码重新登录");
|
|
|
|
|
emit("success");
|
|
|
|
|
emit("update:modelValue", false);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// 错误已经在 axios 拦截器里统一提示过,这里只打印方便排查
|
|
|
|
|
console.error("[updatePassword] failed:", err);
|
|
|
|
|
} finally {
|
|
|
|
|
submitting.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.change-password-form {
|
|
|
|
|
padding: 4px 4px 0;
|
|
|
|
|
|
|
|
|
|
:deep(.el-form-item__label) {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #303133;
|
|
|
|
|
padding-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-input.is-disabled .el-input__wrapper) {
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pwd-strength {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
|
|
|
|
.pwd-strength-bar {
|
|
|
|
|
flex: 1;
|
|
|
|
|
height: 4px;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
background: #ebeef5;
|
|
|
|
|
transition: background-color 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pwd-strength-text {
|
|
|
|
|
flex: none;
|
|
|
|
|
min-width: 56px;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.pwd-strength-1 .pwd-strength-bar:nth-child(1) {
|
|
|
|
|
background: #f56c6c;
|
|
|
|
|
}
|
|
|
|
|
&.pwd-strength-2 .pwd-strength-bar:nth-child(1),
|
|
|
|
|
&.pwd-strength-2 .pwd-strength-bar:nth-child(2) {
|
|
|
|
|
background: #e6a23c;
|
|
|
|
|
}
|
|
|
|
|
&.pwd-strength-3 .pwd-strength-bar:nth-child(1),
|
|
|
|
|
&.pwd-strength-3 .pwd-strength-bar:nth-child(2),
|
|
|
|
|
&.pwd-strength-3 .pwd-strength-bar:nth-child(3) {
|
|
|
|
|
background: #67c23a;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|