Simplify system implementation

This commit is contained in:
yuxuanhui
2026-08-04 18:59:21 +08:00
parent bf8da90433
commit ee0064135c
19 changed files with 418 additions and 1169 deletions
+37 -47
View File
@@ -1,59 +1,49 @@
# Component Guidelines
# 组件与样式
> How components are built in this project.
## 组件形状
---
React 模块使用具名函数组件,不使用无意义的 default export。`features/system/pages/system-status-page.tsx` 展示了页面组件如何组合 query、store 和 shared UI;`shared/ui/card.tsx` 展示了通过原生 HTML 属性扩展 primitive 的方式。
## Overview
```tsx
export function Button({
className,
size,
type = "button",
variant,
...props
}: ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>) {
return (
<button
className={cn(buttonVariants({ size, variant }), className)}
type={type}
{...props}
/>
)
}
```
<!--
Document your project's component conventions here.
可复用组件应保留原生属性和 `className`,通过 `...props` 支持组合;只在确实需要时添加受限的 variant。
Questions to answer:
- What component patterns do you use?
- How are props defined?
- How do you handle composition?
- What accessibility standards apply?
-->
## 样式与组合
(To be filled by the team)
- 使用 Tailwind utility class;主题 token 和全局基础规则放在 `src/styles/globals.css`。
- 使用 `cn`(`shared/ui/utils.ts`)合并可选 class,使用 `class-variance-authority` 管理 Button、Badge 等有限变体。
- 复杂页面用 `Card`、`CardHeader`、`CardContent` 等 composition primitive,而不是复制一套容器样式。
- 新建 shared primitive 前先搜索是否已有 `Button`、`Badge`、`Card` 或 `cn`。
---
## 页面状态
## Component Structure
页面应把加载、错误、成功状态转成用户可见的语义文本。`SystemStatusPage` 根据 query 状态显示“正在连接”“连接异常”“运行正常”,没有数据时使用安全的默认服务名。
<!-- Standard structure of a component file -->
## 可访问性
(To be filled by the team)
- 交互元素必须使用真实的 `<button>` 或其他语义元素;`Button` 默认 `type="button"` 以避免意外提交。
- 只有图形含义的 icon 使用 `aria-hidden="true"`;没有文字的主题切换按钮提供 `aria-label="切换主题"`,参照 `system-status-page.tsx`。
- 使用 `main`、标题、段落等语义结构,文本状态不能只靠颜色表达。
---
## 避免
## Props Conventions
<!-- How props should be defined and typed -->
(To be filled by the team)
---
## Styling Patterns
<!-- How styles are applied (CSS modules, styled-components, Tailwind, etc.) -->
(To be filled by the team)
---
## Accessibility
<!-- A11y requirements and patterns -->
(To be filled by the team)
---
## Common Mistakes
<!-- Component-related mistakes your team has made -->
(To be filled by the team)
- 不要在 shared UI 中请求数据、读取 feature hook 或写业务分支。
- 不要用 `dangerouslySetInnerHTML`、无理由的 `any` 或无语义的 `<div onClick>`。
- 不要把所有页面样式搬进新的全局 CSS;优先使用现有 token 和局部 utility class。
+32 -43
View File
@@ -1,54 +1,43 @@
# Directory Structure
# 前端目录与 feature 边界
> How frontend code is organized in this project.
## 当前布局
---
## Overview
<!--
Document your project's frontend directory structure here.
Questions to answer:
- Where do components live?
- How are features/modules organized?
- Where are shared utilities?
- How are assets organized?
-->
(To be filled by the team)
---
## Directory Layout
```
<!-- Replace with your actual structure -->
src/
├── ...
└── ...
```text
zhixing-web/src/
├── app/ # Provider、Router、QueryClient、应用壳
│ ├── app.tsx
│ ├── providers.tsx
│ ├── query-client.ts
│ └── router.tsx
├── routes/route-tree.tsx # TanStack Router 路由树
├── features/system/ # system 垂直切片
│ ├── api/ # request adapter、query、API 类型
│ └── pages/ # 页面组件和同目录测试
├── shared/
│ ├── api/request-json.ts # 同源 JSON transport 和 ApiError
│ ├── config/ui-store.ts # 持久化 UI 偏好
│ └── ui/ # 可复用的 Button、Card、Badge、cn
├── styles/globals.css # Tailwind 主题和全局基础样式
└── test/setup.ts # Vitest + Testing Library 初始化
```
---
`@/*` 映射到 `src/*`,在源码跨目录导入时使用该 alias;配置文件和同目录相对导入保持现有风格。
## Module Organization
## Feature 组织
<!-- How should new features be organized? -->
每个业务 feature 将 API 适配、类型和页面放在自己的目录,例如 `features/system/api/system.api.ts`、`system.query.ts`、`system.types.ts` 和 `pages/system-status-page.tsx`。页面可以依赖本 feature 的 API 和 `shared`,但 `shared` 不能反向依赖 feature。
(To be filled by the team)
路由只负责把路径映射到页面。当前 `routes/route-tree.tsx` 将 `/` 映射到 `SystemStatusPage`;不要把请求逻辑或全局状态初始化塞进路由声明。
---
## 命名
## Naming Conventions
- React 组件和页面使用 PascalCase 导出,文件使用 kebab-case,例如 `system-status-page.tsx`。
- feature API 文件按职责使用 `*.api.ts`、`*.query.ts`、`*.types.ts`。
- shared UI primitive 使用小写文件名并导出 PascalCase 组件,例如 `button.tsx` 导出 `Button`。
- 测试与被测模块同目录,使用 `.test.tsx` 或 `.test.ts`。
<!-- File and folder naming rules -->
## 反模式
(To be filled by the team)
---
## Examples
<!-- Link to well-organized modules as examples -->
(To be filled by the team)
- 不要创建一个全局 `components/`、`hooks/` 或 `services/` 目录来掩盖 feature 所有权。
- 不要让页面直接 import 远端 URL、调用 `fetch` 或保存 React Query 数据到 Zustand。
- 不要通过 `../../..` 穿透 feature 边界;优先使用 `@/features/...` 或 `@/shared/...`。
+29 -39
View File
@@ -1,51 +1,41 @@
# Hook Guidelines
# Hook 与数据请求
> How hooks are used in this project.
## API 适配三件套
---
服务端数据按 feature 放置为 API 函数、query hook 和类型文件:
## Overview
1. `features/system/api/system.types.ts` 定义 `SystemStatus`。
2. `features/system/api/system.api.ts:getSystemStatus` 调用 shared transport,并把 `AbortSignal` 传给 `fetch`。
3. `features/system/api/system.query.ts:useSystemStatus` 暴露 React Query hook,并使用 `systemStatusQueryKey`。
<!--
Document your project's hook conventions here.
页面只调用 `useSystemStatus`,不直接调用 `fetch`。新增 feature 应保持同样分层。
Questions to answer:
- What custom hooks do you have?
- How do you handle data fetching?
- What are the naming conventions?
- How do you share stateful logic?
-->
```ts
export const systemStatusQueryKey = ["system", "status"] as const
(To be filled by the team)
export function useSystemStatus() {
return useQuery({
queryFn: ({ signal }) => getSystemStatus(signal),
queryKey: systemStatusQueryKey,
})
}
```
---
## React Query
## Custom Hook Patterns
- 服务器状态由 TanStack Query 管理;公共默认值在 `app/query-client.ts`(不跟随窗口刷新、失败重试 1 次、`staleTime` 30 秒)。
- query key 使用 `as const` 常量,避免页面散落字符串。
- 使用 query function 提供的 `signal` 支持取消请求;不要忽略它或在页面手写生命周期 fetch。
- 页面显式处理 `isPending`、`isError` 和 `data`,参照 `SystemStatusPage`。
<!-- How to create and structure custom hooks -->
## Hook 命名与副作用
(To be filled by the team)
- 自定义 hook 以 `use` 开头并表达资源或行为,例如 `useSystemStatus`、`useUiStore`。
- 纯数据 hook 不执行额外副作用;需要同步 DOM 的副作用集中在 `app/providers.tsx:ThemeEffect`,由 Zustand 主题驱动 `document.documentElement` 的 class。
- 不要把一个 hook 同时用作服务器缓存和 UI 偏好存储;两者分别使用 React Query 与 Zustand。
---
## 反模式
## Data Fetching
<!-- How data fetching is handled (React Query, SWR, etc.) -->
(To be filled by the team)
---
## Naming Conventions
<!-- Hook naming rules (use*, etc.) -->
(To be filled by the team)
---
## Common Mistakes
<!-- Hook-related mistakes your team has made -->
(To be filled by the team)
- 不要在组件中直接 `fetch`、重复设置 query key 或把响应复制到本地 `useState`。
- 不要用 Zustand 保存 API 响应以“共享”数据,也不要用 React Query 保存主题等本地偏好。
- 不要为了复用一次性的 `useEffect` 创建泛化 hook;先确认是否已有跨页面重复模式。
+26 -30
View File
@@ -1,39 +1,35 @@
# Frontend Development Guidelines
# 前端开发规格
> Best practices for frontend development in this project.
前端是 `zhixing-web/` 下的 React 19 + TypeScript + Vite 应用,使用 TanStack Router、TanStack Query、Zustand、Tailwind CSS 4 和 Vitest。代码采用 feature 垂直切片;当前只有 `system` feature。
---
## 规格导航
## Overview
| 规格 | 用途 |
| --- | --- |
| [目录与 feature 边界](./directory-structure.md) | `app`、`routes`、`features`、`shared` 的职责 |
| [组件与样式](./component-guidelines.md) | 函数组件、组合、Tailwind、可访问性 |
| [Hook 与数据请求](./hook-guidelines.md) | React Query API 适配和 hook 结构 |
| [状态管理](./state-management.md) | Query、Zustand 与局部状态的边界 |
| [类型安全](./type-safety.md) | 严格 TypeScript、API 类型和类型断言 |
| [质量与测试](./quality-guidelines.md) | Prettier、ESLint、Vitest、构建门禁 |
This directory contains guidelines for frontend development. Fill in each file with your project's specific conventions.
## 开发前检查
---
- 先判断代码属于某个 feature 还是跨 feature 的 shared 能力;不要把业务代码放进 `shared/`。
- API 字段变更时,同时检查后端 Pydantic 响应模型、`features/<name>/api/*.types.ts`、query hook 和页面测试。
- 通过 `/api/v1` 同源路径请求后端。开发代理见 `vite.config.ts`,生产代理见 `nginx/default.conf.template`。
- 先搜索已有的 `cn`、`requestJson`、query key 和 UI primitive,再创建新 helper。
## Guidelines Index
## 质量检查
| Guide | Description | Status |
|-------|-------------|--------|
| [Directory Structure](./directory-structure.md) | Module organization and file layout | To fill |
| [Component Guidelines](./component-guidelines.md) | Component patterns, props, composition | To fill |
| [Hook Guidelines](./hook-guidelines.md) | Custom hooks, data fetching patterns | To fill |
| [State Management](./state-management.md) | Local state, global state, server state | To fill |
| [Quality Guidelines](./quality-guidelines.md) | Code standards, forbidden patterns | To fill |
| [Type Safety](./type-safety.md) | Type patterns, validation | To fill |
在 `zhixing-web/` 下运行:
---
```bash
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
## How to Fill These Guidelines
For each guideline file:
1. Document your project's **actual conventions** (not ideals)
2. Include **code examples** from your codebase
3. List **forbidden patterns** and why
4. Add **common mistakes** your team has made
The goal is to help AI assistants and new team members understand how YOUR project works.
---
**Language**: All documentation should be written in **English**.
根目录 `./dev.sh check` 和 `./dev.sh test` 会执行完整前后端检查。
+25 -41
View File
@@ -1,51 +1,35 @@
# Quality Guidelines
# 前端质量与测试
> Code quality standards for frontend development.
## 工具链与门禁
---
`zhixing-web/package.json` 定义以下命令:
## Overview
- `pnpm format:check`:Prettier 格式检查。
- `pnpm lint`:ESLint,`--max-warnings=0`;React Hooks 规则启用。
- `pnpm typecheck`:`tsc -b --pretty false`。
- `pnpm test`:Vitest 单次运行。
- `pnpm build`:TypeScript project build 后执行 Vite build。
- `pnpm check`:按 format、lint、typecheck、test 的顺序执行。
<!--
Document your project's quality standards here.
提交前运行 `pnpm check`,需要验证产物时再运行 `pnpm build`。根目录 `./dev.sh check` 会把前端检查与后端检查串起来。
Questions to answer:
- What patterns are forbidden?
- What linting rules do you enforce?
- What are your testing requirements?
- What code review standards apply?
-->
## 测试形状
(To be filled by the team)
- Vitest 使用 `jsdom`,公共初始化在 `src/test/setup.ts`,加载 `@testing-library/jest-dom/vitest`。
- React 页面使用 Testing Library 从用户可见行为断言,参照 `features/system/pages/system-status-page.test.tsx`。
- 页面测试通过 `vi.mock` 替换 feature query hook,并在 `beforeEach` 设置稳定的 query 返回值;测试渲染和文案,不测试 React Query 内部实现。
- 新增加载、错误或空数据分支时,至少为关键用户可见状态添加测试。
---
## 代码审查检查项
## Forbidden Patterns
- API 请求是否仍经过 `shared/api/request-json.ts`,并使用同源 `/api/v1` 路径?
- 服务器状态是否留在 React Query,UI 偏好是否只放入必要的 Zustand store?
- 是否保持 strict TypeScript、无 unused、无 lint warning?
- 交互元素是否有语义标签、键盘可用性和必要的 aria 文本?
- feature/shared 边界是否清楚,是否复用了现有 `cn` 和 UI primitive?
<!-- Patterns that should never be used and why -->
## 禁止模式
(To be filled by the team)
---
## Required Patterns
<!-- Patterns that must always be used -->
(To be filled by the team)
---
## Testing Requirements
<!-- What level of testing is expected -->
(To be filled by the team)
---
## Code Review Checklist
<!-- What reviewers should check -->
(To be filled by the team)
- 不要提交格式化、lint、类型检查或测试失败的代码,也不要用 `eslint-disable`/`@ts-ignore` 隐藏问题而不说明原因。
- 不要用实现细节选择器(例如依赖 class 名)替代 Testing Library 的角色、文本或可访问名称。
- 不要在测试中复制被测逻辑或只断言组件成功挂载;断言真实用户可见结果。
+23 -38
View File
@@ -1,51 +1,36 @@
# State Management
# 状态管理
> How state is managed in this project.
## 三类状态
---
| 状态 | 当前方案 | 示例 |
| --- | --- | --- |
| 服务器状态 | TanStack Query | `useSystemStatus` 和 `queryClient` |
| 跨页面 UI 偏好 | Zustand + `persist` | `useUiStore.theme` |
| 组件瞬时状态 | React 自带状态/事件 | 当前页面暂无复杂局部状态 |
## Overview
URL/路由状态由 TanStack Router 承担;当前路由树只有静态 `/`,不要为了静态页面额外引入全局 store。
<!--
Document your project's state management conventions here.
## Zustand 使用边界
Questions to answer:
- What state management solution do you use?
- How is local vs global state decided?
- How do you handle server state?
- What are the patterns for derived state?
-->
`shared/config/ui-store.ts` 只保存主题这一类跨页面 UI 偏好,并以 `zhixing-ui` 持久化到浏览器存储。组件读取最小 selector:
(To be filled by the team)
```tsx
const theme = useUiStore((state) => state.theme)
const toggleTheme = useUiStore((state) => state.toggleTheme)
```
---
新增全局字段前,确认它需要跨多个页面共享且不属于服务端缓存;否则优先放在组件局部或 URL。
## State Categories
## 服务器状态
<!-- Local state, global state, server state, URL state -->
所有 API 数据都通过 `queryClient` 和 feature query hook 管理,利用缓存、重试和失效机制。页面不应把 `data` 再写入 Zustand 或重复维护 `loading` 标志。
(To be filled by the team)
## 副作用
---
主题 class 的 DOM 同步集中在 `AppProviders` 内的 `ThemeEffect`,并依赖 store selector。不要在每个页面分别切换 `document.documentElement`,也不要在渲染阶段直接修改 DOM。
## When to Use Global State
## 常见错误
<!-- Criteria for promoting state to global -->
(To be filled by the team)
---
## Server State
<!-- How server data is cached and synchronized -->
(To be filled by the team)
---
## Common Mistakes
<!-- State management mistakes your team has made -->
(To be filled by the team)
- 把后端响应、错误对象或加载状态复制进 Zustand。
- 通过 `useUiStore((state) => state)` 订阅整个 store,造成无关更新。
- 为一个页面才能使用的开关添加持久化全局状态。
+16 -42
View File
@@ -1,51 +1,25 @@
# Type Safety
# TypeScript 类型安全
> Type safety patterns in this project.
## 编译约束
---
`tsconfig.app.json` 开启 `strict`、`noUnusedLocals`、`noUnusedParameters`、`noFallthroughCasesInSwitch`、`forceConsistentCasingInFileNames`,模块解析为 `Bundler`。新代码应在这些约束下编译,不要通过放宽配置来消除错误。
## Overview
## 类型放置
<!--
Document your project's type safety conventions here.
- 远端响应类型放在所属 feature 的 `api/*.types.ts`,例如 `SystemStatus`;不要在页面内重复声明同一 JSON 形状。
- shared transport 使用泛型 `requestJson<T>`,feature API 负责传入 feature 类型。
- 组件 props 优先复用 React 原生属性并组合库类型,参照 `ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<...>`。
- 只在需要类型本身时使用 `import type`,保持当前模块风格。
Questions to answer:
- What type system do you use?
- How are types organized?
- What validation library do you use?
- How do you handle type inference?
-->
## API 契约
(To be filled by the team)
`SystemStatus.status` 使用字面量类型 `"ok"`,表示后端契约中稳定的枚举值。新增枚举或可选字段时,先确认后端响应模型、错误语义和页面分支,再更新类型和测试。
---
`requestJson` 在 JSON 边界使用 `(await response.json()) as T`;这是当前唯一集中的网络断言。因为它没有运行时 schema 校验,不能把该泛型当成外部输入验证。若数据源不受同一仓库契约控制,应在 feature API 层加入显式解析/校验。
## Type Organization
## 禁止模式
<!-- Where types are defined, shared types vs local types -->
(To be filled by the team)
---
## Validation
<!-- Runtime validation patterns (Zod, Yup, io-ts, etc.) -->
(To be filled by the team)
---
## Common Patterns
<!-- Type utilities, generics, type guards -->
(To be filled by the team)
---
## Forbidden Patterns
<!-- any, type assertions, etc. -->
(To be filled by the team)
- 不要使用 `any`、`@ts-ignore` 或无解释的 `as` 来绕过严格检查。
- 不要把 `Record<string, unknown>` 当作所有 API 的默认类型;为稳定响应定义具名 interface/type。
- 不要在页面中用字符串索引访问未知字段,或把 API 响应重复 cast 成不同形状。
- 只有配置工具需要 default export;React feature/shared 模块沿用当前具名导出风格。