listpage_cli 0.0.291 → 0.0.292
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/bin/cli.js +2 -6
- package/package.json +1 -1
- package/skills/listpage/SKILL.md +15 -8
- package/skills/listpage/api.md +155 -84
- package/skills/listpage/examples.md +175 -81
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/frontend-template/package.json.tmpl +2 -2
- package/templates/package-app-template/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
|
-
const os_1 = __importDefault(require("os"));
|
|
9
8
|
const path_1 = __importDefault(require("path"));
|
|
10
9
|
const prompts_1 = require("./prompts");
|
|
11
10
|
const copy_1 = require("./copy");
|
|
@@ -71,16 +70,13 @@ async function syncDocsCmd() {
|
|
|
71
70
|
}
|
|
72
71
|
async function installSkillCmd() {
|
|
73
72
|
const args = process.argv.slice(2);
|
|
74
|
-
const
|
|
75
|
-
const skillName = args.find((a) => a !== "install-skill" && a !== "--project") || "listpage";
|
|
73
|
+
const skillName = args.find((a) => a !== "install-skill") || "test";
|
|
76
74
|
const sourceDir = path_1.default.join(__dirname, "..", "skills", skillName);
|
|
77
75
|
if (!(0, fs_1.existsSync)(sourceDir)) {
|
|
78
76
|
console.error(`技能 ${skillName} 不存在`);
|
|
79
77
|
process.exit(1);
|
|
80
78
|
}
|
|
81
|
-
const targetDir =
|
|
82
|
-
? path_1.default.join(process.cwd(), ".cursor", "skills", skillName)
|
|
83
|
-
: path_1.default.join(os_1.default.homedir(), ".cursor", "skills", skillName);
|
|
79
|
+
const targetDir = path_1.default.join(process.cwd(), ".cursor", "skills", skillName || "");
|
|
84
80
|
(0, copy_1.copySkillDir)(sourceDir, targetDir);
|
|
85
81
|
console.log(`已安装 ${skillName} 技能到 ${targetDir}`);
|
|
86
82
|
}
|
package/package.json
CHANGED
package/skills/listpage/SKILL.md
CHANGED
|
@@ -5,15 +5,22 @@ description: 使用 listpage-next 实现页面级列表/表格。Use ONLY when u
|
|
|
5
5
|
|
|
6
6
|
# ListPage 使用技能
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
示例与 API 见 [examples.md](examples.md)、[api.md](api.md)。
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## 生成流程(7 步)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
12
|
+
```
|
|
13
|
+
Task Progress:
|
|
14
|
+
- [ ] 步骤 1:创建目录结构(简单页面单文件;复杂页面 config/ + components/)
|
|
15
|
+
- [ ] 步骤 2:定义 filters(FilterFormOption[],AntD 组件 + allowClear)
|
|
16
|
+
- [ ] 步骤 3:定义 columns (ListPageTableColumn[],优先 component: 'text'|'link'|'switch')
|
|
17
|
+
- [ ] 步骤 4:实现 request((params, filters) => Promise<PaginationData<T>>)
|
|
18
|
+
- [ ] 步骤 5:组装 index.tsx(initialValues、注入 request/filter/table/floats )
|
|
19
|
+
- [ ] 步骤 6:注册 floats(若有浮窗:floats=[{ key, render }],ctx.showFloat(key, record, true))
|
|
20
|
+
- [ ] 步骤 7:自检(rowKey、storageKey 唯一、浮窗 key 短横线小写)
|
|
21
|
+
```
|
|
15
22
|
|
|
16
|
-
##
|
|
23
|
+
## 命名约定
|
|
17
24
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
25
|
+
- 浮窗 key:短横线小写(如 `create`、`edit-detail`)
|
|
26
|
+
- 文件:小驼峰(`columns.tsx`、`filters.tsx`)
|
package/skills/listpage/api.md
CHANGED
|
@@ -1,92 +1,163 @@
|
|
|
1
|
-
# API
|
|
1
|
+
# API(listpage-components)
|
|
2
|
+
|
|
3
|
+
基于 `listpage-components` 的 ListPage Props 及使用中涉及的类型。
|
|
2
4
|
|
|
3
5
|
## ListPageProps
|
|
4
6
|
|
|
5
|
-
| 属性 | 说明 | 类型 |
|
|
6
|
-
| --- | --- | --- | --- |
|
|
7
|
-
| `
|
|
8
|
-
| `
|
|
9
|
-
| `
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `table` | 表格配置 | `DataTableProps` | - | 是 |
|
|
16
|
-
| `floats` | 浮窗定义 | `Array<{ key: string; render: (props: FloatComponentProps) => JSX.Element }>` | - | 否 |
|
|
17
|
-
|
|
18
|
-
## FilterFormOption
|
|
19
|
-
|
|
20
|
-
| 属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
21
|
-
| --- | --- | --- | --- | --- |
|
|
22
|
-
| `name` | 字段名 | `string` | - | 是 |
|
|
23
|
-
| `label` | 标签 | `React.ReactNode` | - | 否 |
|
|
24
|
-
| `component` | 组件类型或自定义节点 | `'input' | 'select' | 'async-select' | 'daterange' | ReactElement` | `'input'` | 否 |
|
|
25
|
-
| `colSpan` | 栅格占比(6 表示半行) | `number` | `2` | 否 |
|
|
26
|
-
| `props` | 组件 props(随类型变化) | `ComponentProps<Type>` | - | 否 |
|
|
27
|
-
| `formItemProps` | antd `Form.Item` 属性(去除 children) | `Omit<FormItemProps, 'children'>` | - | 否 |
|
|
28
|
-
|
|
29
|
-
组件映射与值约定:
|
|
30
|
-
- `'input'` → antd `Input`
|
|
31
|
-
- `'select'` → antd `Select`
|
|
32
|
-
- `'async-select'` → 组件库 `AsyncSelect`,`request` 返回 `{ list, total, current, pageSize }`
|
|
33
|
-
- `'daterange'` → 增强版 `RangePicker`,提交值为 `[start: string, end: string]`
|
|
34
|
-
|
|
35
|
-
## DataTableProps(`ListPage.table`)
|
|
36
|
-
|
|
37
|
-
| 属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
38
|
-
| --- | --- | --- | --- | --- |
|
|
39
|
-
| `columns` | 列配置 | `ListPageTableColumn[]` | `[]` | 否 |
|
|
40
|
-
| `title` | 表格卡片标题 | `React.ReactNode` | - | 否 |
|
|
41
|
-
| `extra` | 标题右侧操作(函数可访问 ctx) | `React.ReactNode | ((ctx) => React.ReactNode)` | - | 否 |
|
|
42
|
-
| `tableProps` | antd `Table` 其他属性(除 `dataSource/title/columns`) | `Omit<TableProps, 'dataSource' | 'title' | 'columns'>` | - | 否 |
|
|
43
|
-
| `rowSelectionType` | 选择类型 | `'checkbox' | 'radio'` | - | 否 |
|
|
44
|
-
| `rowTitleKey` | 标题展示所选关键字段 | `string` | - | 否 |
|
|
45
|
-
|
|
46
|
-
分页说明:底部中文本地化;`pageSizeOptions=[10,20,50,100]`;`showTotal="start-end 共 total 条"`;`tableProps.pagination===false` 隐藏分页。
|
|
47
|
-
|
|
48
|
-
## ListPageTableColumn
|
|
49
|
-
|
|
50
|
-
| 属性 | 说明 | 类型 | 默认值 | 必填 |
|
|
51
|
-
| --- | --- | --- | --- | --- |
|
|
52
|
-
| `title` | 列头 | `React.ReactNode` | - | 否 |
|
|
53
|
-
| `dataIndex` | 数据字段 | `string` | - | 否 |
|
|
54
|
-
| `key` | 唯一键 | `string` | - | 否 |
|
|
55
|
-
| `width` | 列宽 | `number` | - | 否 |
|
|
56
|
-
| `fixed` | 固定列 | `'left' | 'right'` | - | 否 |
|
|
57
|
-
| `ellipsis` | 省略展示 | `boolean` | - | 否 |
|
|
58
|
-
| `component` | 内置或自定义渲染 | `'text' | 'time' | 'tag' | 'link' | 'switch' | ((value, record, index, ctx) => React.ReactNode)` | - | 否 |
|
|
59
|
-
| `props` | 随 `component` 类型变化的 props | `any` | - | 否 |
|
|
60
|
-
|
|
61
|
-
内置渲染 props 说明:
|
|
62
|
-
- `text`:透传 `Typography.Text`,默认 `ellipsis={{ tooltip: true }}`
|
|
63
|
-
- `time`:`{ format?: string }`,无值显示 `'-'`
|
|
64
|
-
- `tag`:透传 `TagProps`
|
|
65
|
-
- `link`:`{ titlePropName?: string | (record)=>string; hrefPropName?: string | (record)=>string; target?: HTMLAttributeAnchorTarget }`
|
|
66
|
-
- `switch`:`{ syncChange?: (value: boolean, record) => Promise<void> }`
|
|
67
|
-
|
|
68
|
-
## Floats(浮窗)
|
|
7
|
+
| 属性 | 说明 | 类型 | 必填 |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| `title` | 页面标题 | `ReactNode` | 否 |
|
|
10
|
+
| `extra` | 标题右侧操作区 | `ReactNode \| (ctx: ListPageContextValue) => ReactNode` | 否 |
|
|
11
|
+
| `filter` | 筛选配置 | `FilterConfig \| FilterRenderConfig` | 否 |
|
|
12
|
+
| `toolbar` | 工具栏 | `ToolbarRenderConfig` | 否 |
|
|
13
|
+
| `table` | 表格配置 | `TableConfig \| TableRenderConfig` | 是 |
|
|
14
|
+
| `request` | 数据请求 | `ListPageRequest` | 是 |
|
|
15
|
+
| `floats` | 浮窗列表 | `FloatItem[]` | 否 |
|
|
16
|
+
| `initialValues` | 初始值 | `InitialValues` | 否 |
|
|
69
17
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## InitialValues
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
type InitialValues = {
|
|
24
|
+
filterValues?: any; // 预填筛选
|
|
25
|
+
pageSize?: number; // 默认每页条数
|
|
26
|
+
currentPage?: number; // 默认当前页
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## filter(FilterConfig)
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
type FilterConfig = { items: FilterItem[] };
|
|
36
|
+
|
|
37
|
+
type FilterItem = {
|
|
38
|
+
name: string;
|
|
39
|
+
label?: ReactNode;
|
|
40
|
+
component?: ReactElement; // antd Input、Select 等
|
|
41
|
+
colSpan?: number;
|
|
42
|
+
formItemProps?: Omit<FormItemProps, 'children'>;
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
也可使用 `FilterRenderConfig`:`{ render: (props: FilterRenderProps) => ReactNode }` 自定义渲染。
|
|
74
47
|
|
|
75
|
-
|
|
48
|
+
---
|
|
76
49
|
|
|
77
|
-
##
|
|
50
|
+
## toolbar(ToolbarRenderConfig)
|
|
78
51
|
|
|
79
|
-
|
|
52
|
+
```ts
|
|
53
|
+
type ToolbarRenderConfig = {
|
|
54
|
+
render: (props: ToolbarRenderProps) => ReactNode;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type ToolbarRenderProps = {
|
|
58
|
+
ctx: ListPageContextValue;
|
|
59
|
+
selection?: SelectionState; // { selectedRowKeys; selectedRows }
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## table(TableConfig)
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
type TableConfig = {
|
|
69
|
+
columns: ListPageColumnType[];
|
|
70
|
+
rowKey?: string;
|
|
71
|
+
rowSelection?: boolean | object;
|
|
72
|
+
title?: ReactNode; // 表格标题
|
|
73
|
+
extra?: ReactNode | (ctx: ListPageContextValue) => ReactNode;
|
|
74
|
+
rowTitleKey?: string; // 选中项 Tooltip 展示的字段
|
|
75
|
+
pagination?: PaginationProps; // antd 分页配置
|
|
76
|
+
[key: string]: any; // 透传至 antd Table
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## ListPageColumnType
|
|
83
|
+
|
|
84
|
+
继承 antd `ColumnType`,额外支持:
|
|
85
|
+
|
|
86
|
+
| 属性 | 说明 | 类型 |
|
|
80
87
|
| --- | --- | --- |
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
| `renderer` | 内置渲染器 | `ColumnRendererType` |
|
|
89
|
+
| `rendererOptions` | 渲染器配置 | `ColumnRendererOptions[renderer]` |
|
|
90
|
+
| `component` | 自定义渲染(优先于 renderer) | `(props: ColumnComponentProps) => ReactNode` |
|
|
91
|
+
|
|
92
|
+
**ColumnComponentProps**:`{ ctx: ListPageContextValue; record; index; value }`
|
|
93
|
+
|
|
94
|
+
**ColumnRendererType**:`'text' | 'ellipsis' | 'tag' | 'badge' | 'date' | 'datetime' | 'number' | 'money' | 'percent' | 'boolean' | 'link' | 'enum' | 'copyable' | 'avatar' | 'image'`
|
|
95
|
+
|
|
96
|
+
**ColumnRendererOptions**(按 renderer 选配):
|
|
97
|
+
- `ellipsis`: `{ maxLength?: number }`
|
|
98
|
+
- `tag`: `{ colorMap?: Record<string, string>; options?: Array<{ value; label; color? }> }`
|
|
99
|
+
- `badge`: `{ colorMap }`
|
|
100
|
+
- `date` / `datetime`: `{ format?: string }`
|
|
101
|
+
- `number` / `money` / `percent`: `{ precision?: number }`,`money` 额外 `{ prefix?: string }`
|
|
102
|
+
- `boolean`: `{ labels?: [string, string] }`
|
|
103
|
+
- `link`: `{ onClick?: (record, ctx?) => void; href?: (record) => string }`
|
|
104
|
+
- `enum`: `{ options: Array<{ value; label }> }`
|
|
105
|
+
- `avatar`: `{ fallback?: string }`
|
|
106
|
+
- `image`: `{ width?: number; height?: number }`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## request(ListPageRequest)
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
type ListPageRequest = (
|
|
114
|
+
pageParams: RequestParams,
|
|
115
|
+
filterValues: any,
|
|
116
|
+
sortParams: SortParams
|
|
117
|
+
) => Promise<{ list: any[]; total: number }>;
|
|
118
|
+
|
|
119
|
+
type RequestParams = { current: number; pageSize: number };
|
|
120
|
+
type SortParams = { field?: string; order?: 'ascend' | 'descend' | null };
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## floats(FloatItem)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
type FloatItem = {
|
|
129
|
+
key: string;
|
|
130
|
+
render: (props: FloatComponentProps) => ReactNode;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type FloatComponentProps<T = any> = {
|
|
134
|
+
record: T;
|
|
135
|
+
visible: boolean;
|
|
136
|
+
onClose: (code?: number) => void | (() => void);
|
|
137
|
+
// (code?: number) => void:onClose() 取消不刷新,onClose(1) 确认并刷新
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ListPageContextValue
|
|
144
|
+
|
|
145
|
+
`extra`、`toolbar.render`、`table.extra`、列 `component` 中通过 `useListPageContext()` 获取。
|
|
146
|
+
|
|
147
|
+
| 属性 | 说明 |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `filters` | 当前筛选值 |
|
|
150
|
+
| `initialFilterValues` | 初始筛选值 |
|
|
151
|
+
| `pagination` | `{ current; pageSize; total }` |
|
|
152
|
+
| `dataSource` | 表格数据 |
|
|
153
|
+
| `loading` | 加载中 |
|
|
154
|
+
| `selection` | `{ selectedRowKeys; selectedRows }` |
|
|
155
|
+
| `sort` | `{ field?; order? }` |
|
|
156
|
+
| `refreshTable` | 刷新表格 |
|
|
157
|
+
| `submitFilters` | 提交筛选并刷新 |
|
|
158
|
+
| `resetFilters` | 重置筛选 |
|
|
159
|
+
| `updatePagination` | 更新分页 `(page, pageSize)` |
|
|
160
|
+
| `updateSort` | 更新排序 `(field?, order?)` |
|
|
161
|
+
| `showFloat` | 打开浮窗 `(key, record, onCloseCallback?: true \| (() => void))` |
|
|
162
|
+
| `hideFloat` | 关闭浮窗 |
|
|
163
|
+
| `onSelectionChange` | 选中变更 `(keys, rows)` |
|
|
@@ -1,108 +1,202 @@
|
|
|
1
1
|
# ListPage 示例
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1. 最小示例(单文件内联,listpage-components)
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
import { ListPage
|
|
7
|
-
import { Button, Input, Select, Space } from
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
{ name:
|
|
11
|
-
{
|
|
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
|
+
},
|
|
12
24
|
];
|
|
13
25
|
|
|
14
|
-
const columns
|
|
15
|
-
{ title:
|
|
16
|
-
{ title:
|
|
26
|
+
const columns = [
|
|
27
|
+
{ title: "姓名", dataIndex: "name", key: "name", renderer: "text" },
|
|
28
|
+
{ title: "地址", dataIndex: "address", key: "address", ellipsis: true },
|
|
17
29
|
];
|
|
18
30
|
|
|
19
|
-
const request = async (
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
);
|
|
22
44
|
const skip = (current - 1) * pageSize;
|
|
23
|
-
return { list: list.slice(skip, skip + pageSize), total: list.length
|
|
45
|
+
return { list: list.slice(skip, skip + pageSize), total: list.length };
|
|
24
46
|
};
|
|
25
47
|
|
|
26
48
|
export default function Page() {
|
|
27
49
|
return (
|
|
28
50
|
<ListPage
|
|
29
|
-
|
|
30
|
-
|
|
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" }}
|
|
31
59
|
request={request}
|
|
32
|
-
|
|
33
|
-
filter={{ options: filters }}
|
|
34
|
-
table={{ columns, tableProps: { rowKey: 'id' } }}
|
|
60
|
+
initialValues={{ currentPage: 1, pageSize: 10 }}
|
|
35
61
|
/>
|
|
36
62
|
);
|
|
37
63
|
}
|
|
38
64
|
```
|
|
39
65
|
|
|
40
|
-
## 2.
|
|
66
|
+
## 2. 复杂用法(内置渲染器、toolbar、多浮窗)
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
import { ListPage, type FilterFormOption, type ListPageTableColumn } from 'listpage-next';
|
|
44
|
-
import { Button, Input, Drawer, Space } from 'antd';
|
|
45
|
-
import { useState } from 'react';
|
|
46
|
-
|
|
47
|
-
const filters: FilterFormOption[] = [{ name: 'name', label: '姓名', component: <Input placeholder="请输入" /> }];
|
|
48
|
-
const columns: ListPageTableColumn[] = [
|
|
49
|
-
{ title: '姓名', dataIndex: 'name', key: 'name', component: 'text' },
|
|
50
|
-
];
|
|
68
|
+
内置列渲染器:`text`、`ellipsis`、`tag`、`badge`、`date`、`datetime`、`number`、`money`、`percent`、`boolean`、`link`、`enum`、`copyable`、`avatar`、`image`。浮窗 `onClose(1)` 表示确认并刷新,`onClose()` 表示取消。
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Input placeholder="姓名" />
|
|
62
|
-
<Button type="primary" onClick={onClose}>保存</Button>
|
|
63
|
-
</Drawer>
|
|
64
|
-
);
|
|
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 };
|
|
65
79
|
};
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
);
|
|
79
148
|
```
|
|
80
149
|
|
|
81
|
-
## 3.
|
|
150
|
+
## 3. 带初始状态(initialValues)
|
|
151
|
+
|
|
152
|
+
`initialValues` 用于预填筛选条件、分页等,组件首次挂载时生效。
|
|
82
153
|
|
|
83
154
|
```tsx
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
/>
|
|
90
166
|
```
|
|
91
167
|
|
|
92
|
-
## 4.
|
|
168
|
+
## 4. 带行选择
|
|
93
169
|
|
|
94
170
|
```tsx
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</Space>
|
|
101
|
-
),
|
|
171
|
+
table={{
|
|
172
|
+
columns,
|
|
173
|
+
rowKey: "id",
|
|
174
|
+
rowSelection: true,
|
|
175
|
+
rowTitleKey: "name",
|
|
102
176
|
}}
|
|
103
177
|
```
|
|
104
178
|
|
|
105
|
-
## 5.
|
|
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 拆分目录)
|
|
106
200
|
|
|
107
201
|
目录结构:
|
|
108
202
|
|
|
@@ -122,27 +216,27 @@ pages/MemberList/
|
|
|
122
216
|
**config/index.tsx** 汇总导出:
|
|
123
217
|
|
|
124
218
|
```tsx
|
|
125
|
-
export { columns } from
|
|
126
|
-
export { filters } from
|
|
127
|
-
export { floats } from
|
|
128
|
-
export { request } from
|
|
219
|
+
export { columns } from "./columns";
|
|
220
|
+
export { filters } from "./filters";
|
|
221
|
+
export { floats } from "./floats";
|
|
222
|
+
export { request } from "./request";
|
|
129
223
|
```
|
|
130
224
|
|
|
131
|
-
**index.tsx**
|
|
225
|
+
**index.tsx** 组装(listpage-components):
|
|
132
226
|
|
|
133
227
|
```tsx
|
|
134
|
-
import { ListPage } from
|
|
135
|
-
import { columns, filters, floats, request } from
|
|
228
|
+
import { ListPage } from "listpage-components";
|
|
229
|
+
import { columns, filters, floats, request } from "./config";
|
|
136
230
|
|
|
137
231
|
export default function MemberListPage() {
|
|
138
232
|
return (
|
|
139
233
|
<ListPage
|
|
140
|
-
|
|
234
|
+
title="会员列表"
|
|
235
|
+
filter={{ items: filters }}
|
|
236
|
+
table={{ columns, rowKey: "id" }}
|
|
141
237
|
request={request}
|
|
142
|
-
header={{ title: '会员列表' }}
|
|
143
|
-
filter={{ options: filters }}
|
|
144
|
-
table={{ columns, tableProps: { rowKey: 'id' } }}
|
|
145
238
|
floats={floats}
|
|
239
|
+
initialValues={{ pageSize: 10, currentPage: 1 }}
|
|
146
240
|
/>
|
|
147
241
|
);
|
|
148
242
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"react": "^19.2.0",
|
|
14
14
|
"react-dom": "^19.2.0",
|
|
15
|
-
"listpage-next": "~0.0.
|
|
15
|
+
"listpage-next": "~0.0.292",
|
|
16
16
|
"react-router-dom": ">=6.0.0",
|
|
17
17
|
"@ant-design/v5-patch-for-react-19": "~1.0.3",
|
|
18
18
|
"ahooks": "^3.9.5",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"styled-components": "^6.1.19",
|
|
24
24
|
"mobx": "~6.15.0",
|
|
25
25
|
"@ant-design/icons": "~6.0.2",
|
|
26
|
-
"listpage-components": "~0.0.
|
|
26
|
+
"listpage-components": "~0.0.292",
|
|
27
27
|
"lucide-react": "~0.575.0"
|
|
28
28
|
"mobx-react-lite": "~4.1.1"
|
|
29
29
|
},
|