listpage_cli 0.0.293 → 0.0.294

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 (33) hide show
  1. package/bin/adapters/node-fs-adapter.js +109 -0
  2. package/bin/app/dispatch.js +15 -0
  3. package/bin/app/execute.js +33 -0
  4. package/bin/app/parse-args.js +34 -0
  5. package/bin/cli.js +55 -74
  6. package/bin/commands/build-project-command.js +9 -0
  7. package/bin/commands/init-command.js +9 -0
  8. package/bin/commands/install-skill-command.js +9 -0
  9. package/bin/copy.js +14 -126
  10. package/bin/domain/command-result.js +34 -0
  11. package/bin/domain/interaction-result.js +14 -0
  12. package/bin/ports/build-project-command.js +2 -0
  13. package/bin/ports/filesystem-capability.js +2 -0
  14. package/bin/ports/fs-port.js +22 -0
  15. package/bin/ports/init-command.js +2 -0
  16. package/bin/ports/install-skill-command.js +2 -0
  17. package/bin/prompts.js +105 -16
  18. package/bin/services/artifact-validator.js +2 -0
  19. package/bin/services/build-project-service.js +190 -0
  20. package/bin/services/command-runner.js +44 -0
  21. package/bin/services/config-loader.js +113 -0
  22. package/bin/services/filesystem-capability-service.js +136 -0
  23. package/bin/services/init-service.js +64 -0
  24. package/bin/services/install-skill-service.js +34 -0
  25. package/package.json +6 -4
  26. package/templates/backend-template/package.json.tmpl +1 -1
  27. package/templates/frontend-template/package.json.tmpl +2 -2
  28. package/templates/package-app-template/package.json +1 -1
  29. package/templates/skills-template/listpage/examples.md +565 -0
  30. package/skills/listpage/examples.md +0 -243
  31. package/templates/rush-template/docs/ListPage-AI/347/224/237/346/210/220/350/247/204/350/214/203.md +0 -305
  32. /package/{skills → templates/skills-template}/listpage/SKILL.md +0 -0
  33. /package/{skills → templates/skills-template}/listpage/api.md +0 -0
@@ -0,0 +1,565 @@
1
+ # ListPage 示例
2
+
3
+ ## 1. 最小示例(单文件内联,listpage-components)
4
+
5
+ ```tsx
6
+ import { ListPage } from "listpage-components";
7
+ import { Button, Input, Select, Space } from "antd";
8
+
9
+ const filterItems = [
10
+ { name: "name", label: "姓名", component: <Input placeholder="请输入" /> },
11
+ {
12
+ name: "status",
13
+ label: "状态",
14
+ component: (
15
+ <Select
16
+ allowClear
17
+ options={[
18
+ { value: "enabled", label: "启用" },
19
+ { value: "disabled", label: "停用" },
20
+ ]}
21
+ />
22
+ ),
23
+ },
24
+ ];
25
+
26
+ const columns = [
27
+ { title: "姓名", dataIndex: "name", key: "name", renderer: "text" },
28
+ { title: "地址", dataIndex: "address", key: "address", ellipsis: true },
29
+ ];
30
+
31
+ const request = async (
32
+ { current, pageSize }: { current: number; pageSize: number },
33
+ filterValues: any,
34
+ _sortParams: { field?: string; order?: "ascend" | "descend" | null }
35
+ ) => {
36
+ const all = Array.from({ length: 100 }).map((_, i) => ({
37
+ id: i + 1,
38
+ name: `Edward ${i + 1}`,
39
+ address: `Address ${i + 1}`,
40
+ }));
41
+ let list = all.filter(
42
+ (r) => !filterValues?.name || r.name.includes(filterValues.name)
43
+ );
44
+ const skip = (current - 1) * pageSize;
45
+ return { list: list.slice(skip, skip + pageSize), total: list.length };
46
+ };
47
+
48
+ export default function Page() {
49
+ return (
50
+ <ListPage
51
+ title="用户列表"
52
+ extra={(ctx) => (
53
+ <Space>
54
+ <Button onClick={() => ctx.refreshTable()}>刷新</Button>
55
+ </Space>
56
+ )}
57
+ filter={{ items: filterItems }}
58
+ table={{ columns, rowKey: "id" }}
59
+ request={request}
60
+ initialValues={{ currentPage: 1, pageSize: 10 }}
61
+ />
62
+ );
63
+ }
64
+ ```
65
+
66
+ ## 2. 复杂用法(内置渲染器、toolbar、多浮窗)
67
+
68
+ 内置列渲染器:`text`、`ellipsis`、`tag`、`badge`、`date`、`datetime`、`number`、`money`、`percent`、`boolean`、`link`、`enum`、`copyable`、`avatar`、`image`。浮窗 `onClose(1)` 表示确认并刷新,`onClose()` 表示取消。
69
+
70
+ ```tsx
71
+ import { ListPage } from "listpage-components";
72
+ import { Button, Input, Select, Space, Drawer, Modal, message } from "antd";
73
+
74
+ const request = async (pageParams, filterValues) => {
75
+ const { current, pageSize } = pageParams;
76
+ // 模拟数据与筛选...
77
+ const list = [
78
+ {
79
+ id: "1",
80
+ name: "张三",
81
+ dept: "研发",
82
+ status: "启用",
83
+ price: "100.00",
84
+ createTime: "2025-01-01",
85
+ },
86
+ ];
87
+ return { list, total: list.length };
88
+ };
89
+
90
+ const DetailModal = ({ record, visible, onClose }) => (
91
+ <Modal open={visible} title="详情" onCancel={onClose} footer={null}>
92
+ <p>姓名:{record?.name}</p>
93
+ </Modal>
94
+ );
95
+
96
+ const EditModal = ({ record, visible, onClose }) => (
97
+ <Modal
98
+ open={visible}
99
+ title="编辑"
100
+ onCancel={onClose}
101
+ onOk={() => {
102
+ message.success("保存成功");
103
+ onClose(1);
104
+ }}
105
+ >
106
+ <Input defaultValue={record?.name} />
107
+ </Modal>
108
+ );
109
+
110
+ const CreateDrawer = ({ record, visible, onClose }) => (
111
+ <Drawer open={visible} title="新建" onClose={() => onClose()}>
112
+ <Input placeholder="姓名" />
113
+ <Button
114
+ type="primary"
115
+ onClick={() => {
116
+ message.success("新建成功");
117
+ onClose(1);
118
+ }}
119
+ >
120
+ 保存
121
+ </Button>
122
+ </Drawer>
123
+ );
124
+
125
+ export default () => (
126
+ <ListPage
127
+ title="用户列表"
128
+ initialValues={{ filterValues: {}, pageSize: 10, currentPage: 1 }}
129
+ extra={(ctx) => (
130
+ <Button type="primary" onClick={() => ctx.showFloat("create", {}, true)}>
131
+ 新建
132
+ </Button>
133
+ )}
134
+ filter={{
135
+ items: [
136
+ {
137
+ name: "name",
138
+ label: "姓名",
139
+ component: <Input allowClear placeholder="姓名" />,
140
+ },
141
+ {
142
+ name: "dept",
143
+ label: "部门",
144
+ component: (
145
+ <Select allowClear options={[{ label: "研发", value: "研发" }]} />
146
+ ),
147
+ },
148
+ {
149
+ name: "status",
150
+ label: "状态",
151
+ component: (
152
+ <Select allowClear options={[{ label: "启用", value: "启用" }]} />
153
+ ),
154
+ },
155
+ ],
156
+ }}
157
+ toolbar={{
158
+ render: ({ ctx }) => (
159
+ <Button onClick={() => ctx.refreshTable()}>刷新</Button>
160
+ ),
161
+ }}
162
+ table={{
163
+ title: "数据列表",
164
+ rowSelection: true,
165
+ rowTitleKey: "name",
166
+ extra: (ctx) => (
167
+ <Button size="small" onClick={() => ctx.refreshTable()}>
168
+ 刷新
169
+ </Button>
170
+ ),
171
+ rowKey: "id",
172
+ columns: [
173
+ {
174
+ title: "ID",
175
+ dataIndex: "id",
176
+ key: "id",
177
+ width: 100,
178
+ renderer: "copyable",
179
+ },
180
+ {
181
+ title: "姓名",
182
+ dataIndex: "name",
183
+ key: "name",
184
+ width: 100,
185
+ renderer: "link",
186
+ rendererOptions: {
187
+ onClick: (record, ctx) => ctx?.showFloat("detail", record),
188
+ },
189
+ },
190
+ {
191
+ title: "部门",
192
+ dataIndex: "dept",
193
+ key: "dept",
194
+ width: 90,
195
+ renderer: "tag",
196
+ rendererOptions: { colorMap: { 研发: "blue" } },
197
+ },
198
+ {
199
+ title: "状态",
200
+ dataIndex: "status",
201
+ key: "status",
202
+ width: 90,
203
+ renderer: "tag",
204
+ rendererOptions: { colorMap: { 启用: "success" } },
205
+ },
206
+ {
207
+ title: "价格",
208
+ dataIndex: "price",
209
+ key: "price",
210
+ width: 110,
211
+ renderer: "money",
212
+ rendererOptions: { precision: 2, prefix: "¥" },
213
+ },
214
+ {
215
+ title: "创建时间",
216
+ dataIndex: "createTime",
217
+ key: "createTime",
218
+ width: 110,
219
+ renderer: "date",
220
+ rendererOptions: { format: "YYYY-MM-DD" },
221
+ },
222
+ {
223
+ title: "操作",
224
+ key: "action",
225
+ width: 140,
226
+ fixed: "right",
227
+ component: ({ ctx, record }) => (
228
+ <Space size="small">
229
+ <Button
230
+ type="link"
231
+ size="small"
232
+ onClick={() => ctx.showFloat("detail", record)}
233
+ >
234
+ 详情
235
+ </Button>
236
+ <Button
237
+ type="link"
238
+ size="small"
239
+ onClick={() => ctx.showFloat("edit", record, true)}
240
+ >
241
+ 编辑
242
+ </Button>
243
+ </Space>
244
+ ),
245
+ },
246
+ ],
247
+ }}
248
+ request={request}
249
+ floats={[
250
+ { key: "detail", render: DetailModal },
251
+ { key: "edit", render: EditModal },
252
+ { key: "create", render: CreateDrawer },
253
+ ]}
254
+ />
255
+ );
256
+ ```
257
+
258
+ ## 3. 带初始状态(initialValues)
259
+
260
+ `initialValues` 用于预填筛选条件、分页等,组件首次挂载时生效。
261
+
262
+ ```tsx
263
+ <ListPage
264
+ title="用户列表"
265
+ initialValues={{
266
+ filterValues: { name: "张三", status: "启用" },
267
+ pageSize: 20,
268
+ currentPage: 1,
269
+ }}
270
+ filter={{ items: filterItems }}
271
+ table={{ columns, rowKey: "id" }}
272
+ request={request}
273
+ />
274
+ ```
275
+
276
+ ## 4. 带行选择
277
+
278
+ ```tsx
279
+ table={{
280
+ columns,
281
+ rowKey: "id",
282
+ rowSelection: true,
283
+ rowTitleKey: "name",
284
+ }}
285
+ ```
286
+
287
+ ## 5. 内置渲染器速查(listpage-components)
288
+
289
+ | renderer | 说明 | rendererOptions 示例 |
290
+ | -------- | -------- | ---------------------------------------------------------------- |
291
+ | text | 文本 | - |
292
+ | ellipsis | 省略 | `{ maxLength: 10 }` |
293
+ | tag | 标签 | `{ colorMap: { 启用: "success" } }` |
294
+ | badge | 徽标 | `{ colorMap: { 0: "default" } }` |
295
+ | date | 日期 | `{ format: "YYYY-MM-DD" }` |
296
+ | datetime | 日期时间 | `{ format: "YYYY-MM-DD HH:mm:ss" }` |
297
+ | number | 数字 | `{ precision: 2 }` |
298
+ | money | 金额 | `{ precision: 2, prefix: "¥" }` |
299
+ | percent | 百分比 | `{ precision: 1 }` |
300
+ | boolean | 布尔 | `{ labels: ["否", "是"] }` |
301
+ | link | 链接 | `{ onClick: (record, ctx) => ctx?.showFloat("detail", record) }` |
302
+ | enum | 枚举 | `{ options: [{ value: 0, label: "男" }] }` |
303
+ | copyable | 可复制 | - |
304
+ | avatar | 头像 | `{ fallback: "?" }` |
305
+ | image | 图片 | `{ width: 40, height: 40 }` |
306
+
307
+ ## 6. 复杂结构(config 拆分目录)
308
+
309
+ 目录结构:
310
+
311
+ ```
312
+ pages/ContractTypeManage/
313
+ ├── index.tsx
314
+ ├── config/
315
+ │ ├── columns.tsx
316
+ │ ├── filters.tsx
317
+ │ ├── floats.tsx
318
+ │ └── request.ts
319
+ └── components/
320
+ ├── ContractTypeForm.tsx
321
+ └── ContractTypeFormFloat.tsx
322
+ ```
323
+
324
+ **components/ContractTypeForm.tsx** 表单组件:
325
+
326
+ ```tsx
327
+ import { Form, Input, Select, type FormProps, type FormInstance } from "antd";
328
+
329
+ export const ContractTypeForm = (
330
+ props: FormProps & { formRef?: React.Ref<FormInstance> }
331
+ ) => {
332
+ const { formRef, ...rest } = props;
333
+ return (
334
+ <Form ref={formRef} layout="vertical" {...rest}>
335
+ <Form.Item
336
+ name="name"
337
+ label="类型名称"
338
+ rules={[{ required: true, message: "请输入类型名称" }]}
339
+ >
340
+ <Input placeholder="请输入类型名称" allowClear />
341
+ </Form.Item>
342
+ <Form.Item
343
+ name="payment_nodes"
344
+ label="付款节点"
345
+ rules={[{ required: true, message: "请配置付款节点" }]}
346
+ extra="输入节点名称后按回车添加"
347
+ >
348
+ <Select
349
+ mode="tags"
350
+ style={{ width: "100%" }}
351
+ placeholder="请输入或选择付款节点"
352
+ options={[
353
+ { value: "预付", label: "预付" },
354
+ { value: "发货", label: "发货" },
355
+ { value: "验收", label: "验收" },
356
+ { value: "进度", label: "进度" },
357
+ { value: "尾款", label: "尾款" },
358
+ { value: "到货", label: "到货" },
359
+ { value: "送电", label: "送电" },
360
+ ]}
361
+ />
362
+ </Form.Item>
363
+ </Form>
364
+ );
365
+ };
366
+ ```
367
+
368
+ **components/ContractTypeFormFloat.tsx** 浮窗组件:
369
+
370
+ ```tsx
371
+ import { useRef } from "react";
372
+ import { Button, Space, type FormInstance } from "antd";
373
+ import { ProDrawer, type FloatComponentProps } from "listpage-components";
374
+ import api from "@/api";
375
+ import type { ContractType } from "@/api/contract-type";
376
+ import { ContractTypeForm } from "./ContractTypeForm";
377
+
378
+ export const ContractTypeFormFloat = (
379
+ props: FloatComponentProps<ContractType>
380
+ ) => {
381
+ const { record, visible, onClose } = props;
382
+ const formRef = useRef<FormInstance>(null);
383
+ const isEdit = !!record;
384
+
385
+ const handleSubmit = async (values: any) => {
386
+ if (isEdit) {
387
+ await api.contractType.update({ id: record.id, ...values });
388
+ } else {
389
+ await api.contractType.create(values);
390
+ }
391
+ onClose(1); // 1 表示刷新列表
392
+ };
393
+
394
+ return (
395
+ <ProDrawer
396
+ title={isEdit ? "编辑合同类型" : "新增合同类型"}
397
+ open={visible}
398
+ onClose={() => onClose()}
399
+ footer={
400
+ <Space>
401
+ <Button onClick={() => onClose()}>取消</Button>
402
+ <Button type="primary" onClick={() => formRef.current?.submit()}>
403
+ 确定
404
+ </Button>
405
+ </Space>
406
+ }
407
+ >
408
+ <div className="p-6">
409
+ <ContractTypeForm
410
+ formRef={formRef}
411
+ initialValues={record}
412
+ onFinish={handleSubmit}
413
+ // 关键:通过 key 强制重新渲染,确保 initialValues 生效,代替 useEffect 重置表单
414
+ key={visible ? record?.id || "new" : "hidden"}
415
+ />
416
+ </div>
417
+ </ProDrawer>
418
+ );
419
+ };
420
+ ```
421
+
422
+ **config/floats.tsx** 浮窗配置:
423
+
424
+ ```tsx
425
+ import type { FloatItem } from "listpage-components";
426
+ import { ContractTypeFormFloat } from "../components/ContractTypeFormFloat";
427
+
428
+ export const floats: FloatItem[] = [
429
+ {
430
+ key: "create",
431
+ render: ContractTypeFormFloat,
432
+ },
433
+ {
434
+ key: "update",
435
+ render: ContractTypeFormFloat,
436
+ },
437
+ ];
438
+ ```
439
+
440
+ **config/columns.tsx** 表格列配置:
441
+
442
+ ```tsx
443
+ import { Tag, Space, Button, Popconfirm, message } from "antd";
444
+ import type { ListPageColumn } from "listpage-components";
445
+ import api from "@/api";
446
+
447
+ export const columns: ListPageColumn[] = [
448
+ {
449
+ title: "类型名称",
450
+ dataIndex: "name",
451
+ key: "name",
452
+ },
453
+ {
454
+ title: "付款节点",
455
+ dataIndex: "payment_nodes",
456
+ key: "payment_nodes",
457
+ render: (nodes: string[]) => (
458
+ <Space wrap>
459
+ {nodes.map((node) => (
460
+ <Tag key={node} color="blue">
461
+ {node}
462
+ </Tag>
463
+ ))}
464
+ </Space>
465
+ ),
466
+ },
467
+ {
468
+ title: "操作",
469
+ key: "action",
470
+ width: 150,
471
+ render: (_, record, __, ctx) => (
472
+ <Space>
473
+ <Button
474
+ type="link"
475
+ size="small"
476
+ onClick={() => ctx.showFloat("update", record)}
477
+ >
478
+ 编辑
479
+ </Button>
480
+ <Popconfirm
481
+ title="确定删除吗?"
482
+ onConfirm={async () => {
483
+ await api.contractType.remove(record.id);
484
+ message.success("删除成功");
485
+ ctx.refreshTable();
486
+ }}
487
+ >
488
+ <Button type="link" size="small" danger>
489
+ 删除
490
+ </Button>
491
+ </Popconfirm>
492
+ </Space>
493
+ ),
494
+ },
495
+ ];
496
+ ```
497
+
498
+ **config/filters.tsx** 筛选配置:
499
+
500
+ ```tsx
501
+ import type { FilterConfig } from "listpage-components";
502
+
503
+ export const filters: FilterConfig["items"] = [
504
+ {
505
+ name: "name",
506
+ label: "类型名称",
507
+ type: "input",
508
+ },
509
+ ];
510
+ ```
511
+
512
+ **config/request.ts** 请求配置:
513
+
514
+ ```ts
515
+ import type { ListPageRequest } from "listpage-components";
516
+ import api from "@/api";
517
+
518
+ export const request: ListPageRequest = async (params) => {
519
+ const { currentPage, pageSize, ...rest } = params;
520
+ const data = await api.contractType.list({
521
+ current: currentPage,
522
+ pageSize,
523
+ ...rest,
524
+ });
525
+
526
+ return {
527
+ list: data.list,
528
+ total: data.total,
529
+ };
530
+ };
531
+ ```
532
+
533
+ **index.tsx** 页面组装:
534
+
535
+ ```tsx
536
+ import { Button } from "antd";
537
+ import { ListPage, type ListPageContextValue } from "listpage-components";
538
+ import { filters, columns, request, floats } from "./config";
539
+
540
+ export const ContractTypeManage = () => {
541
+ return (
542
+ <ListPage
543
+ title="合同类型管理"
544
+ initialValues={{ currentPage: 1, pageSize: 10 }}
545
+ request={request}
546
+ extra={(ctx: ListPageContextValue) => (
547
+ <Button
548
+ type="primary"
549
+ onClick={() => ctx.showFloat("create", undefined, true)}
550
+ >
551
+ 新建
552
+ </Button>
553
+ )}
554
+ table={{
555
+ columns,
556
+ rowKey: "id",
557
+ }}
558
+ filter={{ items: filters }}
559
+ floats={floats}
560
+ />
561
+ );
562
+ };
563
+
564
+ export default ContractTypeManage;
565
+ ```