文件
hyb-oa/.trellis/spec/hyb-oa/frontend-react-vite-design.md
T

503 行
20 KiB
Markdown

# Frontend React / Vite / Design
## Source Of Truth
Frontend visual and interaction design uses the installed `frontend-design`
skill as the primary design authority. `docs/产品设计规则.md` remains a product
experience and business-boundary fact source, not a visual, brand or aesthetic
rulebook. For PC/mobile/WeCom pages, add Playwright or visual verification when
reasonable.
This spec keeps hyb-oa-specific React/Vite, shared-component, business-boundary
and verification contracts. Do not copy broad aesthetic guidance from
`frontend-design` into this file; use the skill directly for design judgment.
## Rules
- Use existing React/Vite/Tailwind patterns and shared components before adding
page-local UI systems.
- When `frontend-design` and older project visual rules conflict, follow
`frontend-design` for visual direction while preserving security, privacy,
Trellis, deployment and business data boundaries.
- Default product framing is a small-team Chinese workbench for domestic tech
startups with fewer than 10 people. User-facing copy should be natural
Chinese; avoid English product words such as `Portal`, `Dashboard` and
`Workflow` unless they are internal test fixtures or technical identifiers.
- PC pages should keep dense but orderly OA layouts: clear header, filters,
table/list, status, details and predictable actions.
- Mobile pages must reuse the existing unified mobile shell, card/list rows,
safe-area and sticky action patterns unless the current task intentionally
redesigns them with `frontend-design`.
- Do not add decorative landing-page styling to operational OA pages.
- Run `cd client && npm run typecheck && npm run build` for frontend changes.
## Frozen / Boundary Items
Frozen modules and Enterprise WeChat-owned HR/attendance entries must be labeled
or hidden according to the current Trellis task; do not silently re-enable them.
- Use the shared `WorkspaceNotice` pattern for trial boundaries, safety notes,
and post-trial capabilities instead of page-local warning blocks.
- Trial-facing PC lists must hide obvious TEST/SMOKE records by default when a
page-level toggle exists; the toggle may reveal them for troubleshooting.
## Scenario: Business Flow Tests And Frozen Modules
### 1. Scope / Trigger
- Trigger: Playwright or QA shell tests need to create workflow data while some
product modules are frozen for launch.
- Applies to `client/tests/e2e/**`, `scripts/qa/run-business-flow-gates.sh`,
and frontend route/permission assertions.
### 2. Signatures
- QA command: `BUSINESS_FLOW_GROUPS=<groups> bash scripts/qa/run-business-flow-gates.sh local`.
- Frozen-module API expectation: non-admin launch roles may receive `403` for
frozen module list/create/submit endpoints.
- Enabled workflow test data sources should use active entities such as leave,
reimbursement or procurement unless the task explicitly reopens the frozen
module.
### 3. Contracts
- Do not use a frozen module as fixture setup for an unrelated enabled feature.
Example: mobile approval offline sync should not create a travel request when
travel permissions are intentionally frozen.
- Permission direct-access tests must assert the frozen contract directly, such
as employee `GET /api/travel-requests?...` returning `403`.
- UI route smoke may still verify that a frozen page displays a boundary state
without entering a management or write-capable mode.
- Do not loosen roles or seed permissions merely to make old E2E data setup
pass.
### 4. Validation & Error Matrix
- Frozen role calls frozen module create/submit/list endpoint -> `403` or the
documented feature-gate status.
- Enabled workflow feature needs pending task data -> create data through an
enabled workflow entity.
- E2E fails with `PERM_001` while setting up unrelated feature data -> update
the test data source before changing app permissions.
### 5. Good/Base/Bad Cases
- Good: mobile approval offline test creates a submitted leave request, then
verifies pending-task deep link, offline queue, sync success and conflict.
- Base: travel-specific tests are skipped or admin-scoped while travel remains
frozen.
- Bad: grant employee `travel.create` only so a mobile approval test can build
a fixture.
### 6. Tests Required
- Focused E2E group that previously failed.
- Full affected business-flow group after the focused fix.
- `cd client && npm run typecheck && npm run build` when TypeScript test code
changes.
### 7. Wrong vs Correct
#### Wrong
```ts
await createSubmittedTravelRequestViaApi({ user: EMPLOYEE });
```
#### Correct
```ts
await createSubmittedLeaveViaApi({ user: EMPLOYEE });
```
## Convention: PC Operation List Density
**What**: PC list/workflow pages that combine status metrics, tabs/filters,
range controls, export and table/list content should reuse the shared operation
classes in `client/src/index.css`:
- `ops-status-strip` for compact status metrics.
- `ops-command-bar`, `ops-command-bar__row`, `ops-command-bar__tabs`,
`ops-command-bar__actions` and `ops-filter-label` for tabs, filters and
secondary actions.
- `ops-table-focus` for the primary desktop table container.
- `ops-inline-alert` for temporary mode hints such as batch operation notices.
- `PrimaryTable` already applies `ops-table-focus` by default; page code may
add responsive visibility classes such as `hidden md:block`, but should not
recreate a separate table frame.
- Finance/admin status summaries should prefer the shared `finance-status-strip`
and `finance-metric-card` classes unless the task introduces a new reusable
metric component.
**Why**: This keeps high-frequency PC pages dense and predictable without
recreating page-local Tailwind command bars. It also lets `frontend-design`
change the visual treatment once while preserving component contracts.
**Example**:
```tsx
<StatStrip columns={4} className="ops-status-strip">
<MetricCard title="待办总数" value={summary.pendingTotal} />
</StatStrip>
<FilterBar className="ops-command-bar !p-3 md:!p-3.5">
<div className="ops-command-bar__row">
<Tabs value={status}>
<TabsList className="ops-command-bar__tabs !h-auto !justify-start !bg-slate-50">
<TabsTrigger value="pending">待处理</TabsTrigger>
</TabsList>
</Tabs>
<div className="ops-command-bar__actions">...</div>
</div>
</FilterBar>
<PrimaryTable className="hidden md:block">...</PrimaryTable>
```
**Tests Required**:
- `cd client && npm run typecheck`
- `cd client && npm run build`
- Focused Playwright screenshot coverage when changing login, Portal, primary
PC list pages or command-bar/table styling.
## Convention: Global Shell And Page Header Baseline
**What**: PC page redesigns should preserve the shared Wave0-2 shell and
header baseline instead of recreating page-local wrappers:
- `AppShellLayout` owns the PC shell background and content offset through
`app-shell` and `app-shell-main`.
- `Sidebar` and `NavLinks` own primary navigation grouping. The mental model is
`工作` -> `业务` -> `财务` -> `管理` -> collapsed `更多`.
- `Topbar` is a compact command surface: page title, group badge, user context,
global search and notifications. Do not expand it back into a large hero.
- `PageHeader` and `WorkspaceHeader` should use `page-header-shell`,
`page-header-shell__eyebrow`, `page-header-shell__title`,
`page-header-shell__subtitle` and `page-header-shell__actions`.
- Auth guidance and step hints may use `ux-command-note`, `ux-step-list` and
`ux-step-item` when they explain the next action without adding page-local
warning cards.
**Why**: The shell/header baseline gives Wave3-5 pages a stable visual entry
point and prevents each route from inventing a different title/action layout.
**Example**:
```tsx
<WorkspaceHeader
eyebrow="审批"
title="审批中心"
subtitle="先看待处理,再进入详情核对依据。"
/>
```
**Tests Required**:
- `cd client && npm run typecheck`
- `cd client && npm run build`
- Focused Playwright coverage when changing `AppShellLayout`, `Sidebar`,
`Topbar`, `NavLinks`, `PageHeader`, `WorkspaceHeader`, login, Portal or other
A-class pages.
## Convention: A-Class PC Task-First Pages
**What**: A-class PC pages such as Portal, approvals, reimbursement list and
reimbursement detail should make the current user task obvious before exposing
secondary operations:
- Treat the primary persona as a small-team operator wearing multiple hats, not
a dedicated OA clerk. The first screen should reduce decision cost for a
founder, finance/admin owner, project lead or employee who may switch roles
during the same session.
- First visible task surface should answer: what needs attention, what can be
started, and where recent progress lives.
- Keep one primary action in the first screen. Allow at most two secondary
entries; move export, batch, delete, templates, complex filters, statistics
and admin/finance tools into `更多`, `高级 / 台账`, detail actions, role-only
panels or confirmation dialogs.
- Do not turn small-team workflows into platform-builder surfaces by default:
approval designers, broad configuration matrices, multi-level enterprise
dashboards and batch admin consoles need explicit Trellis task scope.
- Explanation text should only be default-visible for risk, exception or empty
states. Do not use repeated subtitles or notice cards to explain normal
workflow steps that the layout can express.
- Low-frequency capabilities are hidden or moved, not removed, unless the
current Trellis task explicitly deletes the capability.
**Why**: hyb-oa serves small teams with overlapping roles. Core pages must make
the next click clear for ordinary employees and approvers, while keeping finance
and admin tools available without dominating the default path.
**Tests Required**:
- `cd client && npm run typecheck`
- `cd client && npm run build`
- Focused Playwright or screenshot checks for changed A-class PC pages,
asserting the task surface, primary action and secondary/advanced entry
behavior.
## Convention: Detail And Mobile Command Surfaces
**What**: PC detail pages and notification-like flows should reuse the Wave3
surface classes in `client/src/index.css` before adding page-local Tailwind
card systems:
- `detail-workspace-grid` for the main PC detail two-column workspace.
- `detail-command-panel`, `detail-fact-grid` and `detail-fact-card` for compact
status, summary, payment, export and evidence facts.
- `detail-evidence-card` for attachments, invoices and other proof material.
- `detail-timeline` and `detail-timeline-item` for approval progress and
history.
- `detail-action-stack` and `detail-action-card` for approve/reject/return,
payment and other high-risk command groups.
- `notification-inbox`, `notification-row-card` and
`notification-row-card--unread` for PC notification streams; avoid reverting
notifications to a desktop table unless a task explicitly requires tabular
comparison.
Mobile key pages should reuse the mobile command classes in
`client/src/styles/mobile-touch.css`:
- `mobile-focus-card` for the current task, current list or account summary.
- `mobile-command-surface` for tabs, search and filters.
- `mobile-list-card--focus` and `mobile-list-card--unread` for state emphasis.
**Why**: Detail pages should feel like quiet command surfaces: status, summary,
primary action, evidence and history have predictable places. Mobile pages
should stay card/list based, with no PC tables, no horizontal overflow and no
button text squeeze.
**Tests Required**:
- `cd client && npm run typecheck`
- `cd client && npm run build`
- Focused Playwright screenshot coverage for changed PC detail, notification
and mobile pages.
- UI design rules check when changing shared visual classes.
## Scenario: Auth Pages And PWA Updates
### 1. Scope / Trigger
- Trigger: login, password change, password recovery or other unauthenticated
forms are active while the PWA service worker finds a new frontend version.
- Applies to `client/src/pwa.ts`, `client/src/layouts/AuthLayout.tsx`, and
auth routes such as `/login`, `/change-password`, `/forgot-password`, and
future reset-password pages.
### 2. Signatures
- Event name: `hyb-oa:pwa-update-ready`.
- Event detail: `{ applyUpdate?: () => void | Promise<void> }`.
- Route predicate: auth input pages must be checked by pathname before calling
`updateServiceWorker(true)`.
### 3. Contracts
- Auth input pages must not auto-reload when `onNeedRefresh()` fires.
- Auth input pages must show an explicit update prompt with a user-triggered
refresh action.
- Non-auth pages may keep automatic service-worker update behavior when the
task does not change broader runtime policy.
- Passwords and verification codes must stay only in React component state; do
not write them to `localStorage`, `sessionStorage`, URL params, reports, or
Trellis task evidence.
- Password visibility controls must use icon buttons with accessible labels and
preserve the current input value.
### 4. Validation & Error Matrix
- PWA update on `/login` -> dispatch update-ready event and keep form state.
- PWA update on `/forgot-password` -> dispatch update-ready event and keep
email/code/password fields in memory.
- User clicks refresh in the prompt -> run the captured update handler.
- User dismisses prompt -> no reload and no form reset.
- Password reset request returns success for any email -> show generic sent
message; do not reveal whether the account exists.
### 5. Good/Base/Bad Cases
- Good: user is typing a password, update prompt appears, typed values remain.
- Base: user is on a normal portal page, PWA update can refresh per existing
runtime behavior.
- Bad: service-worker update auto-refreshes an auth form and clears credentials.
- Bad: tests use real-looking personal email addresses or persist example
passwords outside the test file.
### 6. Tests Required
- Frontend typecheck and build.
- Focused Playwright auth UX test asserting:
- login password can be shown/hidden without losing value;
- forgot-password flow calls request-code and reset endpoints;
- auth-page PWA update prompt appears without clearing inputs.
- UI design rules check when auth page layout changes.
- Any Playwright test that renders `/login`, `/forgot-password` or another auth
route with mocked `/api/*` responses must mock `/api/auth/capabilities` with
`emailDelivery.ready`, `emailLoginCode.enabled` and `passwordReset.enabled`.
Returning `{}` for unknown auth APIs can crash auth pages before the visual or
navigation assertion actually runs.
### 7. Wrong vs Correct
#### Wrong
```ts
onNeedRefresh() {
void updateServiceWorker?.(true);
}
```
#### Correct
```ts
onNeedRefresh() {
if (isAuthInputPath(window.location.pathname)) {
window.dispatchEvent(new CustomEvent(PWA_UPDATE_READY_EVENT, { detail: { applyUpdate } }));
return;
}
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.
- Keep the DOM/tab order aligned with the main form flow even when CSS places
`忘记密码?` visually beside the password label: username/email input -> password
input -> secondary controls. Do not render the forgot-password link before the
password input in DOM order.
- 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`;
- keyboard Tab from the username/email field lands on the password input, not
the forgot-password link;
- 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
<AuthLayout>
<Card>
<Card>
<LoginForm />
</Card>
</Card>
</AuthLayout>
```
#### Correct
```tsx
<AuthPanel title="登录工作台" subtitle="账号密码登录">
<LoginForm />
<SecondaryLoginMethods />
</AuthPanel>
```
#### Wrong
```tsx
<Label htmlFor="login-password">密码</Label>
<Link to="/forgot-password">忘记密码?</Link>
<PasswordInput id="login-password" />
```
#### Correct
```tsx
<Label htmlFor="login-password">密码</Label>
<PasswordInput id="login-password" />
<Link className="row-start-1" to="/forgot-password">忘记密码?</Link>
```
## Convention: Finance And Admin Console Surfaces
**What**: PC finance and C-class admin pages should reuse Wave4 shared classes
from `client/src/index.css` before creating page-local card/table layouts:
- `finance-status-strip`, `finance-metric-card`,
`finance-metric-card__label` and `finance-metric-card__value` for compact
five-column status summaries.
- `finance-workspace-grid`, `finance-command-panel`, `finance-list-card` and
`finance-list-card--active` for payment/AP/invoice operational workspaces.
- `admin-command-panel`, `admin-tile-grid`, `admin-tile-card` and
`admin-workspace-grid` for settings, workflow, feature-flag, user, master
data, notification-channel and audit pages.
**Why**: Finance/admin pages are lower-frequency but high-risk operational
surfaces. They need dense scanability, stable command regions and clear risk
hierarchy without turning every page into a bespoke Tailwind card system.
**Example**:
```tsx
<div className="finance-status-strip" data-testid="payment-batches-summary">
<div className="finance-metric-card">
<div className="finance-metric-card__label">可处理</div>
<div className="finance-metric-card__value">{actionableCount}</div>
</div>
</div>
<div className="finance-workspace-grid">
<section className="finance-command-panel">...</section>
<section className="finance-command-panel">...</section>
</div>
```
**Tests Required**:
- `cd client && npm run typecheck`
- `cd client && npm run build`
- Focused Playwright coverage for changed finance/admin pages. The Wave4
baseline test is `client/tests/e2e/pc-finance-admin-visual-wave4.spec.ts`.
- UI design rules check when changing these shared classes.