@pactor-app/skills 1.31.0 → 1.33.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/package.json
CHANGED
package/pactor-prd/SKILL.md
CHANGED
|
@@ -30,7 +30,7 @@ version: 1.3.0
|
|
|
30
30
|
3. **样式与原产品一致**:主题令牌(含深浅色)、自定义 CSS(如登录页)、换肤入口全部移植对齐,见 `references/theme-and-style.md`
|
|
31
31
|
4. **功能与原产品一致**:线框图描述的筛选/搜索/跳转/弹窗/提交等交互必须经 events/actions 闭环可用,禁止只画静态骨架;错误分支(校验失败提示等)也要实现
|
|
32
32
|
5. **表单一律用 `Form` 组件**:凡是一组「字段 + 校验 + 提交」的表单——独立表单页、页面内嵌的配置面板(如树/列表右侧的编辑表单)、弹窗/抽屉表单、**搜索栏(关键词 + 查询/重置 的筛选区,用 `Form layout: inline`)**——都必须用 `Form` 包裹,字段经 `props.name` 绑定,校验用字段级 `rules`(required + message),提交用 `submit: true` 的 Button 触发 `onSubmit`;**禁止**散放 `Input`/`Select` + `setState` 攒草稿 + `if` 动作手写校验。上下结构的字段一律用 `Field` 包裹:label、必填星号(`required: true`)、行右提示(`labelHint`)由 `Field` 渲染,控件上只留 `name`/`rules` 等(`Form layout: inline` 的搜索栏除外,label 直接挂控件);禁止手写 `Text("*") + Label` 拼标签。回填联动(如点击列表/树节点填充表单)用 `form.setValues` 动作,不要绑 `${state.x}` 到字段 value;即点即生效的 ChipFilter 快筛行没有提交语义,不属于表单
|
|
33
|
-
6. **列表页操作列规范**:操作列按钮样式统一 `Button variant: link`;「编辑」与「新建」指向同一个表单(同一路由页或同一弹窗),唯一区别是编辑以行数据预填(路由页 `initialValues: ${data.detail}`,弹窗/内嵌面板 `form.setValues
|
|
33
|
+
6. **列表页操作列规范**:操作列按钮样式统一 `Button variant: link`;「编辑」与「新建」指向同一个表单(同一路由页或同一弹窗),唯一区别是编辑以行数据预填(路由页 `initialValues: ${data.detail}`,弹窗/内嵌面板 `form.setValues`);**数据列表页(搜索 + 表格 + 新建/编辑)整体结构默认套用 `references/page-templates.md` 的「数据列表标准模型」**
|
|
34
34
|
7. **文案以线框图为准**:标题、按钮、空态文案逐字取自线框图/PRD
|
|
35
35
|
8. DSL 语法细节(组件 props、表达式、动作)以 `pactor-dsl` skill 与 packages/ui 当前导出为准,本 skill 只约定流程与产出物
|
|
36
36
|
|
|
@@ -93,7 +93,7 @@ version: 1.3.0
|
|
|
93
93
|
| `references/incremental-wireframe-delta.md` | **已有工程**:线框图 add/update/deprecate、改 sitemap/mock/样式 |
|
|
94
94
|
| `references/project-structure.md` | 阶段 2:目录结构约定与 sitemap.yaml 骨架 |
|
|
95
95
|
| `references/component-mapping.md` | 阶段 3:线框图区域 → pactor 组件映射 |
|
|
96
|
-
| `references/page-templates.md` | 阶段 3:列表/详情/表单页 yaml
|
|
96
|
+
| `references/page-templates.md` | 阶段 3:列表/详情/表单页 yaml 模板;数据列表页默认用「数据列表标准模型」(搜索 Form + 表格 + 抽屉新建/编辑) |
|
|
97
97
|
| `references/mock-data-routes.md` | 阶段 4:mock 路由表模板与命名约定 |
|
|
98
98
|
| `references/theme-and-style.md` | 阶段 5:主题令牌提取、皮肤、自定义 CSS、换肤 |
|
|
99
99
|
| `references/acceptance-checklist.md` | 阶段 6:验收清单与结果输出格式 |
|
|
@@ -1,8 +1,344 @@
|
|
|
1
1
|
# 页面 YAML 模板
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
常用页面模板。占位符:`{{kebab-id}}` 页面 id、`{{页面标题}}`、`{{模块名}}`、`{{resource}}` 数据接口名(单段,见 mock-data-routes.md)、`{{实体名}}` 业务实体中文名。DSL 语法细节以 `pactor-dsl` skill 与 packages/ui 当前导出为准。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**数据列表页(搜索 + 表格 + 新建/编辑)默认套用「数据列表标准模型」**;仅当新建/编辑是独立路由表单页时才用轻量变体。
|
|
6
|
+
|
|
7
|
+
## 数据列表标准模型(搜索 Form + 表格 + 抽屉新建/编辑)
|
|
8
|
+
|
|
9
|
+
从成熟实现提取的完整闭环:搜索过滤、新建/编辑共用抽屉表单(分区 + 条件显隐 + 预填)、操作列 link 按钮。结构照抄,字段按线框图替换。
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
dsl: "1.0"
|
|
13
|
+
page:
|
|
14
|
+
id: {{kebab-id}}
|
|
15
|
+
title: {{页面标题}}
|
|
16
|
+
name: {{camelName}}
|
|
17
|
+
state:
|
|
18
|
+
# 搜索条件镜像:Form submit 后写入,驱动列表请求参数
|
|
19
|
+
keyword: ""
|
|
20
|
+
status: all
|
|
21
|
+
editingId: ""
|
|
22
|
+
# 表单条件显隐镜像(onValuesChange 写入;按线框图需要增减,可多个)
|
|
23
|
+
fType: ""
|
|
24
|
+
dataSources:
|
|
25
|
+
list:
|
|
26
|
+
type: http
|
|
27
|
+
autoLoad: true
|
|
28
|
+
request:
|
|
29
|
+
method: GET
|
|
30
|
+
url: /api/data/{{resource}}
|
|
31
|
+
params:
|
|
32
|
+
keyword: ${state.keyword}
|
|
33
|
+
status: ${state.status}
|
|
34
|
+
editing: # 编辑预填;autoLoad: false,打开抽屉前手动 reload
|
|
35
|
+
type: http
|
|
36
|
+
autoLoad: false
|
|
37
|
+
request:
|
|
38
|
+
method: GET
|
|
39
|
+
url: /api/data/{{resource}}/${state.editingId}
|
|
40
|
+
body:
|
|
41
|
+
type: Page
|
|
42
|
+
children:
|
|
43
|
+
- type: Breadcrumb
|
|
44
|
+
props:
|
|
45
|
+
items:
|
|
46
|
+
- label: {{模块名}}
|
|
47
|
+
- label: {{页面标题}}
|
|
48
|
+
# 标题行:左标题 + 副标题,右新建按钮
|
|
49
|
+
- type: Flex
|
|
50
|
+
props:
|
|
51
|
+
justify: space-between
|
|
52
|
+
align: center
|
|
53
|
+
children:
|
|
54
|
+
- type: Flex
|
|
55
|
+
props:
|
|
56
|
+
direction: vertical
|
|
57
|
+
gap: 2
|
|
58
|
+
children:
|
|
59
|
+
- type: Typography
|
|
60
|
+
props:
|
|
61
|
+
variant: h3
|
|
62
|
+
text: {{页面标题}}
|
|
63
|
+
- type: Typography
|
|
64
|
+
props:
|
|
65
|
+
variant: muted
|
|
66
|
+
text: {{页面副标题(取自线框图说明文字)}}
|
|
67
|
+
- type: Button
|
|
68
|
+
props:
|
|
69
|
+
text: + 新建{{实体名}}
|
|
70
|
+
variant: primary
|
|
71
|
+
events:
|
|
72
|
+
onClick:
|
|
73
|
+
- action: setState # 清空编辑态与显隐镜像
|
|
74
|
+
params:
|
|
75
|
+
editingId: ""
|
|
76
|
+
fType: ""
|
|
77
|
+
- action: reload # 清空 editing 数据(${state.editingId} 为空 → 接口返回空对象)
|
|
78
|
+
params:
|
|
79
|
+
dataSource: editing
|
|
80
|
+
- action: overlay.open # 锚点 &entityDrawer 在此定义,表格行内用 *entityDrawer 引用
|
|
81
|
+
params:
|
|
82
|
+
overlay: &entityDrawer
|
|
83
|
+
type: drawer
|
|
84
|
+
title: "${state.editingId == '' ? '新建{{实体名}}' : '编辑{{实体名}}'}"
|
|
85
|
+
width: 720
|
|
86
|
+
content:
|
|
87
|
+
type: Form
|
|
88
|
+
props:
|
|
89
|
+
name: entityForm
|
|
90
|
+
layout: vertical
|
|
91
|
+
initialValues: ${data.editing}
|
|
92
|
+
events:
|
|
93
|
+
# 条件显隐字段镜像到 state(其他字段变化时保持原值)
|
|
94
|
+
onValuesChange:
|
|
95
|
+
- action: setState
|
|
96
|
+
params:
|
|
97
|
+
fType: "${event.type != null ? event.type : state.fType}"
|
|
98
|
+
onSubmit:
|
|
99
|
+
- action: request
|
|
100
|
+
params:
|
|
101
|
+
method: POST
|
|
102
|
+
url: /api/data/ops/{{resource}}-save
|
|
103
|
+
body:
|
|
104
|
+
id: ${state.editingId}
|
|
105
|
+
name: ${event.name}
|
|
106
|
+
# ... 其余字段照抄 event
|
|
107
|
+
- action: message
|
|
108
|
+
params:
|
|
109
|
+
messageType: success
|
|
110
|
+
text: 已保存(演示)
|
|
111
|
+
- action: overlay.close
|
|
112
|
+
children:
|
|
113
|
+
# 分区结构:标题 → 分割线 → 字段组(不用 Card)
|
|
114
|
+
- type: Flex
|
|
115
|
+
props:
|
|
116
|
+
direction: vertical
|
|
117
|
+
gap: middle
|
|
118
|
+
children:
|
|
119
|
+
- type: Text
|
|
120
|
+
props:
|
|
121
|
+
text: 基本信息
|
|
122
|
+
strong: true
|
|
123
|
+
- type: Separator
|
|
124
|
+
- type: Flex
|
|
125
|
+
props:
|
|
126
|
+
direction: vertical
|
|
127
|
+
gap: small
|
|
128
|
+
children:
|
|
129
|
+
- type: Field
|
|
130
|
+
props:
|
|
131
|
+
label: 名称
|
|
132
|
+
required: true
|
|
133
|
+
children:
|
|
134
|
+
- type: Input
|
|
135
|
+
props:
|
|
136
|
+
name: name
|
|
137
|
+
rules:
|
|
138
|
+
- required: true
|
|
139
|
+
message: 请输入名称
|
|
140
|
+
# 成对字段用 Grid 两列
|
|
141
|
+
- type: Grid
|
|
142
|
+
props:
|
|
143
|
+
columns: 2
|
|
144
|
+
gap: 16
|
|
145
|
+
children:
|
|
146
|
+
- type: Field
|
|
147
|
+
props:
|
|
148
|
+
label: 类型
|
|
149
|
+
required: true
|
|
150
|
+
children:
|
|
151
|
+
- type: Select
|
|
152
|
+
props:
|
|
153
|
+
name: type
|
|
154
|
+
placeholder: 请选择类型
|
|
155
|
+
options: ${data.list.filters.type}
|
|
156
|
+
rules:
|
|
157
|
+
- required: true
|
|
158
|
+
message: 请选择类型
|
|
159
|
+
- type: Field
|
|
160
|
+
props:
|
|
161
|
+
label: 排序
|
|
162
|
+
children:
|
|
163
|
+
- type: NumberInput
|
|
164
|
+
props:
|
|
165
|
+
name: sort
|
|
166
|
+
className: w-full
|
|
167
|
+
min: 0
|
|
168
|
+
precision: 0
|
|
169
|
+
# 条件显隐字段:依赖镜像 state
|
|
170
|
+
- when: ${state.fType == 'XXX'}
|
|
171
|
+
type: Field
|
|
172
|
+
props:
|
|
173
|
+
label: 条件字段
|
|
174
|
+
children:
|
|
175
|
+
- type: Input
|
|
176
|
+
props:
|
|
177
|
+
name: extra
|
|
178
|
+
# 编辑时禁用关键字段
|
|
179
|
+
- type: Field
|
|
180
|
+
props:
|
|
181
|
+
label: 编码
|
|
182
|
+
required: true
|
|
183
|
+
children:
|
|
184
|
+
- type: Input
|
|
185
|
+
props:
|
|
186
|
+
name: code
|
|
187
|
+
disabled: ${state.editingId != ''}
|
|
188
|
+
rules:
|
|
189
|
+
- required: true
|
|
190
|
+
message: 请输入编码
|
|
191
|
+
# 底部按钮:取消关抽屉,保存触发 Form onSubmit
|
|
192
|
+
- type: Flex
|
|
193
|
+
props:
|
|
194
|
+
justify: end
|
|
195
|
+
gap: small
|
|
196
|
+
className: mt-4
|
|
197
|
+
children:
|
|
198
|
+
- type: Button
|
|
199
|
+
props:
|
|
200
|
+
text: 取消
|
|
201
|
+
events:
|
|
202
|
+
onClick:
|
|
203
|
+
- action: overlay.close
|
|
204
|
+
- type: Button
|
|
205
|
+
props:
|
|
206
|
+
text: 保存
|
|
207
|
+
variant: primary
|
|
208
|
+
submit: true
|
|
209
|
+
htmlType: submit
|
|
210
|
+
- action: reload # overlay.open 阻塞到关闭,关闭后刷新列表
|
|
211
|
+
params:
|
|
212
|
+
dataSource: list
|
|
213
|
+
# 搜索栏:Form(layout: inline) + Field 包裹控件
|
|
214
|
+
- type: Card
|
|
215
|
+
children:
|
|
216
|
+
- type: Form
|
|
217
|
+
props:
|
|
218
|
+
name: search
|
|
219
|
+
layout: inline
|
|
220
|
+
events:
|
|
221
|
+
onSubmit:
|
|
222
|
+
- action: setState
|
|
223
|
+
params:
|
|
224
|
+
keyword: ${event.keyword}
|
|
225
|
+
status: ${event.status}
|
|
226
|
+
- action: reload
|
|
227
|
+
params:
|
|
228
|
+
dataSource: list
|
|
229
|
+
children:
|
|
230
|
+
- type: Field
|
|
231
|
+
props:
|
|
232
|
+
label: 编码/名称
|
|
233
|
+
children:
|
|
234
|
+
- type: Input
|
|
235
|
+
props:
|
|
236
|
+
name: keyword
|
|
237
|
+
placeholder: 支持模糊查询
|
|
238
|
+
- type: Field
|
|
239
|
+
props:
|
|
240
|
+
label: 状态
|
|
241
|
+
children:
|
|
242
|
+
- type: Select
|
|
243
|
+
props:
|
|
244
|
+
name: status
|
|
245
|
+
placeholder: 请选择状态
|
|
246
|
+
options: ${data.list.filters.status} # 选项由接口下发
|
|
247
|
+
# 按钮也放进 Field:label 用不间断空格(U+00A0)占位,与左侧控件底部对齐
|
|
248
|
+
- type: Field
|
|
249
|
+
props:
|
|
250
|
+
label: " "
|
|
251
|
+
children:
|
|
252
|
+
- type: Flex
|
|
253
|
+
props:
|
|
254
|
+
gap: small
|
|
255
|
+
align: center
|
|
256
|
+
children:
|
|
257
|
+
- type: Button
|
|
258
|
+
props:
|
|
259
|
+
text: 查询
|
|
260
|
+
submit: true
|
|
261
|
+
htmlType: submit
|
|
262
|
+
- type: Button
|
|
263
|
+
props:
|
|
264
|
+
text: 重置
|
|
265
|
+
events:
|
|
266
|
+
onClick:
|
|
267
|
+
- action: form.setValues
|
|
268
|
+
params:
|
|
269
|
+
form: search
|
|
270
|
+
values:
|
|
271
|
+
keyword: ""
|
|
272
|
+
status: all
|
|
273
|
+
- action: setState
|
|
274
|
+
params:
|
|
275
|
+
keyword: ""
|
|
276
|
+
status: all
|
|
277
|
+
- action: reload
|
|
278
|
+
params:
|
|
279
|
+
dataSource: list
|
|
280
|
+
# 表格:名称列/操作列按钮统一 variant: link size: small
|
|
281
|
+
- type: Table
|
|
282
|
+
props:
|
|
283
|
+
dataSource: ${data.list.items}
|
|
284
|
+
rowKey: id
|
|
285
|
+
pagination: false
|
|
286
|
+
className: mt-4
|
|
287
|
+
columns:
|
|
288
|
+
- title: 名称
|
|
289
|
+
cell:
|
|
290
|
+
type: Button
|
|
291
|
+
props:
|
|
292
|
+
text: ${row.name}
|
|
293
|
+
variant: link
|
|
294
|
+
size: small
|
|
295
|
+
events:
|
|
296
|
+
onClick: &editRow # 行点击 = 编辑,锚点在此定义
|
|
297
|
+
- action: setState # 写入编辑 id + 显隐镜像(取自行数据)
|
|
298
|
+
params:
|
|
299
|
+
editingId: ${row.id}
|
|
300
|
+
fType: ${row.type}
|
|
301
|
+
- action: reload
|
|
302
|
+
params:
|
|
303
|
+
dataSource: editing
|
|
304
|
+
- action: overlay.open
|
|
305
|
+
params:
|
|
306
|
+
overlay: *entityDrawer
|
|
307
|
+
- action: reload
|
|
308
|
+
params:
|
|
309
|
+
dataSource: list
|
|
310
|
+
- title: 编码
|
|
311
|
+
dataIndex: code
|
|
312
|
+
- title: 状态
|
|
313
|
+
dataIndex: status
|
|
314
|
+
- title: 操作
|
|
315
|
+
cell:
|
|
316
|
+
type: Button
|
|
317
|
+
props:
|
|
318
|
+
text: 编辑
|
|
319
|
+
variant: link
|
|
320
|
+
size: small
|
|
321
|
+
events:
|
|
322
|
+
onClick: *editRow # 与名称列同一动作
|
|
323
|
+
- type: Text
|
|
324
|
+
props:
|
|
325
|
+
text: 共 ${data.list.total} 条
|
|
326
|
+
type: secondary
|
|
327
|
+
className: mt-3
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
要点:
|
|
331
|
+
|
|
332
|
+
- **新建/编辑共用同一抽屉**:YAML 锚点 `&entityDrawer` 在「新建」按钮的 `overlay.open` 中定义(必须先出现),表格行内 `*entityDrawer` 引用;`title` 用 `${state.editingId == '' ? ... }` 三元切换;唯一区别是编辑以 `initialValues: ${data.editing}` 预填
|
|
333
|
+
- **抽屉宽度 720**(`overlay.open type: drawer` 的 `width` 生效);抽屉内分区**不用 Card**:`Text(strong) 标题 → Separator → 字段组`,多个分区重复此结构
|
|
334
|
+
- **搜索栏按钮对齐**:查询/重置放进 `Field`,其 `label` 用不间断空格 U+00A0(直接敲 `" "` 普通空格可能被压缩),与左侧控件基线对齐
|
|
335
|
+
- **Form 不能嵌套**(渲染原生 `<form>`,submit 冒泡):抽屉内只用一个 Form;主从多值(如字典项)的子项编辑在**同一 Form** 内做「子 Table + 行下展开表单」,`onSubmit` 用 `if` 动作按 state 分流提交目标(子项保存走独立 ops 接口 + `reload editing`);表达式不支持数组方法/箭头函数,子项草稿必须走 mock 接口不能前端拼
|
|
336
|
+
- **编辑预填链路**:`setState(editingId + 显隐镜像)` → `reload editing` → `overlay.open`;镜像值取自行数据(`${row.type}`),不要等 editing 接口
|
|
337
|
+
- **overlay.open 阻塞到关闭**,关闭后 `reload list`;新建按钮同理(先重置 state 再 reload editing 清空)
|
|
338
|
+
- **条件显隐**:`onValuesChange` 把字段值镜像到 state(`"${event.x != null ? event.x : state.x}"` 保持其他字段变化时原值),再用 `when` 控制显隐;编辑时关键字段 `disabled: ${state.editingId != ''}`
|
|
339
|
+
- 空 `editingId` 时 mock 的 detail 接口须返回空对象而非 404
|
|
340
|
+
|
|
341
|
+
## 列表页(轻量变体:新建/编辑跳独立表单页)
|
|
6
342
|
|
|
7
343
|
```yaml
|
|
8
344
|
dsl: "1.0"
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"build:test": "node scripts/build.mjs test"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pactor-app/core": "^1.
|
|
15
|
-
"@pactor-app/runtime": "^1.
|
|
16
|
-
"@pactor-app/ui": "^1.
|
|
14
|
+
"@pactor-app/core": "^1.33.0",
|
|
15
|
+
"@pactor-app/runtime": "^1.33.0",
|
|
16
|
+
"@pactor-app/ui": "^1.33.0",
|
|
17
17
|
"lucide-react": "1.45.0",
|
|
18
18
|
"next": "16.3.5",
|
|
19
|
-
"pactor-app": "^1.
|
|
19
|
+
"pactor-app": "^1.33.0",
|
|
20
20
|
"react": "19.2.8",
|
|
21
21
|
"react-dom": "19.2.8",
|
|
22
22
|
"yaml": "^2.9.1"
|