@pactor-app/skills 1.24.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/LICENSE +21 -0
- package/README.md +40 -0
- package/package.json +26 -0
- package/pactor/SKILL.md +373 -0
- package/pactor/references/dsl-authoring.md +448 -0
- package/prd-to-app/SKILL.md +75 -0
- package/prd-to-app/references/acceptance-checklist.md +47 -0
- package/prd-to-app/references/component-mapping.md +41 -0
- package/prd-to-app/references/mock-data-routes.md +66 -0
- package/prd-to-app/references/page-templates.md +274 -0
- package/prd-to-app/references/project-structure.md +89 -0
- package/prd-to-app/references/theme-and-style.md +41 -0
- package/prd-to-app/template/README.md +26 -0
- package/prd-to-app/template/app/[[...slug]]/page.tsx +7 -0
- package/prd-to-app/template/app/api/auth/login/route.ts +32 -0
- package/prd-to-app/template/app/api/auth/logout/route.ts +22 -0
- package/prd-to-app/template/app/api/data/[...parts]/route.ts +118 -0
- package/prd-to-app/template/app/api/public/pages/[id]/route.ts +40 -0
- package/prd-to-app/template/app/api/sitemap/route.ts +21 -0
- package/prd-to-app/template/app/api/users/route.ts +57 -0
- package/prd-to-app/template/app/favicon.ico +0 -0
- package/prd-to-app/template/app/globals.css +248 -0
- package/prd-to-app/template/app/layout.tsx +49 -0
- package/prd-to-app/template/app/login.css +122 -0
- package/prd-to-app/template/eslint.config.mjs +18 -0
- package/prd-to-app/template/next-env.d.ts +7 -0
- package/prd-to-app/template/next.config.js +32 -0
- package/prd-to-app/template/package.json +34 -0
- package/prd-to-app/template/pnpm-lock.yaml +6854 -0
- package/prd-to-app/template/pnpm-workspace.yaml +7 -0
- package/prd-to-app/template/postcss.config.mjs +7 -0
- package/prd-to-app/template/public/file.svg +1 -0
- package/prd-to-app/template/public/globe.svg +1 -0
- package/prd-to-app/template/public/next.svg +1 -0
- package/prd-to-app/template/public/skins/chenwunaifu.css +111 -0
- package/prd-to-app/template/public/skins/taijingshenlin.css +109 -0
- package/prd-to-app/template/public/skins/xuanzhimoyun.css +110 -0
- package/prd-to-app/template/public/vercel.svg +1 -0
- package/prd-to-app/template/public/window.svg +1 -0
- package/prd-to-app/template/scripts/build.mjs +35 -0
- package/prd-to-app/template/src/pactor/App.tsx +55 -0
- package/prd-to-app/template/src/pactor/actions.ts +438 -0
- package/prd-to-app/template/src/pactor/components/card-links.tsx +86 -0
- package/prd-to-app/template/src/pactor/components/chip-filter.tsx +89 -0
- package/prd-to-app/template/src/pactor/components/chip-node.tsx +46 -0
- package/prd-to-app/template/src/pactor/components/debug-chat.tsx +189 -0
- package/prd-to-app/template/src/pactor/components/index.ts +28 -0
- package/prd-to-app/template/src/pactor/components/log-status.tsx +64 -0
- package/prd-to-app/template/src/pactor/components/status-badge.tsx +46 -0
- package/prd-to-app/template/src/pactor/components/theme-switcher.tsx +136 -0
- package/prd-to-app/template/src/pactor/config.ts +28 -0
- package/prd-to-app/template/src/pactor/mocks/audit-mock.ts +403 -0
- package/prd-to-app/template/src/pactor/mocks/call-logs-mock.ts +875 -0
- package/prd-to-app/template/src/pactor/mocks/client-debug-mock.ts +188 -0
- package/prd-to-app/template/src/pactor/mocks/client-templates-mock.ts +497 -0
- package/prd-to-app/template/src/pactor/mocks/clients-mock.ts +483 -0
- package/prd-to-app/template/src/pactor/mocks/costs-mock.ts +406 -0
- package/prd-to-app/template/src/pactor/mocks/dashboard-mock.ts +192 -0
- package/prd-to-app/template/src/pactor/mocks/model-debug-mock.ts +144 -0
- package/prd-to-app/template/src/pactor/mocks/models-mock.ts +799 -0
- package/prd-to-app/template/src/pactor/mocks/ops-usage-mock.ts +806 -0
- package/prd-to-app/template/src/pactor/mocks/providers-mock.ts +424 -0
- package/prd-to-app/template/src/pactor/mocks/routes-mock.ts +604 -0
- package/prd-to-app/template/src/pactor/services.ts +185 -0
- package/prd-to-app/template/src/pactor/skin.ts +52 -0
- package/prd-to-app/template/src/server/accounts.ts +63 -0
- package/prd-to-app/template/src/server/cors.ts +34 -0
- package/prd-to-app/template/src/server/sitemap.ts +176 -0
- package/prd-to-app/template/src/server/yaml/common/dashboard.yaml +498 -0
- package/prd-to-app/template/src/server/yaml/common/login.yaml +84 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-create.yaml +340 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-detail.yaml +780 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-edit.yaml +366 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-list.yaml +1989 -0
- package/prd-to-app/template/src/server/yaml/route/route-create.yaml +511 -0
- package/prd-to-app/template/src/server/yaml/route/route-detail.yaml +610 -0
- package/prd-to-app/template/src/server/yaml/route/route-edit.yaml +809 -0
- package/prd-to-app/template/src/server/yaml/route/route-list.yaml +1793 -0
- package/prd-to-app/template/src/server/yaml/sitemap.yaml +250 -0
- package/prd-to-app/template/tsconfig.json +34 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# Pactor DSL Authoring Reference
|
|
2
|
+
|
|
3
|
+
Read this before writing or editing any Pactor DSL file (`*.page.yaml`). It defines the complete page grammar, data sources, expressions, events/actions, and battle-tested recipes. Everything here matches `@pactor-app/schema` (validation) and `@pactor-app/runtime` (execution).
|
|
4
|
+
|
|
5
|
+
A DSL file is data, never code: no functions, no arbitrary JavaScript — only declared state, data sources, component trees, events, and actions.
|
|
6
|
+
|
|
7
|
+
## 1. Page skeleton (PageDsl)
|
|
8
|
+
|
|
9
|
+
Every page file has this top-level shape. Only `dsl`, `page`, and a UI root (`body` or root-level `children`) matter for simple pages; everything else is optional.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
dsl: '1.0' # REQUIRED. DSL spec version, currently always '1.0'
|
|
13
|
+
page: # REQUIRED. Page meta
|
|
14
|
+
id: customer-list # REQUIRED. Globally unique; menus and overlays address pages by it
|
|
15
|
+
title: Customer List # shown in layout tab / breadcrumb / browser title
|
|
16
|
+
# name: customerList # programmatic name (optional)
|
|
17
|
+
# description: ... # read by tooling, not the runtime
|
|
18
|
+
# type: list # page pattern hint (list / detail / form ...)
|
|
19
|
+
# permission: customer:view # page-level guard; denied users get a 403 page
|
|
20
|
+
meta: # optional free-form metadata, runtime ignores it
|
|
21
|
+
version: '1.0'
|
|
22
|
+
state: # optional, reactive page state (initial values)
|
|
23
|
+
keyword: ''
|
|
24
|
+
pageSize: 20
|
|
25
|
+
dataSources: # optional, named data sources → ${data.<name>}
|
|
26
|
+
customers:
|
|
27
|
+
type: service
|
|
28
|
+
service: customer.list
|
|
29
|
+
autoLoad: true
|
|
30
|
+
actions: # optional, reusable named actions (invoked via `call`)
|
|
31
|
+
deleteRow: [...]
|
|
32
|
+
components: # optional, reusable named subtrees (referenced via `component:`)
|
|
33
|
+
searchForm: { type: Form, children: [...] }
|
|
34
|
+
lifecycle: # optional, page lifecycle hooks
|
|
35
|
+
onInit:
|
|
36
|
+
- action: setState
|
|
37
|
+
params: { ready: true }
|
|
38
|
+
body: # UI tree root — a single DslNode (conventionally `type: Page`)
|
|
39
|
+
type: Page
|
|
40
|
+
props: { title: Customer List }
|
|
41
|
+
children: [...]
|
|
42
|
+
# children: [...] # alternative root: node array, used when `body` is absent
|
|
43
|
+
# (describes a page fragment without a Page wrapper)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Rules:
|
|
47
|
+
|
|
48
|
+
- `body` wins over root-level `children` when both exist.
|
|
49
|
+
- Top-level `actions` = behavior reuse (called with `{ action: call, params: { name } }`, inherits caller scope incl. `row`/`event`). Top-level `components` = structure reuse (referenced with `component: <key>`; the referencing node's `props`/`events` override the subtree root's).
|
|
50
|
+
- `lifecycle`: `onInit` (runtime created) and `onLoad` (first autoLoad finished) fire automatically; `onReady`/`onShow`/`onHide`/`onDestroy` are fired by the host router.
|
|
51
|
+
|
|
52
|
+
## 2. DslNode (the UI tree node)
|
|
53
|
+
|
|
54
|
+
`body`, every `children` array, Table column `cell` templates, Tabs `items[].children`, and overlay `content` are all the same DslNode tree:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- type: Button # component type from the Component Registry (or `component: <key>` to reference a named subtree — the two are mutually exclusive)
|
|
58
|
+
id: submitBtn # optional node id, usually omitted
|
|
59
|
+
props: # component props; any value may contain ${...} expressions
|
|
60
|
+
text: Save
|
|
61
|
+
className: my-btn # universal prop: root class, merged with built-in styles
|
|
62
|
+
style: { marginLeft: 8 } # universal prop: root inline style object
|
|
63
|
+
children: [] # DslNode array (or a dynamic source ref, advanced)
|
|
64
|
+
events: # event name → single action OR action array (serial, a failure does NOT stop the rest — wrap in `sequence` for fail-stop)
|
|
65
|
+
onClick:
|
|
66
|
+
- action: setState
|
|
67
|
+
params: { saved: true }
|
|
68
|
+
permission: customer:edit # node-level guard (see §7)
|
|
69
|
+
when: ${state.mode != 'readonly'} # conditional render; false → node and subtree skipped
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Component types come from the registry (built-in list in SKILL.md "UI at a glance"). Never invent type names; unknown types render an "Unsupported" placeholder.
|
|
73
|
+
|
|
74
|
+
## 3. state
|
|
75
|
+
|
|
76
|
+
Reactive page state. Declare initial values under `state:`, read via `${state.x}`, update ONLY via actions (`setState` shallow-merges; `resetState` restores declared initials). Any update re-renders everything that references it — you never touch components directly.
|
|
77
|
+
|
|
78
|
+
Controlled-loop pattern (the fundamental interaction idiom):
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- type: Input
|
|
82
|
+
props:
|
|
83
|
+
value: ${state.keyword} # state → UI
|
|
84
|
+
events:
|
|
85
|
+
onChange:
|
|
86
|
+
- action: setState # UI → state (event carries the new string)
|
|
87
|
+
params: { keyword: ${event} }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`Form` submit also writes values to `state.form`, readable as `${form.<field>}`.
|
|
91
|
+
|
|
92
|
+
## 4. dataSources
|
|
93
|
+
|
|
94
|
+
Named loading configs; results read via `${data.<name>}`, load status via `${dataStatus.<name>}` (`{ loading: boolean, error?: Error }`).
|
|
95
|
+
|
|
96
|
+
Three implemented types (`computed`/`resource` are declared in the protocol but NOT implemented):
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
dataSources:
|
|
100
|
+
# static — value returned as-is (enums, demo data)
|
|
101
|
+
statusOptions:
|
|
102
|
+
type: static
|
|
103
|
+
value:
|
|
104
|
+
- { label: Enabled, value: active }
|
|
105
|
+
- { label: Disabled, value: inactive }
|
|
106
|
+
|
|
107
|
+
# service — calls a host-registered service function; params support expressions, evaluated at each load
|
|
108
|
+
customers:
|
|
109
|
+
type: service
|
|
110
|
+
service: customer.list
|
|
111
|
+
autoLoad: true # load on page init; otherwise only loads on `reload`
|
|
112
|
+
params:
|
|
113
|
+
keyword: ${state.keyword} # picks up the NEW value when reloaded
|
|
114
|
+
|
|
115
|
+
# http — request via the host's HttpAdapter (auth token / baseURL handled by host)
|
|
116
|
+
detail:
|
|
117
|
+
type: http
|
|
118
|
+
autoLoad: true
|
|
119
|
+
request:
|
|
120
|
+
method: GET # optional, default GET
|
|
121
|
+
url: /api/customers/${state.currentId}
|
|
122
|
+
params: { verbose: true } # query params
|
|
123
|
+
# headers: {} # body: {...} for POST/PUT
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Other fields: `pagination: { page, pageSize }` (values may be expressions), and option mapping `valueField` / `labelField` / `disabledField` (normalize array items to `{ label, value, disabled }` for direct binding to `Select options`).
|
|
127
|
+
|
|
128
|
+
Refresh: the `reload` action — `params: { dataSource: customers }` refreshes one source; omitting `dataSource` refreshes all autoLoad sources. Query linkage idiom:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
events:
|
|
132
|
+
onSubmit:
|
|
133
|
+
- action: setState
|
|
134
|
+
params: { keyword: ${event.keyword} }
|
|
135
|
+
- action: reload # data source params reference state → re-fetches with new value
|
|
136
|
+
params: { dataSource: customers }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 5. Expressions and scopes
|
|
140
|
+
|
|
141
|
+
`${...}` is evaluated by a controlled AST engine — NO eval, NO globals (`window`/`fetch`/`localStorage` unreachable), no method calls on data.
|
|
142
|
+
|
|
143
|
+
Two evaluation forms:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
count: ${state.count} # whole string is one expression → original type kept (number)
|
|
147
|
+
title: Total ${state.count} items # mixed with text → string concatenation (null/undefined → '')
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Syntax: member access `${state.user.name}`, index `${data.list[0].name}`, computed member `${state.cityMap[state.region]}`, literals (string/number/boolean/null/array/object `${{ name: user.name }}`), ternary `cond ? a : b`, logic `&& || !`, comparison `== != === !== < <= > >=`, arithmetic `+ - * / %`, whitelisted functions `${t('menu.home')}`, `${permission('customer:edit')}`.
|
|
151
|
+
|
|
152
|
+
Scope chain (inner shadows outer): `global → app → page → overlay → form → component → row → event`.
|
|
153
|
+
|
|
154
|
+
Global scope keys:
|
|
155
|
+
|
|
156
|
+
| Key | Content | Example |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| `app` / `user` | bootstrap app / user info | `${user.name}` |
|
|
159
|
+
| `page` | current page meta | `${page.title}` |
|
|
160
|
+
| `state` | page state | `${state.keyword}` |
|
|
161
|
+
| `form` | alias of `state.form` (written on Form submit) | `${form.keyword}` |
|
|
162
|
+
| `data` / `dataStatus` | data source results / `{ loading, error }` | `${data.customers}`, `${dataStatus.list.loading}` |
|
|
163
|
+
| `route` | routing context | `${route.params.id}`, `${route.query.tab}` |
|
|
164
|
+
| `params` | alias of `route.params`; inside an overlay, the overlay-injected params | `${params.id}` |
|
|
165
|
+
| `actions` | results of named actions (by `id`) | `${actions.editResult.success}` |
|
|
166
|
+
| `locale` / `t` | current locale / translate fn | `${t('menu.home')}` |
|
|
167
|
+
|
|
168
|
+
Local scope keys (only where produced): `event` (event payload), `row` / `rowIndex` (Table `cell` templates), `parent` (parent page snapshot inside overlays).
|
|
169
|
+
|
|
170
|
+
## 6. Events and actions
|
|
171
|
+
|
|
172
|
+
ActionConfig fields: `action` (required, registry name), `id` (named action — result written to `actions.<id>`: success → result data; failure → `{ ok: false, error }`), `params` (expressions evaluated before execution), `condition` (falsy → skipped), `permission`.
|
|
173
|
+
|
|
174
|
+
Event payloads (`${event}`):
|
|
175
|
+
|
|
176
|
+
| Event | Payload |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| Button `onClick` | none |
|
|
179
|
+
| Input / Select / Switch `onChange` | the new value (`${event}`) |
|
|
180
|
+
| Tabs `onChange` | new active key |
|
|
181
|
+
| Table `onChange` | `{ current, pageSize, total }` |
|
|
182
|
+
| Form `onSubmit` | all values (`${event.name}`) |
|
|
183
|
+
| Form `onValuesChange` | ONLY the changed field `{ <field>: newValue }` |
|
|
184
|
+
|
|
185
|
+
Action catalog:
|
|
186
|
+
|
|
187
|
+
| Action | Params | Notes |
|
|
188
|
+
| --- | --- | --- |
|
|
189
|
+
| `setState` / `resetState` | any key-values / — | shallow merge / restore initial |
|
|
190
|
+
| `reload` | `dataSource?` | omit → all autoLoad sources |
|
|
191
|
+
| `request` | `url` (req), `method?`, `params?`, `headers?`, `body?`, `target?` | `target` writes response to a state path |
|
|
192
|
+
| `service.call` | `service`, `params?` | call a host-registered service |
|
|
193
|
+
| `navigate` | `to` (required) | route push; path supports interpolation: `/customers/${row.id}/edit` |
|
|
194
|
+
| `message` | `text`, `messageType?` (success/error/warning/info) | NEVER use a `type` key in params — see §9 |
|
|
195
|
+
| `confirm` | `title?`, `content?` | result: true/false (cancel is NOT a failure — name it and branch with `if`) |
|
|
196
|
+
| `if` | `condition`, `then?`, `else?` | branches are action(s), evaluated on execution |
|
|
197
|
+
| `sequence` / `parallel` | `actions` | serial fail-stop / parallel all-settle |
|
|
198
|
+
| `call` | `name` | run a top-level named action, inherits caller scope |
|
|
199
|
+
| `delay` | `ms` | wait |
|
|
200
|
+
| `form.setValues` | `values` (req), `form?` (default `default`) | write field values; does NOT trigger validation or onValuesChange |
|
|
201
|
+
| `overlay.open` / `overlay.close` | see below | open overlay and await close; close `payload` becomes open's result |
|
|
202
|
+
|
|
203
|
+
`overlay.open` params: `{ overlay: { type: modal|drawer, title?, width?, content: <DslNode> | page: { pageId }, params? } }`. Overlay content evaluates in overlay scope: `${params.x}` = injected params, `${parent.x}` = parent page snapshot, `${state}`/`${data}` still live.
|
|
204
|
+
|
|
205
|
+
Multiple forms on one page: every `Form` registers under `props.name` (default `default`) — name additional forms explicitly (e.g. a drawer edit form `name: edit`) and address them with `form.setValues`'s `form` param.
|
|
206
|
+
|
|
207
|
+
## 7. Permission
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
permission: customer:delete # single code
|
|
211
|
+
permission: [customer:delete, customer:admin] # any match passes
|
|
212
|
+
permission: { code: customer:delete, denied: disabled } # hidden (default) | disabled | readonly
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Applies at page / menu / component / action levels. In expressions: `${!permission('customer:edit')}`. Frontend permissions only shape UI — the backend must still authorize.
|
|
216
|
+
|
|
217
|
+
## 8. Recipes (copy-paste starting points)
|
|
218
|
+
|
|
219
|
+
### 8.1 Query form + data table (the most common page)
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
dsl: '1.0'
|
|
223
|
+
page: { id: customer-list, title: Customer List, permission: customer:view }
|
|
224
|
+
|
|
225
|
+
state:
|
|
226
|
+
keyword: ''
|
|
227
|
+
|
|
228
|
+
dataSources:
|
|
229
|
+
customers:
|
|
230
|
+
type: service # or http with request: { url: /api/customers, params: {...} }
|
|
231
|
+
service: customer.list
|
|
232
|
+
autoLoad: true
|
|
233
|
+
params:
|
|
234
|
+
keyword: ${state.keyword}
|
|
235
|
+
|
|
236
|
+
body:
|
|
237
|
+
type: Page
|
|
238
|
+
props: { title: Customer List }
|
|
239
|
+
children:
|
|
240
|
+
- type: Card
|
|
241
|
+
props: { title: Search }
|
|
242
|
+
children:
|
|
243
|
+
- type: Form
|
|
244
|
+
props: { layout: inline }
|
|
245
|
+
events:
|
|
246
|
+
onSubmit:
|
|
247
|
+
- action: setState
|
|
248
|
+
params: { keyword: ${event.keyword} }
|
|
249
|
+
- action: reload
|
|
250
|
+
params: { dataSource: customers }
|
|
251
|
+
children:
|
|
252
|
+
- type: Input
|
|
253
|
+
props: { name: keyword, label: Keyword, placeholder: Name / owner }
|
|
254
|
+
- type: Select
|
|
255
|
+
props:
|
|
256
|
+
name: status
|
|
257
|
+
label: Status
|
|
258
|
+
options:
|
|
259
|
+
- { label: Enabled, value: active }
|
|
260
|
+
- { label: Disabled, value: inactive }
|
|
261
|
+
- type: Button
|
|
262
|
+
props: { text: Search, variant: primary, submit: true } # submit: true, NOT htmlType
|
|
263
|
+
- type: Card
|
|
264
|
+
props: { title: Customer Data }
|
|
265
|
+
children:
|
|
266
|
+
- type: Table
|
|
267
|
+
props:
|
|
268
|
+
dataSource: ${data.customers} # EXPRESSION, not the source name
|
|
269
|
+
loading: ${dataStatus.customers.loading}
|
|
270
|
+
pagination: { pageSize: 10 }
|
|
271
|
+
columns:
|
|
272
|
+
- { title: Name, dataIndex: name }
|
|
273
|
+
- { title: Phone, dataIndex: phone }
|
|
274
|
+
- title: Actions # column template key is `cell`
|
|
275
|
+
cell:
|
|
276
|
+
type: Button
|
|
277
|
+
props: { text: Edit, variant: primary, size: small }
|
|
278
|
+
events:
|
|
279
|
+
onClick:
|
|
280
|
+
- action: navigate
|
|
281
|
+
params:
|
|
282
|
+
to: /customers/${row.id}/edit # param name is `to`
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 8.2 Drawer edit (overlay.open named action + close payload)
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
events:
|
|
289
|
+
onClick:
|
|
290
|
+
- id: editResult # named: suspends until overlay closes
|
|
291
|
+
action: overlay.open
|
|
292
|
+
params:
|
|
293
|
+
overlay:
|
|
294
|
+
type: drawer
|
|
295
|
+
title: Edit Customer
|
|
296
|
+
width: 420
|
|
297
|
+
params: { id: ${row.id}, name: ${row.name} } # injected into overlay scope
|
|
298
|
+
content:
|
|
299
|
+
type: Form
|
|
300
|
+
props: { name: edit } # explicit name — page query form already owns `default`
|
|
301
|
+
events:
|
|
302
|
+
onSubmit:
|
|
303
|
+
- id: updateResult
|
|
304
|
+
action: service.call
|
|
305
|
+
params:
|
|
306
|
+
service: customer.update
|
|
307
|
+
params: { id: ${params.id}, name: ${event.name} }
|
|
308
|
+
- action: if
|
|
309
|
+
params:
|
|
310
|
+
condition: ${actions.updateResult.id}
|
|
311
|
+
then:
|
|
312
|
+
- action: overlay.close
|
|
313
|
+
params: { payload: { success: true } } # success semantics
|
|
314
|
+
else:
|
|
315
|
+
- action: message
|
|
316
|
+
params: { messageType: error, text: Save failed, please retry }
|
|
317
|
+
children:
|
|
318
|
+
- type: Input
|
|
319
|
+
props:
|
|
320
|
+
name: name
|
|
321
|
+
label: Name
|
|
322
|
+
value: ${params.name} # field-level initial value (row data backfill)
|
|
323
|
+
rules:
|
|
324
|
+
- { required: true, message: Please enter a name }
|
|
325
|
+
- type: Button
|
|
326
|
+
props: { text: Save, variant: primary, submit: true }
|
|
327
|
+
- action: if # runs after the drawer closes; cancel (X / mask / Esc) skips
|
|
328
|
+
params:
|
|
329
|
+
condition: ${actions.editResult.success}
|
|
330
|
+
then:
|
|
331
|
+
- action: reload
|
|
332
|
+
params: { dataSource: customers }
|
|
333
|
+
- action: message
|
|
334
|
+
params: { messageType: success, text: Customer updated }
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 8.3 Server-side pagination
|
|
338
|
+
|
|
339
|
+
Table's own `pagination` paginates the data already bound to it (client-side). For a server-paginated API, turn it off and pair the table with a standalone `Pagination` component driving state + reload:
|
|
340
|
+
|
|
341
|
+
```yaml
|
|
342
|
+
state: { page: 1, pageSize: 10 }
|
|
343
|
+
dataSources:
|
|
344
|
+
customers:
|
|
345
|
+
type: http
|
|
346
|
+
autoLoad: true
|
|
347
|
+
request:
|
|
348
|
+
method: GET
|
|
349
|
+
url: /api/customers
|
|
350
|
+
params:
|
|
351
|
+
keyword: ${state.keyword}
|
|
352
|
+
page: ${state.page}
|
|
353
|
+
pageSize: ${state.pageSize}
|
|
354
|
+
# in the table card, after the Table node:
|
|
355
|
+
- type: Table
|
|
356
|
+
props:
|
|
357
|
+
dataSource: ${data.customers.list} # bind the page slice; path depends on your API shape
|
|
358
|
+
pagination: false
|
|
359
|
+
columns: [...]
|
|
360
|
+
- type: Pagination
|
|
361
|
+
props:
|
|
362
|
+
total: ${data.customers.total} # total count from the response
|
|
363
|
+
page: ${state.page} # controlled current page
|
|
364
|
+
pageSize: ${state.pageSize}
|
|
365
|
+
events:
|
|
366
|
+
onChange: # event = target page number
|
|
367
|
+
- action: setState
|
|
368
|
+
params: { page: ${event} }
|
|
369
|
+
- action: reload
|
|
370
|
+
params: { dataSource: customers }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Table also supports `rowKey` (row key field, default `'id'`); Table `onChange` (`{ current, pageSize, total }`) exists for its built-in client-side pager.
|
|
374
|
+
|
|
375
|
+
### 8.4 Confirm then act
|
|
376
|
+
|
|
377
|
+
```yaml
|
|
378
|
+
events:
|
|
379
|
+
onClick:
|
|
380
|
+
- id: cfm
|
|
381
|
+
action: confirm
|
|
382
|
+
params: { title: Confirm deletion, content: This cannot be undone }
|
|
383
|
+
- action: if
|
|
384
|
+
params:
|
|
385
|
+
condition: ${actions.cfm}
|
|
386
|
+
then:
|
|
387
|
+
- action: request
|
|
388
|
+
params: { method: DELETE, url: /api/customers/${row.id} }
|
|
389
|
+
- action: reload
|
|
390
|
+
params: { dataSource: customers }
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### 8.5 Select cascade (option linkage + field clearing)
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
state:
|
|
397
|
+
cityMap: { Zhejiang: [{ label: Hangzhou, value: Hangzhou }], Jiangsu: [{ label: Nanjing, value: Nanjing }] }
|
|
398
|
+
cityOptions: []
|
|
399
|
+
# fields:
|
|
400
|
+
- type: Select
|
|
401
|
+
props: { name: region, label: Province, options: [{ label: Zhejiang, value: Zhejiang }, { label: Jiangsu, value: Jiangsu }] }
|
|
402
|
+
events:
|
|
403
|
+
onChange:
|
|
404
|
+
- action: form.setValues # clear the child field first
|
|
405
|
+
params: { values: { city: '' } } # add `form: edit` when targeting a named form
|
|
406
|
+
- action: setState # then update child options (computed member access)
|
|
407
|
+
params: { cityOptions: ${state.cityMap[event]} }
|
|
408
|
+
- type: Select
|
|
409
|
+
props:
|
|
410
|
+
name: city
|
|
411
|
+
label: City
|
|
412
|
+
options: ${state.cityOptions}
|
|
413
|
+
disabled: ${state.cityOptions.length === 0}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## 9. Common mistakes (observed — do not do these)
|
|
417
|
+
|
|
418
|
+
| Wrong | Right |
|
|
419
|
+
| --- | --- |
|
|
420
|
+
| Flat top level `{ id, title, body }` | `{ dsl: '1.0', page: { id, title }, body }` |
|
|
421
|
+
| Missing `dsl: '1.0'` | Always first line, required |
|
|
422
|
+
| `dataSource: customers` (source name) | `dataSource: ${data.customers}` (expression) |
|
|
423
|
+
| Column template key `render` | `cell` |
|
|
424
|
+
| `pagination: true` | `pagination: { pageSize: 10 }` |
|
|
425
|
+
| `htmlType: submit` | `submit: true` |
|
|
426
|
+
| `navigate` param `path` / `url` | `to` |
|
|
427
|
+
| `message` param `type: success` | `messageType: success` — a `type` (or `action`) key inside action `params` makes the engine treat params as a nested DSL/action config and SKIP expression evaluation |
|
|
428
|
+
| Reading full form values in `onValuesChange` | It only carries the changed field; use `onSubmit` or `${form.x}` |
|
|
429
|
+
| Expecting actions to re-render UI | Only the state/data changes they produce do |
|
|
430
|
+
| Inventing component `type` names or props | Use registry names (SKILL.md list) and documented props |
|
|
431
|
+
|
|
432
|
+
## 10. Validate before delivering
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npx pactor validate path/to.page.yaml # consumer projects (@pactor-app/cli installed)
|
|
436
|
+
node packages/cli/dist/cli.js validate <file> # inside the pactor monorepo
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The validator checks schema shape (top-level keys, node structure, action configs). It does NOT validate component props — correctness of props comes from this reference and the component docs.
|
|
440
|
+
|
|
441
|
+
## 11. Deeper references (monorepo)
|
|
442
|
+
|
|
443
|
+
- Exhaustive DSL reference: `docs/src/content/docs/reference/dsl.md` (named actions/components, dynamic source refs, lifecycle details)
|
|
444
|
+
- Per-component props + minimal YAML per component: `docs/src/content/docs/components/` (index.md lists all 71)
|
|
445
|
+
- Events & form linkage deep-dive: `docs/src/content/docs/guides/events.md`
|
|
446
|
+
- Data flow deep-dive: `docs/src/content/docs/guides/data-flow.md`
|
|
447
|
+
- Runnable example pages: `examples/admin/src/pages/*.page.yaml`
|
|
448
|
+
- Authoritative schemas: `packages/schema/src/schemas/page-dsl.ts`, `packages/schema/src/schemas/dsl-node.ts`
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prd-to-app
|
|
3
|
+
description: |
|
|
4
|
+
Use when generating a complete pactor (pactor-app) project from product documents / markdown wireframes — pages described in YAML DSL (*.page.yaml). Triggers: 根据 PRD / 产品文档 / 线框图 (wireframe) 创建 / 生成管理后台或 Web 应用原型; turning a PRD directory (overview README + per-page ASCII wireframe markdown) into a runnable Next.js + pactor project, including sitemap.yaml, page yaml, http dataSources, mock API routes, theme/skin alignment with the original product, and browser acceptance.
|
|
5
|
+
version: 1.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PRD → YAML 页面应用生成
|
|
9
|
+
|
|
10
|
+
把「产品文档 + Markdown 线框图」翻译成可运行的 pactor 项目:页面全部 YAML DSL 描述,数据一律 http 数据源 + mock 路由,样式与功能与原产品一致。
|
|
11
|
+
|
|
12
|
+
## 输入要求
|
|
13
|
+
|
|
14
|
+
- PRD/线框图目录:总览 README(模块 × 页面 × 优先级表格)+ 分页线框图 markdown(ASCII 线框 + 区域说明 + 交互描述)
|
|
15
|
+
- 原产品代码仓(可选但强烈建议):用于提取设计令牌、自定义 CSS、交互行为
|
|
16
|
+
- 目标目录:空目录或目标仓库
|
|
17
|
+
|
|
18
|
+
输入格式与样例不同时,先归纳其「模块 → 页面 → 区域 → 交互」的等价结构,再套用本流程,不要因格式差异中止。
|
|
19
|
+
|
|
20
|
+
## 红线(必须遵守)
|
|
21
|
+
|
|
22
|
+
1. **dataSources 一律 `type: http`**,指向 `/api/data/...`,不写 `type: service`,便于后续替换真实接口
|
|
23
|
+
2. **布局、样式、尺寸、间距以 `template/` 模板工程为准**:线框图只用于提取内容结构与功能(有哪些区域、字段、操作),不逐像素还原 ASCII 原型;排版模式(页头、筛选区排布、卡片网格列数、表格密度、表单分区、间距档位)一律套用模板既有页面的写法
|
|
24
|
+
3. **样式与原产品一致**:主题令牌(含深浅色)、自定义 CSS(如登录页)、换肤入口全部移植对齐,见 `references/theme-and-style.md`
|
|
25
|
+
4. **功能与原产品一致**:线框图描述的筛选/搜索/跳转/弹窗/提交等交互必须经 events/actions 闭环可用,禁止只画静态骨架;错误分支(校验失败提示等)也要实现
|
|
26
|
+
5. **表单一律用 `Form` 组件**:凡是一组「字段 + 校验 + 提交」的表单——独立表单页、页面内嵌的配置面板(如树/列表右侧的编辑表单)、弹窗/抽屉表单、**搜索栏(关键词 + 查询/重置 的筛选区,用 `Form layout: inline`)**——都必须用 `Form` 包裹,字段经 `props.name` 绑定,校验用字段级 `rules`(required + message),提交用 `submit: true` 的 Button 触发 `onSubmit`;**禁止**散放 `Input`/`Select` + `setState` 攒草稿 + `if` 动作手写校验。回填联动(如点击列表/树节点填充表单)用 `form.setValues` 动作,不要绑 `${state.x}` 到字段 value;即点即生效的 ChipFilter 快筛行没有提交语义,不属于表单
|
|
27
|
+
6. **列表页操作列规范**:操作列按钮样式统一 `Button variant: link`;「编辑」与「新建」指向同一个表单(同一路由页或同一弹窗),唯一区别是编辑以行数据预填(路由页 `initialValues: ${data.detail}`,弹窗/内嵌面板 `form.setValues`)
|
|
28
|
+
7. **文案以线框图为准**:标题、按钮、空态文案逐字取自线框图/PRD
|
|
29
|
+
8. DSL 语法细节(组件 props、表达式、动作)以 pactor skill 与 packages/ui 当前导出为准,本 skill 只约定流程与产出物
|
|
30
|
+
|
|
31
|
+
## 流程(六阶段)
|
|
32
|
+
|
|
33
|
+
### 1. 输入解析
|
|
34
|
+
|
|
35
|
+
- 读总览 README:提取模块树、页面清单(页面 id kebab-case、标题、路由路径、菜单分组、P0/P1/P2 优先级)
|
|
36
|
+
- 逐页读线框图:提取页面类型(列表/详情/表单/仪表盘)、布局区域、组件清单、字段与数据需求、交互(跳转/弹窗/操作)
|
|
37
|
+
- 输出一页纸页面清单供确认;P2 等未排期页面登记但不生成
|
|
38
|
+
|
|
39
|
+
### 2. 脚手架
|
|
40
|
+
|
|
41
|
+
- **整体复制 `template/` 目录作为新项目起点**(可运行的参考实现:完整接线 + 公共页面 + 两个参考模块),不要从空目录或 `pnpm create pactor-app` 开始
|
|
42
|
+
- 确定工程名称:**用户未明确指定时,根据原型目录 README 中的产品名生成英文名**(kebab-case):取 README 主标题/产品名(如「订单管理系统产品原型线框图」→ `order-console`),中文名先译为简洁英文;派生出的名称在创建前向用户确认一次
|
|
43
|
+
- 改名并安装:package.json name、yaml/sitemap.yaml 的 app.name 与品牌改为新产品名;`pnpm install` + 启动 dev server 验证模板可运行
|
|
44
|
+
- 按 `references/project-structure.md` 核对目录结构:`src/server/yaml/`(sitemap.yaml + 按模块分目录的页面 yaml)与 `app/api/` 路由(sitemap / public/pages/[id] / data/[...path])
|
|
45
|
+
- 模板中的 provider / route 两个模块作为页面写法参考保留在手边,新模块照其结构生成;确认全部页面生成完毕后删除参考模块及其 mock
|
|
46
|
+
|
|
47
|
+
### 3. 页面生成
|
|
48
|
+
|
|
49
|
+
- 按 `references/component-mapping.md` 把线框图区域映射为 pactor 组件
|
|
50
|
+
- 按 `references/page-templates.md` 的三类模板逐页生成 `yaml/<模块>/<id>.yaml`:列表页(筛选 + 表格/卡片 + 操作列)、详情页(描述列表 + Tab/分区)、表单页(校验 + 提交闭环);**所有表单(含页面内嵌的配置面板、弹窗/抽屉表单)一律 `Form` 组件包裹**,写法见 page-templates.md「内嵌表单面板」
|
|
51
|
+
- 每页 dataSources 全部 `type: http`;交互经 events + actions 闭环(setState → reload dataSource → router.push 等)
|
|
52
|
+
|
|
53
|
+
### 4. mock 服务
|
|
54
|
+
|
|
55
|
+
- 按 `references/mock-data-routes.md` 在 `app/api/data/[...path]/route.ts` 注册路由表 handler,返回符合线框图字段的 mock 数据(含空态/多态数据各至少一条)
|
|
56
|
+
|
|
57
|
+
### 5. 样式移植
|
|
58
|
+
|
|
59
|
+
- 按 `references/theme-and-style.md`:提取原产品设计令牌写入皮肤文件(深浅色俱全)、移植页面级自定义 CSS 并挂载、对齐换肤入口与皮肤数量、确认图表随肤切换
|
|
60
|
+
|
|
61
|
+
### 6. 功能与视觉验收(必经,不可跳过)
|
|
62
|
+
|
|
63
|
+
- 按 `references/acceptance-checklist.md` 逐页浏览器验收并输出验收结果表:路由可达、数据源 200、渲染符合线框、交互逐项操作(含错误分支)、控制台无错、深浅色切换、与原产品同页面对比
|
|
64
|
+
|
|
65
|
+
## references 索引
|
|
66
|
+
|
|
67
|
+
| 文件 | 何时加载 |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `template/` | 阶段 2:可运行的模板工程(完整接线 + 公共页面 + provider/route 两个参考模块),整体复制后按 PRD 改造,用法见其 README.md |
|
|
70
|
+
| `references/project-structure.md` | 阶段 2:目录结构约定与 sitemap.yaml 骨架 |
|
|
71
|
+
| `references/component-mapping.md` | 阶段 3:线框图区域 → pactor 组件映射 |
|
|
72
|
+
| `references/page-templates.md` | 阶段 3:列表/详情/表单页 yaml 模板 |
|
|
73
|
+
| `references/mock-data-routes.md` | 阶段 4:mock 路由表模板与命名约定 |
|
|
74
|
+
| `references/theme-and-style.md` | 阶段 5:主题令牌提取、皮肤、自定义 CSS、换肤 |
|
|
75
|
+
| `references/acceptance-checklist.md` | 阶段 6:验收清单与结果输出格式 |
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 验收清单
|
|
2
|
+
|
|
3
|
+
验收是必经阶段。启动 dev server,用浏览器(playwright-cli / WebBridge 均可)逐页执行,输出验收结果表。**全部通过才算完成;任何一项不通过,修复后重验该页全部项。**
|
|
4
|
+
|
|
5
|
+
## 每页检查项
|
|
6
|
+
|
|
7
|
+
| # | 检查项 | 通过标准 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 1 | 路由可达 | 菜单点击进入或直接访问 path,页面正常渲染,无 404 |
|
|
10
|
+
| 2 | 数据源 | 页面所有 http 数据源返回 200(Network 面板确认),无 401/404/500 |
|
|
11
|
+
| 3 | 渲染符合线框图 | 区域结构、字段、文案与线框图一致;无臆造元素,无缺失区域。布局/尺寸/间距不要求还原线框图,以模板工程同类页面为准 |
|
|
12
|
+
| 4 | 交互闭环 | 线框图描述的每个交互逐项操作:筛选变化触发列表刷新、按钮跳转/弹窗、表单可提交并有成功反馈 |
|
|
13
|
+
| 5 | 表单结构 | 所有表单(独立页/内嵌面板/弹窗抽屉/**搜索栏**)均由 `Form` 包裹(搜索栏 `layout: inline`):字段 `name` 绑定、`rules` 校验就地报错、`submit: true` 提交;回填经 `form.setValues`;无散放字段 + setState 手写校验;表格操作列按钮统一 `Button variant: link`,「编辑」打开与「新建」相同的表单且数据预填正确 |
|
|
14
|
+
| 6 | 错误分支 | 校验失败有提示、错误操作有反馈(如登录错误密码 toast)、删除有确认 |
|
|
15
|
+
| 7 | 控制台 | 无 error/warning(hydration、key、表达式求值失败等) |
|
|
16
|
+
| 8 | 空态/多态 | 筛到无结果有空态;特殊状态条目(告警/停用等)按线框图展示 |
|
|
17
|
+
|
|
18
|
+
## 全站检查项(最后执行一次)
|
|
19
|
+
|
|
20
|
+
| # | 检查项 | 通过标准 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| A | 菜单完整性 | 菜单与 PRD 模块树一致,页面清单中每个页面均可达(含 hidden 的详情/编辑页经列表跳转) |
|
|
23
|
+
| B | 深浅色 | 切换深色模式:无白边、无死色、文字对比正常;markdown 代码块在深色下可读 |
|
|
24
|
+
| C | 换肤 | 每套皮肤切换一次:全站组件(含图表)随肤变化,首屏无闪烁 |
|
|
25
|
+
| D | 与原产品对比 | 关键页面(首页/登录/至少一个列表页 + 一个详情页)与原产品并排目检:视觉风格(令牌/皮肤/自定义 CSS)一致、交互行为一致;排版细节以模板工程为准 |
|
|
26
|
+
| E | 自定义 CSS 挂载 | 原产品的页面级 CSS(如登录页)确实生效——**浏览器目检**,不以文件存在为准 |
|
|
27
|
+
|
|
28
|
+
## 结果输出格式
|
|
29
|
+
|
|
30
|
+
```markdown
|
|
31
|
+
## 验收结果
|
|
32
|
+
|
|
33
|
+
| 页面 | 路由 | 数据源 | 渲染 | 交互 | 表单 | 错误分支 | 控制台 | 结论 |
|
|
34
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
35
|
+
| 项目列表 | ✅ | ✅ | ✅ | ✅ | -(无表单) | ✅ | ✅ | 通过 |
|
|
36
|
+
| 路由详情 | ✅ | ✅ | ⚠️ 缺「健康检查」区 | - | - | - | ✅ | 不通过:补区域后重验 |
|
|
37
|
+
|
|
38
|
+
全站:深浅色 ✅ / 换肤 ✅(3 套)/ 原产品对比 ✅ / 自定义 CSS ✅
|
|
39
|
+
结论:N/M 页通过;不通过项:<列表与修复计划>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 常见坑(历史踩坑记录)
|
|
43
|
+
|
|
44
|
+
- **dev server 不会因 yaml 改动热重载**(yaml 在服务端模块加载时读取):改 `src/server/yaml/**` 后重启 dev server 再验
|
|
45
|
+
- **端口冲突**:启动前 `lsof -nP -iTCP:<port> -sTCP:LISTEN`,已有 dev server 直接复用
|
|
46
|
+
- **两段数据路径被当详情解析**:选项类接口用单段路径(`/api/data/project-members` 而非 `project/members`)
|
|
47
|
+
- **toast 消失快误判未触发**:验证错误提示时在操作后立即截图/读 DOM
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 线框图区域 → pactor 组件映射表
|
|
2
|
+
|
|
3
|
+
按线框图的「区域」反查组件。只列稳定组件与常用 props 模式;**以 packages/ui 当前导出(`packages/ui/src/components/`)为准**,新增/改名时同步本表。
|
|
4
|
+
|
|
5
|
+
| 线框图区域 | pactor 组件 | 常用模式 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| 面包屑(模块 / 页面) | `Breadcrumb` | `items: [{label, href?}]`,列表项带 href,末项纯文本 |
|
|
8
|
+
| 页头标题 + 副标题 | `Typography` | `variant: h3` + `variant: muted` 并排(`Flex align: baseline`) |
|
|
9
|
+
| 页头右侧操作(新建/导入) | `Button` | `text` + `icon`,点击 `router.push` 或弹 `Dialog` |
|
|
10
|
+
| 搜索栏(关键词 + 下拉筛选 + 查询/重置) | `Form layout: inline` + `Input`/`Select` | 字段 `name` 绑定;查询按钮 `submit: true`,onSubmit 中 setState + reload;重置用 `form.setValues`。即点即生效的 ChipFilter 快筛除外 |
|
|
11
|
+
| 快筛行(状态/类型单选) | `ChipFilter` | `mode: single`,`value` 绑 state,`items` 可经接口下发 |
|
|
12
|
+
| 标签多选筛选 | `ChipFilter` | `mode: multi`,value 为数组 state |
|
|
13
|
+
| 下拉筛选(操作人等枚举) | `Select` | `options` 可绑 `${data.xxx}`(接口下发选项,单段路径) |
|
|
14
|
+
| 日期范围筛选 | `DatePicker` | range 模式绑 state,onChange reload |
|
|
15
|
+
| 数据表格 | `Table` | `columns` + `dataSource: ${data.x.items}`;操作列按钮统一 `Button variant: link`;「编辑」与「新建」共用同一表单,仅以预填数据区分(initialValues / form.setValues) |
|
|
16
|
+
| 卡片网格(约 3-4 列) | `Grid` + `Card` | `columns: 3, gap: 16`;「添加卡」用 `Card` 虚线边框自定义样式 |
|
|
17
|
+
| 状态徽标(启用/停用/告警) | `Badge` / `StatusBadge` | 颜色语义化,状态文案取自线框图 |
|
|
18
|
+
| 启停开关图标(⏻) | `StatusToggleIcon` | 卡片/行内启停,onChange 闭环动作 + reload |
|
|
19
|
+
| 权限控制显隐(无权限隐藏新建卡等) | 表达式 `hidden` / `if` 动作 | 绑 `${data.session.permissions}` 等,按会话裁剪 |
|
|
20
|
+
| 统计数字卡 | `Statistic` + `Card` | 仪表盘顶部指标行,`Grid` 排布 |
|
|
21
|
+
| 趋势/占比图 | `chart/` 下 AreaChart 等 | 图表随肤切换由库内建,无需额外处理 |
|
|
22
|
+
| 描述列表(详情字段) | `Descriptions` | `items` 由 detail 接口下发或静态声明 |
|
|
23
|
+
| 详情 Tab | `Tabs` | `items: [{key, label}]`;线框图为纵向分区时改用多个 `Card` 顺序排布 |
|
|
24
|
+
| 表单(独立页 / 内嵌面板 / 弹窗抽屉) | `Form` 包裹 `Input`/`Select`/`NumberInput`/`RadioGroup`/`Checkbox`/`Textarea` 等 | 字段 `props.name` 绑定;校验用 `rules`;提交按钮 `submit: true` + `onSubmit`;回填联动用 `form.setValues` 动作。禁止散放字段 + setState 手写校验 |
|
|
25
|
+
| 弹窗(确认/新建) | `Dialog`(破坏性操作用 `AlertDialog`) | open 绑 state,确认后动作闭环 + `message` 反馈 |
|
|
26
|
+
| 抽屉(详情侧滑) | `Drawer` | 线框图标明「侧滑/抽屉」时替代路由跳转 |
|
|
27
|
+
| 提示条(告警/说明) | `Alert` | 密钥告警、页面说明等 |
|
|
28
|
+
| 空态 | `Empty` | 线框图有空态文案时显式配置,否则用组件缺省 |
|
|
29
|
+
| 操作反馈 | `message` 动作 | `messageType: success/error/info`,不要静默 |
|
|
30
|
+
| 进度 | `Progress` | 配额/用量占比 |
|
|
31
|
+
| 图标 | `Icon` | `name` 走白名单(kebab-case),未收录的按需补充 |
|
|
32
|
+
| 布局容器 | `Flex` / `Grid` / `Card` / `Page` | 间距用 `gap: small/middle/large`,避免手写 margin |
|
|
33
|
+
|
|
34
|
+
## 映射原则
|
|
35
|
+
|
|
36
|
+
1. **先区域后组件**:从线框图区域结构出发逐区翻译,不要先想「用什么组件炫酷」
|
|
37
|
+
2. **布局尺寸以模板为准**:线框图的 ASCII 排版只是示意——列数、卡片尺寸、间距、对齐一律沿用 `template/` 既有页面的同类写法(如卡片网格列数、筛选区纵向堆叠、页头 h3+muted),不为还原线框图而发明新排版
|
|
38
|
+
3. **能内置不定制**:筛选、空态、加载、分页优先用组件内建能力;线框图有明确要求才自定义
|
|
39
|
+
4. **自定义样式走 className + 语义令牌**:如 `bg-(--muted)`、`text-(--muted-foreground)`,禁止写死色值(深浅色会破)
|
|
40
|
+
5. **线框图没有的不要加**:不臆造按钮、列、Tab;有疑问登记到页面清单的待确认项,不要自行发挥
|
|
41
|
+
6. **线框图有但组件没有的**:优先组合现有组件逼近;差距大时在目标项目注册业务组件(`components/` 目录),并在验收清单标注
|