优化顶栏导航与登录请求体验

This commit is contained in:
Cheng Zhou
2026-06-17 17:22:22 +08:00
parent 061792c73f
commit 53249daf8d
9 changed files with 1479 additions and 203 deletions
+2 -57
View File
@@ -80,8 +80,8 @@
placeholder="请输入密码"
show-password
size="large"
:name="form.rememberPassword ? 'password' : 'ctms-login-password'"
:autocomplete="form.rememberPassword ? 'current-password' : 'new-password'"
name="ctms-login-password"
autocomplete="new-password"
class="login-input"
>
<template #prefix>
@@ -94,9 +94,6 @@
</el-form-item>
<div class="login-options">
<el-checkbox v-model="form.rememberPassword" class="remember-checkbox">
<span class="remember-text">记住密码</span>
</el-checkbox>
<el-checkbox v-model="form.agreeProtocol" class="protocol-checkbox">
<span class="protocol-text">
我已阅读并同意
@@ -160,17 +157,12 @@ import { authProtocolSections } from "../content/authProtocol";
const auth = useAuthStore();
const router = useRouter();
const REMEMBER_PASSWORD_KEY = "ctms_remember_password";
const AGREE_PROTOCOL_KEY = "ctms_agree_protocol";
type PasswordCredentialConstructor = new (data: { id: string; password: string; name?: string }) => Credential;
type StoredPasswordCredential = Credential & { id?: string; password?: string };
const formRef = ref<FormInstance>();
const form = reactive({
email: "",
password: "",
rememberPassword: false,
agreeProtocol: false,
});
@@ -210,9 +202,7 @@ onMounted(async () => {
};
}
form.email = localStorage.getItem("ctms_last_login_email") || "";
form.rememberPassword = localStorage.getItem(REMEMBER_PASSWORD_KEY) === "true";
form.agreeProtocol = localStorage.getItem(AGREE_PROTOCOL_KEY) === "true";
await tryLoadBrowserCredential();
});
watch(
@@ -222,40 +212,6 @@ watch(
},
);
const tryLoadBrowserCredential = async () => {
if (!form.rememberPassword || !navigator.credentials?.get) return;
try {
const credential = (await navigator.credentials.get({
password: true,
mediation: "optional",
} as CredentialRequestOptions)) as StoredPasswordCredential | null;
if (!credential?.password) return;
form.email = credential.id || form.email;
form.password = credential.password;
} catch {
// 浏览器可能不支持读取密码凭据,保留用户手动输入流程。
}
};
const tryStoreBrowserCredential = async (email: string, password: string) => {
if (!form.rememberPassword) {
localStorage.removeItem(REMEMBER_PASSWORD_KEY);
return;
}
localStorage.setItem(REMEMBER_PASSWORD_KEY, "true");
const PasswordCredential = (window as typeof window & { PasswordCredential?: PasswordCredentialConstructor })
.PasswordCredential;
if (!navigator.credentials?.store || !PasswordCredential) return;
try {
await navigator.credentials.store(new PasswordCredential({ id: email, password, name: email }));
} catch {
// 浏览器或用户可能拒绝保存凭据,不影响登录主流程。
}
};
const openProtocolDialog = () => {
protocolDialogVisible.value = true;
};
@@ -276,7 +232,6 @@ const onSubmit = async () => {
loading.value = true;
try {
await auth.login(form.email, form.password);
await tryStoreBrowserCredential(form.email, form.password);
const studyStore = useStudyStore();
const userKey = auth.user?.email || form.email;
await studyStore.restoreStudyForUser(userKey, { preferActive: !!auth.user?.is_admin });
@@ -511,7 +466,6 @@ const onSubmit = async () => {
margin: -4px 0 26px;
}
.remember-checkbox,
.protocol-checkbox {
display: flex;
align-items: flex-start;
@@ -519,12 +473,10 @@ const onSubmit = async () => {
min-height: 24px;
}
.remember-checkbox :deep(.el-checkbox__input),
.protocol-checkbox :deep(.el-checkbox__input) {
margin-top: 2px;
}
.remember-checkbox :deep(.el-checkbox__label),
.protocol-checkbox :deep(.el-checkbox__label) {
display: block;
padding-left: 10px;
@@ -532,13 +484,6 @@ const onSubmit = async () => {
white-space: normal;
}
.remember-text {
color: #52657b;
font-size: 12px;
font-weight: 700;
line-height: 18px;
}
.protocol-text {
color: #475569;
font-size: 12px;