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
@@ -1,243 +0,0 @@
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 = [{ id: "1", name: "张三", dept: "研发", status: "启用", price: "100.00", createTime: "2025-01-01" }];
78
- return { list, total: list.length };
79
- };
80
-
81
- const DetailModal = ({ record, visible, onClose }) => (
82
- <Modal open={visible} title="详情" onCancel={onClose} footer={null}>
83
- <p>姓名:{record?.name}</p>
84
- </Modal>
85
- );
86
-
87
- const EditModal = ({ record, visible, onClose }) => (
88
- <Modal open={visible} title="编辑" onCancel={onClose} onOk={() => { message.success("保存成功"); onClose(1); }}>
89
- <Input defaultValue={record?.name} />
90
- </Modal>
91
- );
92
-
93
- const CreateDrawer = ({ record, visible, onClose }) => (
94
- <Drawer open={visible} title="新建" onClose={() => onClose()}>
95
- <Input placeholder="姓名" />
96
- <Button type="primary" onClick={() => { message.success("新建成功"); onClose(1); }}>保存</Button>
97
- </Drawer>
98
- );
99
-
100
- export default () => (
101
- <ListPage
102
- title="用户列表"
103
- initialValues={{ filterValues: {}, pageSize: 10, currentPage: 1 }}
104
- extra={(ctx) => <Button type="primary" onClick={() => ctx.showFloat("create", {}, true)}>新建</Button>}
105
- filter={{
106
- items: [
107
- { name: "name", label: "姓名", component: <Input allowClear placeholder="姓名" /> },
108
- { name: "dept", label: "部门", component: <Select allowClear options={[{ label: "研发", value: "研发" }]} /> },
109
- { name: "status", label: "状态", component: <Select allowClear options={[{ label: "启用", value: "启用" }]} /> },
110
- ],
111
- }}
112
- toolbar={{ render: ({ ctx }) => <Button onClick={() => ctx.refreshTable()}>刷新</Button> }}
113
- table={{
114
- title: "数据列表",
115
- rowSelection: true,
116
- rowTitleKey: "name",
117
- extra: (ctx) => <Button size="small" onClick={() => ctx.refreshTable()}>刷新</Button>,
118
- rowKey: "id",
119
- columns: [
120
- { title: "ID", dataIndex: "id", key: "id", width: 100, renderer: "copyable" },
121
- { title: "姓名", dataIndex: "name", key: "name", width: 100, renderer: "link", rendererOptions: { onClick: (record, ctx) => ctx?.showFloat("detail", record) } },
122
- { title: "部门", dataIndex: "dept", key: "dept", width: 90, renderer: "tag", rendererOptions: { colorMap: { 研发: "blue" } } },
123
- { title: "状态", dataIndex: "status", key: "status", width: 90, renderer: "tag", rendererOptions: { colorMap: { 启用: "success" } } },
124
- { title: "价格", dataIndex: "price", key: "price", width: 110, renderer: "money", rendererOptions: { precision: 2, prefix: "¥" } },
125
- { title: "创建时间", dataIndex: "createTime", key: "createTime", width: 110, renderer: "date", rendererOptions: { format: "YYYY-MM-DD" } },
126
- {
127
- title: "操作",
128
- key: "action",
129
- width: 140,
130
- fixed: "right",
131
- component: ({ ctx, record }) => (
132
- <Space size="small">
133
- <Button type="link" size="small" onClick={() => ctx.showFloat("detail", record)}>详情</Button>
134
- <Button type="link" size="small" onClick={() => ctx.showFloat("edit", record, true)}>编辑</Button>
135
- </Space>
136
- ),
137
- },
138
- ],
139
- }}
140
- request={request}
141
- floats={[
142
- { key: "detail", render: DetailModal },
143
- { key: "edit", render: EditModal },
144
- { key: "create", render: CreateDrawer },
145
- ]}
146
- />
147
- );
148
- ```
149
-
150
- ## 3. 带初始状态(initialValues)
151
-
152
- `initialValues` 用于预填筛选条件、分页等,组件首次挂载时生效。
153
-
154
- ```tsx
155
- <ListPage
156
- title="用户列表"
157
- initialValues={{
158
- filterValues: { name: "张三", status: "启用" },
159
- pageSize: 20,
160
- currentPage: 1,
161
- }}
162
- filter={{ items: filterItems }}
163
- table={{ columns, rowKey: "id" }}
164
- request={request}
165
- />
166
- ```
167
-
168
- ## 4. 带行选择
169
-
170
- ```tsx
171
- table={{
172
- columns,
173
- rowKey: "id",
174
- rowSelection: true,
175
- rowTitleKey: "name",
176
- }}
177
- ```
178
-
179
- ## 5. 内置渲染器速查(listpage-components)
180
-
181
- | renderer | 说明 | rendererOptions 示例 |
182
- | --- | --- | --- |
183
- | text | 文本 | - |
184
- | ellipsis | 省略 | `{ maxLength: 10 }` |
185
- | tag | 标签 | `{ colorMap: { 启用: "success" } }` |
186
- | badge | 徽标 | `{ colorMap: { 0: "default" } }` |
187
- | date | 日期 | `{ format: "YYYY-MM-DD" }` |
188
- | datetime | 日期时间 | `{ format: "YYYY-MM-DD HH:mm:ss" }` |
189
- | number | 数字 | `{ precision: 2 }` |
190
- | money | 金额 | `{ precision: 2, prefix: "¥" }` |
191
- | percent | 百分比 | `{ precision: 1 }` |
192
- | boolean | 布尔 | `{ labels: ["否", "是"] }` |
193
- | link | 链接 | `{ onClick: (record, ctx) => ctx?.showFloat("detail", record) }` |
194
- | enum | 枚举 | `{ options: [{ value: 0, label: "男" }] }` |
195
- | copyable | 可复制 | - |
196
- | avatar | 头像 | `{ fallback: "?" }` |
197
- | image | 图片 | `{ width: 40, height: 40 }` |
198
-
199
- ## 6. 复杂结构(config 拆分目录)
200
-
201
- 目录结构:
202
-
203
- ```
204
- pages/MemberList/
205
- ├── index.tsx
206
- ├── config/
207
- │ ├── index.tsx
208
- │ ├── columns.tsx
209
- │ ├── filters.tsx
210
- │ ├── floats.tsx
211
- │ └── request.ts
212
- └── components/
213
- └── SalesFormFloat.tsx
214
- ```
215
-
216
- **config/index.tsx** 汇总导出:
217
-
218
- ```tsx
219
- export { columns } from "./columns";
220
- export { filters } from "./filters";
221
- export { floats } from "./floats";
222
- export { request } from "./request";
223
- ```
224
-
225
- **index.tsx** 组装(listpage-components):
226
-
227
- ```tsx
228
- import { ListPage } from "listpage-components";
229
- import { columns, filters, floats, request } from "./config";
230
-
231
- export default function MemberListPage() {
232
- return (
233
- <ListPage
234
- title="会员列表"
235
- filter={{ items: filters }}
236
- table={{ columns, rowKey: "id" }}
237
- request={request}
238
- floats={floats}
239
- initialValues={{ pageSize: 10, currentPage: 1 }}
240
- />
241
- );
242
- }
243
- ```
@@ -1,305 +0,0 @@
1
- # AI 生成规范
2
-
3
- ## 总则
4
-
5
- - 本规范仅约束 `listpage-next` 的使用与输出格式,避免输出与其无关的项目通用规则。
6
- - 生成的页面应围绕 `ListPage` 组件完整配置:`storageKey`、`initialValues`、`request`、`filter.options`、`table.columns`、`tableProps.rowKey` 必须齐全。
7
- - 页面内的动作分层清晰:页面级动作用 `header.extra`,表格级动作用 `table.extra`,浮层通过 `floats` 管理。
8
- - 所有数据请求函数必须返回 `PaginationData<T>`,筛选与分页参数通过 `params`/`filters` 传入。
9
-
10
- ## AI 生成流程(步骤)
11
-
12
- - 步骤 1:创建目录结构
13
- - `pages/<Feature>/index.tsx`、`config/{filters,columns,request,floats}.tsx`、`components/*`(浮层)、`hooks/*`(可选)、`types.ts`(可选)。
14
- - 步骤 2:定义筛选 `filters.tsx`
15
- - 使用 AntD 组件,默认加 `allowClear`;
16
- - 步骤 3:定义表格列 `columns.tsx`
17
- - 原子列优先使用 `component: 'text' | 'link' | 'switch'`;复杂渲染用 `render(value, record)`。
18
- - 固定关键列(如选择、操作)并设置 `width`、`ellipsis`、`onHeaderCell`。
19
- - 步骤 4:实现请求 `request.ts`
20
- - 签名:`(params, filters) => Promise<PaginationData<T>>`;失败返回空分页结构。
21
- - 处理日期区间:`daterange` 在请求中展开为 `startDate`/`endDate`。
22
- - 步骤 5:组装页面 `index.tsx`
23
- - 填写唯一 `storageKey`(命名见下),配置 `initialValues`,注入 `request`、`filter.options`、`table.columns` 与 `tableProps.rowKey`。
24
- - 页面级动作放到 `header.extra`,表格级动作放到 `table.extra`。
25
- - 步骤 6:注册浮层 `floats`
26
- - `[{ key, render }]`,组件遵循 `FloatComponentProps<T>`;在页面用 `ctx.showFloat(key, record, true)` 调用。
27
- - 步骤 7:自检并修正
28
- - 通过检查清单核对命名、分层、交互、返回类型与滚动/分页设置。
29
-
30
- ## 命名与输出约定
31
-
32
- - `storageKey`:`LISTPAGE_{FEATURE}` 或 `LISTPAGE_{FEATURE}_{VERSION}`,示例:`LISTPAGE_POLICY`、`LISTPAGE_CRAWLER_DATASOURCES_V2`。
33
- - 浮层 `key`:短横线小写,例如 `create`、`runtime-config`、`logs`。
34
- - 目录与文件:英文小驼峰;`columns.tsx`、`filters.tsx`、`request.ts`、`render.tsx`、`types.ts`。
35
- - 事件命名:语义化动词名词组合,如 `summary`、`batch-start`。
36
- - 行键 `rowKey`:必须与数据唯一字段一致,如 `id` 或 `name`。
37
-
38
- ## 禁止事项
39
-
40
- - 不在组件中直接修改列表记录对象;通过接口更新后调用 `ctx.refreshTable()`。
41
- - 不在筛选输入变更时自动请求;仅在筛选操作或分页/排序变更时请求。
42
-
43
- ## 1. 目标与范围
44
-
45
- - 统一基于 `listpage-next` 的列表页开发方式,降低重复和不一致。
46
- - 适用于包含 Header、Filter、Toolbar、Table、Floats 的页面。
47
-
48
- ## 2. 页面架构与目录约定
49
-
50
- - 目录结构建议:
51
- - `pages/<Feature>/index.tsx`:页面入口与 `ListPage` 组装。
52
- - `pages/<Feature>/config/filters.tsx`:筛选表单项定义(`FilterFormOption[]`)。
53
- - `pages/<Feature>/config/columns.tsx`:表格列定义(`ListPageTableColumn[]`)。
54
- - `pages/<Feature>/config/floats.tsx`:浮层定义(`ListPageFloatProps[]`)。
55
- - `pages/<Feature>/config/request.ts`:数据请求函数(`(params, filters) => PaginationData<T>`)。
56
- - `pages/<Feature>/config/render.tsx`:表格或工具栏扩展渲染。
57
- - `pages/<Feature>/components/*`:组件,浮层组件(Drawer/Modal)的 props 定义统一为 `FloatComponentProps<T>`。
58
- - `pages/<Feature>/hooks/*`:与页面相关的 Hook(如 URL 参数到初始筛选值)。
59
- - `pages/<Feature>/types.ts`:页面内专用类型。
60
- - 文件命名:语义化英文小驼峰;浮层键名使用短横线小写,例如 `create`、`runtime-config`、`logs`。
61
-
62
- ## 3. 核心组件与属性
63
-
64
- - `ListPage` 由以下部分组成:
65
- - `header`:页面级标题与入口动作(新建、导入)。
66
- - `filter`:筛选区域(`options: FilterFormOption[]`)。
67
- - `toolbar`:页面级工具栏,自定义与整体相关的批量动作。
68
- - `table`:数据表格(列、选择、扩展渲染、`tableProps`)。
69
- - `floats`:浮层体系(`Array<{ key: string; render: FloatRender }>`)。
70
- - 推荐属性:
71
- - `storageKey`:必填且唯一,持久化页内状态(示例:`LISTPAGE_POLICY`、`LISTPAGE_CRAWLER_DATASOURCES_V2`)。
72
- - `initialValues`:一次性初始化值(仅组件首次挂载读取,如 `currentPage`、`pageSize`、`filterValues`)。
73
- - `request(params, filters)`:统一返回 `PaginationData<T>`;参数包含分页、排序等。
74
- - `table.tableProps.rowKey`:必填,保证选择与行操作正确。
75
- - `rowSelectionType`:`'checkbox' | 'radio'`,开启选择能力。
76
- - `pageRef`:`Ref<ListPageContext>`,用于外部控制或组合式页面。
77
-
78
- ## 3.1 ListPageProps 速查(AI 必须输出)
79
-
80
- - `storageKey: string` 唯一键名用于状态持久化。
81
- - `initialValues?: { currentPage?: number; pageSize?: number; filterValues?: Record<string, any> }`
82
- - `request: (params: any, filters: any) => Promise<PaginationData<any>>`
83
- - `header?: { title?: string; extra?: (ctx: ListPageContext) => React.ReactNode }`
84
- - `filter?: { options: FilterFormOption[]; labelInline?: boolean }`
85
- - `toolbar?: { render?: (ctx: ListPageContext) => React.ReactNode }`
86
- - `table: { columns: ListPageTableColumn[]; extra?: (ctx: ListPageContext) => React.ReactNode; rowSelectionType?: 'checkbox' | 'radio'; rowTitleKey?: string; tableProps?: { rowKey: string; size?: 'small'|'middle'|'large'; scroll?: { x?: number; y?: number }; pagination?: { pageSizeOptions?: number[] } } }`
87
- - `floats?: Array<{ key: string; render: FloatRender }>`
88
- - `pageRef?: React.RefObject<ListPageContext>`
89
- - `styles?: { page?: React.CSSProperties }`
90
-
91
- ## 3.2 类型优先策略(AI 必须遵循)
92
-
93
- - 为页面定义明确的记录类型 `TRecord` 与筛选类型 `TFilters`,避免使用 `any`。
94
- - `columns` 使用 `ListPageTableColumn<TRecord>[]`,保证 `render`、`component` 的入参类型正确。
95
- - `request(params, filters)` 返回 `Promise<PaginationData<TRecord>>`,`params` 显式声明 `{ current?: number; pageSize?: number }`。
96
- - 浮层组件签名使用 `React.FC<FloatComponentProps<TRecord>>`;`ctx.showFloat(key, record as TRecord, true)` 调用。
97
- - `header.extra` / `table.extra` 的回调参数类型标注为 `ListPageContext`,避免未定义属性调用。
98
- - 枚举或布尔型筛选项使用联合类型(如 `status?: 'enabled' | 'disabled'`)。
99
- - 日期区间 `TFilters['dateRange']` 建议声明为 `[string, string]`,在 `request` 中展开为 `startDate`、`endDate`。
100
-
101
- ## 4. 数据请求与类型规范
102
-
103
- - 返回类型:`PaginationData<T> = { list: T[]; total: number; current: number; pageSize: number }`。
104
- - 入参:`params`(分页、排序),`filters`(筛选表单值)。
105
- - 实现要点:
106
- - 保持幂等与可重入,失败时返回空数据结构而非抛错。
107
- - 仅在筛选变更时重置到第 1 页;分页变更不重置筛选条件。
108
- - 日期字符串统一为 `YYYY-MM-DD`;区间使用 `daterange` 组件并在 `request` 中展开为 `startDate`/`endDate`。
109
-
110
- ## 5. 筛选 Filter 规范
111
-
112
- - 组件:使用 AntD 控件或 `listpage-next` 内置组件(如 `AsyncSelect`、`daterange`)。
113
- - 定义:`FilterFormOption[]`,每项包含 `name`、`label`、`component`(或字符串组件类型)与可选 `props`/`colSpan`。
114
- - 交互:
115
- - 表单值变化不自动请求;点击筛选或分页变更才请求。
116
- - `allowClear` 作为默认交互;多选时使用 `mode='multiple'` 并控制 `maxTagCount`。
117
- - 异步选择:
118
- - `AsyncSelect.request` 返回包含 `list` 的分页结构;`list` 元素支持 `{ label, value }` 或纯值,统一为对象更易维护。
119
- - 支持 `showSearch` 与服务端过滤(`(_, search) => { ... }`)。
120
-
121
- ## 6. 表格 Table 规范
122
-
123
- - 列定义:`ListPageTableColumn<T>` 支持:
124
- - 原子渲染 `component: 'text' | 'link' | 'switch' | ...` 或自定义函数 `render(value, record, index)`。
125
- - 列样式:`width`、`ellipsis`、`fixed`、`align`、`onHeaderCell`。
126
- - 行内操作列统一放右侧,宽度尽量固定;危险操作配合二次确认。
127
- - 表格扩展:
128
- - `table.extra(ctx)` 提供与表格数据相关的工具入口(如批量操作),通过 `ctx.selection` 读取选择态。
129
- - `rowTitleKey` 用于选择态顶部标题展示,提升可读性。
130
- - 分页:统一在 `tableProps.pagination` 配置页码与每页条数选项;切换时保留当前筛选条件。
131
-
132
- ## 7. Header 与 Toolbar 规范
133
-
134
- - 分层:
135
- - 页面级动作放 `header.extra`(新建、导入、全局跳转)。
136
- - 表格级动作放 `table.extra`(列设置、导出、批量执行)。
137
- - 访问上下文:`(ctx) => ...`,通过 `ctx.refreshTable()` 刷新列表,通过 `ctx.showFloat(key, record, true)` 打开浮层。
138
-
139
- ## 8. 浮层 Floats 规范
140
-
141
- - 注册:`floats: Array<{ key: string; render: FloatRender }>`,`key` 使用短横线小写(如 `create`、`runtime-config`、`logs`)。
142
- - 组件约束:遵循 `FloatComponentProps<T>`:`{ record?: T; visible: boolean; onClose: () => void }`。
143
- - 浮层组件 props 不得包含 onSuccess ,保存成功后仅调用 onClose() ;
144
- ## 9. 状态持久化与初始化策略
145
-
146
- - `storageKey` 用于在本地持久化分页与筛选状态。
147
- - `initialValues` 仅在首次挂载时读取并覆盖一次,之后页面内部表单驱动状态,外部变更 `state` 不实时生效,以避免性能问题与状态抖动。
148
-
149
- ## 10. 上下文与事件机制
150
-
151
- - `ListPageContext` 提供:`refreshTable`、`showFloat`、`selection`、`emit(event, payload)` 等能力。
152
- - 事件范式:在列或按钮中 `ctx.emit('summary', record)`,页面或上层组件监听并处理跨区域动作。
153
- - 引用:通过 `pageRef` 访问上下文能力,组合式页面可外部控制。
154
-
155
- ## 11. 组件扩展与常用内置
156
-
157
- - `AsyncButton`:处理异步点击,自动 loading,建议用于批量/长耗时操作。
158
- - `AsyncSelect`:异步下拉;统一返回分页结构,支持搜索与多选。
159
- - `daterange`:日期区间组件,统一格式与占位文案;在 `request` 中落盘为 `startDate`/`endDate`。
160
-
161
- ## 12. 性能与可维护性
162
-
163
- - 在 `render` 中避免重计算;复杂渲染提取到独立组件并 `memo`。
164
- - 控制列宽、固定必要列,保证表格横向滚动体验;开启 `scroll: { x, y }`。
165
- - 远程筛选与分页尽量由后端承担,前端仅拼装参数。
166
- - 选择态相关操作尽量批量提交,减少重复请求。
167
-
168
- ## 13. 最小模板示例
169
-
170
- ```ts
171
- import {
172
- ListPage,
173
- type FilterFormOption,
174
- type ListPageTableColumn,
175
- type PaginationData,
176
- type ListPageContext,
177
- type ListPageFloatProps,
178
- type FloatComponentProps,
179
- } from "listpage-next";
180
- import { Button, Input, Select, Space } from "antd";
181
-
182
- type User = { id: number; name: string; address: string; active?: boolean };
183
- type UserFilters = { name?: string; status?: "enabled" | "disabled" };
184
-
185
- const filters: FilterFormOption[] = [
186
- {
187
- name: "name",
188
- label: "姓名",
189
- component: <Input placeholder="请输入" allowClear />,
190
- },
191
- {
192
- name: "status",
193
- label: "状态",
194
- component: (
195
- <Select
196
- allowClear
197
- options={[
198
- { value: "enabled", label: "启用" },
199
- { value: "disabled", label: "停用" },
200
- ]}
201
- />
202
- ),
203
- },
204
- ];
205
-
206
- const columns: ListPageTableColumn<User>[] = [
207
- { title: "姓名", dataIndex: "name", key: "name", component: "text" },
208
- { title: "地址", dataIndex: "address", key: "address", ellipsis: true },
209
- {
210
- title: '操作',
211
- key: 'actions',
212
- width: 160,
213
- fixed: 'right',
214
- component: ({ ctx, record }) => (
215
- <Space>
216
- <Button type="link" size="small" onClick={() => ctx.showFloat('edit', record, true)}>
217
- 编辑
218
- </Button>
219
- <Popconfirm
220
- title="确定要删除吗?"
221
- onConfirm={async () => {
222
- await api.sales.remove(record.id);
223
- message.success('删除成功');
224
- ctx.refreshTable();
225
- }}
226
- >
227
- <Button type="link" size="small" danger>
228
- 删除
229
- </Button>
230
- </Popconfirm>
231
- </Space>
232
- ),
233
- },
234
- ];
235
-
236
- const EditorModal = (props: FloatComponentProps<User>) => {
237
- return <Modal></Modal>
238
- }
239
-
240
- const EditorDrawer = (props: FloatComponentProps<User>) => {
241
- return <Drawer></Drawer>
242
- }
243
-
244
-
245
- const floats: ListPageFloatProps[] = [
246
- { key: 'edit', render: EditorModal },
247
- { key: 'create', render: EditorDrawer },
248
- ];
249
-
250
- const request = async (
251
- params: { current?: number; pageSize?: number },
252
- filters: UserFilters
253
- ): Promise<PaginationData<User>> => {
254
- const { current = 1, pageSize = 10 } = params || {};
255
- const all: User[] = Array.from({ length: 100 }).map((_, i) => ({
256
- id: i + 1,
257
- name: `Edward ${i + 1}`,
258
- address: `Address ${i + 1}`,
259
- active: i % 2 === 0,
260
- }));
261
- let list = all.filter(
262
- (r) => !filters?.name || r.name.includes(filters.name!)
263
- );
264
- const skip = (current - 1) * pageSize;
265
- return {
266
- list: list.slice(skip, skip + pageSize),
267
- total: list.length,
268
- current,
269
- pageSize,
270
- };
271
- };
272
-
273
- export default function Page() {
274
- return (
275
- <ListPage
276
- storageKey="demo-listpage"
277
- initialValues={{ currentPage: 1, pageSize: 10 }}
278
- request={request}
279
- header={{
280
- title: "用户列表",
281
- extra: (ctx: ListPageContext) => (
282
- <Space>
283
- <Button onClick={() => ctx.refreshTable()}>刷新</Button>
284
- </Space>
285
- ),
286
- }}
287
- filter={{ options: filters }}
288
- table={{ columns, tableProps: { rowKey: "id" } }}
289
- />
290
- );
291
- }
292
- ```
293
-
294
- ## 14. 生成检查清单(AI 必须自检)
295
-
296
- - `storageKey` 唯一且语义化(如 `LISTPAGE_{FEATURE}`)。
297
- - `initialValues` 首次挂载设置分页与 `filterValues`(如需)。
298
- - `request(params, filters)` 返回 `PaginationData<T>`,失败返回空结构。
299
- - `filter.options` 定义完整,异步选择遵循返回结构;日期区间用 `daterange` 并在请求中展开。
300
- - `table.columns` 使用原子组件或 `render`;关键列固定、宽度合理、开启 `ellipsis`。
301
- - 设置 `tableProps.rowKey` 与分页/滚动;选择态按需开启 `rowSelectionType` 与 `rowTitleKey`。
302
- - 页面级动作在 `header.extra`;表格级动作在 `table.extra`;浮层通过 `floats` 管理。
303
- - 成功后 `message.success` ;危险操作二次确认。
304
-
305
- 本规范自发布之日起执行,后续将根据使用反馈迭代完善。