修复修改密码弹窗样式并拆分逻辑与悬浮说明

This commit is contained in:
xuans 2026-09-25 01:40:51 +08:00
parent daa7c01116
commit d943eb3012
4 changed files with 133 additions and 83 deletions

View File

@ -433,3 +433,24 @@ test("serverModal.compilesAndPreservesDeploymentFields", async () => {
assert.ok(html.includes("nginx\n-s\nreload"));
}
});
test("changePassword.loadsScopedBaseStylesAndCompiles", async () => {
const { readFile } = await import("node:fs/promises");
const { parse, compileScript, compileTemplate } = await import("@vue/compiler-sfc");
const source = await readFile(new URL("../components/ChangePassword/ChangePassword.vue", import.meta.url), "utf8");
const { descriptor, errors } = parse(source);
assert.deepEqual(errors, []);
assert.ok(compileScript(descriptor, { id: "change-password" }));
const compiled = compileTemplate({
source: descriptor.template.content,
filename: "ChangePassword.vue",
id: "change-password",
});
assert.deepEqual(compiled.errors, []);
assert.ok(descriptor.styles.some(style => style.scoped && style.src === "../../Dashboard/Dashboard.css"));
assert.ok(descriptor.styles.some(style => style.scoped && style.src === "./ChangePassword.css"));
assert.match(source, /<HelpTip>/);
const base = await readFile(new URL("./Dashboard.css", import.meta.url), "utf8");
assert.match(base, /\.modal-backdrop\s*\{[^}]*position:\s*fixed;/);
assert.match(base, /\.field\s*\{/);
});

View File

@ -4,7 +4,7 @@
.change-password-form {
display: grid;
gap: 16px;
gap: 0;
}
.change-password-form small {
@ -39,3 +39,13 @@
width: min(100%, calc(100vw - 28px));
}
}
.change-password-modal .field > span {
display: flex;
align-items: center;
gap: 6px;
}
.change-password-modal .modal-actions {
padding-top: 12px;
}

View File

@ -0,0 +1,85 @@
import { onBeforeUnmount, ref } from "vue";
export function useChangePassword() {
const visible = ref(false);
const submitting = ref(false);
const success = ref(false);
const error = ref("");
const notice = ref("");
const form = ref({
currentPassword: "",
newPassword: "",
confirmPassword: "",
});
let redirectTimer = 0;
function open() {
visible.value = true;
success.value = false;
error.value = "";
notice.value = "";
}
function close() {
if (submitting.value || success.value) {
return;
}
visible.value = false;
error.value = "";
notice.value = "";
form.value = {
currentPassword: "",
newPassword: "",
confirmPassword: "",
};
}
async function submit() {
if (submitting.value) {
return;
}
error.value = "";
notice.value = "";
if (form.value.newPassword !== form.value.confirmPassword) {
error.value = "两次输入的新密码不一致。";
return;
}
submitting.value = true;
try {
const response = await fetch("/api/v1/auth/password", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify({
current_password: form.value.currentPassword,
new_password: form.value.newPassword,
confirm_password: form.value.confirmPassword,
}),
});
const result = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(result.error || "修改密码失败,请稍后重试。");
}
success.value = true;
notice.value = result.message || "密码修改成功,请重新登录。";
redirectTimer = window.setTimeout(() => {
window.location.href = "/login";
}, 1600);
} catch (submitError) {
error.value = submitError.message || "修改密码失败,请稍后重试。";
} finally {
submitting.value = false;
}
}
onBeforeUnmount(() => {
if (redirectTimer) {
window.clearTimeout(redirectTimer);
}
});
return { visible, submitting, success, error, notice, form, open, close, submit };
}

View File

@ -1,85 +1,18 @@
<script setup>
import { onBeforeUnmount, ref } from "vue";
import HelpTip from "../HelpTip/HelpTip.vue";
import { useChangePassword } from "./ChangePassword.js";
const visible = ref(false);
const submitting = ref(false);
const success = ref(false);
const error = ref("");
const notice = ref("");
const form = ref({
currentPassword: "",
newPassword: "",
confirmPassword: "",
});
let redirectTimer = 0;
function open() {
visible.value = true;
success.value = false;
error.value = "";
notice.value = "";
}
function close() {
if (submitting.value || success.value) {
return;
}
visible.value = false;
error.value = "";
notice.value = "";
form.value = {
currentPassword: "",
newPassword: "",
confirmPassword: "",
};
}
async function submit() {
if (submitting.value) {
return;
}
error.value = "";
notice.value = "";
if (form.value.newPassword !== form.value.confirmPassword) {
error.value = "两次输入的新密码不一致。";
return;
}
submitting.value = true;
try {
const response = await fetch("/api/v1/auth/password", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify({
current_password: form.value.currentPassword,
new_password: form.value.newPassword,
confirm_password: form.value.confirmPassword,
}),
});
const result = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(result.error || "修改密码失败,请稍后重试。");
}
success.value = true;
notice.value = result.message || "密码修改成功,请重新登录。";
redirectTimer = window.setTimeout(() => {
window.location.href = "/login";
}, 1600);
} catch (submitError) {
error.value = submitError.message || "修改密码失败,请稍后重试。";
} finally {
submitting.value = false;
}
}
onBeforeUnmount(() => {
if (redirectTimer) {
window.clearTimeout(redirectTimer);
}
});
const {
visible,
submitting,
success,
error,
notice,
form,
open,
close,
submit,
} = useChangePassword();
</script>
<template>
@ -123,7 +56,7 @@ onBeforeUnmount(() => {
/>
</label>
<label class="field">
<span>新密码 <small>8 到 12 个字符,需同时包含字母和数字</small></span>
<span>新密码 <HelpTip>8 到 12 个字符,需同时包含字母和数字</HelpTip></span>
<input
v-model="form.newPassword"
type="password"
@ -168,4 +101,5 @@ onBeforeUnmount(() => {
</div>
</template>
<style src="./ChangePassword.css"></style>
<style scoped src="../../Dashboard/Dashboard.css"></style>
<style scoped src="./ChangePassword.css"></style>