@teamix-evo/skills 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/manifest.json +63 -46
- package/package.json +3 -3
- package/src/teamix-evo-code-opentrek/SKILL.md +94 -0
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/api-layering.md +8 -5
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/checklist.md +4 -2
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/error-and-loading.md +38 -25
- package/src/teamix-evo-code-opentrek/file-structure.md +282 -0
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/forms-and-validation.md +14 -12
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/reuse-first.md +27 -17
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/routing-and-codesplit.md +23 -21
- package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/testing.md +32 -28
- package/src/teamix-evo-code-uni-manager/SKILL.md +97 -0
- package/src/teamix-evo-code-uni-manager/api-layering.md +372 -0
- package/src/teamix-evo-code-uni-manager/checklist.md +195 -0
- package/src/teamix-evo-code-uni-manager/error-and-loading.md +391 -0
- package/src/teamix-evo-code-uni-manager/file-structure.md +341 -0
- package/src/teamix-evo-code-uni-manager/forms-and-validation.md +461 -0
- package/src/teamix-evo-code-uni-manager/reuse-first.md +190 -0
- package/src/teamix-evo-code-uni-manager/routing-and-codesplit.md +452 -0
- package/src/teamix-evo-code-uni-manager/testing.md +398 -0
- package/src/teamix-evo-design-opentrek/SKILL.md +64 -0
- package/src/teamix-evo-design-opentrek/boundaries.md +533 -0
- package/src/teamix-evo-design-opentrek/brand.md +154 -0
- package/src/teamix-evo-design-opentrek/checklist.md +85 -0
- package/src/teamix-evo-design-opentrek/components.md +294 -0
- package/src/teamix-evo-design-opentrek/flows.md +51 -0
- package/src/teamix-evo-design-opentrek/foundations.md +274 -0
- package/src/teamix-evo-design-opentrek/generation-flow.md +243 -0
- package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +96 -0
- package/src/teamix-evo-design-opentrek/patterns/dashboard.md +33 -0
- package/src/teamix-evo-design-opentrek/patterns/detail-page.md +203 -0
- package/src/teamix-evo-design-opentrek/patterns/form-page.md +292 -0
- package/src/teamix-evo-design-opentrek/patterns/list-page.md +367 -0
- package/src/teamix-evo-design-opentrek/patterns/page-types.md +159 -0
- package/src/teamix-evo-design-opentrek/patterns/sidebar.md +122 -0
- package/src/teamix-evo-design-opentrek/philosophy.md +98 -0
- package/src/teamix-evo-design-opentrek/rules/README.md +39 -0
- package/src/teamix-evo-design-opentrek/rules/boundaries.rules.json +391 -0
- package/src/teamix-evo-design-uni-manager/SKILL.md +64 -0
- package/src/teamix-evo-design-uni-manager/boundaries.md +567 -0
- package/src/teamix-evo-design-uni-manager/brand.md +202 -0
- package/src/teamix-evo-design-uni-manager/checklist.md +115 -0
- package/src/teamix-evo-design-uni-manager/components.md +257 -0
- package/src/teamix-evo-design-uni-manager/flows.md +63 -0
- package/src/teamix-evo-design-uni-manager/foundations.md +261 -0
- package/src/teamix-evo-design-uni-manager/generation-flow.md +230 -0
- package/src/teamix-evo-design-uni-manager/patterns/dashboard.md +97 -0
- package/src/teamix-evo-design-uni-manager/patterns/detail-page.md +253 -0
- package/src/teamix-evo-design-uni-manager/patterns/form-page.md +366 -0
- package/src/teamix-evo-design-uni-manager/patterns/list-page.md +389 -0
- package/src/teamix-evo-design-uni-manager/patterns/page-types.md +167 -0
- package/src/teamix-evo-design-uni-manager/philosophy.md +108 -0
- package/src/teamix-evo-design-uni-manager/rules/README.md +49 -0
- package/src/teamix-evo-design-uni-manager/rules/boundaries.rules.json +418 -0
- package/src/teamix-evo-manage/SKILL.md +289 -0
- package/skills/teamix-evo-coding-conventions/SKILL.md +0 -92
- package/skills/teamix-evo-coding-conventions/file-structure.md +0 -273
- package/skills/teamix-evo-design-rules/SKILL.md +0 -86
- package/skills/teamix-evo-design-rules/boundaries.md +0 -89
- package/skills/teamix-evo-design-rules/checklist.md +0 -108
- package/skills/teamix-evo-design-rules/generation-flow.md +0 -142
- package/skills/teamix-evo-design-rules/prompts/page-design.md +0 -148
- package/skills/teamix-evo-design-rules-opentrek/SKILL.md +0 -48
- package/skills/teamix-evo-design-rules-opentrek/brand-rules.md +0 -74
- package/skills/teamix-evo-design-rules-uni-manager/SKILL.md +0 -51
- package/skills/teamix-evo-design-rules-uni-manager/ai-scenarios.md +0 -51
- package/skills/teamix-evo-design-rules-uni-manager/command-center.md +0 -108
- package/skills/teamix-evo-design-rules-uni-manager/danger-ops.md +0 -87
- package/skills/teamix-evo-manage/SKILL.md +0 -178
- package/skills/teamix-evo-ui-upgrade/SKILL.md +0 -75
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# 业务工程目录约定
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 3 的强制读取项。AI 确定新文件放置位置前必须读完本文件。
|
|
4
|
+
|
|
5
|
+
> 业内主流 React/Vite 业务工程的标准目录骨架。本文件是 AI 决定"新建文件应该放在哪"的唯一参考。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 顶层 `src/` 骨架
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/
|
|
13
|
+
├── pages/ # 路由页面(每个目录对应一条路由)
|
|
14
|
+
├── components/ # 跨页面复用的业务组件
|
|
15
|
+
├── services/ # 后端通信(纯函数,见 api-layering.md)
|
|
16
|
+
├── hooks/ # 自定义 Hook(包含数据 hook、UI hook)
|
|
17
|
+
├── contexts/ # React Context(跨子树共享、低频更新的全局态)
|
|
18
|
+
├── stores/ # 高频全局态(zustand / jotai / redux,首次需要时引入,只用一种)
|
|
19
|
+
├── types/ # Domain / API 类型声明(.ts 仅类型,无运行时)
|
|
20
|
+
├── utils/ # 纯函数工具(无 React、无副作用)
|
|
21
|
+
├── lib/ # 三方 SDK 包装、http 实例、环境配置、monitor
|
|
22
|
+
├── routes/ # 路由配置 + 守卫(若用 react-router 集中式声明)
|
|
23
|
+
├── layouts/ # 应用级 / 区域级布局(ConsoleLayout、AuthLayout)
|
|
24
|
+
├── test/ # 测试基础设施(setup.ts、handlers.ts、render.tsx、fixtures/)
|
|
25
|
+
├── styles/ # 全局样式 / token 覆盖(尽量少)
|
|
26
|
+
├── assets/ # 图片 / 字体 / 静态文件
|
|
27
|
+
├── locales/ # i18n 资源(可选)
|
|
28
|
+
├── main.tsx # 应用入口
|
|
29
|
+
└── App.tsx # 根组件
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`api/` 和 `services/`、`views/` 和 `pages/`)。`*.test.ts(x)` 文件**就近**放在被测文件同目录,**不要**集中堆到顶层 `tests/`(见 [`testing.md`](testing.md))。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 各目录职责与边界
|
|
37
|
+
|
|
38
|
+
### `src/pages/`
|
|
39
|
+
|
|
40
|
+
**职责**:路由页面。一个目录 = 一条路由。
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
src/pages/
|
|
44
|
+
├── orders/
|
|
45
|
+
│ ├── index.tsx # 列表页(/orders)
|
|
46
|
+
│ ├── [id].tsx # 详情页(/orders/:id),命名按路由方案
|
|
47
|
+
│ ├── _components/ # 仅本页面用的子组件(下划线前缀,不导出给外部)
|
|
48
|
+
│ │ └── OrderFilterBar.tsx
|
|
49
|
+
│ └── _hooks/ # 仅本页面用的 hook
|
|
50
|
+
│ └── useOrderFilter.ts
|
|
51
|
+
└── users/
|
|
52
|
+
└── ...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
约束:
|
|
56
|
+
|
|
57
|
+
- 页面组件**默认 default export**,与文件名一致
|
|
58
|
+
- `_components/` `_hooks/` 用下划线前缀标记**私有**,**禁止跨页面 import**
|
|
59
|
+
- 跨页面用得到的组件/hook → 升到 `src/components/` `src/hooks/`
|
|
60
|
+
|
|
61
|
+
### `src/components/`
|
|
62
|
+
|
|
63
|
+
**职责**:跨页面复用的业务组件。
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
src/components/
|
|
67
|
+
├── OrderStatusBadge.tsx
|
|
68
|
+
├── UserAvatar.tsx
|
|
69
|
+
└── filters/
|
|
70
|
+
├── DateRangeFilter.tsx
|
|
71
|
+
└── StatusFilter.tsx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
约束:
|
|
75
|
+
|
|
76
|
+
- 命名 PascalCase,文件名 = 组件名
|
|
77
|
+
- 单组件 = 单文件;复杂组件可建子目录 `<Name>/{index.tsx, types.ts, hooks.ts}`
|
|
78
|
+
- **不要**重复 `@teamix-evo/ui` 已经提供的能力(见 [reuse-first.md](reuse-first.md))
|
|
79
|
+
- **不要**写"小工具组件"如 `Wrapper`、`Container` —— 用 div + tailwind 即可
|
|
80
|
+
|
|
81
|
+
### `src/services/`
|
|
82
|
+
|
|
83
|
+
**职责**:后端通信纯函数。详见 [`api-layering.md`](api-layering.md)。
|
|
84
|
+
|
|
85
|
+
### `src/hooks/`
|
|
86
|
+
|
|
87
|
+
**职责**:跨页面复用的自定义 Hook。
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
src/hooks/
|
|
91
|
+
├── useOrderList.ts # 数据 hook(包 react-query)
|
|
92
|
+
├── useCreateOrder.ts
|
|
93
|
+
├── useDebounce.ts # UI hook
|
|
94
|
+
└── useMediaQuery.ts
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
约束:
|
|
98
|
+
|
|
99
|
+
- 命名必须 `use` 开头
|
|
100
|
+
- 单 hook = 单文件
|
|
101
|
+
- 数据 hook 调 `src/services/`,**禁止**直接 fetch
|
|
102
|
+
|
|
103
|
+
### 全局态选型决策
|
|
104
|
+
|
|
105
|
+
**装机时默认不预装任何状态库**。按"最小够用"逐级升级,不要直接跳到 zustand:
|
|
106
|
+
|
|
107
|
+
| 场景 | 用什么 |
|
|
108
|
+
| ------------------------------------------------ | -------------------------------------------------------- |
|
|
109
|
+
| 组件内状态 | `useState` / `useReducer` |
|
|
110
|
+
| 跨少量组件、低频更新(当前用户、主题、语言、租户) | React `Context` |
|
|
111
|
+
| 跨域、多页面、**高频**更新的客户端态 | `src/stores/`(zustand / jotai / redux 选一) |
|
|
112
|
+
| 服务端数据(列表、详情、表单提交) | `react-query` / `swr`,**永远不进 Context、也不进 store** |
|
|
113
|
+
|
|
114
|
+
「高频」的粗略判定:一个状态被 10+ 处读取、且每秒可能变化一次以上 —— Context 会触发整子树重渲染,这时才有引 store 的收益。
|
|
115
|
+
|
|
116
|
+
Context 文件位置:
|
|
117
|
+
|
|
118
|
+
- 全应用级(UserContext、ThemeContext)→ `src/contexts/<Name>Context.tsx`
|
|
119
|
+
- 仅一个页面/子树用 → `src/pages/<id>/_components/<Name>Context.tsx`
|
|
120
|
+
|
|
121
|
+
> 如果项目自始至终只用 Context + react-query 就能解决,**不需要**建 `src/stores/`,也不需要引 zustand。等真出现跨域高频全局态再装。
|
|
122
|
+
|
|
123
|
+
### `src/stores/`
|
|
124
|
+
|
|
125
|
+
**职责**:跨域、高频更新的客户端全局态。**不是**全局态的默认入口,先看上方「全局态选型决策」。
|
|
126
|
+
|
|
127
|
+
约束:
|
|
128
|
+
|
|
129
|
+
- 一个项目**只用一种**状态库(zustand / jotai / redux 选一,首次需要时引入并固定)
|
|
130
|
+
- 按 domain 拆 store:`useOrderStore.ts`、`useUserStore.ts`
|
|
131
|
+
- **不要**把服务端数据放 store —— 用 `react-query` / `swr` 缓存(它们就是异步 store)
|
|
132
|
+
- **不要**为了消除一处 props drilling 就建 store —— 那是 Context 的场景
|
|
133
|
+
|
|
134
|
+
### `src/types/`
|
|
135
|
+
|
|
136
|
+
**职责**:类型声明。
|
|
137
|
+
|
|
138
|
+
约束:
|
|
139
|
+
|
|
140
|
+
- 只放 `.ts` 类型文件,**不导出运行时代码**
|
|
141
|
+
- 按 domain 拆:`order.ts`、`user.ts`
|
|
142
|
+
- 通用类型放 `api.ts`、`common.ts`
|
|
143
|
+
|
|
144
|
+
### `src/utils/`
|
|
145
|
+
|
|
146
|
+
**职责**:纯函数工具。
|
|
147
|
+
|
|
148
|
+
约束:
|
|
149
|
+
|
|
150
|
+
- **不依赖 React**(用了 React 应该是 hook,放 `src/hooks/`)
|
|
151
|
+
- **不依赖 DOM**(用了 DOM 应该是 lib 层包装)
|
|
152
|
+
- **不发请求**(发请求是 service)
|
|
153
|
+
- 一个文件一个主题:`format.ts`、`validate.ts`、`array.ts`
|
|
154
|
+
|
|
155
|
+
### `src/lib/`
|
|
156
|
+
|
|
157
|
+
**职责**:三方 SDK 包装、http 实例、env 读取。
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
src/lib/
|
|
161
|
+
├── http.ts # axios / fetch 实例(全局唯一)
|
|
162
|
+
├── env.ts # import.meta.env 的类型化封装
|
|
163
|
+
├── analytics.ts # 埋点 SDK 包装
|
|
164
|
+
└── map.ts # 第三方地图 SDK 包装
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
约束:
|
|
168
|
+
|
|
169
|
+
- 这一层**与 React 解耦**,可以在 Node / 测试中跑
|
|
170
|
+
- **不放业务逻辑** —— 业务逻辑去 service / hook
|
|
171
|
+
|
|
172
|
+
### `src/routes/`
|
|
173
|
+
|
|
174
|
+
仅当使用集中式路由声明(react-router data router)时才有:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
src/routes/
|
|
178
|
+
├── index.tsx # createBrowserRouter 声明
|
|
179
|
+
└── guards.tsx # 鉴权守卫
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
文件路由(file-based routing)项目不需要这个目录。
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 命名约定
|
|
187
|
+
|
|
188
|
+
| 类型 | 风格 | 例 |
|
|
189
|
+
| -------------- | ------------------------------------ | ------------------------- |
|
|
190
|
+
| 目录 | kebab-case | `src/pages/order-detail/` |
|
|
191
|
+
| React 组件文件 | PascalCase.tsx | `OrderStatusBadge.tsx` |
|
|
192
|
+
| Hook 文件 | camelCase.ts,`use` 前缀 | `useOrderList.ts` |
|
|
193
|
+
| Service 文件 | camelCase.ts,domain 名 | `order.ts` |
|
|
194
|
+
| 工具文件 | camelCase.ts | `formatDate.ts` |
|
|
195
|
+
| 类型文件 | camelCase.ts,domain 名 | `order.ts` |
|
|
196
|
+
| 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
|
|
197
|
+
| 测试文件 | `*.test.ts(x)` 紧邻被测文件 | `formatDate.test.ts` |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 路径别名
|
|
202
|
+
|
|
203
|
+
业务工程默认配 `@/*` 指向 `src/*`,所有跨目录 import 走别名:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// ✅
|
|
207
|
+
import { Button } from '@teamix-evo/ui';
|
|
208
|
+
import { listOrders } from '@/services/order';
|
|
209
|
+
import { OrderStatusBadge } from '@/components/OrderStatusBadge';
|
|
210
|
+
|
|
211
|
+
// ❌
|
|
212
|
+
import { listOrders } from '../../../services/order';
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Import 边界(单向依赖)
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
pages ──▶ components, hooks, services, types, utils, lib, stores
|
|
221
|
+
components ──▶ hooks, services, types, utils, lib (NOT pages)
|
|
222
|
+
hooks ──▶ services, types, utils, lib, stores (NOT components, NOT pages)
|
|
223
|
+
services ──▶ types, lib (NOT hooks, NOT components, NOT pages)
|
|
224
|
+
stores ──▶ types, utils, lib (NOT services, NOT hooks)
|
|
225
|
+
types ──▶ (no runtime imports)
|
|
226
|
+
utils ──▶ types only
|
|
227
|
+
lib ──▶ types only
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
要点:
|
|
231
|
+
|
|
232
|
+
- **services 不依赖 React** —— 它是纯函数层
|
|
233
|
+
- **types / utils / lib 是叶子层** —— 不依赖业务层,谁都能引
|
|
234
|
+
- **反向依赖直接否决**(component 不能 import page,service 不能 import hook)
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 反模式速查
|
|
239
|
+
|
|
240
|
+
| 反模式 | 为什么禁 | 应该怎么做 |
|
|
241
|
+
| ------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
|
|
243
|
+
| `src/api/` 与 `src/services/` 并存 | 同义层 | 统一叫 `services/` |
|
|
244
|
+
| `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
|
|
245
|
+
| 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
|
|
246
|
+
| 在 `src/utils/` 里写 React Hook | 边界错位 | 放 `src/hooks/` |
|
|
247
|
+
| 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
|
|
248
|
+
| `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
|
|
249
|
+
| 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
|
|
250
|
+
| 服务端数据塞进 Context / store | 与 query 缓存形成双源,易漂移 | 一律走 `react-query` / `swr` |
|
|
251
|
+
| 表单字段用一堆 `useState` 拼 | 字段越多越糟,校验散落 | `useForm` + `zodResolver`(见 [`forms-and-validation.md`](forms-and-validation.md)) |
|
|
252
|
+
| 页面顶部 `import` 不 lazy + 无 Suspense | 首屏 bundle 爆炸 / 进入即崩 | `React.lazy` + Layout 挂 Suspense(见 [`routing-and-codesplit.md`](routing-and-codesplit.md)) |
|
|
253
|
+
| 鉴权写在每个 page 顶部 | 散乱、易漏、UI 与 URL 两层不一致 | `src/routes/guards.tsx` 集中,URL 与 sidebar 两层都判 |
|
|
254
|
+
| 全局没有 ErrorBoundary | 任何未捕获异常直接白屏 | App 根挂 `ErrorBoundary` + `reportError` |
|
|
255
|
+
| 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
|
|
256
|
+
| `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 决策速查表
|
|
261
|
+
|
|
262
|
+
| AI 收到的请求 | 文件应该放在 |
|
|
263
|
+
| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
264
|
+
| 新增订单列表页 | `src/pages/orders/index.tsx` |
|
|
265
|
+
| 新增订单状态徽章(跨页面用) | `src/components/OrderStatusBadge.tsx` |
|
|
266
|
+
| 新增订单状态徽章(只在订单页用) | `src/pages/orders/_components/OrderStatusBadge.tsx` |
|
|
267
|
+
| 新增"取消订单"接口调用 | `src/services/order.ts` 加 `cancelOrder` |
|
|
268
|
+
| 新增"取消订单"的 mutation hook | `src/hooks/useCancelOrder.ts` |
|
|
269
|
+
| 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
|
|
270
|
+
| 新增鉴权拦截器 | `src/lib/http.ts` 加 interceptor |
|
|
271
|
+
| 新增订单 domain 类型 | `src/types/order.ts` |
|
|
272
|
+
| 跨少量组件分享当前用户 / 主题 | `src/contexts/UserContext.tsx`(用 React Context) |
|
|
273
|
+
| 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
|
|
274
|
+
| 列表 / 详情等服务端数据 | `src/hooks/useXxx.ts` 包 `react-query`,**不进** store / Context |
|
|
275
|
+
| 新增"下单表单"(react-hook-form + zod) | `src/pages/orders/new/_components/CreateOrderForm.tsx` + `src/services/order.schema.ts` |
|
|
276
|
+
| 跨页面复用表单(地址 / 客户搜索) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
|
|
277
|
+
| 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
|
|
278
|
+
| 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
|
|
279
|
+
| 全局 ErrorBoundary fallback 组件 | `src/components/GlobalErrorFallback.tsx` |
|
|
280
|
+
| Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
|
|
281
|
+
| 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
|
|
282
|
+
| 纯函数 / service / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# 表单与校验规范
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 4 的条件读取项。任务涉及表单时必须读取。
|
|
4
|
+
|
|
3
5
|
> **核心约定**:表单状态用 `react-hook-form`,schema 用 `zod`,**永远不要**用一堆 `useState` 拼表单。
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## 为什么选这个组合(业界事实标准)
|
|
8
10
|
|
|
9
|
-
| 维度
|
|
10
|
-
|
|
|
11
|
-
| 重渲染
|
|
12
|
-
| 校验
|
|
13
|
-
| 复杂表单
|
|
14
|
-
| TypeScript | 从 schema 自动推导类型
|
|
11
|
+
| 维度 | react-hook-form + zod | 多 `useState` 拼 |
|
|
12
|
+
| ---------- | ------------------------------------ | -------------------------- |
|
|
13
|
+
| 重渲染 | 字段级订阅,只渲染变化的字段 | 任意字段变化整个表单重渲染 |
|
|
14
|
+
| 校验 | schema 驱动,前后端共用 | 散落在 onChange 里,易漏 |
|
|
15
|
+
| 复杂表单 | resolver + watch + array fields 内建 | 自己实现一遍 |
|
|
16
|
+
| TypeScript | 从 schema 自动推导类型 | 手写类型,易漂移 |
|
|
15
17
|
|
|
16
18
|
参考:[React Hook Form](https://react-hook-form.com/) + [Zod](https://zod.dev/) 是 2024+ React 生态默认组合,被 shadcn/ui、Vercel、Linear、Cal.com 等广泛采用。
|
|
17
19
|
|
|
@@ -141,12 +143,12 @@ export function CreateOrderForm() {
|
|
|
141
143
|
|
|
142
144
|
## 校验规则
|
|
143
145
|
|
|
144
|
-
| 场景
|
|
145
|
-
|
|
|
146
|
-
| 字段格式(邮箱 / 手机号 / URL)
|
|
147
|
-
| 业务规则(库存检查 / 唯一性)
|
|
148
|
-
| 跨字段约束(开始日期 < 结束日期) | zod `.refine()` / `.superRefine()`
|
|
149
|
-
| 异步校验(用户名是否被占用)
|
|
146
|
+
| 场景 | 写在哪 |
|
|
147
|
+
| ------------------------------- | ----------------------------------------------- |
|
|
148
|
+
| 字段格式(邮箱 / 手机号 / URL) | zod schema(`z.string().email()`) |
|
|
149
|
+
| 业务规则(库存检查 / 唯一性) | service 层,服务端返回错误 → hook 暴露 → UI 显示 |
|
|
150
|
+
| 跨字段约束(开始日期 < 结束日期) | zod `.refine()` / `.superRefine()` |
|
|
151
|
+
| 异步校验(用户名是否被占用) | `useQuery` + debounce,**不放 schema** |
|
|
150
152
|
|
|
151
153
|
### 反模式
|
|
152
154
|
|
package/{skills/teamix-evo-coding-conventions → src/teamix-evo-code-opentrek}/reuse-first.md
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Reuse-First · 组件 / 工具复用决策流
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 1 的强制读取项。AI 写任何新代码前必须先读完本文件并执行复用查询。
|
|
4
|
+
|
|
3
5
|
> 在写**任何**新组件 / Hook / 工具函数之前,AI 必须按本流程跑一遍。"没找到现成的"必须是查询的结论,不能是默认假设。
|
|
4
6
|
|
|
5
7
|
---
|
|
@@ -13,8 +15,8 @@
|
|
|
13
15
|
│ ├── 是 → 走 §1(查 @teamix-evo/ui 注册表)
|
|
14
16
|
│ └── 否 → 跳 Step 2
|
|
15
17
|
│
|
|
16
|
-
├── Step 2: 这是业务组件 /
|
|
17
|
-
│ ├── 是 → 走 §2(查 @teamix-evo/biz-ui
|
|
18
|
+
├── Step 2: 这是业务组件 / 页面骨架吗?
|
|
19
|
+
│ ├── 是 → 走 §2(查 @teamix-evo/biz-ui;页面骨架走 design skill 的 patterns/)
|
|
18
20
|
│ └── 否 → 跳 Step 3
|
|
19
21
|
│
|
|
20
22
|
├── Step 3: 这是工具函数 / Hook 吗?
|
|
@@ -39,12 +41,12 @@
|
|
|
39
41
|
|
|
40
42
|
### 命中后的处理
|
|
41
43
|
|
|
42
|
-
| 情况
|
|
43
|
-
|
|
|
44
|
-
| 注册表里有,本项目已装
|
|
45
|
-
| 注册表里有,本项目未装
|
|
44
|
+
| 情况 | 处理 |
|
|
45
|
+
| ------------------------------- | ---------------------------------------------- |
|
|
46
|
+
| 注册表里有,本项目已装 | 直接 `import` 复用,**不要复制源码** |
|
|
47
|
+
| 注册表里有,本项目未装 | 提示用户跑 `teamix-evo ui add <id>`,装机后再用 |
|
|
46
48
|
| 注册表里有近义但 props 不完全够 | **优先组合**(包一层 wrapper),不要 fork ui 源码 |
|
|
47
|
-
| 注册表里完全没有
|
|
49
|
+
| 注册表里完全没有 | 进 §4 |
|
|
48
50
|
|
|
49
51
|
### 反模式(禁止)
|
|
50
52
|
|
|
@@ -54,20 +56,28 @@
|
|
|
54
56
|
|
|
55
57
|
---
|
|
56
58
|
|
|
57
|
-
## §2 ·
|
|
59
|
+
## §2 · 业务组件:查 `biz-ui`(页面骨架走 design skill 的 patterns/)
|
|
58
60
|
|
|
59
|
-
| 包
|
|
60
|
-
|
|
|
61
|
+
| 包 | 用途 | 查询入口 |
|
|
62
|
+
| -------------------- | -------------------------------------- | ------------------------------------------------------ |
|
|
61
63
|
| `@teamix-evo/biz-ui` | 业务化的复合组件(变体感知 + slot 边界) | 本项目 `node_modules/@teamix-evo/biz-ui/manifest.json` |
|
|
62
|
-
|
|
64
|
+
|
|
65
|
+
### 页面骨架来源(列表 / 详情 / 表单 / 仪表盘)
|
|
66
|
+
|
|
67
|
+
> ⚠️ **AI 默认不调用 `@teamix-evo/templates` 包**(见 [ADR 0031](../../../docs/adr/0031-skill-templates-decoupling.md))。页面骨架按下列优先级生成:
|
|
68
|
+
|
|
69
|
+
1. **首选** —— 读 `teamix-evo-design-opentrek` skill 的 [`patterns/{list|detail|form|dashboard}-page.md`](../teamix-evo-design-opentrek/patterns/),按 Zone Map 与决策树用 ui 原子件 + biz-ui 业务件**直接拼装**到 `src/pages/<id>/`
|
|
70
|
+
2. **patterns/ 未覆盖时** —— 按业界流行的中后台页面架构(antd Pro / shadcn Examples 等)与用户描述自由实现,**不要回退到 templates 包**
|
|
71
|
+
3. **`@teamix-evo/templates` 包与 CLI 命令仍保留**,但仅在用户**显式**要求(如"用 templates 包的 frozen 骨架")时才走 `teamix-evo templates add`
|
|
63
72
|
|
|
64
73
|
### 决策表
|
|
65
74
|
|
|
66
|
-
| 场景
|
|
67
|
-
|
|
|
68
|
-
| 列表页 / 详情页 /
|
|
69
|
-
| "带筛选的用户表格"、"带审批流的卡片"
|
|
70
|
-
| 一次性的、强领域绑定的组合
|
|
75
|
+
| 场景 | 选择 |
|
|
76
|
+
| ------------------------------------- | --------------------------------------------------------- |
|
|
77
|
+
| 列表页 / 详情页 / 表单页 / 仪表盘骨架 | 读 design skill `patterns/*.md` → ui + biz-ui 拼装 |
|
|
78
|
+
| "带筛选的用户表格"、"带审批流的卡片" | 优先 `biz-ui` |
|
|
79
|
+
| 一次性的、强领域绑定的组合 | 写在 `src/components/<domain>/` 里,**不要污染 biz-ui** |
|
|
80
|
+
| patterns/ 未覆盖的特殊页面 | 按业界流行架构 + 用户描述自由实现,**不调用 templates 包** |
|
|
71
81
|
|
|
72
82
|
---
|
|
73
83
|
|
|
@@ -102,7 +112,7 @@
|
|
|
102
112
|
1. **命名要能让下次复用查得到** —— 起 `OrderStatusBadge` 而不是 `MyBadge`;`useOrderList` 而不是 `useList`。
|
|
103
113
|
2. **抽象层级别低于一次性页面** —— 跨页面会用的才放 `src/components/`,只在一个页面用的写在 `src/pages/<id>/_components/`。
|
|
104
114
|
3. **暴露面尽量窄** —— 默认只 `export` 这次需要的 API,别一次性 `export *`。
|
|
105
|
-
4. **写完登记** —— 在 PR 描述里明确"新增 `<path>`,因为 ui / biz-ui /
|
|
115
|
+
4. **写完登记** —— 在 PR 描述里明确"新增 `<path>`,因为 ui / biz-ui / patterns / 本项目均未提供 X 能力"(注:不再以 `templates` 包作为兜底依据,见 [ADR 0031](../../../docs/adr/0031-skill-templates-decoupling.md))。
|
|
106
116
|
|
|
107
117
|
---
|
|
108
118
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# 路由与代码分包规范
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 6 的条件读取项。任务涉及路由或页面入口时必须读取。
|
|
4
|
+
|
|
5
|
+
> **核心约定**:页面级 `React.lazy` 默认分包,鉴权 / 角色守卫集中在 `src/routes/`,404 / 401 / 403 必有兗底。
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
@@ -8,11 +10,11 @@
|
|
|
8
10
|
|
|
9
11
|
teamix-evo console preset 默认 `react-router-dom@6`(Data Router 模式,业界主流)。
|
|
10
12
|
|
|
11
|
-
| 候选
|
|
12
|
-
|
|
|
13
|
-
| `react-router-dom@6` Data Router | **默认**。中后台 / SPA / 多页应用
|
|
14
|
-
| `@tanstack/react-router`
|
|
15
|
-
| 文件路由(Next.js / Remix)
|
|
13
|
+
| 候选 | 何时选 |
|
|
14
|
+
| -------------------------------- | ------------------------------------ | ------------------- |
|
|
15
|
+
| `react-router-dom@6` Data Router | **默认**。中后台 / SPA / 多页应用 |
|
|
16
|
+
| `@tanstack/react-router` | 类型安全要求极高、复杂 search params | (替换需在 ADR 记录) |
|
|
17
|
+
| 文件路由(Next.js / Remix) | 不在本 preset 范围 |
|
|
16
18
|
|
|
17
19
|
**一个项目只用一种**。已经用了 react-router 就不要混入 tanstack-router。
|
|
18
20
|
|
|
@@ -181,11 +183,11 @@ export function RoleGuard({
|
|
|
181
183
|
|
|
182
184
|
每个项目**必须**有这三个页面,放在 `src/pages/_xxx/`(下划线前缀,表示非业务路由):
|
|
183
185
|
|
|
184
|
-
| 路径
|
|
185
|
-
|
|
|
186
|
-
| `*` (404)
|
|
187
|
-
| `/_forbidden` (403) | `src/pages/_forbidden/index.tsx` | 角色无权访问
|
|
188
|
-
| `/_error` (500)
|
|
186
|
+
| 路径 | 文件 | 触发场景 |
|
|
187
|
+
| ------------------- | -------------------------------- | -------------------------- |
|
|
188
|
+
| `*` (404) | `src/pages/_not-found/index.tsx` | 任何未匹配的 URL |
|
|
189
|
+
| `/_forbidden` (403) | `src/pages/_forbidden/index.tsx` | 角色无权访问 |
|
|
190
|
+
| `/_error` (500) | `src/pages/_error/index.tsx` | router `errorElement` 兜底 |
|
|
189
191
|
|
|
190
192
|
未登录(401)由 `AuthGuard` 重定向到 `/login`,**不**单独建页面。
|
|
191
193
|
|
|
@@ -229,7 +231,7 @@ function OrderListPage() {
|
|
|
229
231
|
import { useNavigate, Link } from 'react-router-dom';
|
|
230
232
|
|
|
231
233
|
// 声明式(链接)
|
|
232
|
-
<Link to={`/orders/${order.id}`}>查看</Link
|
|
234
|
+
<Link to={`/orders/${order.id}`}>查看</Link>;
|
|
233
235
|
|
|
234
236
|
// 命令式(mutation success)
|
|
235
237
|
const navigate = useNavigate();
|
|
@@ -249,15 +251,15 @@ mutation.mutate(values, {
|
|
|
249
251
|
|
|
250
252
|
## 反模式速查
|
|
251
253
|
|
|
252
|
-
| 反模式
|
|
253
|
-
|
|
|
254
|
-
| 全部页面顶部 `import` 不 lazy | 首屏 bundle 爆炸
|
|
255
|
-
| Lazy 但没 Suspense 兜底
|
|
256
|
-
| 鉴权写在每个 page 里
|
|
257
|
-
| 路由声明分散在多处
|
|
258
|
-
| 列表筛选状态不进 URL
|
|
259
|
-
| `window.location.href` 跳转
|
|
260
|
-
| 路由 path 写魔法字符串
|
|
254
|
+
| 反模式 | 为什么禁 | 应该 |
|
|
255
|
+
| ----------------------------- | ---------------------- | ----------------------------- |
|
|
256
|
+
| 全部页面顶部 `import` 不 lazy | 首屏 bundle 爆炸 | `React.lazy` 每页 |
|
|
257
|
+
| Lazy 但没 Suspense 兜底 | 进入页面报错 | Layout 挂一次 Suspense |
|
|
258
|
+
| 鉴权写在每个 page 里 | 散乱、易漏 | 守卫组件 + 路由层 |
|
|
259
|
+
| 路由声明分散在多处 | 难维护、易循环 | 唯一在 `src/routes/index.tsx` |
|
|
260
|
+
| 列表筛选状态不进 URL | 刷新 / 分享 / 后退失效 | `useSearchParams` |
|
|
261
|
+
| `window.location.href` 跳转 | 整页刷新 | `useNavigate` / `<Link>` |
|
|
262
|
+
| 路由 path 写魔法字符串 | 改路径漏改 | 抽 `src/routes/paths.ts` 集中 |
|
|
261
263
|
|
|
262
264
|
---
|
|
263
265
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# 测试规范
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 7 的读取项。纯函数和 zod schema 必测,其余按关键路径覆盖。
|
|
4
|
+
|
|
3
5
|
> **核心约定**:`vitest` + `@testing-library/react` + `msw`,文件就近 `*.test.ts(x)`。**不追求覆盖率数字**,追求"关键路径必有测试 + 纯函数全测"。
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## 选型(业界主流)
|
|
8
10
|
|
|
9
|
-
| 类型
|
|
10
|
-
|
|
|
11
|
-
| 测试 runner | `vitest`
|
|
12
|
-
| 组件测试
|
|
13
|
-
| API mock
|
|
14
|
-
| 断言
|
|
15
|
-
| E2E(可选)
|
|
11
|
+
| 类型 | 工具 | 理由 |
|
|
12
|
+
| ----------- | -------------------------------------------------------- | ------------------------------------------------ |
|
|
13
|
+
| 测试 runner | `vitest` | 与 Vite 原生集成,ESM 友好,API 与 Jest 兼容 |
|
|
14
|
+
| 组件测试 | `@testing-library/react` + `@testing-library/user-event` | 测行为不测实现,业界事实标准(Kent C. Dodds) |
|
|
15
|
+
| API mock | `msw`(Mock Service Worker) | 拦截真实 fetch / axios,测试与生产共用 service 层 |
|
|
16
|
+
| 断言 | `vitest` 内置 `expect`(兼容 Jest) | 无需 chai |
|
|
17
|
+
| E2E(可选) | `playwright` | 关键流程冒烟,**不在本 skill 范围** |
|
|
16
18
|
|
|
17
19
|
**不要混用**:已经选 vitest 就不要再加 jest;已经用 msw 就不要再自己 mock axios。
|
|
18
20
|
|
|
@@ -57,16 +59,16 @@ src/test/
|
|
|
57
59
|
|
|
58
60
|
按 ROI 排序:
|
|
59
61
|
|
|
60
|
-
| 优先级 | 类型
|
|
61
|
-
|
|
|
62
|
-
| P0
|
|
63
|
-
| P0
|
|
64
|
-
| P0
|
|
65
|
-
| P1
|
|
66
|
-
| P1
|
|
67
|
-
| P2
|
|
68
|
-
| ❌
|
|
69
|
-
| ❌
|
|
62
|
+
| 优先级 | 类型 | 例子 | 必测? |
|
|
63
|
+
| ------ | ------------------------------------- | ----------------------------------- | -------------- |
|
|
64
|
+
| P0 | 纯函数(`src/utils/`、`src/services/`) | `formatMoney`、`isValidOrder` | ✅ **必测** |
|
|
65
|
+
| P0 | zod schema | `CreateOrderInputSchema.parse(...)` | ✅ **必测** |
|
|
66
|
+
| P0 | 关键业务路径(下单 / 支付 / 鉴权) | E2E-style 组件测 | ✅ **必测** |
|
|
67
|
+
| P1 | Hook(数据 hook + 自定义 UI hook) | `useOrderList`、`useDebounce` | 推荐 |
|
|
68
|
+
| P1 | 复用业务组件(`src/components/`) | `OrderStatusBadge` 各状态渲染 | 推荐 |
|
|
69
|
+
| P2 | 页面组件 | 用 RTL 渲染 + msw mock | 可选 |
|
|
70
|
+
| ❌ | ui 包源码 | 已在 `@teamix-evo/ui` 包内测过 | **不要重复测** |
|
|
71
|
+
| ❌ | 三方库 | `react-router`、`react-query` | **不要测** |
|
|
70
72
|
|
|
71
73
|
**红线**:不要为了凑覆盖率数字测 `Button` 能不能渲染、`React.useState` 能不能用。
|
|
72
74
|
|
|
@@ -158,7 +160,9 @@ function wrapper({ children }: { children: React.ReactNode }) {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
it('加载订单列表', async () => {
|
|
161
|
-
const { result } = renderHook(() => useOrderList({ status: 'all' }), {
|
|
163
|
+
const { result } = renderHook(() => useOrderList({ status: 'all' }), {
|
|
164
|
+
wrapper,
|
|
165
|
+
});
|
|
162
166
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
163
167
|
expect(result.current.data).toHaveLength(1);
|
|
164
168
|
});
|
|
@@ -238,16 +242,16 @@ renderWithProviders(<OrderListPage />, { route: '/orders?status=pending' });
|
|
|
238
242
|
|
|
239
243
|
## §5 · 反模式速查
|
|
240
244
|
|
|
241
|
-
| 反模式
|
|
242
|
-
|
|
|
243
|
-
| 测 `setState` 是否被调用
|
|
244
|
-
| `getByTestId` 当默认查询
|
|
245
|
-
| 一个 it 里 expect 10 次无关断言
|
|
246
|
-
| mock 整个 hook(`vi.mock('@/hooks/...')`) | 失去集成价值
|
|
247
|
-
| 自己 mock fetch / axios
|
|
248
|
-
| 测试里写 `setTimeout(..., 1000)` 等异步
|
|
249
|
-
| 全局 mock console.error 静默
|
|
250
|
-
| 追求 80% 覆盖率刷数字
|
|
245
|
+
| 反模式 | 为什么禁 | 应该 |
|
|
246
|
+
| ---------------------------------------- | ---------------------------- | ----------------------------------------------- |
|
|
247
|
+
| 测 `setState` 是否被调用 | 测了实现,重构即崩 | 测渲染结果 / 用户行为 |
|
|
248
|
+
| `getByTestId` 当默认查询 | data-testid 跟可访问性绑不上 | `getByRole` / `getByLabelText` 优先 |
|
|
249
|
+
| 一个 it 里 expect 10 次无关断言 | 失败定位难 | 拆多个 it |
|
|
250
|
+
| mock 整个 hook(`vi.mock('@/hooks/...')`) | 失去集成价值 | msw mock 后端,hook 真跑 |
|
|
251
|
+
| 自己 mock fetch / axios | 与生产代码路径不一致 | 用 msw |
|
|
252
|
+
| 测试里写 `setTimeout(..., 1000)` 等异步 | 慢且不稳定 | `await waitFor` / `findBy*` |
|
|
253
|
+
| 全局 mock console.error 静默 | 错过真实告警 | 让测试输出 console.error,但 setup 里 fail on it |
|
|
254
|
+
| 追求 80% 覆盖率刷数字 | 测了没价值的代码 | 按"优先级表"测,不看覆盖率盲冲 |
|
|
251
255
|
|
|
252
256
|
---
|
|
253
257
|
|