@teamix-evo/skills 0.2.0 → 0.4.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.
Files changed (56) hide show
  1. package/README.md +18 -8
  2. package/_template/SKILL.md.hbs +14 -1
  3. package/manifest.json +79 -6
  4. package/package.json +7 -3
  5. package/src/teamix-evo-code-opentrek/SKILL.md +92 -0
  6. package/src/teamix-evo-code-opentrek/api-layering.md +225 -0
  7. package/src/teamix-evo-code-opentrek/checklist.md +173 -0
  8. package/src/teamix-evo-code-opentrek/error-and-loading.md +269 -0
  9. package/src/teamix-evo-code-opentrek/file-structure.md +273 -0
  10. package/src/teamix-evo-code-opentrek/forms-and-validation.md +220 -0
  11. package/src/teamix-evo-code-opentrek/reuse-first.md +130 -0
  12. package/src/teamix-evo-code-opentrek/routing-and-codesplit.md +298 -0
  13. package/src/teamix-evo-code-opentrek/testing.md +313 -0
  14. package/src/teamix-evo-code-uni-manager/SKILL.md +95 -0
  15. package/src/teamix-evo-code-uni-manager/api-layering.md +370 -0
  16. package/src/teamix-evo-code-uni-manager/checklist.md +193 -0
  17. package/src/teamix-evo-code-uni-manager/error-and-loading.md +389 -0
  18. package/src/teamix-evo-code-uni-manager/file-structure.md +339 -0
  19. package/src/teamix-evo-code-uni-manager/forms-and-validation.md +459 -0
  20. package/src/teamix-evo-code-uni-manager/reuse-first.md +188 -0
  21. package/src/teamix-evo-code-uni-manager/routing-and-codesplit.md +450 -0
  22. package/src/teamix-evo-code-uni-manager/testing.md +396 -0
  23. package/src/teamix-evo-design-opentrek/SKILL.md +71 -0
  24. package/src/teamix-evo-design-opentrek/boundaries.md +513 -0
  25. package/src/teamix-evo-design-opentrek/brand.md +154 -0
  26. package/src/teamix-evo-design-opentrek/checklist.md +83 -0
  27. package/src/teamix-evo-design-opentrek/components.md +245 -0
  28. package/src/teamix-evo-design-opentrek/flows.md +51 -0
  29. package/src/teamix-evo-design-opentrek/foundations.md +271 -0
  30. package/src/teamix-evo-design-opentrek/generation-flow.md +185 -0
  31. package/src/teamix-evo-design-opentrek/patterns/dashboard.md +31 -0
  32. package/src/teamix-evo-design-opentrek/patterns/detail-page.md +202 -0
  33. package/src/teamix-evo-design-opentrek/patterns/form-page.md +289 -0
  34. package/src/teamix-evo-design-opentrek/patterns/list-page.md +334 -0
  35. package/src/teamix-evo-design-opentrek/patterns/page-types.md +154 -0
  36. package/src/teamix-evo-design-opentrek/philosophy.md +96 -0
  37. package/src/teamix-evo-design-opentrek/rules/README.md +39 -0
  38. package/src/teamix-evo-design-opentrek/rules/boundaries.rules.json +391 -0
  39. package/src/teamix-evo-design-uni-manager/SKILL.md +73 -0
  40. package/src/teamix-evo-design-uni-manager/boundaries.md +564 -0
  41. package/src/teamix-evo-design-uni-manager/brand.md +202 -0
  42. package/src/teamix-evo-design-uni-manager/checklist.md +115 -0
  43. package/src/teamix-evo-design-uni-manager/components.md +254 -0
  44. package/src/teamix-evo-design-uni-manager/flows.md +63 -0
  45. package/src/teamix-evo-design-uni-manager/foundations.md +258 -0
  46. package/src/teamix-evo-design-uni-manager/generation-flow.md +194 -0
  47. package/src/teamix-evo-design-uni-manager/patterns/dashboard.md +95 -0
  48. package/src/teamix-evo-design-uni-manager/patterns/detail-page.md +224 -0
  49. package/src/teamix-evo-design-uni-manager/patterns/form-page.md +329 -0
  50. package/src/teamix-evo-design-uni-manager/patterns/list-page.md +356 -0
  51. package/src/teamix-evo-design-uni-manager/patterns/page-types.md +165 -0
  52. package/src/teamix-evo-design-uni-manager/philosophy.md +106 -0
  53. package/src/teamix-evo-design-uni-manager/rules/README.md +49 -0
  54. package/src/teamix-evo-design-uni-manager/rules/boundaries.rules.json +418 -0
  55. package/src/teamix-evo-manage/SKILL.md +281 -0
  56. package/skills/teamix-evo-manage/SKILL.md +0 -138
@@ -0,0 +1,273 @@
1
+ # 业务工程目录约定
2
+
3
+ > 业内主流 React/Vite 业务工程的标准目录骨架。本文件是 AI 决定"新建文件应该放在哪"的唯一参考。
4
+
5
+ ---
6
+
7
+ ## 顶层 `src/` 骨架
8
+
9
+ ```
10
+ src/
11
+ ├── pages/ # 路由页面(每个目录对应一条路由)
12
+ ├── components/ # 跨页面复用的业务组件
13
+ ├── services/ # 后端通信(纯函数,见 api-layering.md)
14
+ ├── hooks/ # 自定义 Hook(包含数据 hook、UI hook)
15
+ ├── contexts/ # React Context(跨子树共享、低频更新的全局态)
16
+ ├── stores/ # 高频全局态(zustand / jotai / redux,首次需要时引入,只用一种)
17
+ ├── types/ # Domain / API 类型声明(.ts 仅类型,无运行时)
18
+ ├── utils/ # 纯函数工具(无 React、无副作用)
19
+ ├── lib/ # 三方 SDK 包装、http 实例、环境配置、monitor
20
+ ├── routes/ # 路由配置 + 守卫(若用 react-router 集中式声明)
21
+ ├── layouts/ # 应用级 / 区域级布局(ConsoleLayout、AuthLayout)
22
+ ├── test/ # 测试基础设施(setup.ts、handlers.ts、render.tsx、fixtures/)
23
+ ├── styles/ # 全局样式 / token 覆盖(尽量少)
24
+ ├── assets/ # 图片 / 字体 / 静态文件
25
+ ├── locales/ # i18n 资源(可选)
26
+ ├── main.tsx # 应用入口
27
+ └── App.tsx # 根组件
28
+ ```
29
+
30
+ > 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`api/` 和 `services/`、`views/` 和 `pages/`)。`*.test.ts(x)` 文件**就近**放在被测文件同目录,**不要**集中堆到顶层 `tests/`(见 [`testing.md`](testing.md))。
31
+
32
+ ---
33
+
34
+ ## 各目录职责与边界
35
+
36
+ ### `src/pages/`
37
+
38
+ **职责**:路由页面。一个目录 = 一条路由。
39
+
40
+ ```
41
+ src/pages/
42
+ ├── orders/
43
+ │ ├── index.tsx # 列表页(/orders)
44
+ │ ├── [id].tsx # 详情页(/orders/:id),命名按路由方案
45
+ │ ├── _components/ # 仅本页面用的子组件(下划线前缀,不导出给外部)
46
+ │ │ └── OrderFilterBar.tsx
47
+ │ └── _hooks/ # 仅本页面用的 hook
48
+ │ └── useOrderFilter.ts
49
+ └── users/
50
+ └── ...
51
+ ```
52
+
53
+ 约束:
54
+ - 页面组件**默认 default export**,与文件名一致
55
+ - `_components/` `_hooks/` 用下划线前缀标记**私有**,**禁止跨页面 import**
56
+ - 跨页面用得到的组件/hook → 升到 `src/components/` `src/hooks/`
57
+
58
+ ### `src/components/`
59
+
60
+ **职责**:跨页面复用的业务组件。
61
+
62
+ ```
63
+ src/components/
64
+ ├── OrderStatusBadge.tsx
65
+ ├── UserAvatar.tsx
66
+ └── filters/
67
+ ├── DateRangeFilter.tsx
68
+ └── StatusFilter.tsx
69
+ ```
70
+
71
+ 约束:
72
+ - 命名 PascalCase,文件名 = 组件名
73
+ - 单组件 = 单文件;复杂组件可建子目录 `<Name>/{index.tsx, types.ts, hooks.ts}`
74
+ - **不要**重复 `@teamix-evo/ui` 已经提供的能力(见 [reuse-first.md](reuse-first.md))
75
+ - **不要**写"小工具组件"如 `Wrapper`、`Container` —— 用 div + tailwind 即可
76
+
77
+ ### `src/services/`
78
+
79
+ **职责**:后端通信纯函数。详见 [`api-layering.md`](api-layering.md)。
80
+
81
+ ### `src/hooks/`
82
+
83
+ **职责**:跨页面复用的自定义 Hook。
84
+
85
+ ```
86
+ src/hooks/
87
+ ├── useOrderList.ts # 数据 hook(包 react-query)
88
+ ├── useCreateOrder.ts
89
+ ├── useDebounce.ts # UI hook
90
+ └── useMediaQuery.ts
91
+ ```
92
+
93
+ 约束:
94
+ - 命名必须 `use` 开头
95
+ - 单 hook = 单文件
96
+ - 数据 hook 调 `src/services/`,**禁止**直接 fetch
97
+
98
+ ### 全局态选型决策
99
+
100
+ **装机时默认不预装任何状态库**。按"最小够用"逐级升级,不要直接跳到 zustand:
101
+
102
+ | 场景 | 用什么 |
103
+ | --- | --- |
104
+ | 组件内状态 | `useState` / `useReducer` |
105
+ | 跨少量组件、低频更新(当前用户、主题、语言、租户) | React `Context` |
106
+ | 跨域、多页面、**高频**更新的客户端态 | `src/stores/`(zustand / jotai / redux 选一) |
107
+ | 服务端数据(列表、详情、表单提交) | `react-query` / `swr`,**永远不进 Context、也不进 store** |
108
+
109
+ 「高频」的粗略判定:一个状态被 10+ 处读取、且每秒可能变化一次以上 —— Context 会触发整子树重渲染,这时才有引 store 的收益。
110
+
111
+ Context 文件位置:
112
+
113
+ - 全应用级(UserContext、ThemeContext)→ `src/contexts/<Name>Context.tsx`
114
+ - 仅一个页面/子树用 → `src/pages/<id>/_components/<Name>Context.tsx`
115
+
116
+ > 如果项目自始至终只用 Context + react-query 就能解决,**不需要**建 `src/stores/`,也不需要引 zustand。等真出现跨域高频全局态再装。
117
+
118
+ ### `src/stores/`
119
+
120
+ **职责**:跨域、高频更新的客户端全局态。**不是**全局态的默认入口,先看上方「全局态选型决策」。
121
+
122
+ 约束:
123
+
124
+ - 一个项目**只用一种**状态库(zustand / jotai / redux 选一,首次需要时引入并固定)
125
+ - 按 domain 拆 store:`useOrderStore.ts`、`useUserStore.ts`
126
+ - **不要**把服务端数据放 store —— 用 `react-query` / `swr` 缓存(它们就是异步 store)
127
+ - **不要**为了消除一处 props drilling 就建 store —— 那是 Context 的场景
128
+
129
+ ### `src/types/`
130
+
131
+ **职责**:类型声明。
132
+
133
+ 约束:
134
+ - 只放 `.ts` 类型文件,**不导出运行时代码**
135
+ - 按 domain 拆:`order.ts`、`user.ts`
136
+ - 通用类型放 `api.ts`、`common.ts`
137
+
138
+ ### `src/utils/`
139
+
140
+ **职责**:纯函数工具。
141
+
142
+ 约束:
143
+ - **不依赖 React**(用了 React 应该是 hook,放 `src/hooks/`)
144
+ - **不依赖 DOM**(用了 DOM 应该是 lib 层包装)
145
+ - **不发请求**(发请求是 service)
146
+ - 一个文件一个主题:`format.ts`、`validate.ts`、`array.ts`
147
+
148
+ ### `src/lib/`
149
+
150
+ **职责**:三方 SDK 包装、http 实例、env 读取。
151
+
152
+ ```
153
+ src/lib/
154
+ ├── http.ts # axios / fetch 实例(全局唯一)
155
+ ├── env.ts # import.meta.env 的类型化封装
156
+ ├── analytics.ts # 埋点 SDK 包装
157
+ └── map.ts # 第三方地图 SDK 包装
158
+ ```
159
+
160
+ 约束:
161
+ - 这一层**与 React 解耦**,可以在 Node / 测试中跑
162
+ - **不放业务逻辑** —— 业务逻辑去 service / hook
163
+
164
+ ### `src/routes/`
165
+
166
+ 仅当使用集中式路由声明(react-router data router)时才有:
167
+
168
+ ```
169
+ src/routes/
170
+ ├── index.tsx # createBrowserRouter 声明
171
+ └── guards.tsx # 鉴权守卫
172
+ ```
173
+
174
+ 文件路由(file-based routing)项目不需要这个目录。
175
+
176
+ ---
177
+
178
+ ## 命名约定
179
+
180
+ | 类型 | 风格 | 例 |
181
+ | --- | --- | --- |
182
+ | 目录 | kebab-case | `src/pages/order-detail/` |
183
+ | React 组件文件 | PascalCase.tsx | `OrderStatusBadge.tsx` |
184
+ | Hook 文件 | camelCase.ts,`use` 前缀 | `useOrderList.ts` |
185
+ | Service 文件 | camelCase.ts,domain 名 | `order.ts` |
186
+ | 工具文件 | camelCase.ts | `formatDate.ts` |
187
+ | 类型文件 | camelCase.ts,domain 名 | `order.ts` |
188
+ | 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
189
+ | 测试文件 | `*.test.ts(x)` 紧邻被测文件 | `formatDate.test.ts` |
190
+
191
+ ---
192
+
193
+ ## 路径别名
194
+
195
+ 业务工程默认配 `@/*` 指向 `src/*`,所有跨目录 import 走别名:
196
+
197
+ ```ts
198
+ // ✅
199
+ import { Button } from '@teamix-evo/ui';
200
+ import { listOrders } from '@/services/order';
201
+ import { OrderStatusBadge } from '@/components/OrderStatusBadge';
202
+
203
+ // ❌
204
+ import { listOrders } from '../../../services/order';
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Import 边界(单向依赖)
210
+
211
+ ```
212
+ pages ──▶ components, hooks, services, types, utils, lib, stores
213
+ components ──▶ hooks, services, types, utils, lib (NOT pages)
214
+ hooks ──▶ services, types, utils, lib, stores (NOT components, NOT pages)
215
+ services ──▶ types, lib (NOT hooks, NOT components, NOT pages)
216
+ stores ──▶ types, utils, lib (NOT services, NOT hooks)
217
+ types ──▶ (no runtime imports)
218
+ utils ──▶ types only
219
+ lib ──▶ types only
220
+ ```
221
+
222
+ 要点:
223
+ - **services 不依赖 React** —— 它是纯函数层
224
+ - **types / utils / lib 是叶子层** —— 不依赖业务层,谁都能引
225
+ - **反向依赖直接否决**(component 不能 import page,service 不能 import hook)
226
+
227
+ ---
228
+
229
+ ## 反模式速查
230
+
231
+ | 反模式 | 为什么禁 | 应该怎么做 |
232
+ | --- | --- | --- |
233
+ | `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
234
+ | `src/api/` 与 `src/services/` 并存 | 同义层 | 统一叫 `services/` |
235
+ | `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
236
+ | 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
237
+ | 在 `src/utils/` 里写 React Hook | 边界错位 | 放 `src/hooks/` |
238
+ | 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
239
+ | `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
240
+ | 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
241
+ | 服务端数据塞进 Context / store | 与 query 缓存形成双源,易漂移 | 一律走 `react-query` / `swr` |
242
+ | 表单字段用一堆 `useState` 拼 | 字段越多越糟,校验散落 | `useForm` + `zodResolver`(见 [`forms-and-validation.md`](forms-and-validation.md)) |
243
+ | 页面顶部 `import` 不 lazy + 无 Suspense | 首屏 bundle 爆炸 / 进入即崩 | `React.lazy` + Layout 挂 Suspense(见 [`routing-and-codesplit.md`](routing-and-codesplit.md)) |
244
+ | 鉴权写在每个 page 顶部 | 散乱、易漏、UI 与 URL 两层不一致 | `src/routes/guards.tsx` 集中,URL 与 sidebar 两层都判 |
245
+ | 全局没有 ErrorBoundary | 任何未捕获异常直接白屏 | App 根挂 `ErrorBoundary` + `reportError` |
246
+ | 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
247
+ | `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
248
+
249
+ ---
250
+
251
+ ## 决策速查表
252
+
253
+ | AI 收到的请求 | 文件应该放在 |
254
+ | --- | --- |
255
+ | 新增订单列表页 | `src/pages/orders/index.tsx` |
256
+ | 新增订单状态徽章(跨页面用) | `src/components/OrderStatusBadge.tsx` |
257
+ | 新增订单状态徽章(只在订单页用) | `src/pages/orders/_components/OrderStatusBadge.tsx` |
258
+ | 新增"取消订单"接口调用 | `src/services/order.ts` 加 `cancelOrder` |
259
+ | 新增"取消订单"的 mutation hook | `src/hooks/useCancelOrder.ts` |
260
+ | 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
261
+ | 新增鉴权拦截器 | `src/lib/http.ts` 加 interceptor |
262
+ | 新增订单 domain 类型 | `src/types/order.ts` |
263
+ | 跨少量组件分享当前用户 / 主题 | `src/contexts/UserContext.tsx`(用 React Context) |
264
+ | 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
265
+ | 列表 / 详情等服务端数据 | `src/hooks/useXxx.ts` 包 `react-query`,**不进** store / Context |
266
+ | 新增"下单表单"(react-hook-form + zod) | `src/pages/orders/new/_components/CreateOrderForm.tsx` + `src/services/order.schema.ts` |
267
+ | 跨页面复用表单(地址 / 客户搜索) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
268
+ | 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
269
+ | 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
270
+ | 全局 ErrorBoundary fallback 组件 | `src/components/GlobalErrorFallback.tsx` |
271
+ | Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
272
+ | 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
273
+ | 纯函数 / service / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
@@ -0,0 +1,220 @@
1
+ # 表单与校验规范
2
+
3
+ > **核心约定**:表单状态用 `react-hook-form`,schema 用 `zod`,**永远不要**用一堆 `useState` 拼表单。
4
+
5
+ ---
6
+
7
+ ## 为什么选这个组合(业界事实标准)
8
+
9
+ | 维度 | react-hook-form + zod | 多 `useState` 拼 |
10
+ | --- | --- | --- |
11
+ | 重渲染 | 字段级订阅,只渲染变化的字段 | 任意字段变化整个表单重渲染 |
12
+ | 校验 | schema 驱动,前后端共用 | 散落在 onChange 里,易漏 |
13
+ | 复杂表单 | resolver + watch + array fields 内建 | 自己实现一遍 |
14
+ | TypeScript | 从 schema 自动推导类型 | 手写类型,易漂移 |
15
+
16
+ 参考:[React Hook Form](https://react-hook-form.com/) + [Zod](https://zod.dev/) 是 2024+ React 生态默认组合,被 shadcn/ui、Vercel、Linear、Cal.com 等广泛采用。
17
+
18
+ ---
19
+
20
+ ## Schema 落点
21
+
22
+ ```
23
+ src/services/
24
+ ├── order.ts # 请求函数:listOrders、createOrder
25
+ └── order.schema.ts # zod schema:CreateOrderInput、UpdateOrderInput
26
+ ```
27
+
28
+ 约定:
29
+
30
+ - **每个 domain 一个 `<domain>.schema.ts`**,与 service 同目录
31
+ - 从 schema 派生类型:`export type CreateOrderInput = z.infer<typeof CreateOrderInputSchema>`
32
+ - service 的入参 / 返回类型**优先用** schema 推导,而不是手写 interface
33
+ - **不要**把 schema 写在页面 / 组件文件里 —— schema 是 domain 资产,跨页面复用
34
+
35
+ ### 示例
36
+
37
+ ```ts
38
+ // src/services/order.schema.ts
39
+ import { z } from 'zod';
40
+
41
+ export const CreateOrderInputSchema = z.object({
42
+ customerId: z.string().uuid(),
43
+ items: z
44
+ .array(
45
+ z.object({
46
+ skuId: z.string(),
47
+ quantity: z.number().int().positive(),
48
+ }),
49
+ )
50
+ .min(1, '至少添加一项'),
51
+ remark: z.string().max(200).optional(),
52
+ });
53
+
54
+ export type CreateOrderInput = z.infer<typeof CreateOrderInputSchema>;
55
+ ```
56
+
57
+ ```ts
58
+ // src/services/order.ts
59
+ import { http } from '@/lib/http';
60
+ import type { CreateOrderInput } from './order.schema';
61
+ import type { Order } from '@/types/order';
62
+
63
+ export async function createOrder(input: CreateOrderInput): Promise<Order> {
64
+ // 服务端层也可在这里再 parse 一次防御
65
+ const { data } = await http.post('/api/orders', input);
66
+ return data;
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 表单组件结构
73
+
74
+ ### 简单表单(单页面专用)
75
+
76
+ 放在页面私有目录:
77
+
78
+ ```
79
+ src/pages/orders/new/
80
+ ├── index.tsx # 路由组件
81
+ └── _components/
82
+ └── CreateOrderForm.tsx # useForm + UI
83
+ ```
84
+
85
+ ### 跨页面复用表单
86
+
87
+ 升到 `src/components/forms/<Name>Form.tsx`:
88
+
89
+ ```
90
+ src/components/forms/
91
+ ├── AddressForm.tsx # 地址表单(下单页 + 用户中心都用)
92
+ └── CustomerSearchForm.tsx
93
+ ```
94
+
95
+ ### 标准实现模板
96
+
97
+ ```tsx
98
+ // src/pages/orders/new/_components/CreateOrderForm.tsx
99
+ import { useForm } from 'react-hook-form';
100
+ import { zodResolver } from '@hookform/resolvers/zod';
101
+ import { useCreateOrder } from '@/hooks/useCreateOrder';
102
+ import {
103
+ CreateOrderInputSchema,
104
+ type CreateOrderInput,
105
+ } from '@/services/order.schema';
106
+ import { Button, Input, Form, FormField, FormItem } from '@/components/ui';
107
+
108
+ export function CreateOrderForm() {
109
+ const form = useForm<CreateOrderInput>({
110
+ resolver: zodResolver(CreateOrderInputSchema),
111
+ defaultValues: { customerId: '', items: [], remark: '' },
112
+ });
113
+ const mutation = useCreateOrder();
114
+
115
+ return (
116
+ <Form {...form}>
117
+ <form
118
+ onSubmit={form.handleSubmit((values) => mutation.mutate(values))}
119
+ className="space-y-4"
120
+ >
121
+ <FormField
122
+ control={form.control}
123
+ name="customerId"
124
+ render={({ field }) => (
125
+ <FormItem>
126
+ <Input {...field} />
127
+ </FormItem>
128
+ )}
129
+ />
130
+ {/* ... 其他字段 */}
131
+ <Button type="submit" disabled={mutation.isPending}>
132
+ {mutation.isPending ? '提交中…' : '提交'}
133
+ </Button>
134
+ </form>
135
+ </Form>
136
+ );
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 校验规则
143
+
144
+ | 场景 | 写在哪 |
145
+ | --- | --- |
146
+ | 字段格式(邮箱 / 手机号 / URL) | zod schema(`z.string().email()`) |
147
+ | 业务规则(库存检查 / 唯一性) | service 层,服务端返回错误 → hook 暴露 → UI 显示 |
148
+ | 跨字段约束(开始日期 < 结束日期) | zod `.refine()` / `.superRefine()` |
149
+ | 异步校验(用户名是否被占用) | `useQuery` + debounce,**不放 schema** |
150
+
151
+ ### 反模式
152
+
153
+ - ❌ 在 `onChange` 里手写 `if (value.length > 20) setError(...)`
154
+ - ❌ 校验报错信息硬编码在组件里("请输入正确的手机号") —— 写在 schema 里,UI 只读
155
+ - ❌ 表单状态用 `useState`,然后再加 `useEffect` 同步校验 —— 直接用 `useForm`
156
+ - ❌ schema 在组件文件里就地定义 —— 抽到 `<domain>.schema.ts`
157
+ - ❌ 提交逻辑写在 `<form onSubmit>` 里直接 fetch —— 走 mutation hook
158
+
159
+ ---
160
+
161
+ ## 表单提交错误如何展示
162
+
163
+ ```tsx
164
+ const mutation = useCreateOrder();
165
+
166
+ // 提交后:
167
+ mutation.mutate(values, {
168
+ onError: (err) => {
169
+ // 1. 字段级错误回填 form(后端返回 { field, message }[])
170
+ if (isFieldErrors(err)) {
171
+ err.fields.forEach(({ field, message }) =>
172
+ form.setError(field as keyof CreateOrderInput, { message }),
173
+ );
174
+ return;
175
+ }
176
+ // 2. 通用错误走 toast(全局错误归一化在 lib/http.ts)
177
+ toast.error(err.message);
178
+ },
179
+ });
180
+ ```
181
+
182
+ 要点:
183
+
184
+ - 字段级错误 → `form.setError`(高亮对应输入框)
185
+ - 通用错误 → `toast`(由 `lib/http.ts` 已归一化的 `Error.message`)
186
+ - **不要**把后端错误对象塞到 form,先归一化
187
+
188
+ ---
189
+
190
+ ## 与 ui 包的边界
191
+
192
+ `@teamix-evo/ui` 提供 `Form`、`FormField`、`FormItem`、`FormLabel`、`FormMessage`(shadcn 同款,基于 react-hook-form Context)。
193
+
194
+ - **必须用 ui 包提供的 Form 组件**,不要自己 `<form>` + `<label>` 手撸
195
+ - 业务表单只负责:schema + 字段 + 提交 callback,UI 一律走 ui 包
196
+
197
+ ---
198
+
199
+ ## 装机指引
200
+
201
+ 如果项目里还没装:
202
+
203
+ ```bash
204
+ pnpm add react-hook-form zod @hookform/resolvers
205
+ ```
206
+
207
+ `@hookform/resolvers` 提供 `zodResolver`,把 zod schema 接入 react-hook-form。
208
+
209
+ ---
210
+
211
+ ## AI 必须输出的表单日志
212
+
213
+ ```
214
+ ## 表单改动
215
+
216
+ - src/services/order.schema.ts: 新增 CreateOrderInputSchema(zod)、派生类型
217
+ - src/services/order.ts: createOrder 入参用派生类型
218
+ - src/pages/orders/new/_components/CreateOrderForm.tsx: useForm + zodResolver
219
+ - src/hooks/useCreateOrder.ts: useMutation 包 createOrder,onError 区分字段级 / 通用
220
+ ```
@@ -0,0 +1,130 @@
1
+ # Reuse-First · 组件 / 工具复用决策流
2
+
3
+ > 在写**任何**新组件 / Hook / 工具函数之前,AI 必须按本流程跑一遍。"没找到现成的"必须是查询的结论,不能是默认假设。
4
+
5
+ ---
6
+
7
+ ## 决策树
8
+
9
+ ```
10
+ 收到「写一个 xxx」请求
11
+
12
+ ├── Step 1: 这是 UI 组件吗?
13
+ │ ├── 是 → 走 §1(查 @teamix-evo/ui 注册表)
14
+ │ └── 否 → 跳 Step 2
15
+
16
+ ├── Step 2: 这是业务组件 / 页面骨架吗?
17
+ │ ├── 是 → 走 §2(查 @teamix-evo/biz-ui;页面骨架走 design skill 的 patterns/)
18
+ │ └── 否 → 跳 Step 3
19
+
20
+ ├── Step 3: 这是工具函数 / Hook 吗?
21
+ │ ├── 是 → 走 §3(grep 本项目 + 查常见库)
22
+ │ └── 否 → 跳 Step 4
23
+
24
+ └── Step 4: 全部未命中 → 走 §4(写新代码,但要遵守归位规则)
25
+ ```
26
+
27
+ ---
28
+
29
+ ## §1 · UI 组件:必须先查 `@teamix-evo/ui` 注册表
30
+
31
+ `@teamix-evo/ui` 是源码注入式 UI 组件库,**89 个条目**(button、input、dialog、data-table、…)。在写新 UI 组件之前:
32
+
33
+ ### 查询顺序(从快到慢)
34
+
35
+ 1. **MCP `list_components`** —— 一次列全部,扫描是否有同名 / 近义。
36
+ 2. **MCP `find_components` query=<关键词>** —— 子串匹配 id / name / description。
37
+ 3. **本项目 grep** —— `grep -r "from '@/components/ui'" src/` 看哪些已经装机。
38
+ 4. **本项目 `.teamix-evo/ui/manifest.lock.json`** —— 已装机的 ui entry 清单。
39
+
40
+ ### 命中后的处理
41
+
42
+ | 情况 | 处理 |
43
+ | ------------------------------- | ---------------------------------------------- |
44
+ | 注册表里有,本项目已装 | 直接 `import` 复用,**不要复制源码** |
45
+ | 注册表里有,本项目未装 | 提示用户跑 `teamix-evo ui add <id>`,装机后再用 |
46
+ | 注册表里有近义但 props 不完全够 | **优先组合**(包一层 wrapper),不要 fork ui 源码 |
47
+ | 注册表里完全没有 | 进 §4 |
48
+
49
+ ### 反模式(禁止)
50
+
51
+ - ❌ 在 `src/components/` 下重写 `Button` / `Input` / `Dialog`(已经在 ui 包里)
52
+ - ❌ 复制 ui 包源码到 `src/components/` 改样式 —— 改样式应当通过 design token 覆盖
53
+ - ❌ 包 `antd` / `mui` / `chakra` 的同类组件 —— teamix-evo 的设计系统是 shadcn-based,混用会破坏 token 链路
54
+
55
+ ---
56
+
57
+ ## §2 · 业务组件:查 `biz-ui`(页面骨架走 design skill 的 patterns/)
58
+
59
+ | 包 | 用途 | 查询入口 |
60
+ | -------------------- | -------------------------------------- | ------------------------------------------------------ |
61
+ | `@teamix-evo/biz-ui` | 业务化的复合组件(变体感知 + slot 边界) | 本项目 `node_modules/@teamix-evo/biz-ui/manifest.json` |
62
+
63
+ ### 页面骨架来源(列表 / 详情 / 表单 / 仪表盘)
64
+
65
+ > ⚠️ **AI 默认不调用 `@teamix-evo/templates` 包**(见 [ADR 0031](../../../docs/adr/0031-skill-templates-decoupling.md))。页面骨架按下列优先级生成:
66
+
67
+ 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>/`
68
+ 2. **patterns/ 未覆盖时** —— 按业界流行的中后台页面架构(antd Pro / shadcn Examples 等)与用户描述自由实现,**不要回退到 templates 包**
69
+ 3. **`@teamix-evo/templates` 包与 CLI 命令仍保留**,但仅在用户**显式**要求(如"用 templates 包的 frozen 骨架")时才走 `teamix-evo templates add`
70
+
71
+ ### 决策表
72
+
73
+ | 场景 | 选择 |
74
+ | ------------------------------------- | --------------------------------------------------------- |
75
+ | 列表页 / 详情页 / 表单页 / 仪表盘骨架 | 读 design skill `patterns/*.md` → ui + biz-ui 拼装 |
76
+ | "带筛选的用户表格"、"带审批流的卡片" | 优先 `biz-ui` |
77
+ | 一次性的、强领域绑定的组合 | 写在 `src/components/<domain>/` 里,**不要污染 biz-ui** |
78
+ | patterns/ 未覆盖的特殊页面 | 按业界流行架构 + 用户描述自由实现,**不调用 templates 包** |
79
+
80
+ ---
81
+
82
+ ## §3 · 工具函数 / Hook:本项目优先,再查通用库
83
+
84
+ ### 顺序
85
+
86
+ 1. **本项目 grep**:
87
+ ```bash
88
+ grep -rn "function <近义名>" src/utils/ src/lib/ src/hooks/
89
+ ```
90
+ 2. **`@teamix-evo/*` 是否提供**(少数情况,如格式化、token 工具)
91
+ 3. **依赖里的成熟库** —— 例如:
92
+ - 日期 → `date-fns` / `dayjs`(看项目里已经装哪个,不要再装第三个)
93
+ - 数据请求 → `@tanstack/react-query` / `swr`
94
+ - 表单 → `react-hook-form` + `zod`
95
+ - 工具 → `lodash-es`、`es-toolkit`
96
+ 4. **完全没有现成** → 进 §4
97
+
98
+ ### 反模式
99
+
100
+ - ❌ 写 `formatDate` 时不看项目用的是 dayjs 还是 date-fns,凭直觉新加一个
101
+ - ❌ 同一项目里既装 `lodash` 又装 `lodash-es` 又装 `es-toolkit`
102
+ - ❌ 自己写防抖 / 节流 / 深拷贝(标准库都有)
103
+
104
+ ---
105
+
106
+ ## §4 · 必须新写时的归位规则
107
+
108
+ 走到这一步,代码确实需要新写。归位见 [`file-structure.md`](file-structure.md);本节只讲**写新代码时**的最小约束:
109
+
110
+ 1. **命名要能让下次复用查得到** —— 起 `OrderStatusBadge` 而不是 `MyBadge`;`useOrderList` 而不是 `useList`。
111
+ 2. **抽象层级别低于一次性页面** —— 跨页面会用的才放 `src/components/`,只在一个页面用的写在 `src/pages/<id>/_components/`。
112
+ 3. **暴露面尽量窄** —— 默认只 `export` 这次需要的 API,别一次性 `export *`。
113
+ 4. **写完登记** —— 在 PR 描述里明确"新增 `<path>`,因为 ui / biz-ui / patterns / 本项目均未提供 X 能力"(注:不再以 `templates` 包作为兜底依据,见 [ADR 0031](../../../docs/adr/0031-skill-templates-decoupling.md))。
114
+
115
+ ---
116
+
117
+ ## AI 必须输出的决策日志
118
+
119
+ 完成代码改动时,AI **必须**在响应里附一段决策日志,例如:
120
+
121
+ ```
122
+ ## 复用决策
123
+
124
+ - ✅ 复用 `@teamix-evo/ui` 的 `DataTable`(已装机)
125
+ - ✅ 复用 `@teamix-evo/ui` 的 `Button`、`Badge`
126
+ - 🆕 新写 `src/components/OrderStatusBadge.tsx` —— ui 包提供通用 `Badge`,但订单状态 → 颜色映射是业务逻辑,组合一层
127
+ - ✅ 复用 `src/utils/format.ts` 的 `formatMoney`
128
+ ```
129
+
130
+ 没有这段日志,任务不算完成。