From afa03c0b719908aec5d5e8b7b9117b9eb9d1c696 Mon Sep 17 00:00:00 2001 From: shunli Date: Tue, 26 May 2026 21:23:05 +0800 Subject: [PATCH] =?UTF-8?q?oa-auth-layout-wecom-login-ux=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=AE=A4=E8=AF=81=E9=A1=B5=E7=99=BB=E5=BD=95=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spec/hyb-oa/frontend-react-vite-design.md | 77 ++++++++++++ .../check.jsonl | 6 + .../implement.jsonl | 8 ++ .../implement.md | 40 ++++++ .../prd.md | 118 ++++++++++++++++++ .../research/login-page-patterns.md | 23 ++++ .../task.json | 33 +++++ client/src/components/auth/AuthPanel.tsx | 31 +++++ client/src/layouts/AuthLayout.tsx | 8 +- client/src/pages/ChangePasswordPage.tsx | 28 +---- client/src/pages/ForgotPasswordPage.tsx | 27 ++-- client/src/pages/LoginPage.tsx | 66 +++++----- client/tests/e2e/wecom-entry.spec.ts | 18 --- 13 files changed, 392 insertions(+), 91 deletions(-) create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/check.jsonl create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.jsonl create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.md create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/prd.md create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/research/login-page-patterns.md create mode 100644 .trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/task.json create mode 100644 client/src/components/auth/AuthPanel.tsx diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index dfbcd064..53b0f27f 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -105,3 +105,80 @@ onNeedRefresh() { applyUpdate(); } ``` + +## Scenario: Auth Page Layout And Login Entry Hierarchy + +### 1. Scope / Trigger + +- Trigger: login, password recovery, forced password change or future + unauthenticated auth forms need layout changes. +- Applies to `client/src/layouts/AuthLayout.tsx`, shared auth components under + `client/src/components/auth/`, and auth routes such as `/login`, + `/forgot-password` and `/change-password`. + +### 2. Component Contract + +- Auth routes should use one shared auth panel for brand, page title, subtitle, + content and optional footer links. +- `AuthLayout` owns only the auth page shell: background, width constraint, + centering, PWA update prompt and route outlet. +- Page implementations own only their form fields, submit actions, validation + messages and route-specific login-entry choices. + +### 3. Visual Contracts + +- Do not nest a form card inside another login card. +- Put the brand block inside the auth panel so logo, system name, title and + form share one visual axis. +- Keep the panel width stable around the current auth-shell width; auth pages + should feel like an OA workstation entry, not a marketing split hero. +- Place `忘记密码?` beside the password label or directly below the password + input on the right; do not bury it in help text or page footer. +- Use password visibility icon buttons with accessible labels and preserve the + current input value. + +### 4. Login Entry Hierarchy + +- Normal browser: account/password login is primary; Enterprise WeChat and OIDC + entries belong below the primary submit button as secondary methods. +- Enterprise WeChat WebView: Enterprise WeChat one-click entry is primary; + account/password login remains available as the fallback path. +- Enterprise WeChat entry changes must remain frontend entry hierarchy changes + unless the task explicitly approves backend OAuth, binding or WeCom API work. +- Email verification-code login is a separate login mode. Do not infer it from + password-recovery verification-code flows without a backend login-code + contract and abuse-control plan. + +### 5. Tests Required + +- Frontend typecheck and build. +- Focused auth E2E covering: + - password visibility and forgot-password navigation on `/login`; + - password recovery request-code and reset flow on `/forgot-password`; + - normal-browser Enterprise WeChat entry as secondary CTA; + - WeCom WebView Enterprise WeChat entry as primary CTA with password fallback. +- UI design rules check and Playwright screenshot evidence when the auth layout + changes visibly. + +### 6. Wrong vs Correct + +#### Wrong + +```tsx + + + + + + + +``` + +#### Correct + +```tsx + + + + +``` diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/check.jsonl b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/check.jsonl new file mode 100644 index 00000000..b430994e --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/check.jsonl @@ -0,0 +1,6 @@ +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Validate Trellis task structure, checks and commit readiness."} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "Verify auth page layout, PWA prompt behavior, typecheck/build and visual checks."} +{"file": ".trellis/spec/hyb-oa/wecom-boundary.md", "reason": "Verify Enterprise WeChat login entry remains an entry point only."} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Verify no sensitive auth values are persisted or written to evidence."} +{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend quality review entry point."} +{"file": "docs/产品设计规则.md", "reason": "Verify page style follows Hyinda OA design baseline."} diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.jsonl b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.jsonl new file mode 100644 index 00000000..fe3c1d8e --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.jsonl @@ -0,0 +1,8 @@ +{"file": ".trellis/spec/hyb-oa/index.md", "reason": "hyb-oa Trellis spec entry point and read order."} +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Task contract, implementation approval, context curation and commit rules."} +{"file": ".trellis/spec/hyb-oa/frontend-react-vite-design.md", "reason": "Frontend auth page, PWA update and UI verification contracts."} +{"file": ".trellis/spec/hyb-oa/wecom-boundary.md", "reason": "Enterprise WeChat ownership and API/write boundaries for login entry changes."} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Sensitive-data handling for auth, password and Trellis evidence."} +{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend React/Vite guideline entry point."} +{"file": "docs/产品设计规则.md", "reason": "Hyinda brand, PC/mobile OA visual and layout rules."} +{"file": ".trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/research/login-page-patterns.md", "reason": "Login page pattern research and chosen layout hierarchy."} diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.md b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.md new file mode 100644 index 00000000..3bdbeb2f --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/implement.md @@ -0,0 +1,40 @@ +# 认证页布局与企业微信登录体验优化实施记录 + +## Implementation Scope + +- 新增共享认证页组件 `AuthPanel`,统一品牌、标题、副标题、内容区和页脚样式。 +- 调整 `AuthLayout`,仅保留认证页面背景、居中容器、PWA 更新提示和路由出口。 +- 重构登录页,移除卡片套卡片;普通浏览器以账号密码登录为主,企业微信/SSO 放入次级登录区。 +- 重构企业微信 WebView 登录页层级,企业微信一键进入为主 CTA,账号密码保留为兜底入口。 +- 重构找回密码页和修改密码页,复用同一认证面板,不改后端认证接口。 +- 收窄企业微信入口 E2E,避免前端 UI 用例依赖真实登录和个人设置页跳转。 + +## Safety Boundaries + +- 不新增邮箱验证码登录。 +- 不改后端登录、找回密码、企业微信 OAuth 或绑定接口。 +- 不写入企业微信后台配置,不写真实业务数据。 +- 不记录账号密码、验证码、token、Secret 或真实 payload。 +- 部署和公网 smoke 需在本地验证通过后另行确认。 + +## Validation Log + +- `cd client && npm run typecheck`:已通过。 +- `cd client && npm run build`:已通过,production build 和 PWA precache 正常生成。 +- `node scripts/qa/check-ui-design-rules.mjs --strict`:已通过。 +- `python3 ./.trellis/scripts/task.py validate 05-26-oa-auth-layout-wecom-login-ux`:已通过。 +- `node scripts/qa/check-trellis-governance.mjs`:已通过;仅提示 `docs/产品设计规则.md` 是非 spec/task research 上下文。 +- `git diff --check`:已通过。 +- 登录/找回密码 focused E2E:已通过,2 passed。 +- 企业微信入口 E2E:已通过,2 passed。 +- 完整 `login-portal-ux.spec.ts`:认证页、找回密码、企业微信 WebView 相关 5 passed;Portal 历史文案断言 3 failed,见下方已知漂移。 +- 视觉截图已生成: + - `output/playwright/oa-auth-layout-wecom-login-ux/desktop-login.png` + - `output/playwright/oa-auth-layout-wecom-login-ux/desktop-forgot-password.png` + - `output/playwright/oa-auth-layout-wecom-login-ux/mobile-wecom-login.png` +- 移动企业微信主按钮计算背景色:`rgb(0, 57, 138)`。 + +## Known Unrelated Test Drift + +- `client/tests/e2e/login-portal-ux.spec.ts` 的完整套件仍有 Portal 历史文案断言漂移,涉及旧文案 `快捷操作栏`、`角色重点`、`常用入口`。 +- 该漂移不属于本认证页任务,本任务仅保留登录页、找回密码和企业微信入口 focused 用例作为验收依据。 diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/prd.md b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/prd.md new file mode 100644 index 00000000..c9f96323 --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/prd.md @@ -0,0 +1,118 @@ +# 认证页布局与企业微信登录体验优化 + +## Goal + +把登录、找回密码、改密三类认证页统一成成熟、克制、可复用的 OA 认证页模板,解决当前登录页布局随意、卡片嵌套、企业微信入口层级不清的问题。第一期只优化布局和企业微信登录入口,不新增邮箱验证码登录。 + +## Scope + +- 前端认证页布局:`AuthLayout`、登录页、找回密码页、修改密码页。 +- 共享认证页组件:品牌、标题、副标题、内容区和页脚的统一面板。 +- 企业微信登录入口层级:普通浏览器为次级入口,企业微信 WebView 为主入口。 +- 认证页自动化验证:登录页、找回密码、企业微信入口和 PWA 更新不打断输入。 +- Trellis task 治理:PRD、research、implement/check 上下文和验证记录。 + +## What I Already Know + +- 用户已确认按推荐方案执行:先重做登录页布局,强化企业微信入口;邮箱验证码登录作为后续增强。 +- 当前 `LoginPage` 已有账号密码登录、忘记密码入口、企业微信入口和企业微信 WebView 优先逻辑。 +- 当前 `AuthLayout` 在卡片外展示品牌区,页面内又有主卡片和登录表单内层卡片,视觉层级显得临时拼接。 +- 当前 `ForgotPasswordPage`、`ChangePasswordPage` 与登录页样式不完全一致。 +- 项目设计规则要求 OA 页面参考 Hyinda 官网品牌气质,保持浅色工作台、克制字号、清晰边界,不做营销页。 +- 企业微信边界要求企业微信只做入口、绑定、免登录和消息回流,不替代 OA 主业务页面。 + +## Research References + +- `research/login-page-patterns.md`:中后台和开源系统登录页布局共性,结论是主认证面板、忘记密码放密码字段附近、企业微信/SSO 入口作为明确但分层的登录方式。 + +## Requirements + +- 新增或重构认证页共享模板,使登录、找回密码、改密页使用一致的面板宽度、内边距、标题层级、表单间距和提示样式。 +- 登录页只保留一层主面板,不再出现表单卡片套登录卡片。 +- 品牌区进入主面板顶部,Logo、系统名、标题、表单左侧对齐。 +- 普通浏览器登录页: + - 账号密码表单为主。 + - “忘记密码?”放在密码 label 同一行右侧。 + - 登录按钮为主按钮。 + - 企业微信登录在主按钮下方作为次级入口。 + - OIDC/SSO 如启用,仍放在次级入口区。 +- 企业微信 WebView 登录页: + - 企业微信一键进入作为主 CTA。 + - 账号密码登录作为兜底入口,可展开或保留在下方。 + - 保留未绑定/异常提示。 +- 找回密码页: + - 使用同一认证面板。 + - 保留邮箱验证码找回流程,不改后端接口。 + - 返回登录入口放在页脚弱链接。 +- 改密页: + - 使用同一认证面板。 + - 保留旧密码、新密码、确认新密码和密码显隐。 +- 不新增邮箱验证码登录 tab,不新增后端验证码登录接口。 +- 不持久化密码、验证码或 token。 +- 认证页 PWA 更新提示仍不能自动刷新表单。 + +## Acceptance Criteria + +- [ ] 登录页无卡片套卡片,主认证面板视觉统一。 +- [ ] 普通浏览器下账号密码为主,企业微信登录为次级入口。 +- [ ] 企业微信 WebView 下企业微信一键进入为主,账号密码为兜底入口。 +- [ ] 忘记密码入口位于密码 label 右侧,密码显隐控件仍可用。 +- [ ] 找回密码页和改密页复用同一认证页视觉模板。 +- [ ] 原有登录、找回密码、企业微信入口 E2E 仍通过。 +- [ ] 前端 typecheck、build、设计规则检查通过。 +- [ ] 不新增或输出真实账号、密码、验证码、token、Secret。 + +## Definition of Done + +- 本地验证通过:`cd client && npm run typecheck && npm run build`。 +- Focused E2E 覆盖登录页布局、忘记密码、企业微信入口。 +- UI 设计规则检查通过。 +- Trellis validate、governance、`git diff --check` 通过。 +- 提交后再请求部署批准;只有部署和公网检查通过后,才能告知可公网测试。 + +## Out of Scope + +- 不做邮箱验证码登录。 +- 不改后端认证接口。 +- 不新增短信登录。 +- 不改企业微信 OAuth/绑定后端逻辑。 +- 不修改真实企业微信后台配置。 +- 不执行真实账号重置或真实业务写入。 + +## Proposed Implementation + +1. 新增共享认证页组件,例如 `AuthPanel`,统一品牌、标题、副标题、内容区和页脚链接。 +2. 调整 `AuthLayout`,只负责页面背景、居中、PWA 更新提示和 Outlet,不再单独摆放品牌区。 +3. 重构 `LoginPage`: + - 去掉内层登录卡片。 + - 普通浏览器按账号密码主表单 + 次级企业微信入口布局。 + - 企业微信 WebView 按企业微信主 CTA + 账号密码兜底布局。 +4. 重构 `ForgotPasswordPage`、`ChangePasswordPage` 使用共享认证面板。 +5. 更新 Playwright 用例: + - 登录页密码显隐、忘记密码入口、PWA 更新输入不丢失。 + - 企业微信入口仍显示并跳转。 +6. 运行本任务测试计划。 + +## Test Plan + +```bash +cd client && npm run typecheck +cd client && npm run build +E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/login-portal-ux.spec.ts tests/e2e/wecom-entry.spec.ts --workers=1 --reporter=list +node scripts/qa/check-ui-design-rules.mjs --strict +python3 ./.trellis/scripts/task.py validate 05-26-oa-auth-layout-wecom-login-ux +node scripts/qa/check-trellis-governance.mjs +git diff --check +``` + +## Technical Notes + +- Design rules: `docs/产品设计规则.md` +- Frontend spec: `.trellis/spec/hyb-oa/frontend-react-vite-design.md` +- WeCom boundary: `.trellis/spec/hyb-oa/wecom-boundary.md` +- Security/privacy: `.trellis/spec/hyb-oa/security-privacy.md` +- Login page: `client/src/pages/LoginPage.tsx` +- Forgot password page: `client/src/pages/ForgotPasswordPage.tsx` +- Change password page: `client/src/pages/ChangePasswordPage.tsx` +- Auth layout: `client/src/layouts/AuthLayout.tsx` +- Existing auth E2E: `client/tests/e2e/login-portal-ux.spec.ts`、`client/tests/e2e/wecom-entry.spec.ts` diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/research/login-page-patterns.md b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/research/login-page-patterns.md new file mode 100644 index 00000000..2b43456b --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/research/login-page-patterns.md @@ -0,0 +1,23 @@ +# 登录页布局参考研究 + +## 参考对象 + +- Ant Design ProComponents `LoginForm`:中后台登录常见模式,支持账号密码、验证码、忘记密码、其他登录方式等组合。参考链接:https://procomponents.ant.design/en-US/components/login-form/ +- `vue-element-admin`:开源后台模板,登录页以账号密码为主,强调简单、稳定、权限系统入口清楚。参考链接:https://github.com/PanJiaChen/vue-element-admin +- JeecgBoot:国内低代码/中后台系统,支持账号登录和第三方登录能力,适合参考“企业内部系统需要多登录入口但入口层级要清晰”。参考链接:https://github.com/jeecgboot/JeecgBoot + +## 共性结论 + +- 登录页常见结构是一个主认证面板,不使用多层嵌套卡片。 +- “忘记密码”通常放在密码 label 同一行右侧,或者密码输入框下方右对齐。 +- 账号密码是默认兜底入口;验证码登录通常作为 tab 或切换项,不和找回密码验证码复用。 +- 企业微信/微信/SSO 这类第三方入口通常放在主登录按钮下方,或在对应容器内作为主 CTA。 +- 内部 OA 不需要营销型左右分屏、大图背景或长功能说明;更适合克制、稳定、工作台式页面。 + +## 映射到 hyb-oa + +- 当前任务只做第一期:认证页布局统一 + 企业微信入口层级优化。 +- 邮箱验证码登录需要新增后端登录验证码接口、投递能力和风控,不纳入本期。 +- 普通浏览器:账号密码为主,企业微信登录为次级入口。 +- 企业微信 WebView:企业微信一键进入为主,账号密码为兜底入口。 +- 登录页、找回密码页、改密页应复用同一套认证页面板尺寸、标题层级、表单间距和提示样式。 diff --git a/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/task.json b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/task.json new file mode 100644 index 00000000..09e57a8c --- /dev/null +++ b/.trellis/tasks/05-26-oa-auth-layout-wecom-login-ux/task.json @@ -0,0 +1,33 @@ +{ + "id": "oa-auth-layout-wecom-login-ux", + "name": "oa-auth-layout-wecom-login-ux", + "title": "认证页布局与企业微信登录体验优化", + "description": "统一登录、找回密码、改密三类认证页布局,强化普通浏览器与企业微信 WebView 的登录入口层级;邮箱验证码登录作为后续增强不纳入本期。", + "status": "in_progress", + "dev_type": null, + "scope": "client/auth-ux", + "package": null, + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-26", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [ + "client/src/layouts/AuthLayout.tsx", + "client/src/pages/LoginPage.tsx", + "client/src/pages/ForgotPasswordPage.tsx", + "client/src/pages/ChangePasswordPage.tsx", + "client/tests/e2e/login-portal-ux.spec.ts", + "client/tests/e2e/wecom-entry.spec.ts" + ], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/client/src/components/auth/AuthPanel.tsx b/client/src/components/auth/AuthPanel.tsx new file mode 100644 index 00000000..daef680e --- /dev/null +++ b/client/src/components/auth/AuthPanel.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from "react"; +import { AppBrand } from "../shell/AppBrand"; +import { cn } from "../../lib/utils"; + +type AuthPanelProps = { + title: string; + subtitle?: string; + children: ReactNode; + footer?: ReactNode; + className?: string; + "data-testid"?: string; +}; + +export function AuthPanel({ title, subtitle, children, footer, className, "data-testid": testId }: AuthPanelProps) { + return ( +
+
+ +
+

{title}

+ {subtitle ?

{subtitle}

: null} +
+
{children}
+ {footer ?
{footer}
: null} +
+
+ ); +} diff --git a/client/src/layouts/AuthLayout.tsx b/client/src/layouts/AuthLayout.tsx index a669b571..a146189e 100644 --- a/client/src/layouts/AuthLayout.tsx +++ b/client/src/layouts/AuthLayout.tsx @@ -1,5 +1,4 @@ import { Outlet } from "react-router-dom"; -import { AppBrand } from "../components/shell/AppBrand"; import { Button } from "../components/ui/button"; import { usePwaUpdatePrompt } from "../hooks/usePwaUpdatePrompt"; @@ -7,11 +6,8 @@ export function AuthLayout() { const { updateReady, applyUpdate, dismissUpdate } = usePwaUpdatePrompt(); return ( -
-
-
- -
+
+
{updateReady ? (
系统有新版本
diff --git a/client/src/pages/ChangePasswordPage.tsx b/client/src/pages/ChangePasswordPage.tsx index 3adf965d..bfe49396 100644 --- a/client/src/pages/ChangePasswordPage.tsx +++ b/client/src/pages/ChangePasswordPage.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { isApiError } from "../api/types"; import { mapApiErrorMessage } from "../api/errorMessages"; import { useAuth } from "../auth/AuthContext"; +import { AuthPanel } from "../components/auth/AuthPanel"; import { Button } from "../components/ui/button"; import { Label } from "../components/ui/label"; import { PasswordInput } from "../components/ui/password-input"; @@ -50,17 +51,9 @@ export function ChangePasswordPage() { } return ( -
-
-

hyb-oa

-

修改密码

-

- 为了账号安全,请先修改初始密码后再进入系统。 -

-
- +
-
+
-
+
密码要求:{PASSWORD_REQUIREMENTS.join(";")}。

-
-

建议的新密码规则

-
    - {PASSWORD_REQUIREMENTS.map((item) => ( -
  • {item}
  • - ))} -
-
- -
+
-
+ ); } diff --git a/client/src/pages/ForgotPasswordPage.tsx b/client/src/pages/ForgotPasswordPage.tsx index 3056e980..65ed110d 100644 --- a/client/src/pages/ForgotPasswordPage.tsx +++ b/client/src/pages/ForgotPasswordPage.tsx @@ -3,6 +3,7 @@ import { Link, useNavigate } from "react-router-dom"; import { forgotPassword, resetPassword } from "../api/auth"; import { mapApiErrorMessage } from "../api/errorMessages"; import { isApiError } from "../api/types"; +import { AuthPanel } from "../components/auth/AuthPanel"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; @@ -93,13 +94,17 @@ export function ForgotPasswordPage() { } return ( -
-
-

找回密码

-

使用账号邮箱接收验证码,设置新密码后重新登录。

-
- -
+ + 返回登录 + + )} + > +
) : null} - -
- - 返回登录 - -
-
+
); } diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 555adb4f..14700937 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -5,6 +5,7 @@ import { fetchSsoProviders, fetchWecomConfig } from "../api/auth"; import { mapApiErrorMessage } from "../api/errorMessages"; import { isApiError } from "../api/types"; import { useAuth } from "../auth/AuthContext"; +import { AuthPanel } from "../components/auth/AuthPanel"; import { getDeviceFingerprint } from "../auth/deviceFingerprint"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; @@ -108,7 +109,7 @@ export function LoginPage() { function renderPasswordPanel(testId: string) { return ( -
+
@@ -150,29 +151,22 @@ export function LoginPage() { } return ( -
-
-

登录工作台

-

- {showWecomPrimary ? "企业微信进入优先。" : "账号密码登录。"} -

-
- -
+ +
{wecomNotice ? ( -
+
{wecomNotice.title}
{wecomNotice.description}
) : null} {showWecomPrimary ? ( -
+
{wecomConfig && !wecomEntry ? ( -
+
企业微信入口不可用,请用账号密码。
) : null} @@ -192,25 +186,35 @@ export function LoginPage() { ? renderPasswordPanel(showWecomPrimary ? "login-password-secondary-panel" : "login-password-primary-panel") : null} - {!showWecomPrimary && wecomEntry ? ( - - ) : null} + {!showWecomPrimary && (wecomEntry || oidcProvider) ? ( +
+
+ + 其他登录方式 + +
- {!showWecomPrimary && oidcProvider ? ( - + {wecomEntry ? ( + + ) : null} + + {oidcProvider ? ( + + ) : null} +
) : null}
-
+ ); } diff --git a/client/tests/e2e/wecom-entry.spec.ts b/client/tests/e2e/wecom-entry.spec.ts index 85ff855c..56203cce 100644 --- a/client/tests/e2e/wecom-entry.spec.ts +++ b/client/tests/e2e/wecom-entry.spec.ts @@ -1,17 +1,5 @@ import { expect, test } from "@playwright/test"; -import { EMPLOYEE, login } from "./helpers/auth"; -import { openProfileSettings } from "./helpers/navigation"; - -function runtimeEmployee() { - const identifier = process.env.LIVE_SMOKE_EMPLOYEE_IDENTIFIER?.trim(); - const password = process.env.LIVE_SMOKE_EMPLOYEE_PASSWORD?.trim(); - if (identifier && password) { - return { identifier, password, name: "Live Smoke Employee" }; - } - return EMPLOYEE; -} - function mockWecomConfigPayload() { return { enabled: true, @@ -52,12 +40,6 @@ test("E2E 企微入口:普通浏览器下企微按钮为次级入口", async ( await page.getByTestId("wecom-entry-secondary").click(); await expect(page).toHaveURL(/\/api\/auth\/wecom\/login\?returnTo=/); - - const employee = runtimeEmployee(); - await login(page, employee); - await openProfileSettings(page, employee.name); - await expect(page.getByRole("heading", { name: "企业微信绑定" })).toBeVisible(); - await expect(page.getByText("接入状态", { exact: true })).toBeVisible(); }); test("E2E 企微入口:企业微信场景优先显示企微 CTA,并展示未绑定提示", async ({ page }) => {