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 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 projectMode = args.includes("--project");
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 = projectMode
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "listpage_cli",
3
- "version": "0.0.291",
3
+ "version": "0.0.292",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "listpage_cli": "bin/cli.js"
@@ -5,15 +5,22 @@ description: 使用 listpage-next 实现页面级列表/表格。Use ONLY when u
5
5
 
6
6
  # ListPage 使用技能
7
7
 
8
- 使用 `listpage-next`。示例与 API 见 [examples.md](examples.md)、[api.md](api.md)。
8
+ 示例与 API 见 [examples.md](examples.md)、[api.md](api.md)。
9
9
 
10
- ## 核心要点
10
+ ## 生成流程(7 步)
11
11
 
12
- - **request**:`(pageParams, filterValues) => Promise<{ list, total, current, pageSize }>`
13
- - **必配**:`request`、`table.columns`、`table.tableProps.rowKey`
14
- - **浮窗**:`floats=[{ key, render }]`,`ctx.showFloat(key, record, true)` 关闭后刷新
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
- - **简单页面**:单文件内联 columns、filters、request
19
- - **复杂页面**(筛选项 > 3、列 > 5、浮窗 > 1):`pages/<Feature>/config/{columns,filters,floats,request}.tsx` + `components/`
25
+ - 浮窗 key:短横线小写(如 `create`、`edit-detail`)
26
+ - 文件:小驼峰(`columns.tsx`、`filters.tsx`)
@@ -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
- | `styles` | 页面容器样式覆盖 | `{ page?: React.CSSProperties }` | - | 否 |
8
- | `storageKey` | 本地存储键名(分页/筛选持久化) | `string` | - | 否 |
9
- | `initialValues` | 初始值(在无 `state` 与缓存时生效) | `{ filterValues?: any; pageSize?: number; currentPage?: number }` | - | 否 |
10
- | `state` | 外部初始化值(优先级最高,非受控) | `{ filterValues?: any; pageSize?: number; currentPage?: number }` | - | 否 |
11
- | `request` | 数据请求函数,返回分页数据 | `(pageParams: { current?: number; pageSize?: number; sort?: string }, filterValues: any) => Promise<{ total: number; pageSize: number; current: number; list: any[] }>` | - | 是 |
12
- | `header` | 页面标题与右侧操作 | `{ title?: React.ReactNode; extra?: React.ReactNode | ((ctx) => React.ReactNode) }` | - | 否 |
13
- | `filter` | 筛选配置 | `{ labelInline?: boolean; options: FilterFormOption[]; onReset?: () => void; onChange?: (value) => void }` | - | 否 |
14
- | `toolbar` | 页面级工具栏 | `{ render: (ctx) => React.ReactNode }` | - | 否 |
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
- | `key` | 浮窗标识,短横线小写 | `string` |
73
- | `render` | 浮窗组件渲染函数 | `(props: { record: any; visible: boolean; onClose: () => void }) => JSX.Element` |
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
- 调用方式:在列或工具栏中使用 `ctx.showFloat(key, record, onCloseCallback)`;传 `true` 关闭后自动刷新列表,传函数自定义回调。
48
+ ---
76
49
 
77
- ## ListPageStore(上下文)
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
- | `filters` | 当前筛选条件 | `any` |
82
- | `dataSource` | 表格数据源 | `any[]` |
83
- | `pagination` | 分页信息 | `{ current: number; pageSize: number; total: number }` |
84
- | `loadingData` | 加载状态 | `boolean` |
85
- | `selection` | 选择集 | `{ selectedRowKeys: Key[]; selectedRows: any[] }` |
86
- | `submitFilters` | 提交筛选并重置页码 | `(value) => void` |
87
- | `showFloat` | 打开浮窗 | `(key: string, record: any, onCloseCallback?: true | (() => void)) => void` |
88
- | `hideFloat` | 关闭浮窗 | `() => void` |
89
- | `updatePage` | 更新页码与页大小 | `(page: number, pageSize: number) => void` |
90
- | `fetchTableData` | 拉取数据 | `() => Promise<void>` |
91
- | `refreshTable` | 刷新数据 | `() => void` |
92
- | `listen/emit` | 事件机制 | `(name: string, cb: (data:any)=>void) / (name: string, data:any) => void` |
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, type FilterFormOption, type ListPageTableColumn } from 'listpage-next';
7
- import { Button, Input, Select, Space } from 'antd';
8
-
9
- const filters: FilterFormOption[] = [
10
- { name: 'name', label: '姓名', component: <Input placeholder="请输入" /> },
11
- { name: 'status', label: '状态', component: <Select allowClear options={[{ value: 'enabled', label: '启用' }, { value: 'disabled', label: '停用' }]} /> },
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: ListPageTableColumn[] = [
15
- { title: '姓名', dataIndex: 'name', key: 'name', component: 'text' },
16
- { title: '地址', dataIndex: 'address', key: 'address', ellipsis: true },
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 ({ current = 1, pageSize = 10 }, filterValues: any) => {
20
- const all = Array.from({ length: 100 }).map((_, i) => ({ id: i + 1, name: `Edward ${i + 1}`, address: `Address ${i + 1}` }));
21
- let list = all.filter((r) => !filterValues?.name || r.name.includes(filterValues.name));
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, current, pageSize };
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
- storageKey="demo-listpage"
30
- initialValues={{ currentPage: 1, pageSize: 10 }}
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
- header={{ title: '用户列表', extra: (ctx) => <Space><Button onClick={() => ctx.refreshTable()}>刷新</Button></Space> }}
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. 带浮窗(新增 Drawer)
66
+ ## 2. 复杂用法(内置渲染器、toolbar、多浮窗)
41
67
 
42
- ```tsx
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
- const request = async ({ current = 1, pageSize = 10 }, f: any) => {
53
- const list = [{ id: 1, name: 'Test' }];
54
- return { list, total: 1, current, pageSize };
55
- };
56
-
57
- const CreateDrawer = (props: { record: any; visible: boolean; onClose: () => void }) => {
58
- const { visible, onClose } = props;
59
- return (
60
- <Drawer open={visible} title="新增" onClose={onClose}>
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
- export default function Page() {
68
- return (
69
- <ListPage
70
- storageKey="LISTPAGE_USER"
71
- request={request}
72
- header={{ title: '用户', extra: (ctx) => <Button type="primary" onClick={() => ctx.showFloat('create', {}, true)}>新增</Button> }}
73
- filter={{ options: filters }}
74
- table={{ columns, tableProps: { rowKey: 'id' } }}
75
- floats={[{ key: 'create', render: CreateDrawer }]}
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
- table={{
85
- columns,
86
- rowSelectionType: 'checkbox',
87
- rowTitleKey: 'name',
88
- tableProps: { rowKey: 'id' },
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. 带 toolbar
168
+ ## 4. 带行选择
93
169
 
94
170
  ```tsx
95
- toolbar={{
96
- render: (ctx) => (
97
- <Space>
98
- <Button onClick={() => ctx.refreshTable()}>刷新</Button>
99
- <Button>导出</Button>
100
- </Space>
101
- ),
171
+ table={{
172
+ columns,
173
+ rowKey: "id",
174
+ rowSelection: true,
175
+ rowTitleKey: "name",
102
176
  }}
103
177
  ```
104
178
 
105
- ## 5. 复杂结构(config 拆分目录)
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 './columns';
126
- export { filters } from './filters';
127
- export { floats } from './floats';
128
- export { request } from './request';
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 'listpage-next';
135
- import { columns, filters, floats, request } from './config';
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
- storageKey="LISTPAGE_MEMBER"
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
  }
@@ -25,7 +25,7 @@
25
25
  "class-transformer": "^0.5.1",
26
26
  "class-validator": "~0.14.2",
27
27
  "rxjs": "^7.8.1",
28
- "listpage-next-nest": "~0.0.291"
28
+ "listpage-next-nest": "~0.0.292"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@nestjs/schematics": "^11.0.0",
@@ -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.291",
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.291",
26
+ "listpage-components": "~0.0.292",
27
27
  "lucide-react": "~0.575.0"
28
28
  "mobx-react-lite": "~4.1.1"
29
29
  },
@@ -9,7 +9,7 @@
9
9
  "publish": "ts-node src/publish.ts"
10
10
  },
11
11
  "dependencies": {
12
- "listpage-next-deploy": "0.0.291"
12
+ "listpage-next-deploy": "0.0.292"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^20.0.0",