@teamix-evo/skills 0.12.0 → 0.13.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 +1 -1
- package/manifest.json +11 -28
- package/package.json +2 -2
- package/src/teamix-evo-code-opentrek/SKILL.md +13 -13
- package/src/teamix-evo-code-opentrek/api-layering.md +53 -44
- package/src/teamix-evo-code-opentrek/checklist.md +24 -24
- package/src/teamix-evo-code-opentrek/file-structure.md +55 -36
- package/src/teamix-evo-code-opentrek/forms-and-validation.md +17 -16
- package/src/teamix-evo-code-opentrek/reuse-first.md +6 -9
- package/src/teamix-evo-code-opentrek/testing.md +14 -14
- package/src/teamix-evo-code-uni-manager/SKILL.md +15 -15
- package/src/teamix-evo-code-uni-manager/api-layering.md +74 -58
- package/src/teamix-evo-code-uni-manager/checklist.md +28 -28
- package/src/teamix-evo-code-uni-manager/error-and-loading.md +2 -2
- package/src/teamix-evo-code-uni-manager/file-structure.md +77 -62
- package/src/teamix-evo-code-uni-manager/forms-and-validation.md +17 -15
- package/src/teamix-evo-code-uni-manager/reuse-first.md +7 -10
- package/src/teamix-evo-code-uni-manager/routing-and-codesplit.md +1 -1
- package/src/teamix-evo-code-uni-manager/testing.md +37 -37
- package/src/teamix-evo-design-opentrek/SKILL.md +41 -20
- package/src/teamix-evo-design-opentrek/boundaries.md +1 -1
- package/src/teamix-evo-design-opentrek/checklist.md +5 -5
- package/src/teamix-evo-design-opentrek/components.md +19 -19
- package/src/teamix-evo-design-opentrek/examples/standard-card-list.html +1 -1
- package/src/teamix-evo-design-opentrek/examples/standard-table-list.html +1 -1
- package/src/teamix-evo-design-opentrek/foundations.md +17 -17
- package/src/teamix-evo-design-opentrek/pages/dashboard-page/SKILL.md +18 -19
- package/src/teamix-evo-design-opentrek/pages/dashboard-page/patterns/dashboard-opentrek.md +6 -6
- package/src/teamix-evo-design-opentrek/pages/detail-page/SKILL.md +24 -25
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/api-doc-detail.md +3 -7
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/comparison-detail.md +3 -7
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/monitor-detail.md +3 -7
- package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/resource-detail.md +10 -10
- package/src/teamix-evo-design-opentrek/pages/form-page/SKILL.md +26 -27
- package/src/teamix-evo-design-opentrek/pages/list-page/SKILL.md +35 -36
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/item-card-spec.md +41 -32
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list-opentrek.md +10 -10
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list.md +23 -23
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list-opentrek.md +8 -8
- package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list.md +8 -8
- package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +2 -2
- package/src/teamix-evo-design-opentrek/patterns/dashboard.md +1 -1
- package/src/teamix-evo-design-opentrek/patterns/detail-page.md +9 -9
- package/src/teamix-evo-design-opentrek/patterns/form-page.md +6 -6
- package/src/teamix-evo-design-opentrek/patterns/list-page.md +9 -9
- package/src/teamix-evo-design-opentrek/patterns/page-types.md +3 -3
- package/src/teamix-evo-design-opentrek/principles.md +541 -0
- package/src/teamix-evo-design-opentrek/rules/common-components.json +206 -76
- package/src/teamix-evo-design-opentrek/rules/component-specs.json +2 -2
- package/src/teamix-evo-design-opentrek/rules/design-tokens.css +223 -218
- package/src/teamix-evo-design-opentrek/rules/design-tokens.json +10 -32
- package/src/teamix-evo-design-opentrek/rules/page-frame.json +197 -193
- package/src/teamix-evo-design-opentrek/{generation-flow.md → workflow.md} +141 -22
- package/src/teamix-evo-design-uni-manager/SKILL.md +30 -6
- package/src/teamix-evo-design-uni-manager/boundaries.md +2 -2
- package/src/teamix-evo-design-uni-manager/brand.md +1 -1
- package/src/teamix-evo-design-uni-manager/checklist.md +2 -2
- package/src/teamix-evo-design-uni-manager/components.md +11 -11
- package/src/teamix-evo-design-uni-manager/foundations.md +7 -7
- package/src/teamix-evo-design-uni-manager/generation-flow.md +3 -3
- package/src/teamix-evo-manage/SKILL.md +111 -709
- package/src/teamix-evo-manage/init.md +98 -0
- package/src/teamix-evo-manage/migrate.md +100 -0
- package/src/teamix-evo-manage/rearchitect-capture-guide.md +174 -0
- package/src/teamix-evo-manage/rearchitect.md +373 -0
- package/src/teamix-evo-manage/update-component-staging.md +188 -0
- package/src/teamix-evo-manage/update-token-rename.md +126 -0
- package/src/teamix-evo-manage/update-token-treatment.md +116 -0
- package/src/teamix-evo-manage/update.md +213 -0
- package/src/teamix-evo-design-opentrek/brand.md +0 -154
- package/src/teamix-evo-design-opentrek/pages/list-page/_shared/search-combo-spec.md +0 -194
- package/src/teamix-evo-design-opentrek/philosophy.md +0 -98
- package/src/teamix-evo-design-opentrek/rules/README.md +0 -39
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AGENT RUNTIME.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI GATEWAY.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI STUDIO.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_DEV-2.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_LOGO.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/_assets/OP_OPS.svg +0 -1
- package/src/teamix-evo-design-opentrek/rules/layout-rules.json +0 -218
- package/src/teamix-evo-design-opentrek/rules/page-header-spec.md +0 -123
- package/src/teamix-evo-design-opentrek/rules/sidebar-spec.md +0 -217
- package/src/teamix-evo-upgrade/SKILL.md +0 -431
- /package/src/teamix-evo-design-opentrek/{rules/boundaries.rules.json → boundaries.json} +0 -0
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
src/
|
|
13
|
+
├── features/ # 业务领域(每个子目录聚合 api/hooks/types/mocks/schema)
|
|
13
14
|
├── pages/ # 路由页面(每个目录对应一条路由)
|
|
14
15
|
├── components/ # 跨页面复用的业务组件
|
|
15
|
-
├──
|
|
16
|
-
├── hooks/ # 自定义 Hook(包含数据 hook、UI hook)
|
|
16
|
+
├── hooks/ # 跨领域共享的通用 Hook(极少数,如 useDebounce / useDangerConfirm)
|
|
17
17
|
├── contexts/ # React Context(跨子树共享、低频更新的全局态;含 TenantContext / RegionContext)
|
|
18
18
|
├── stores/ # 高频全局态(zustand / jotai / redux,首次需要时引入,只用一种)
|
|
19
|
-
├── types/ #
|
|
19
|
+
├── types/ # 跨领域通用类型(ApiResponse<T>、Pagination<T>)
|
|
20
20
|
├── utils/ # 纯函数工具(无 React、无副作用)
|
|
21
21
|
├── lib/ # 三方 SDK 包装、http 实例、active-context、monitor
|
|
22
22
|
├── routes/ # 路由配置 + 守卫(若用 react-router 集中式声明)
|
|
@@ -29,22 +29,22 @@ src/
|
|
|
29
29
|
└── App.tsx # 根组件
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
> 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`
|
|
32
|
+
> 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`services/` 和 `features/`、`views/` 和 `pages/`)。`*.test.ts(x)` 文件**就近**放在被测文件同目录,**不要**集中堆到顶层 `tests/`(见 [`testing.md`](testing.md))。
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
36
|
## uni-manager 增量目录约定
|
|
37
37
|
|
|
38
|
-
| 路径
|
|
39
|
-
|
|
|
40
|
-
| `src/contexts/TenantContext.tsx`
|
|
41
|
-
| `src/contexts/RegionContext.tsx`
|
|
42
|
-
| `src/lib/active-context.ts`
|
|
43
|
-
| `src/lib/http.ts`
|
|
44
|
-
| `src/components/cloud/`
|
|
45
|
-
| `src/components/danger/`
|
|
46
|
-
| `src/components/context/`
|
|
47
|
-
| `src/
|
|
38
|
+
| 路径 | 用途 |
|
|
39
|
+
| ------------------------------------- | ------------------------------------------------------------------------------- |
|
|
40
|
+
| `src/contexts/TenantContext.tsx` | 租户上下文 Provider + `useTenant()` |
|
|
41
|
+
| `src/contexts/RegionContext.tsx` | 区域上下文 Provider + `useRegion()` |
|
|
42
|
+
| `src/lib/active-context.ts` | vanilla TS 实现的 active tenant/region/cloud 单例(供 interceptor 与 React 共享) |
|
|
43
|
+
| `src/lib/http.ts` | 集中注入 `X-Tenant-Id` / `X-Region-Id` / `X-Cloud-Provider` 请求头 |
|
|
44
|
+
| `src/components/cloud/` | 跨云通用组件占位:`CloudBadge`、`RegionBadge`、`ProviderIcon` |
|
|
45
|
+
| `src/components/danger/` | 危险操作组件占位:`DangerConfirmDialog`、`useDangerConfirm` |
|
|
46
|
+
| `src/components/context/` | 上下文切换组件占位:`ContextSwitcher`(切租户/区域时弹 AlertDialog) |
|
|
47
|
+
| `src/features/<domain>/api.cloud.ts` | 跨云查询服务(显式覆盖 interceptor,并发多 cloud) |
|
|
48
48
|
|
|
49
49
|
> 这些目录是"概念占位拼装"的标准落点(详见 [reuse-first.md](reuse-first.md))。biz-ui/uni-manager 已实装 `um-topbar`,其余先以本工程内组件兜底,后续逐步抽包。
|
|
50
50
|
|
|
@@ -73,7 +73,7 @@ src/pages/
|
|
|
73
73
|
|
|
74
74
|
- 页面组件**默认 default export**,与文件名一致
|
|
75
75
|
- `_components/` `_hooks/` 用下划线前缀标记**私有**,**禁止跨页面 import**
|
|
76
|
-
-
|
|
76
|
+
- 跨页面用得到的组件 → 升到 `src/components/`;领域 hook → 放 `src/features/<domain>/hooks.ts`
|
|
77
77
|
|
|
78
78
|
### `src/components/`
|
|
79
79
|
|
|
@@ -104,36 +104,51 @@ src/components/
|
|
|
104
104
|
- **不要**写"小工具组件"如 `Wrapper`、`Container` —— 用 div + tailwind 即可
|
|
105
105
|
- 跨云/危险/上下文相关组件统一放 `cloud/` `danger/` `context/` 子目录,便于后续抽到 biz-ui/uni-manager
|
|
106
106
|
|
|
107
|
-
### `src/
|
|
107
|
+
### `src/features/`
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
**职责**:业务领域代码。每个子目录聚合一个领域的全部代码。详见 [`api-layering.md`](api-layering.md)。
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
```
|
|
112
|
+
src/features/
|
|
113
|
+
├── instance/
|
|
114
|
+
│ ├── api.ts # 请求纯函数(listInstances, createInstance, ...)
|
|
115
|
+
│ ├── api.cloud.ts # 跨云查询(显式覆盖 interceptor,并发多 cloud)
|
|
116
|
+
│ ├── hooks.ts # react-query 封装(useInstanceList, useCreateInstance, ...)
|
|
117
|
+
│ ├── types.ts # 领域类型(Instance, InstanceStatus, ...)
|
|
118
|
+
│ ├── mocks.ts # 本地 mock 数据(开发阶段)
|
|
119
|
+
│ └── schema.ts # zod schema(CreateInstanceInputSchema, ...)
|
|
120
|
+
├── tenant/
|
|
121
|
+
│ └── ...
|
|
122
|
+
├── region/
|
|
123
|
+
│ └── ...
|
|
124
|
+
└── audit-log/
|
|
125
|
+
└── ...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
约束:
|
|
112
129
|
|
|
113
|
-
-
|
|
114
|
-
-
|
|
130
|
+
- 一个领域 = 一个子目录,**不按技术类型拆**
|
|
131
|
+
- api.ts 是纯函数层,**不依赖 React**;**不显式拼 tenantId / regionId**(走 `lib/http.ts` interceptor 自动注入)
|
|
132
|
+
- 例外:跨云/跨租户查询接口 `api.cloud.ts`,可显式 `headers: { 'X-Tenant-Id': tenantId }` 覆盖
|
|
133
|
+
- hooks.ts 调同目录 api.ts,**禁止**直接 fetch
|
|
134
|
+
- types.ts 仅类型,无运行时代码
|
|
135
|
+
- 同领域内用相对路径 `./types`、`./api`
|
|
115
136
|
|
|
116
137
|
### `src/hooks/`
|
|
117
138
|
|
|
118
|
-
|
|
139
|
+
**职责**:跨领域共享的通用 Hook(极少数)。
|
|
119
140
|
|
|
120
141
|
```
|
|
121
142
|
src/hooks/
|
|
122
|
-
├──
|
|
123
|
-
├──
|
|
124
|
-
|
|
125
|
-
├── useRegion.ts
|
|
126
|
-
├── useCloudProvider.ts
|
|
127
|
-
├── useDangerConfirm.ts # 危险操作(输入资源名才执行)
|
|
128
|
-
└── useDebounce.ts
|
|
143
|
+
├── useDangerConfirm.ts # 危险操作(输入资源名才执行,跨领域通用)
|
|
144
|
+
├── useDebounce.ts # 通用 UI hook
|
|
145
|
+
└── useMediaQuery.ts
|
|
129
146
|
```
|
|
130
147
|
|
|
131
148
|
约束:
|
|
132
149
|
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- 数据 hook 调 `src/services/`,**禁止**直接 fetch
|
|
136
|
-
- queryKey **必须**包含 `tenantId` / `regionId`(见 [`api-layering.md`](api-layering.md) §3)
|
|
150
|
+
- 领域数据 hook **不放这里**,放 `src/features/<domain>/hooks.ts`
|
|
151
|
+
- 只放与业务领域无关的通用 hook(如 useDebounce、useMediaQuery)及跨领域通用业务 hook(如 useDangerConfirm)
|
|
137
152
|
|
|
138
153
|
### `src/contexts/`
|
|
139
154
|
|
|
@@ -180,13 +195,13 @@ src/contexts/
|
|
|
180
195
|
|
|
181
196
|
### `src/types/`
|
|
182
197
|
|
|
183
|
-
|
|
198
|
+
**职责**:跨领域通用类型声明。
|
|
184
199
|
|
|
185
200
|
约束:
|
|
186
201
|
|
|
187
202
|
- 只放 `.ts` 类型文件,**不导出运行时代码**
|
|
188
|
-
-
|
|
189
|
-
-
|
|
203
|
+
- Domain 类型放 `src/features/<domain>/types.ts`,**不放这里**
|
|
204
|
+
- 仅放跨领域通用类型:`api.ts`(ApiResponse<T>、Pagination<T>)、`common.ts`
|
|
190
205
|
|
|
191
206
|
### `src/utils/`
|
|
192
207
|
|
|
@@ -194,9 +209,9 @@ src/contexts/
|
|
|
194
209
|
|
|
195
210
|
约束:
|
|
196
211
|
|
|
197
|
-
- **不依赖 React**(用了 React 应该是 hook,放 `src/hooks/`)
|
|
212
|
+
- **不依赖 React**(用了 React 应该是 hook,放 `src/features/<domain>/hooks.ts` 或 `src/hooks/`)
|
|
198
213
|
- **不依赖 DOM**(用了 DOM 应该是 lib 层包装)
|
|
199
|
-
- **不发请求**(发请求是
|
|
214
|
+
- **不发请求**(发请求是 features/<domain>/api.ts)
|
|
200
215
|
- 一个文件一个主题:`format.ts`、`validate.ts`、`array.ts`、`cloud.ts`(云厂商显示名映射)
|
|
201
216
|
|
|
202
217
|
### `src/lib/`
|
|
@@ -215,7 +230,7 @@ src/lib/
|
|
|
215
230
|
约束:
|
|
216
231
|
|
|
217
232
|
- 这一层**与 React 解耦**,可以在 Node / 测试中跑
|
|
218
|
-
- **不放业务逻辑** —— 业务逻辑去
|
|
233
|
+
- **不放业务逻辑** —— 业务逻辑去 features/<domain>/api.ts / hooks.ts
|
|
219
234
|
- `active-context.ts` 是 Context 与 interceptor 的桥梁:Context Provider 内 setter 双向同步它
|
|
220
235
|
|
|
221
236
|
### `src/routes/`
|
|
@@ -240,7 +255,7 @@ src/routes/
|
|
|
240
255
|
| 目录 | kebab-case | `src/pages/instance-detail/` |
|
|
241
256
|
| React 组件文件 | PascalCase.tsx | `InstanceStatusBadge.tsx` |
|
|
242
257
|
| Hook 文件 | camelCase.ts,`use` 前缀 | `useInstanceList.ts` |
|
|
243
|
-
|
|
|
258
|
+
| API 文件 | camelCase.ts,domain 名 | `api.ts` / `api.cloud.ts` |
|
|
244
259
|
| 工具文件 | camelCase.ts | `formatDate.ts` |
|
|
245
260
|
| 类型文件 | camelCase.ts,domain 名 | `instance.ts` |
|
|
246
261
|
| 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
|
|
@@ -256,11 +271,11 @@ src/routes/
|
|
|
256
271
|
// ✅
|
|
257
272
|
import { Button } from '@teamix-evo/ui';
|
|
258
273
|
import { Topbar } from 'biz-ui/uni-manager/um-topbar';
|
|
259
|
-
import { listInstances } from '@/
|
|
274
|
+
import { listInstances } from '@/features/instance/api';
|
|
260
275
|
import { CloudBadge } from '@/components/cloud/CloudBadge';
|
|
261
276
|
|
|
262
277
|
// ❌
|
|
263
|
-
import { listInstances } from '../../../
|
|
278
|
+
import { listInstances } from '../../../features/instance/api';
|
|
264
279
|
```
|
|
265
280
|
|
|
266
281
|
---
|
|
@@ -268,22 +283,22 @@ import { listInstances } from '../../../services/instance';
|
|
|
268
283
|
## Import 边界(单向依赖)
|
|
269
284
|
|
|
270
285
|
```
|
|
271
|
-
pages
|
|
272
|
-
components ──▶
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
contexts
|
|
276
|
-
stores
|
|
277
|
-
types
|
|
278
|
-
utils
|
|
279
|
-
lib
|
|
286
|
+
pages ──▶ components, features, hooks, types, utils, lib, stores, contexts
|
|
287
|
+
components ──▶ features, hooks, types, utils, lib, contexts (NOT pages)
|
|
288
|
+
features/* ──▶ lib, types (内部 api→types, hooks→api+types; NOT components, NOT pages)
|
|
289
|
+
hooks ──▶ types, utils, lib (NOT features, NOT components, NOT pages)
|
|
290
|
+
contexts ──▶ types, utils, lib (NOT features, NOT hooks)
|
|
291
|
+
stores ──▶ types, utils, lib (NOT features, NOT hooks)
|
|
292
|
+
types ──▶ (no runtime imports)
|
|
293
|
+
utils ──▶ types only
|
|
294
|
+
lib ──▶ types only
|
|
280
295
|
```
|
|
281
296
|
|
|
282
297
|
要点:
|
|
283
298
|
|
|
284
|
-
- **
|
|
299
|
+
- **features/*/api.ts 不依赖 React** —— 它是纯函数层(tenant/region 走 interceptor,不在 api 显式读 Context)
|
|
285
300
|
- **types / utils / lib 是叶子层** —— 不依赖业务层,谁都能引
|
|
286
|
-
- **反向依赖直接否决**(component 不能 import page,
|
|
301
|
+
- **反向依赖直接否决**(component 不能 import page,api 不能 import hook)
|
|
287
302
|
|
|
288
303
|
---
|
|
289
304
|
|
|
@@ -292,10 +307,10 @@ lib ──▶ types only
|
|
|
292
307
|
| 反模式 | 为什么禁 | 应该怎么做 |
|
|
293
308
|
| ----------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
294
309
|
| `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
|
|
295
|
-
| `src/
|
|
310
|
+
| `src/services/` 与 `src/features/` 并存 | 同义层 | 统一用 `features/` |
|
|
296
311
|
| `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
|
|
297
312
|
| 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
|
|
298
|
-
| 在 `src/utils/` 里写 React Hook | 边界错位 |
|
|
313
|
+
| 在 `src/utils/` 里写 React Hook | 边界错位 | 领域 hook → `features/<domain>/hooks.ts`;通用 hook → `src/hooks/` |
|
|
299
314
|
| 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
|
|
300
315
|
| `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
|
|
301
316
|
| 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
|
|
@@ -307,7 +322,7 @@ lib ──▶ types only
|
|
|
307
322
|
| 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
|
|
308
323
|
| `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
|
|
309
324
|
| 自建 `Topbar.tsx` 替代 `biz-ui/uni-manager/um-topbar` | 一致性三件套 UM1 红线 | 直接用 `um-topbar`(见 [reuse-first.md](reuse-first.md)) |
|
|
310
|
-
|
|
|
325
|
+
| api 函数手动拼接 `tenantId` | 上下文与传输层耦合,散点漂移 | `lib/http.ts` interceptor 集中注入(见 [`api-layering.md`](api-layering.md)) |
|
|
311
326
|
| 危险删除接口只用 `confirm("确定?")` | UM 红线,误操作概率高 | `useDangerConfirm`(见 [`forms-and-validation.md`](forms-and-validation.md) §10) |
|
|
312
327
|
|
|
313
328
|
---
|
|
@@ -319,15 +334,15 @@ lib ──▶ types only
|
|
|
319
334
|
| 新增实例列表页 | `src/pages/instances/index.tsx` |
|
|
320
335
|
| 新增实例状态徽章(跨页面用) | `src/components/InstanceStatusBadge.tsx` |
|
|
321
336
|
| 新增实例状态徽章(只在实例页用) | `src/pages/instances/_components/InstanceStatusBadge.tsx` |
|
|
322
|
-
| 新增"释放实例"接口调用 | `src/
|
|
323
|
-
| 新增"释放实例"的 mutation hook | `src/hooks
|
|
337
|
+
| 新增"释放实例"接口调用 | `src/features/instance/api.ts` 加 `releaseInstance` |
|
|
338
|
+
| 新增"释放实例"的 mutation hook | `src/features/instance/hooks.ts` 加 `useReleaseInstance`(用 useDangerConfirm 包装) |
|
|
324
339
|
| 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
|
|
325
340
|
| 新增鉴权 + 租户 header 拦截器 | `src/lib/http.ts` 加 interceptor;active 值放 `src/lib/active-context.ts` |
|
|
326
|
-
| 新增实例 domain 类型 | `src/
|
|
341
|
+
| 新增实例 domain 类型 | `src/features/instance/types.ts` |
|
|
327
342
|
| 跨少量组件分享当前用户 / 主题 / 租户 / 区域 | `src/contexts/<Name>Context.tsx`(同时同步 `lib/active-context.ts`) |
|
|
328
343
|
| 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
|
|
329
|
-
| 列表 / 详情等服务端数据 | `src/hooks
|
|
330
|
-
| 新增"创建实例表单" | `src/pages/instances/new/_components/CreateInstanceForm.tsx` + `src/
|
|
344
|
+
| 列表 / 详情等服务端数据 | `src/features/<domain>/hooks.ts` 包 `react-query`,queryKey 含 tenantId/regionId,**不进** store |
|
|
345
|
+
| 新增"创建实例表单" | `src/pages/instances/new/_components/CreateInstanceForm.tsx` + `src/features/instance/schema.ts` |
|
|
331
346
|
| 跨页面复用表单(地址 / 标签编辑) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
|
|
332
347
|
| 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
|
|
333
348
|
| 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
|
|
@@ -335,7 +350,7 @@ lib ──▶ types only
|
|
|
335
350
|
| 跨云资源 404 兜底(资源被删除/迁移) | `src/components/cloud/CloudResourceNotFound.tsx`(见 [`error-and-loading.md`](error-and-loading.md) §7) |
|
|
336
351
|
| Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
|
|
337
352
|
| 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
|
|
338
|
-
| 纯函数 /
|
|
339
|
-
| 跨云查询(并发多 cloud / 显式 tenantId 覆盖) | `src/
|
|
353
|
+
| 纯函数 / api / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
|
|
354
|
+
| 跨云查询(并发多 cloud / 显式 tenantId 覆盖) | `src/features/<domain>/api.cloud.ts`(显式传 header,**不**走 active-context) |
|
|
340
355
|
| 危险确认对话框(输入资源名才执行) | `src/components/danger/DangerConfirmDialog.tsx` + `src/hooks/useDangerConfirm.ts` |
|
|
341
356
|
| 全局 topbar(品牌 / 租户切换 / 区域切换 / 用户菜单) | **直接用** `biz-ui/uni-manager/um-topbar`,**不要**自建 |
|
|
@@ -22,22 +22,24 @@
|
|
|
22
22
|
## Schema 落点
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
src/
|
|
26
|
-
├──
|
|
27
|
-
|
|
25
|
+
src/features/instance/
|
|
26
|
+
├── api.ts # 请求函数:listInstances、createInstance
|
|
27
|
+
├── schema.ts # zod schema:CreateInstanceInput、UpdateInstanceInput
|
|
28
|
+
├── hooks.ts # react-query 封装
|
|
29
|
+
└── types.ts # 领域类型
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
约定:
|
|
31
33
|
|
|
32
|
-
- **每个 domain 一个
|
|
34
|
+
- **每个 domain 一个 `schema.ts`**,与 api / hooks / types 同目录
|
|
33
35
|
- 从 schema 派生类型:`export type CreateInstanceInput = z.infer<typeof CreateInstanceInputSchema>`
|
|
34
|
-
-
|
|
36
|
+
- api 的入参 / 返回类型**优先用** schema 推导,而不是手写 interface
|
|
35
37
|
- **不要**把 schema 写在页面 / 组件文件里 —— schema 是 domain 资产,跨页面复用
|
|
36
38
|
|
|
37
39
|
### 示例
|
|
38
40
|
|
|
39
41
|
```ts
|
|
40
|
-
// src/
|
|
42
|
+
// src/features/instance/schema.ts
|
|
41
43
|
import { z } from 'zod';
|
|
42
44
|
|
|
43
45
|
export const CreateInstanceInputSchema = z.object({
|
|
@@ -55,10 +57,10 @@ export type CreateInstanceInput = z.infer<typeof CreateInstanceInputSchema>;
|
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
```ts
|
|
58
|
-
// src/
|
|
60
|
+
// src/features/instance/api.ts
|
|
59
61
|
import { http } from '@/lib/http';
|
|
60
|
-
import type { CreateInstanceInput } from './
|
|
61
|
-
import type { Instance } from '
|
|
62
|
+
import type { CreateInstanceInput } from './schema';
|
|
63
|
+
import type { Instance } from './types';
|
|
62
64
|
|
|
63
65
|
export async function createInstance(
|
|
64
66
|
input: CreateInstanceInput,
|
|
@@ -100,11 +102,11 @@ src/components/forms/
|
|
|
100
102
|
// src/pages/instances/new/_components/CreateInstanceForm.tsx
|
|
101
103
|
import { useForm } from 'react-hook-form';
|
|
102
104
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
103
|
-
import { useCreateInstance } from '@/hooks
|
|
105
|
+
import { useCreateInstance } from '@/features/instance/hooks';
|
|
104
106
|
import {
|
|
105
107
|
CreateInstanceInputSchema,
|
|
106
108
|
type CreateInstanceInput,
|
|
107
|
-
} from '@/
|
|
109
|
+
} from '@/features/instance/schema';
|
|
108
110
|
import { Button, Input, Form, FormField, FormItem } from '@/components/ui';
|
|
109
111
|
|
|
110
112
|
export function CreateInstanceForm() {
|
|
@@ -160,7 +162,7 @@ export function CreateInstanceForm() {
|
|
|
160
162
|
- ❌ 在 `onChange` 里手写 `if (value.length > 20) setError(...)`
|
|
161
163
|
- ❌ 校验报错信息硬编码在组件里("请输入正确的实例名") —— 写在 schema 里,UI 只读
|
|
162
164
|
- ❌ 表单状态用 `useState`,然后再加 `useEffect` 同步校验 —— 直接用 `useForm`
|
|
163
|
-
- ❌ schema 在组件文件里就地定义 —— 抽到
|
|
165
|
+
- ❌ schema 在组件文件里就地定义 —— 抽到 `features/<domain>/schema.ts`
|
|
164
166
|
- ❌ 提交逻辑写在 `<form onSubmit>` 里直接 fetch —— 走 mutation hook
|
|
165
167
|
|
|
166
168
|
---
|
|
@@ -452,10 +454,10 @@ pnpm add react-hook-form zod @hookform/resolvers
|
|
|
452
454
|
```
|
|
453
455
|
## 表单改动
|
|
454
456
|
|
|
455
|
-
- src/
|
|
456
|
-
- src/
|
|
457
|
+
- src/features/instance/schema.ts: 新增 CreateInstanceInputSchema(zod)、派生类型
|
|
458
|
+
- src/features/instance/api.ts: createInstance 入参用派生类型(不显式拼 tenantId)
|
|
457
459
|
- src/pages/instances/new/_components/CreateInstanceForm.tsx: useForm + zodResolver + useBlocker(脏态拦截)
|
|
458
|
-
- src/hooks
|
|
460
|
+
- src/features/instance/hooks.ts: useCreateInstance — useMutation 包 createInstance,onError 区分字段级 / 通用
|
|
459
461
|
- 危险操作: ✅ 释放实例走 useDangerConfirm(输入 instance.id 校验)
|
|
460
462
|
- 切租户保护: ✅ 表单 isDirty 时拦截 search 变化
|
|
461
463
|
```
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
|
|
41
41
|
### 查询顺序(从快到慢)
|
|
42
42
|
|
|
43
|
-
1.
|
|
44
|
-
2.
|
|
43
|
+
1. **读 `.teamix-evo/meta/ui/manifest.json`** —— 一次列全部,扫描是否有同名 / 近义。
|
|
44
|
+
2. **`grep <关键词> .teamix-evo/meta/ui/manifest.json`** —— 子串匹配 id / name / description;需要详细 Props/示例时读 `.teamix-evo/meta/ui/<id>.md`。
|
|
45
45
|
3. **本项目 grep** —— `grep -r "from '@/components/ui'" src/` 看哪些已经装机。
|
|
46
46
|
4. **本项目 `.teamix-evo/ui/manifest.lock.json`** —— 已装机的 ui entry 清单。
|
|
47
47
|
|
|
@@ -111,21 +111,18 @@
|
|
|
111
111
|
|
|
112
112
|
### 页面骨架来源(列表 / 详情 / 表单 / Console Home)
|
|
113
113
|
|
|
114
|
-
> ⚠️ **AI 默认不调用 `@teamix-evo/templates` 包**(见 ADR 0031 skill-templates-decoupling)。页面骨架按下列优先级生成:
|
|
115
|
-
|
|
116
114
|
1. **首选** —— 读 `teamix-evo-design-uni-manager` skill 的 [`patterns/{list|detail|form|dashboard}-page.md`](../teamix-evo-design-uni-manager/patterns/),按 Zone Map 与决策树用 ui 原子件 + biz-ui/uni-manager 实物件(`um-topbar`) + 概念占位拼装表**直接拼装**到 `src/pages/<id>/`
|
|
117
|
-
2. **patterns/ 未覆盖时** —— 按业界流行的云管 / 控制台页面架构(antd Pro / aliyun-console / shadcn Examples
|
|
118
|
-
3. **`@teamix-evo/templates` 包与 CLI 命令仍保留**,但仅在用户**显式**要求时才走 `teamix-evo templates add`
|
|
115
|
+
2. **patterns/ 未覆盖时** —— 按业界流行的云管 / 控制台页面架构(antd Pro / aliyun-console / shadcn Examples 等)与用户描述自由实现
|
|
119
116
|
|
|
120
117
|
### 决策表
|
|
121
118
|
|
|
122
119
|
| 场景 | 选择 |
|
|
123
120
|
| ---------------------------------------------------- | -------------------------------------------------------------- |
|
|
124
121
|
| 列表页 / 详情页 / 表单页 / Console Home 骨架 | 读 design skill `patterns/*.md` → ui + biz-ui/uni-manager 拼装 |
|
|
125
|
-
|
|
|
122
|
+
| “带筛选的资源表格”、”带审批流的工单卡片” | 优先 `biz-ui` |
|
|
126
123
|
| 一次性的、强领域绑定的组合 | 写在 `src/components/<domain>/` 里,**不要污染 biz-ui** |
|
|
127
124
|
| uni-manager 跨云专属(CloudBadge / OperationLog 等) | 走 §2 概念占位拼装表 |
|
|
128
|
-
| patterns/ 未覆盖的特殊页面 | 按业界流行架构 +
|
|
125
|
+
| patterns/ 未覆盖的特殊页面 | 按业界流行架构 + 用户描述自由实现 |
|
|
129
126
|
|
|
130
127
|
---
|
|
131
128
|
|
|
@@ -135,7 +132,7 @@
|
|
|
135
132
|
|
|
136
133
|
1. **本项目 grep**:
|
|
137
134
|
```bash
|
|
138
|
-
grep -rn "function <近义名>" src/utils/ src/lib/ src/
|
|
135
|
+
grep -rn "function <近义名>" src/utils/ src/lib/ src/features/
|
|
139
136
|
```
|
|
140
137
|
2. **`@teamix-evo/*` 是否提供**(少数情况,如格式化、token 工具)
|
|
141
138
|
3. **依赖里的成熟库** —— 例如:
|
|
@@ -167,7 +164,7 @@
|
|
|
167
164
|
1. **命名要能让下次复用查得到** —— 起 `InstanceStatusBadge` 而不是 `MyBadge`;`useInstanceList` 而不是 `useList`
|
|
168
165
|
2. **抽象层级别低于一次性页面** —— 跨页面会用的才放 `src/components/`,只在一个页面用的写在 `src/pages/<id>/_components/`
|
|
169
166
|
3. **暴露面尽量窄** —— 默认只 `export` 这次需要的 API,别一次性 `export *`
|
|
170
|
-
4. **写完登记** —— 在 PR
|
|
167
|
+
4. **写完登记** —— 在 PR 描述里明确”新增 `<path>`,因为 ui / biz-ui / patterns / 本项目均未提供 X 能力”
|
|
171
168
|
5. **uni-manager 专属**:跨云 / 跨租户的 component 必须放 `src/components/cloud/` 或 `src/components/<domain>/`,方便后续抽到 biz-ui/uni-manager
|
|
172
169
|
|
|
173
170
|
---
|
|
@@ -321,7 +321,7 @@ mutation.mutate(values, {
|
|
|
321
321
|
| ---------- | --------------------------- | ---------------- | ------------------------------------ |
|
|
322
322
|
| `?tenant=` | tenantId | um-topbar 切换时 | TenantContextGuard / TenantProvider |
|
|
323
323
|
| `?region=` | regionId | um-topbar 切换时 | TenantContextGuard / RegionProvider |
|
|
324
|
-
| `?cloud=` | cloudProvider(aws/aliyun..) | 跨云页面 toolbar | 跨云
|
|
324
|
+
| `?cloud=` | cloudProvider(aws/aliyun..) | 跨云页面 toolbar | 跨云 api hook(覆盖 active cloud) |
|
|
325
325
|
|
|
326
326
|
### um-topbar 集成示例
|
|
327
327
|
|
|
@@ -29,12 +29,12 @@ src/
|
|
|
29
29
|
├── utils/
|
|
30
30
|
│ ├── format.ts
|
|
31
31
|
│ └── format.test.ts # ← 紧邻
|
|
32
|
-
├──
|
|
33
|
-
│
|
|
34
|
-
│
|
|
35
|
-
├──
|
|
36
|
-
│
|
|
37
|
-
│
|
|
32
|
+
├── features/
|
|
33
|
+
│ └── instance/
|
|
34
|
+
│ ├── api.ts
|
|
35
|
+
│ ├── api.test.ts # ← 紧邻
|
|
36
|
+
│ ├── hooks.ts
|
|
37
|
+
│ └── hooks.test.tsx
|
|
38
38
|
└── components/
|
|
39
39
|
├── InstanceStatusBadge.tsx
|
|
40
40
|
└── InstanceStatusBadge.test.tsx
|
|
@@ -60,19 +60,19 @@ src/test/
|
|
|
60
60
|
|
|
61
61
|
按 ROI 排序:
|
|
62
62
|
|
|
63
|
-
| 优先级 | 类型
|
|
64
|
-
| ------ |
|
|
65
|
-
| P0 | 纯函数(`src/utils/`、`
|
|
66
|
-
| P0 | zod schema
|
|
67
|
-
| P0 | 关键业务路径(创建实例 / 释放 / 切租户)
|
|
68
|
-
| P0 | **interceptor / active-context 切换**
|
|
69
|
-
| P0 | **danger 流程(useDangerConfirm)**
|
|
70
|
-
| P1 | Hook(数据 hook + 自定义 UI hook)
|
|
71
|
-
| P1 | 复用业务组件(`src/components/`)
|
|
72
|
-
| P2 | 页面组件
|
|
73
|
-
| ❌ | ui 包源码
|
|
74
|
-
| ❌ | biz-ui/uni-manager 包源码(如 um-topbar)
|
|
75
|
-
| ❌ | 三方库
|
|
63
|
+
| 优先级 | 类型 | 例子 | 必测? |
|
|
64
|
+
| ------ | -------------------------------------------- | ---------------------------------------------- | -------------- |
|
|
65
|
+
| P0 | 纯函数(`src/utils/`、`features/*/api.ts`) | `formatMoney`、`isValidInstance` | ✅ **必测** |
|
|
66
|
+
| P0 | zod schema | `CreateInstanceInputSchema.parse(...)` | ✅ **必测** |
|
|
67
|
+
| P0 | 关键业务路径(创建实例 / 释放 / 切租户) | E2E-style 组件测 | ✅ **必测** |
|
|
68
|
+
| P0 | **interceptor / active-context 切换** | 切租户后请求带新 X-Tenant-Id,旧缓存 invalidate | ✅ **必测** |
|
|
69
|
+
| P0 | **danger 流程(useDangerConfirm)** | 输入正确名才 enable 按钮、关闭后状态重置 | ✅ **必测** |
|
|
70
|
+
| P1 | Hook(数据 hook + 自定义 UI hook) | `useInstanceList`、`useDebounce` | 推荐 |
|
|
71
|
+
| P1 | 复用业务组件(`src/components/`) | `CloudBadge` 各 cloud 渲染 | 推荐 |
|
|
72
|
+
| P2 | 页面组件 | 用 RTL 渲染 + msw mock | 可选 |
|
|
73
|
+
| ❌ | ui 包源码 | 已在 `@teamix-evo/ui` 包内测过 | **不要重复测** |
|
|
74
|
+
| ❌ | biz-ui/uni-manager 包源码(如 um-topbar) | 已在 biz-ui 包内测过 | **不要重复测** |
|
|
75
|
+
| ❌ | 三方库 | `react-router`、`react-query` | **不要测** |
|
|
76
76
|
|
|
77
77
|
**红线**:不要为了凑覆盖率数字测 `Button` 能不能渲染、`React.useState` 能不能用。
|
|
78
78
|
|
|
@@ -161,9 +161,9 @@ beforeEach(() => {
|
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
```ts
|
|
164
|
-
// src/
|
|
164
|
+
// src/features/instance/api.test.ts
|
|
165
165
|
import { describe, it, expect } from 'vitest';
|
|
166
|
-
import { listInstances } from './
|
|
166
|
+
import { listInstances } from './api';
|
|
167
167
|
import { setActiveTenant } from '@/lib/active-context';
|
|
168
168
|
|
|
169
169
|
describe('listInstances', () => {
|
|
@@ -224,10 +224,10 @@ export function renderHookWithProviders<R, P>(
|
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
```tsx
|
|
227
|
-
// src/hooks
|
|
227
|
+
// src/features/instance/hooks.test.tsx
|
|
228
228
|
import { waitFor } from '@testing-library/react';
|
|
229
229
|
import { renderHookWithProviders } from '@/test/render';
|
|
230
|
-
import { useInstanceList } from './
|
|
230
|
+
import { useInstanceList } from './hooks';
|
|
231
231
|
|
|
232
232
|
it('加载实例列表(queryKey 含 tenantId)', async () => {
|
|
233
233
|
const { result } = renderHookWithProviders(() =>
|
|
@@ -320,18 +320,18 @@ it('切换租户:invalidate 旧 query + 新请求带新 tenantId', async () => {
|
|
|
320
320
|
|
|
321
321
|
## §5 · 反模式速查
|
|
322
322
|
|
|
323
|
-
| 反模式
|
|
324
|
-
|
|
|
325
|
-
| 测 `setState` 是否被调用
|
|
326
|
-
| `getByTestId` 当默认查询
|
|
327
|
-
| 一个 it 里 expect 10 次无关断言
|
|
328
|
-
| mock 整个 hook(`vi.mock('@/
|
|
329
|
-
| 自己 mock fetch / axios
|
|
330
|
-
| msw handler 不校验 X-Tenant-Id
|
|
331
|
-
| 测试间不重置 active-context
|
|
332
|
-
| 测试里写 `setTimeout(..., 1000)` 等异步
|
|
333
|
-
| 全局 mock console.error 静默
|
|
334
|
-
| 追求 80% 覆盖率刷数字
|
|
323
|
+
| 反模式 | 为什么禁 | 应该 |
|
|
324
|
+
| --------------------------------------------- | --------------------------------- | ----------------------------------------------- |
|
|
325
|
+
| 测 `setState` 是否被调用 | 测了实现,重构即崩 | 测渲染结果 / 用户行为 |
|
|
326
|
+
| `getByTestId` 当默认查询 | data-testid 跟可访问性绑不上 | `getByRole` / `getByLabelText` 优先 |
|
|
327
|
+
| 一个 it 里 expect 10 次无关断言 | 失败定位难 | 拆多个 it |
|
|
328
|
+
| mock 整个 hook(`vi.mock('@/features/...')`) | 失去集成价值 | msw mock 后端,hook 真跑 |
|
|
329
|
+
| 自己 mock fetch / axios | 与生产代码路径不一致 | 用 msw |
|
|
330
|
+
| msw handler 不校验 X-Tenant-Id | interceptor 失效不会被发现 | 每个 handler `assertTenantHeader(request)` |
|
|
331
|
+
| 测试间不重置 active-context | 上一 test 污染下一 test 的 tenant | `beforeEach` 重置 |
|
|
332
|
+
| 测试里写 `setTimeout(..., 1000)` 等异步 | 慢且不稳定 | `await waitFor` / `findBy*` |
|
|
333
|
+
| 全局 mock console.error 静默 | 错过真实告警 | 让测试输出 console.error,但 setup 里 fail on it |
|
|
334
|
+
| 追求 80% 覆盖率刷数字 | 测了没价值的代码 | 按"优先级表"测,不看覆盖率盲冲 |
|
|
335
335
|
|
|
336
336
|
---
|
|
337
337
|
|
|
@@ -388,8 +388,8 @@ pnpm exec msw init public/ --save # 浏览器 worker
|
|
|
388
388
|
## 测试覆盖
|
|
389
389
|
|
|
390
390
|
- 新增 src/utils/cloud.ts → 同目录 cloud.test.ts(formatCloudProvider 4 case)
|
|
391
|
-
- 新增 src/
|
|
392
|
-
- 新增 src/hooks
|
|
391
|
+
- 新增 src/features/instance/api.ts:releaseInstance → api.test.ts(成功 / 422 / tenant header 校验)
|
|
392
|
+
- 新增 src/features/instance/hooks.ts:useReleaseInstance → hooks.test.tsx(success / error / 未确认 dangerName 不调 mutate)
|
|
393
393
|
- 复用 src/components/CloudBadge.tsx → 已有 test 覆盖 aws/aliyun/azure,无需补
|
|
394
394
|
- 切租户集成测试: ✅ TenantContext.test.tsx(invalidate + 新 tenantId header)
|
|
395
395
|
- 跳过测: src/pages/instances/new/index.tsx(纯组合,关键逻辑已在 hook / form 层测)
|