# 认证门户：登录页 UI/UX

## 1. 页面定义

| 项目 | 内容 |
| --- | --- |
| 页面名称 | 认证门户登录页 |
| 真实页面路由 | `/login?next=<目标路径>` |
| 页面实现 | `auth-portal/src/app/login/page.tsx` |
| 样式实现 | `auth-portal/src/app/globals.css` |
| API 客户端 | `auth-portal/src/lib/auth-client.ts` |
| 主应用入口关系 | 主应用 `/login` 只显示跳转提示并重定向到认证门户，不承载登录表单 |
| 核心任务 | 输入用户名和密码，完成认证后通过 handoff 返回目标工作区页面 |
| 测试状态 | 已完成修复并复测通过；所有实测视口无横向越界、无小点击目标、无首屏控件出界 |

## 2. 信息架构与组件层级

```text
portal-page
├── portal-toolbar                         层级 z-index: 10，右上角
│   └── LanguageSwitcher
│       ├── Lang 标签
│       ├── ZH 按钮
│       └── EN 按钮
└── auth-page                              全屏背景层
    └── portal-panel                       登录卡片定位容器
        └── auth-card.login-card           登录主容器，手机竖屏与横屏均做紧凑适配
            ├── Boardware Logo
            ├── 页面标题
            ├── auth-form
            │   ├── 用户名字段
            │   │   ├── 可见 label
            │   │   ├── 用户图标
            │   │   └── 用户名 input
            │   ├── 密码字段
            │   │   ├── 可见 label
            │   │   ├── 锁图标
            │   │   ├── 密码 input
            │   │   └── 显示或隐藏密码按钮
            │   ├── 错误信息区域
            │   └── 登录提交按钮
            ├── “或”分隔线
            └── 注册引导与注册链接
```

### 层级关系

- 背景图属于最低视觉层，用于传达产品品牌和 Agent/Memory/Tools 等能力氛围。
- 登录卡片是唯一主任务容器，具有半透明白色表面、边框和阴影。
- 语言切换器脱离登录卡片，绝对定位在页面右上角，`z-index: 10`。
- 表单错误位于密码字段与登录按钮之间，属于当前提交动作的内联反馈。
- 页面无弹窗、抽屉或二级详情层。

## 3. 布局与大概位置

### 桌面与宽屏，大于 920px

- 页面占满视口，背景图居中并 `cover`。
- 登录卡片位于页面右侧，垂直居中。
- `portal-panel` 宽度使用 `clamp(360px, 34vw, 560px)`。
- 页面右侧留白由 `clamp(24px, 8vw, 128px)` 控制。
- 语言切换器固定在右上角，距顶部约 `20px`，距右侧约 `24px`。
- 实测 `1365×900` 和 `1920×1080` 无横向越界、无组件重叠。

### 平板与中等宽度，小于等于 920px

- 登录卡片从右侧布局切换为水平居中、靠近页面底部。
- 容器最大宽度约 `520px`。
- 页面顶部保留品牌背景展示空间。
- 实测 `768×1024` 和 `1024×768` 无横向越界、无组件重叠。

### 手机，小于等于 640px

- 页面左右边距约 `16px`。
- 登录卡片在矮屏手机下进入紧凑节奏，缩小 Logo、标题和表单间距。
- 输入框高度约 `54px`，字号 `16px`，可避免 iOS 输入自动缩放。
- `320×568`、`375×667`、`390×844` 均可在首屏完整展示主要内容。

### 手机横屏

- 实测 `844×390` 时使用横屏紧凑布局，主要控件完整位于视口内。
- 页面无横向越界，无双层滚动。

## 4. 页面状态

| 状态 | 当前表现 | UX 目的 | 测试结论 |
| --- | --- | --- | --- |
| 初始状态 | 显示 Logo、标题、空用户名和密码、登录按钮、注册入口 | 清晰建立品牌和唯一主任务 | 通过 |
| 输入状态 | 输入框显示文本；密码默认掩码 | 防止密码旁观泄露 | 通过 |
| 密码可见状态 | 点击眼睛按钮后，密码类型切换为 `text`，按钮名称同步变为“隐藏密码” | 降低密码输入错误 | 通过 |
| 浏览器必填校验 | 空表单提交被浏览器阻止，并聚焦用户名 | 避免无效网络请求 | 通过 |
| 提交中 | 登录按钮禁用，并显示“登录中...” | 防止重复提交，告知请求正在处理 | 通过 |
| 登录失败 | 在密码字段下方显示友好错误，按钮恢复可用，错误区域可被播报并获得焦点 | 让用户修正凭据后重试 | 通过 |
| 登录成功 | 构造 `/handoff?code=...&next=...` 并使用 `location.replace` 跳转 | 安全把认证结果交给目标工作区，并避免返回到已提交登录页 | 通过 |
| 语言切换 | ZH/EN 立即更新字段、错误和辅助文案，并写入 cookie/localStorage | 支持中英文用户并保持选择 | 通过，刷新后保持 |
| 注册跳转 | 跳转到 `/register` 并保留 `next` 参数 | 注册完成后仍返回原目标页 | 通过 |

## 5. 操作与 UX 逻辑

| 操作 | 触发方式 | 状态变化与反馈 | UX 目的 | 当前结果 |
| --- | --- | --- | --- | --- |
| 切换中文或英文 | 点击右上角 ZH/EN | 当前语言按钮高亮；页面文案立即更新；刷新后保持 | 降低语言理解成本 | 正常 |
| 聚焦用户名或密码 | 点击或 Tab | 输入框边框、背景和外部阴影变化 | 明确当前输入位置 | 正常 |
| 输入用户名 | 键盘输入 | 受控输入更新，支持 `autocomplete=username` | 减少重复输入 | 正常 |
| 输入密码 | 键盘输入 | 默认掩码，支持 `autocomplete=current-password` | 保护敏感信息并支持密码管理器 | 正常 |
| 显示或隐藏密码 | 点击眼睛图标 | `password/text` 类型切换，可访问名称同步切换 | 帮助用户核对密码 | 正常 |
| 空表单提交 | 点击提交或按 Enter | 浏览器原生 required 校验阻止请求并聚焦用户名 | 及早阻止无效操作 | 正常 |
| 有效表单提交 | 点击提交或在密码框按 Enter | 清空旧错误；按钮禁用；显示加载文案；发起登录请求 | 提供明确进度并防止重复提交 | 正常 |
| 登录失败 | API 返回失败 | 显示本地化错误；按钮恢复可用；错误区域 `role="alert"`/`aria-live` 并获得焦点 | 支持修正后重试 | 正常 |
| 登录成功 | API 返回 token 和 handoff code | 使用 `location.replace` 前往目标前端 handoff 页，保留 `next` | 完成认证并返回原任务 | 正常 |
| 前往注册 | 点击注册链接 | 前往注册页并保留 `next` | 为无账号用户提供明确替代路径 | 正常 |

## 6. 响应式测试矩阵

测试日期：2026-06-04。浏览器：Playwright Chromium。

| 视口 | 横向越界 | 页面纵向滚动 | 卡片内部滚动 | 卡片完整位于视口 | 结论 |
| --- | --- | --- | --- | --- | --- |
| `320×568` | 无 | 无 | 无 | 是 | 通过 |
| `375×667` | 无 | 无 | 无 | 是 | 通过 |
| `390×844` | 无 | 无 | 无 | 是 | 通过 |
| `844×390` 横屏 | 无 | 无 | 无 | 是 | 通过 |
| `768×1024` | 无 | 无 | 无 | 是 | 通过 |
| `1024×768` | 无 | 无 | 无 | 是 | 通过 |
| `1365×900` | 无 | 无 | 无 | 是 | 通过 |
| `1920×1080` | 无 | 无 | 无 | 是 | 通过 |

## 7. 可访问性与触控检查

### 已通过

- 页面存在一个清晰的 `h1`。
- Logo 有 `alt="Boardware logo"`。
- 用户名和密码均存在与 input 关联的 label。
- Tab 顺序符合 DOM 和视觉顺序：ZH → EN → 用户名 → 密码 → 显示密码 → 登录 → 注册。
- 密码显示按钮具有动态可访问名称。
- 用户名和密码支持浏览器自动填充。
- 输入框、语言按钮、显示密码按钮、提交按钮和注册链接的实际命中区域均不小于 `44×44px`。
- 登录按钮具有本地化可访问名称。
- 登录失败错误使用 `role="alert"` 与 `aria-live`，并在失败后聚焦错误区域。

### 待继续观察

- 本轮未使用真实屏幕阅读器做端到端朗读，只通过 DOM、焦点和 Playwright 辅助信息验证。
- 登录背景图片仍可继续做 WebP/AVIF 与响应式加载优化。

## 8. 已修复问题与遗留优化

### 已修复：手机横屏双层纵向滚动

- 复现：使用 `844×390` 访问登录页。
- 用户看到：卡片底部超出首屏；页面可滚动，卡片内部也可滚动。
- 影响：用户难以判断应滚动页面还是卡片；单独滚动卡片后仍无法看到注册链接。
- 相关实现：
  - `.auth-page` 在 `<=920px` 时保留顶部和底部 padding。
  - `.auth-card.login-card` 同时设置基于 `100vh` 的 `max-height` 和 `overflow-y:auto`。
- 复测结论：`844×390` 无横向越界、无双层滚动，提交和注册入口均在首屏可达。

### 已修复：登录按钮缺少可访问名称

- 复现：使用键盘 Tab 到登录按钮，或检查辅助功能树。
- 用户影响：屏幕阅读器只能识别为无名称按钮。
- 相关实现：提交按钮默认仅渲染箭头 SVG，SVG 为 `aria-hidden`。
- 复测结论：按钮拥有本地化 `aria-label`，加载状态继续表达“登录中”。

### 已修复：错误反馈缺少可访问播报和焦点恢复

- 复现：提交错误凭据。
- 当前反馈：显示“接口错误 401: 用户名或密码错误”，但无 `role="alert"`、无 `aria-live`，失败后焦点未落在错误或字段上。
- 用户影响：技术错误码增加理解成本；屏幕阅读器和键盘用户可能不知道提交已经失败。
- 复测结论：
  - 用户文案不暴露 HTTP 状态与“接口错误”前缀。
  - 文案说明原因和恢复方式，例如“用户名或密码错误，请检查后重试”。
  - 错误使用 `role="alert"`/`aria-live`，并聚焦错误摘要。

### 已修复：关键次级操作点击区域过小

- 影响范围：语言按钮、显示密码按钮、注册链接。
- 复测结论：语言按钮、显示密码按钮、提交和注册链接实际命中区域均达到至少 `44×44px`。

### 遗留优化：登录背景资源偏大

- `login-background.png` 约 `1.3MB`，当前未提供 WebP/AVIF 或响应式尺寸。
- 用户影响：弱网和移动网络下首屏背景显示较慢。
- 建议验收标准：提供 WebP/AVIF，并根据视口加载合理尺寸；保留背景空间避免布局变化。

## 9. 当前实现的正向 UX

- 页面只有一个主操作，层级清晰。
- 桌面使用背景品牌视觉和右侧卡片，主任务聚焦明确。
- 手机输入字号为 `16px`，避免 iOS 自动放大。
- 请求中禁用提交按钮，可防止重复登录。
- 错误区域预留最小高度，错误出现时不会明显推动后续内容。
- `next` 参数在注册跳转和成功 handoff 中均正确保留。
- 语言选择刷新后保持。
- 所有实测视口均无横向越界。

## 10. 后续验收清单

- [x] 修复横屏双层滚动后，重新测试 `844×390` 和更低高度视口。
- [x] 为提交按钮添加本地化可访问名称。
- [x] 改善登录失败文案、错误播报和焦点恢复。
- [x] 扩大语言、显示密码和注册链接的触控区域。
- [x] 评估并实现持续可见的字段标签。
- [ ] 优化登录背景图片格式和响应式加载。
- [ ] 在 Safari、iOS Safari 和 Android Chrome 验证动态地址栏与 `100vh` 行为。
- [ ] 使用屏幕阅读器完成一次端到端登录测试。

## 11. 本轮测试证据

- 自动化结果：`/tmp/beaver-login-qa-results.json`
- 截图目录：`/tmp/beaver-login-qa-shots`
- 临时测试脚本：`/tmp/beaver-ui-qa-tests/login-page-qa.spec.js`
- 测试命令：

```bash
./node_modules/.bin/playwright test login-page-qa.spec.js \
  --config=/tmp/beaver-ui-qa-tests/pw.config.js \
  --workers=1
```
