Commit 929d82ef by DaiJiezhang

fix: restore native login password behavior

parent 72fb7fd2
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
} }
} }
/** 代码作用(白话):提交现有账号密码数据、按需记住账号,并在完成后清除页面内存中的密码。关联文件:auth-store.js、router/index.js。关联逻辑(调用链/数据流):表单 submit -> signIn -> 账号本地存储/Jeddy 欢迎提示或通用错误 -> 资产总览。 */ /** 代码作用(白话):提交现有账号密码数据、按需记住账号,并在完成后清除页面内存中的密码。关联文件:auth-store.js、router/index.js。关联逻辑(调用链/数据流):表单 submit -> signIn -> 清除密码/账号本地存储/Jeddy 欢迎提示或通用错误 -> 资产总览。 */
async function submit() { async function submit() {
submitting.value = true; submitting.value = true;
try { try {
...@@ -68,7 +68,9 @@ export default { ...@@ -68,7 +68,9 @@ export default {
<el-input v-model="form.username" class="login-form__input" placeholder="请输入账号" autocomplete="username" aria-label="账号" /> <el-input v-model="form.username" class="login-form__input" placeholder="请输入账号" autocomplete="username" aria-label="账号" />
</el-form-item> </el-form-item>
<el-form-item class="login-form__item"> <el-form-item class="login-form__item">
<div class="login-password-field">
<el-input v-model="form.password" class="login-form__input" placeholder="请输入密码" type="password" show-password autocomplete="current-password" aria-label="密码" @keyup.enter="submit" /> <el-input v-model="form.password" class="login-form__input" placeholder="请输入密码" type="password" show-password autocomplete="current-password" aria-label="密码" @keyup.enter="submit" />
</div>
</el-form-item> </el-form-item>
<el-checkbox v-model="rememberUsername" class="login-form__remember" @change="syncRememberedUsername">记住我的账号</el-checkbox> <el-checkbox v-model="rememberUsername" class="login-form__remember" @change="syncRememberedUsername">记住我的账号</el-checkbox>
<el-button type="primary" native-type="submit" :loading="submitting" class="login-submit">{{ submitting ? '正在登录' : '登录' }}</el-button> <el-button type="primary" native-type="submit" :loading="submitting" class="login-submit">{{ submitting ? '正在登录' : '登录' }}</el-button>
......
...@@ -14,7 +14,7 @@ body { margin: 0; } ...@@ -14,7 +14,7 @@ body { margin: 0; }
.login-brand__mark span { width: 4px; border-radius: 999px; background: rgba(255, 255, 255, .92); box-shadow: 0 0 8px rgba(187, 218, 255, .72); }.login-brand__mark span:nth-child(1) { height: 15px; }.login-brand__mark span:nth-child(2) { height: 25px; }.login-brand__mark span:nth-child(3) { height: 19px; } .login-brand__mark span { width: 4px; border-radius: 999px; background: rgba(255, 255, 255, .92); box-shadow: 0 0 8px rgba(187, 218, 255, .72); }.login-brand__mark span:nth-child(1) { height: 15px; }.login-brand__mark span:nth-child(2) { height: 25px; }.login-brand__mark span:nth-child(3) { height: 19px; }
.login-brand__eyebrow { margin: 0 0 5px; color: rgba(211, 226, 255, .66); font-size: 10px; font-weight: 600; letter-spacing: .16em; }.login-brand h1 { margin: 0; color: var(--login-text-primary); font-size: clamp(22px, 2vw, 28px); font-weight: 700; line-height: 1.25; letter-spacing: .04em; text-shadow: 0 4px 18px rgba(5, 12, 28, .25); white-space: nowrap; } .login-brand__eyebrow { margin: 0 0 5px; color: rgba(211, 226, 255, .66); font-size: 10px; font-weight: 600; letter-spacing: .16em; }.login-brand h1 { margin: 0; color: var(--login-text-primary); font-size: clamp(22px, 2vw, 28px); font-weight: 700; line-height: 1.25; letter-spacing: .04em; text-shadow: 0 4px 18px rgba(5, 12, 28, .25); white-space: nowrap; }
.login-panel__intro { margin: 0 0 26px; color: var(--login-text-secondary); font-size: 14px; text-align: center; } .login-panel__intro { margin: 0 0 26px; color: var(--login-text-secondary); font-size: 14px; text-align: center; }
.login-form__item.el-form-item { margin-bottom: 16px; }.login-form__input .el-input__wrapper { min-height: 60px; padding: 0 18px; border: 1px solid var(--login-field-border); border-radius: 18px; background: linear-gradient(160deg, rgba(255, 255, 255, .012), rgba(255, 255, 255, .003)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035), inset 0 -1px 0 rgba(255, 255, 255, .008), 0 6px 18px rgba(5, 10, 22, .025); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); transition: border-color .2s ease, box-shadow .2s ease, background .2s ease, transform .2s ease; }.login-form__input .el-input__wrapper:hover { border-color: rgba(211, 225, 255, .13); }.login-form__input.is-focus .el-input__wrapper { border-color: var(--login-field-focus); background: linear-gradient(160deg, rgba(255, 255, 255, .025), rgba(255, 255, 255, .007)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055), 0 0 0 4px rgba(136, 182, 255, .055), 0 10px 24px rgba(4, 11, 24, .055); transform: translateY(-1px); }.login-form__input .el-input__inner { color: var(--login-text-primary); font-size: 17px; }.login-form__input .el-input__inner::placeholder { color: rgba(224, 231, 255, .58); }.login-form__input .el-input__password { color: rgba(229, 236, 255, .74); }.login-form__input .el-input__password:hover { color: rgba(255, 255, 255, .96); } .login-form__item.el-form-item { margin-bottom: 16px; }.login-form__input .el-input__wrapper { min-height: 60px; padding: 0 18px; border: 1px solid var(--login-field-border); border-radius: 18px; background: linear-gradient(160deg, rgba(255, 255, 255, .012), rgba(255, 255, 255, .003)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035), inset 0 -1px 0 rgba(255, 255, 255, .008), 0 6px 18px rgba(5, 10, 22, .025); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); transition: border-color .2s ease, box-shadow .2s ease, background .2s ease; }.login-form__input .el-input__wrapper:hover { border-color: rgba(211, 225, 255, .13); }.login-form__input .el-input__wrapper.is-focus { border-color: var(--login-field-focus); background: linear-gradient(160deg, rgba(255, 255, 255, .025), rgba(255, 255, 255, .007)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055), 0 0 0 4px rgba(136, 182, 255, .055), 0 10px 24px rgba(4, 11, 24, .055); }.login-form__input .el-input__inner { color: var(--login-text-primary); }.login-form__input .el-input__inner::placeholder { color: rgba(224, 231, 255, .58); }.login-form__input .el-input__password { color: rgba(229, 236, 255, .74); }.login-form__input .el-input__password:hover { color: rgba(255, 255, 255, .96); }
.login-submit.el-button { width: 100%; height: 60px; margin-top: 8px; border: 1px solid rgba(205, 224, 255, .18); border-radius: 18px; background: linear-gradient(135deg, rgba(194, 223, 255, .46) 0%, rgba(120, 168, 255, .46) 40%, rgba(84, 127, 255, .50) 100%); color: #fff; font-size: 17px; font-weight: 700; letter-spacing: .08em; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 18px 30px rgba(60, 103, 207, .18), 0 4px 10px rgba(4, 9, 18, .16); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); transition: transform .2s ease, box-shadow .2s ease, opacity .2s ease, background .2s ease; }.login-submit.el-button:hover { transform: translateY(-1px); background: linear-gradient(135deg, rgba(194, 223, 255, .56), rgba(120, 168, 255, .56) 40%, rgba(84, 127, 255, .60)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .42), 0 22px 36px rgba(60, 103, 207, .22), 0 6px 14px rgba(4, 9, 18, .22); }.login-submit.el-button:active { transform: translateY(1px) scale(.99); }.login-submit.el-button.is-loading, .login-submit.el-button.is-disabled { opacity: .76; transform: none; } .login-submit.el-button { width: 100%; height: 60px; margin-top: 8px; border: 1px solid rgba(205, 224, 255, .18); border-radius: 18px; background: linear-gradient(135deg, rgba(194, 223, 255, .46) 0%, rgba(120, 168, 255, .46) 40%, rgba(84, 127, 255, .50) 100%); color: #fff; font-size: 17px; font-weight: 700; letter-spacing: .08em; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 18px 30px rgba(60, 103, 207, .18), 0 4px 10px rgba(4, 9, 18, .16); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); transition: transform .2s ease, box-shadow .2s ease, opacity .2s ease, background .2s ease; }.login-submit.el-button:hover { transform: translateY(-1px); background: linear-gradient(135deg, rgba(194, 223, 255, .56), rgba(120, 168, 255, .56) 40%, rgba(84, 127, 255, .60)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .42), 0 22px 36px rgba(60, 103, 207, .22), 0 6px 14px rgba(4, 9, 18, .22); }.login-submit.el-button:active { transform: translateY(1px) scale(.99); }.login-submit.el-button.is-loading, .login-submit.el-button.is-disabled { opacity: .76; transform: none; }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .login-panel { background: linear-gradient(145deg, rgba(10, 18, 37, .72), rgba(4, 8, 19, .62)); }.login-form__input .el-input__wrapper { background: rgba(7, 15, 32, .72); } } @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .login-panel { background: linear-gradient(145deg, rgba(10, 18, 37, .72), rgba(4, 8, 19, .62)); }.login-form__input .el-input__wrapper { background: rgba(7, 15, 32, .72); } }
@media (max-width: 980px) { .login-page { justify-content: center; padding-right: 24px; padding-left: 24px; background-position: 64% center; }.login-panel { margin: 0 auto; } } @media (max-width: 980px) { .login-page { justify-content: center; padding-right: 24px; padding-left: 24px; background-position: 64% center; }.login-panel { margin: 0 auto; } }
...@@ -336,16 +336,24 @@ body { margin: 0; } ...@@ -336,16 +336,24 @@ body { margin: 0; }
margin-bottom: 0; margin-bottom: 0;
} }
.login-form__input .el-input__wrapper { .login-form__input .el-input__wrapper,
background: linear-gradient(160deg, rgba(193, 212, 255, .011), rgba(193, 212, 255, .0015)); .login-form__input .el-input__wrapper:hover,
.login-form__input .el-input__wrapper.is-focus {
color-scheme: dark;
background: #0a1121 !important;
background-color: #0a1121 !important;
background-image: none !important;
border-color: rgba(205, 222, 255, .12); border-color: rgba(205, 222, 255, .12);
} }
.login-form__input .el-input__wrapper:hover { .login-form__input .el-input__wrapper:hover {
background: linear-gradient(160deg, rgba(193, 212, 255, .018), rgba(193, 212, 255, .003));
border-color: rgba(176, 205, 255, .23); border-color: rgba(176, 205, 255, .23);
} }
.login-form__input .el-input__wrapper.is-focus {
border-color: var(--login-field-focus);
}
.login-form__input .el-input__inner, .login-form__input .el-input__inner,
.login-form__input .el-input__inner:focus { .login-form__input .el-input__inner:focus {
color: var(--login-text-primary); color: var(--login-text-primary);
...@@ -358,18 +366,6 @@ body { margin: 0; } ...@@ -358,18 +366,6 @@ body { margin: 0; }
color: rgba(187, 204, 232, .54); color: rgba(187, 204, 232, .54);
} }
/* Chrome 自动填充会给 input 注入浅色底色;透明内阴影让它继续露出外层玻璃背景。 */
.login-form__input .el-input__inner:-webkit-autofill,
.login-form__input .el-input__inner:-webkit-autofill:hover,
.login-form__input .el-input__inner:-webkit-autofill:focus,
.login-form__input .el-input__inner:-webkit-autofill:active {
-webkit-text-fill-color: var(--login-text-primary) !important;
caret-color: var(--login-text-primary);
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
box-shadow: 0 0 0 1000px transparent inset !important;
background-color: transparent !important;
}
.login-form__input .el-input__suffix-inner { .login-form__input .el-input__suffix-inner {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -494,39 +490,32 @@ body { margin: 0; } ...@@ -494,39 +490,32 @@ body { margin: 0; }
transform: translateY(-50%); transform: translateY(-50%);
} }
/* 登录输入文字在初始、聚焦和浏览器自动填充时使用同一字号,避免点击后出现字号跳变。 */ /* Chrome/Edge 自动填充时不在原生 input 内容区绘制背景,避免内阴影的矩形边缘形成横线。 */
.login-form__input .el-input__inner,
.login-form__input .el-input__inner:focus,
.login-form__input .el-input__inner:-webkit-autofill,
.login-form__input .el-input__inner:-webkit-autofill:hover,
.login-form__input .el-input__inner:-webkit-autofill:focus,
.login-form__input .el-input__inner:-webkit-autofill:active {
font-size: 16px !important;
line-height: 24px !important;
border: 0 !important;
outline: 0 !important;
-webkit-appearance: none;
appearance: none;
-webkit-text-size-adjust: 100%;
}
/* Chrome/Edge 自动填充会延后重绘浅色背景;禁用延迟并裁剪背景,保持外层玻璃输入框可见。 */
.login-form__input .el-input__inner:-webkit-autofill, .login-form__input .el-input__inner:-webkit-autofill,
.login-form__input .el-input__inner:-webkit-autofill:hover, .login-form__input .el-input__inner:-webkit-autofill:hover,
.login-form__input .el-input__inner:-webkit-autofill:focus, .login-form__input .el-input__inner:-webkit-autofill:focus,
.login-form__input .el-input__inner:-webkit-autofill:active { .login-form__input .el-input__inner:-webkit-autofill:active {
-webkit-text-fill-color: var(--login-text-primary) !important; -webkit-text-fill-color: var(--login-text-primary) !important;
caret-color: var(--login-text-primary); caret-color: var(--login-text-primary);
background: #091120 !important; background: transparent !important;
background-color: #091120 !important; background-color: transparent !important;
background-image: none !important; background-image: none !important;
-webkit-background-clip: border-box; -webkit-background-clip: text !important;
background-clip: border-box; background-clip: text !important;
-webkit-box-shadow: 0 0 0 1000px #091120 inset !important; -webkit-box-shadow: none !important;
box-shadow: 0 0 0 1000px #091120 inset !important; box-shadow: none !important;
transition: none !important; transition: none !important;
} }
/* 代码作用(白话):让密码框外层占满 Element Plus 表单项的可用宽度,保持它与账号框同宽。关联文件:LoginView.js、login-experience.spec.js。关联逻辑(调用链/数据流):登录表单渲染 -> 密码外层宽度 -> 原生密码 input 宽度 -> 页面视觉一致。 */
.login-password-field {
width: 100%;
}
.login-password-field .el-input__inner[type="password"]::placeholder {
-webkit-text-fill-color: rgba(187, 204, 232, .54) !important;
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
.login-panel { .login-panel {
left: clamp(48px, 8vw, 96px); left: clamp(48px, 8vw, 96px);
......
...@@ -15,10 +15,17 @@ async function mockUnauthenticatedSession(page) { ...@@ -15,10 +15,17 @@ async function mockUnauthenticatedSession(page) {
/** Plain purpose: submit one complete credential form so each scenario exercises the real login view bindings. Related files: LoginView.js, auth-store.js. Flow: input values -> submit -> auth API mock -> route/message. */ /** Plain purpose: submit one complete credential form so each scenario exercises the real login view bindings. Related files: LoginView.js, auth-store.js. Flow: input values -> submit -> auth API mock -> route/message. */
async function submitLogin(page, username = 'demo_operator', password = 'a-safe-test-password') { async function submitLogin(page, username = 'demo_operator', password = 'a-safe-test-password') {
await page.getByRole('textbox', { name: '账号' }).fill(username); await page.getByRole('textbox', { name: '账号' }).fill(username);
await page.getByRole('textbox', { name: '密码' }).fill(password); await typePassword(page, password);
await page.getByRole('button', { name: '登录' }).click(); await page.getByRole('button', { name: '登录' }).click();
} }
/** Code purpose (plain language): enter a test password through the visible native field and keyboard path that a person uses, without reading any password value. Related files: LoginView.js, app.css. Flow: native input click -> keyboard input -> v-model -> native browser password presentation. */
async function typePassword(page, password) {
const passwordInput = page.getByRole('textbox', { name: '密码' });
await passwordInput.click();
await passwordInput.pressSequentially(password);
}
/** Plain purpose: confirm a route guard protects business pages when no valid session exists. Related files: router/index.js, App.js. Flow: browser hash -> auth/me 401 -> login route -> no application shell. */ /** Plain purpose: confirm a route guard protects business pages when no valid session exists. Related files: router/index.js, App.js. Flow: browser hash -> auth/me 401 -> login route -> no application shell. */
test('redirects an unauthenticated protected route to login without the sidebar', async ({ page }) => { test('redirects an unauthenticated protected route to login without the sidebar', async ({ page }) => {
await mockUnauthenticatedSession(page); await mockUnauthenticatedSession(page);
...@@ -125,3 +132,26 @@ test('renders the login page with its Vite-resolved black-hole background', asyn ...@@ -125,3 +132,26 @@ test('renders the login page with its Vite-resolved black-hole background', asyn
expect(backgroundImage).toContain('url('); expect(backgroundImage).toContain('url(');
expect(backgroundImage).toContain('black_hole'); expect(backgroundImage).toContain('black_hole');
}); });
/** Code purpose (plain language): protect the browser-owned password presentation by ensuring the login page adds no custom dots while keeping the native field and standard eye icon usable. Related files: LoginView.js, app.css. Flow: login render -> native input value -> browser password dots and Element Plus suffix icon. */
test('uses browser-native password presentation and the standard eye icon', async ({ page }) => {
await mockUnauthenticatedSession(page);
await page.goto('/asset/#/login');
const field = page.locator('.login-password-field');
const nativeInput = field.locator('input');
await expect(nativeInput).toBeVisible();
await typePassword(page, 'eye-icon-test');
await expect(field.locator('.login-password-mask')).toHaveCount(0);
await expect(field.locator('.el-input__password')).toBeVisible();
});
/** Code purpose (plain language): keep the password field as wide as the account field after the custom-dot wrapper is removed. Related files: LoginView.js, app.css. Flow: login form render -> wrapper width -> native input width -> visual layout regression check. */
test('keeps the password field the same width as the account field', async ({ page }) => {
await mockUnauthenticatedSession(page);
await page.goto('/asset/#/login');
const accountWidth = await page.getByRole('textbox', { name: '账号' }).locator('xpath=..').evaluate(wrapper => wrapper.getBoundingClientRect().width);
const passwordWidth = await page.getByRole('textbox', { name: '密码' }).locator('xpath=..').evaluate(wrapper => wrapper.getBoundingClientRect().width);
expect(passwordWidth).toBeCloseTo(accountWidth, 0);
});
...@@ -19,9 +19,15 @@ ...@@ -19,9 +19,15 @@
## Decisions ## Decisions
### 保留浏览器密码管理器,并统一输入层颜色
登录组件继续使用 `autocomplete="username"``autocomplete="current-password"`,让 Chrome/Edge 在其受保护的密码库中保存并自动填写账号、密码。项目只继续保存复选框状态和账号名,不保存可还原密码。
输入框背景只由外层 `.el-input__wrapper` 承载;`:-webkit-autofill` 的原生 input 保持透明,并使用背景裁剪(限制浏览器自动填充背景的绘制范围)隐藏 Chrome/Edge 的默认填充色。这样浏览器仍可保存和自动填写密码,但不会再由 input 内容区的内阴影形成矩形边缘线。此前的 `1000px inset` 内阴影将删除,只保留一组自动填充规则。
### 使用“覆盖浏览器绘制层”而非超长过渡 ### 使用“覆盖浏览器绘制层”而非超长过渡
`input:-webkit-autofill` 同时设置文本颜色、背景裁剪和与字段玻璃层一致的极低透明 inset shadow,并删除 `9999s` 背景过渡。长过渡只能延后浏览器浅色背景出现,不能阻止其重绘;即时覆盖才能在停留后保持稳定 该方案不再使用。透明输入层会与浏览器自动填充的内层形成可见边界;改用上一节的统一固定深色输入层
备选方案是给输入本体设置不透明深色底色。该方案可以隐藏白底,但会破坏用户要求的透明玻璃效果,因此不采用。 备选方案是给输入本体设置不透明深色底色。该方案可以隐藏白底,但会破坏用户要求的透明玻璃效果,因此不采用。
...@@ -29,6 +35,11 @@ ...@@ -29,6 +35,11 @@
普通、聚焦和自动填充选择器必须使用同一个显式字号与行高。浏览器自动填充状态不再单独继承或覆盖字号,避免首次进入和点击后视觉跳变。 普通、聚焦和自动填充选择器必须使用同一个显式字号与行高。浏览器自动填充状态不再单独继承或覆盖字号,避免首次进入和点击后视觉跳变。
登录样式中只保留 `16px / 24px` 这一处输入文字规格;普通、聚焦、悬停自动填充和自动填充聚焦状态共享同一条规则,并固定字体族,避免后续规则重新引入不同字号。
密码框不再展示浏览器原生掩码圆点,而是保留原生 password input 处理自动填写、光标、显示/隐藏和提交;页面只根据密码长度生成不含密码内容的圆点显示层。自动填写样式动画与组件挂载后的同步逻辑都只读取长度,不保存、不输出密码内容。这样未聚焦和聚焦状态始终由同一套 `16px / 24px` 圆点规则绘制。
### 按最新参考图重新校准面板位置与色彩 ### 按最新参考图重新校准面板位置与色彩
桌面端将以参考图的约 19% 视口左距作为视觉基准,并使用 `clamp` 计算而非单一像素值;1200px 以下缩小偏移,768px 以下居中。按钮采用暗蓝紫低饱和渐变和轻微边缘高光,避免当前浅蓝按钮脱离黑洞背景。 桌面端将以参考图的约 19% 视口左距作为视觉基准,并使用 `clamp` 计算而非单一像素值;1200px 以下缩小偏移,768px 以下居中。按钮采用暗蓝紫低饱和渐变和轻微边缘高光,避免当前浅蓝按钮脱离黑洞背景。
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
## What Changes ## What Changes
- 保留 Chrome/Edge 的安全密码管理器与账号、密码自动填写;项目自身不得把可还原密码写入 localStorage、IndexedDB 或接口响应。
- 将登录输入框从“输入本体透明”调整为“固定深色输入底”;空输入和浏览器自动填写后的输入本体使用同一个颜色,消除两层绘制交界的横线。
- 修复 Chrome/Edge 自动填充在页面停留或再次聚焦后出现的浅灰、白色输入框内层背景,并固定各状态的文字尺寸。 - 修复 Chrome/Edge 自动填充在页面停留或再次聚焦后出现的浅灰、白色输入框内层背景,并固定各状态的文字尺寸。
- 以最新参考图校准登录卡片的桌面端位置、尺寸、透明玻璃层级和黑洞背景关系;卡片接近黑洞左侧发光区域,但不遮挡最亮环带。 - 以最新参考图校准登录卡片的桌面端位置、尺寸、透明玻璃层级和黑洞背景关系;卡片接近黑洞左侧发光区域,但不遮挡最亮环带。
- 将登录按钮从高亮浅蓝改为与背景协调的低饱和深蓝紫渐变,同时保留清晰的主要操作层级。 - 将登录按钮从高亮浅蓝改为与背景协调的低饱和深蓝紫渐变,同时保留清晰的主要操作层级。
......
## ADDED Requirements ## ADDED Requirements
### Requirement: 自动填充输入框保持透明稳定 ### Requirement: 自动填充输入框保持透明稳定
系统 SHALL 使登录页账号和密码输入框在 Chrome/Edge 的普通、聚焦、自动填充及页面停留后的重绘状态下,保持深色透明玻璃外观,不得显示浅灰、白色或浅蓝色内部矩形背景 系统 SHALL 保留 Chrome/Edge 对账号和密码的安全自动填写,并使登录页账号和密码输入框在普通、聚焦、自动填充及页面停留后的重绘状态下,保持相同的固定深色输入底;卡片外层继续保持透明玻璃外观。系统不得将可还原密码保存到项目的浏览器存储或接口响应中
#### Scenario: 已保存账号被浏览器自动填充 #### Scenario: 已保存账号被浏览器自动填充
- **WHEN** 用户打开登录页面,Chrome 或 Edge 自动填入已保存的账号和密码 - **WHEN** 用户打开登录页面,Chrome 或 Edge 自动填入已保存的账号和密码
- **THEN** 输入文字保持可读,输入框内部不出现浅色矩形,外层玻璃边框与背景持续可见 - **THEN** 输入文字保持可读,输入框内部不出现浅色矩形或分层横线,且与空输入框使用相同的深色输入底
#### Scenario: 自动填充页面停留后重绘 #### Scenario: 自动填充页面停留后重绘
- **WHEN** 自动填充后的登录页保持打开状态并发生浏览器重绘或用户再次聚焦输入框 - **WHEN** 自动填充后的登录页保持打开状态并发生浏览器重绘或用户再次聚焦输入框
......
## 1. 输入框自动填充稳定性 ## 1. 输入框自动填充稳定性
- [x] 1.1 在 `frontend/src/styles/app.css` 移除登录输入框自动填充的超长背景过渡,并为普通、聚焦与自动填充状态统一字号、行高、文本颜色和即时透明覆盖规则 - [x] 1.1 在 `frontend/src/styles/app.css` 删除重复的自动填充补丁,使 Chrome/Edge 自动填充 input 保持透明、由外层统一绘制背景,并固定字号、行高和文字颜色
- [ ] 1.2 在 Chrome 与 Edge 中使用已保存账号密码打开登录页,等待页面停留并重复聚焦输入框,确认不存在浅灰、白色或浅蓝色内部矩形。 - [ ] 1.2 在 Chrome 与 Edge 中使用已保存账号密码打开登录页,等待页面停留并重复聚焦输入框,确认不存在浅灰、白色或浅蓝色内部矩形。
- [ ] 1.3 在 Chrome 和 Edge 中对比自动填写密码框的初始状态与聚焦状态,确认圆点尺寸一致。
- [ ] 1.4 验证手动输入、自动填写、显示密码和登录成功后的清空流程均正确同步统一圆点显示层,且不写入任何密码存储。
## 2. 参考图布局与视觉层级 ## 2. 参考图布局与视觉层级
- [x] 2.1 在 `frontend/src/styles/app.css` 将登录卡片桌面端左距校准到最新参考图约 19% 视口宽度,并以 `clamp` 和 1200px、768px 媒体查询保证小屏安全布局。 - [x] 2.1 在 `frontend/src/styles/app.css` 将登录卡片桌面端左距校准到最新参考图约 19% 视口宽度,并以 `clamp` 和 1200px、768px 媒体查询保证小屏安全布局。
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment