crud-page-react 0.1.2 → 0.1.3
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/README.md +31 -280
- package/dist/index.esm.js +32 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +30 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,50 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
一个基于 React + Ant Design 的动态 CRUD 组件库,支持通过 JSON Schema 配置生成完整的增删改查界面。
|
|
4
4
|
|
|
5
|
-
## 🌐 在线演示
|
|
6
|
-
|
|
7
|
-
**[https://crud.fufanghao.space/](https://crud.fufanghao.space/)**
|
|
8
|
-
|
|
9
|
-
- 🎮 **Demo 示例** - 查看完整功能演示和字段类型
|
|
10
|
-
- 🛠️ **配置生成器** - 可视化配置工具,零代码生成 CRUD 页面
|
|
11
|
-
- 🤖 **AI 提示词** - 使用 AI 从接口文档生成配置
|
|
12
|
-
|
|
13
5
|
## 特性
|
|
14
6
|
|
|
15
|
-
- 🚀 **零代码配置** - 通过 JSON Schema
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- 🌐 **动态 URL 参数** - 支持任意字段作为 URL 参数 (`:id`, `:orderNo`, `:customerName` 等)
|
|
22
|
-
- ⚡ **自定义操作** - 支持配置自定义按钮操作和 API 调用
|
|
23
|
-
- 📱 **响应式设计** - 适配移动端和桌面端
|
|
24
|
-
- 🎯 **TypeScript 支持** - 完整的类型定义
|
|
7
|
+
- 🚀 **零代码配置** - 通过 JSON Schema 快速生成 CRUD 界面
|
|
8
|
+
- 🎨 **美观易用** - 基于 Ant Design 设计语言
|
|
9
|
+
- 🔧 **高度可定制** - 支持自定义 API、字段配置、操作按钮
|
|
10
|
+
- 📱 **响应式设计** - 自适应各种屏幕尺寸
|
|
11
|
+
- 🔒 **类型安全** - 完整的 TypeScript 类型定义
|
|
12
|
+
- 🎯 **操作分组** - 自定义操作自动折叠到下拉菜单
|
|
25
13
|
|
|
26
14
|
## 安装
|
|
27
15
|
|
|
28
16
|
```bash
|
|
29
17
|
npm install crud-page-react
|
|
30
|
-
# 或
|
|
31
|
-
yarn add crud-page-react
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 依赖要求
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"react": ">=16.8.0",
|
|
39
|
-
"react-dom": ">=16.8.0",
|
|
40
|
-
"antd": ">=5.0.0",
|
|
41
|
-
"dayjs": ">=1.11.0"
|
|
42
|
-
}
|
|
43
18
|
```
|
|
44
19
|
|
|
45
20
|
## 快速开始
|
|
46
21
|
|
|
47
|
-
### 基础用法
|
|
48
|
-
|
|
49
22
|
```tsx
|
|
50
23
|
import React from 'react';
|
|
51
24
|
import { CrudPage } from 'crud-page-react';
|
|
@@ -55,22 +28,10 @@ const schema: CrudPageSchema = {
|
|
|
55
28
|
id: 'users',
|
|
56
29
|
title: '用户管理',
|
|
57
30
|
api: {
|
|
58
|
-
list: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
create: {
|
|
63
|
-
url: '/api/users',
|
|
64
|
-
method: 'POST'
|
|
65
|
-
},
|
|
66
|
-
update: {
|
|
67
|
-
url: '/api/users/:id',
|
|
68
|
-
method: 'PUT'
|
|
69
|
-
},
|
|
70
|
-
delete: {
|
|
71
|
-
url: '/api/users/:id',
|
|
72
|
-
method: 'DELETE'
|
|
73
|
-
},
|
|
31
|
+
list: { url: '/api/users', method: 'GET' },
|
|
32
|
+
create: { url: '/api/users', method: 'POST' },
|
|
33
|
+
update: { url: '/api/users/:id', method: 'PUT' },
|
|
34
|
+
delete: { url: '/api/users/:id', method: 'DELETE' },
|
|
74
35
|
},
|
|
75
36
|
fields: [
|
|
76
37
|
{
|
|
@@ -95,260 +56,50 @@ const schema: CrudPageSchema = {
|
|
|
95
56
|
filter: true,
|
|
96
57
|
table: true,
|
|
97
58
|
form: { required: true },
|
|
98
|
-
rules: ['email'],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
key: 'status',
|
|
102
|
-
label: '状态',
|
|
103
|
-
type: 'string',
|
|
104
|
-
widget: 'select',
|
|
105
|
-
config: {
|
|
106
|
-
options: [
|
|
107
|
-
{ label: '启用', value: 'active' },
|
|
108
|
-
{ label: '禁用', value: 'inactive' },
|
|
109
|
-
],
|
|
110
|
-
},
|
|
111
|
-
filter: true,
|
|
112
|
-
table: true,
|
|
113
|
-
form: true,
|
|
114
59
|
},
|
|
115
60
|
],
|
|
116
61
|
rowKey: 'id',
|
|
117
62
|
};
|
|
118
63
|
|
|
119
64
|
function App() {
|
|
120
|
-
return
|
|
121
|
-
<div>
|
|
122
|
-
<CrudPage schema={schema} />
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
65
|
+
return <CrudPage schema={schema} />;
|
|
125
66
|
}
|
|
126
|
-
|
|
127
|
-
export default App;
|
|
128
67
|
```
|
|
129
68
|
|
|
130
|
-
|
|
69
|
+
## 新特性:操作按钮分组 (v0.1.3)
|
|
131
70
|
|
|
132
|
-
|
|
71
|
+
自定义操作现在会自动折叠到"其它操作"下拉菜单中,保持界面整洁:
|
|
133
72
|
|
|
134
73
|
```tsx
|
|
135
|
-
const
|
|
136
|
-
id: 'orders',
|
|
137
|
-
title: '订单管理',
|
|
138
|
-
api: {
|
|
139
|
-
// 基础配置
|
|
140
|
-
list: {
|
|
141
|
-
url: '/api/orders',
|
|
142
|
-
method: 'GET'
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
// 扩展配置
|
|
146
|
-
create: {
|
|
147
|
-
url: '/api/orders',
|
|
148
|
-
method: 'POST',
|
|
149
|
-
headers: {
|
|
150
|
-
'X-Request-Source': 'admin-panel'
|
|
151
|
-
},
|
|
152
|
-
data: {
|
|
153
|
-
source: 'web',
|
|
154
|
-
createdBy: '{{currentUser}}',
|
|
155
|
-
timestamp: '{{timestamp}}'
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
update: {
|
|
160
|
-
url: '/api/orders/:id',
|
|
161
|
-
method: 'PUT',
|
|
162
|
-
data: {
|
|
163
|
-
updatedBy: '{{currentUser}}',
|
|
164
|
-
updateTime: '{{timestamp}}'
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
delete: {
|
|
169
|
-
url: '/api/orders/:orderNo',
|
|
170
|
-
method: 'DELETE',
|
|
171
|
-
data: {
|
|
172
|
-
deletedBy: '{{currentUser}}',
|
|
173
|
-
deleteReason: '{{deleteReason}}',
|
|
174
|
-
timestamp: '{{timestamp}}'
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
},
|
|
74
|
+
const schema: CrudPageSchema = {
|
|
178
75
|
// ... 其他配置
|
|
179
|
-
};
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### 支持的配置选项
|
|
183
|
-
|
|
184
|
-
- **HTTP 方法**:GET、POST、PUT、PATCH、DELETE
|
|
185
|
-
- **自定义请求头**:添加认证、来源标识等
|
|
186
|
-
- **请求体数据**:固定参数和动态参数
|
|
187
|
-
- **模板变量**:`{{fieldName}}` 格式的动态值替换
|
|
188
|
-
- **URL 占位符**:`:fieldName` 格式的动态 URL 构建
|
|
189
|
-
|
|
190
|
-
### 💥 Breaking Changes (v0.1.0)
|
|
191
|
-
|
|
192
|
-
v0.1.0 版本移除了简单字符串配置支持,所有 API 配置必须使用对象格式:
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
// ❌ 不再支持 (v0.0.x)
|
|
196
|
-
api: {
|
|
197
|
-
list: '/api/users',
|
|
198
|
-
create: '/api/users'
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ✅ 必须使用 (v0.1.0+)
|
|
202
|
-
api: {
|
|
203
|
-
list: {
|
|
204
|
-
url: '/api/users',
|
|
205
|
-
method: 'GET'
|
|
206
|
-
},
|
|
207
|
-
create: {
|
|
208
|
-
url: '/api/users',
|
|
209
|
-
method: 'POST'
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## 动态 URL 参数
|
|
215
|
-
|
|
216
|
-
支持在 API 配置中使用任意字段作为 URL 参数:
|
|
217
|
-
|
|
218
|
-
```tsx
|
|
219
|
-
const schema = {
|
|
220
|
-
api: {
|
|
221
|
-
list: '/api/orders',
|
|
222
|
-
create: '/api/orders',
|
|
223
|
-
update: '/api/orders/:id', // 使用 id 字段
|
|
224
|
-
delete: '/api/orders/:orderNo', // 使用 orderNo 字段
|
|
225
|
-
detail: '/api/orders/:id',
|
|
226
|
-
},
|
|
227
76
|
actions: [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
key: 'notify',
|
|
240
|
-
label: '通知客户',
|
|
241
|
-
type: 'custom',
|
|
242
|
-
api: {
|
|
243
|
-
url: '/api/notify/:customerName', // 使用 customerName 字段
|
|
244
|
-
method: 'POST',
|
|
245
|
-
data: {
|
|
246
|
-
message: '您的订单状态已更新'
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
77
|
+
// 基础操作 - 显示为独立按钮
|
|
78
|
+
{ key: 'view', label: '查看', type: 'view' },
|
|
79
|
+
{ key: 'edit', label: '编辑', type: 'edit' },
|
|
80
|
+
{ key: 'delete', label: '删除', type: 'delete' },
|
|
81
|
+
|
|
82
|
+
// 自定义操作 - 自动折叠到下拉菜单
|
|
83
|
+
{ key: 'approve', label: '审批', type: 'custom', apiKey: 'approve' },
|
|
84
|
+
{ key: 'reject', label: '拒绝', type: 'custom', apiKey: 'reject', danger: true },
|
|
85
|
+
{ key: 'export', label: '导出', type: 'custom', apiKey: 'export' },
|
|
250
86
|
],
|
|
251
|
-
// ... 其他配置
|
|
252
|
-
};
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### 支持的占位符格式
|
|
256
|
-
- `:id` → 记录中的 `id` 字段值
|
|
257
|
-
- `:orderNo` → 记录中的 `orderNo` 字段值
|
|
258
|
-
- `:customerName` → 记录中的 `customerName` 字段值
|
|
259
|
-
- 任意 `:fieldName` → 记录中的 `fieldName` 字段值
|
|
260
|
-
|
|
261
|
-
## 自定义 API 请求
|
|
262
|
-
|
|
263
|
-
```tsx
|
|
264
|
-
import { CrudPage, ApiRequest } from 'crud-page-react';
|
|
265
|
-
|
|
266
|
-
// 自定义 API 请求函数
|
|
267
|
-
const customApiRequest: ApiRequest = async (url, options) => {
|
|
268
|
-
const token = localStorage.getItem('token');
|
|
269
|
-
const response = await fetch(url, {
|
|
270
|
-
headers: {
|
|
271
|
-
'Content-Type': 'application/json',
|
|
272
|
-
'Authorization': token ? `Bearer ${token}` : '',
|
|
273
|
-
...options?.headers,
|
|
274
|
-
},
|
|
275
|
-
...options,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
if (!response.ok) {
|
|
279
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return response.json();
|
|
283
87
|
};
|
|
284
|
-
|
|
285
|
-
// 使用自定义 API 请求
|
|
286
|
-
<CrudPage
|
|
287
|
-
schema={schema}
|
|
288
|
-
apiRequest={customApiRequest}
|
|
289
|
-
/>
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## API 文档
|
|
293
|
-
|
|
294
|
-
### CrudPageProps
|
|
295
|
-
|
|
296
|
-
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
297
|
-
|------|------|------|--------|------|
|
|
298
|
-
| schema | CrudPageSchema | ✓ | - | 页面配置 schema |
|
|
299
|
-
| initialData | Record<string, unknown>[] | ✗ | [] | 初始数据,API 失败时作为降级数据 |
|
|
300
|
-
| apiRequest | ApiRequest | ✗ | 内置 fetch | 自定义 API 请求函数 |
|
|
301
|
-
|
|
302
|
-
### ApiRequest
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
interface ApiRequest {
|
|
306
|
-
<T = unknown>(url: string, options?: RequestInit): Promise<T>;
|
|
307
|
-
}
|
|
308
88
|
```
|
|
309
89
|
|
|
310
|
-
###
|
|
311
|
-
|
|
312
|
-
完整的 schema 配置请参考类型定义文件。
|
|
313
|
-
|
|
314
|
-
## 字段类型支持
|
|
90
|
+
### 操作类型
|
|
315
91
|
|
|
316
|
-
- `
|
|
317
|
-
- `
|
|
318
|
-
-
|
|
319
|
-
- `date` - 日期
|
|
320
|
-
- `datetime` - 日期时间
|
|
321
|
-
- `array` - 数组
|
|
322
|
-
- `objectArray` - 对象数组
|
|
92
|
+
- **基础操作** (`view`, `edit`, `delete`) - 显示为独立的图标按钮
|
|
93
|
+
- **自定义操作** (`custom`) - 折叠到"其它操作"下拉菜单中
|
|
94
|
+
- **复制功能** - 始终在下拉菜单中提供"复制 JSON"功能
|
|
323
95
|
|
|
324
|
-
##
|
|
96
|
+
## 文档
|
|
325
97
|
|
|
326
|
-
|
|
327
|
-
- `textarea` - 文本域
|
|
328
|
-
- `inputNumber` - 数字输入
|
|
329
|
-
- `select` - 下拉选择
|
|
330
|
-
- `multiselect` - 多选下拉
|
|
331
|
-
- `radio` - 单选按钮
|
|
332
|
-
- `checkbox` - 复选框
|
|
333
|
-
- `switch` - 开关
|
|
334
|
-
- `datePicker` - 日期选择
|
|
335
|
-
- `rangePicker` - 日期范围
|
|
336
|
-
- `timePicker` - 时间选择
|
|
337
|
-
- `editableTable` - 可编辑表格
|
|
338
|
-
- `jsonInput` - JSON 编辑器
|
|
98
|
+
更多详细文档和示例,请查看 [example.md](./example.md)
|
|
339
99
|
|
|
340
|
-
##
|
|
100
|
+
## 更新日志
|
|
341
101
|
|
|
342
|
-
|
|
343
|
-
# 安装依赖
|
|
344
|
-
npm install
|
|
345
|
-
|
|
346
|
-
# 开发模式
|
|
347
|
-
npm run dev
|
|
348
|
-
|
|
349
|
-
# 构建
|
|
350
|
-
npm run build
|
|
351
|
-
```
|
|
102
|
+
查看 [CHANGELOG.md](./CHANGELOG.md) 了解版本更新内容。
|
|
352
103
|
|
|
353
104
|
## 许可证
|
|
354
105
|
|
package/dist/index.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
3
|
-
import { DatePicker, Form, Row, Col, Space, Button, Input, Switch, Radio, Select, InputNumber, Tooltip, Popconfirm, Table, message, Tag, Modal, Divider, Card, Checkbox, Typography } from 'antd';
|
|
4
|
-
import { SearchOutlined, ReloadOutlined, EyeOutlined, EditOutlined, DeleteOutlined,
|
|
3
|
+
import { DatePicker, Form, Row, Col, Space, Button, Input, Switch, Radio, Select, InputNumber, Tooltip, Popconfirm, Dropdown, Table, message, Tag, Modal, Divider, Card, Checkbox, Typography } from 'antd';
|
|
4
|
+
import { SearchOutlined, ReloadOutlined, CopyOutlined, EyeOutlined, EditOutlined, DeleteOutlined, MoreOutlined, FormOutlined, CodeOutlined, PlusOutlined } from '@ant-design/icons';
|
|
5
5
|
import dayjs from 'dayjs';
|
|
6
6
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
7
7
|
|
|
@@ -213,7 +213,7 @@ function renderCell(field, value) {
|
|
|
213
213
|
return jsx("span", { children: String(value) });
|
|
214
214
|
}
|
|
215
215
|
function DynamicTable({ schema, data, loading, pagination, onView, onEdit, onDelete, onCustomAction, }) {
|
|
216
|
-
var _a, _b, _c, _d
|
|
216
|
+
var _a, _b, _c, _d;
|
|
217
217
|
const tableFields = schema.fields.filter((f) => f.table !== false && f.table !== undefined);
|
|
218
218
|
const columns = tableFields.map((field) => {
|
|
219
219
|
const cfg = getTableConfig(field);
|
|
@@ -244,10 +244,32 @@ function DynamicTable({ schema, data, loading, pagination, onView, onEdit, onDel
|
|
|
244
244
|
title: '操作',
|
|
245
245
|
key: '__actions',
|
|
246
246
|
fixed: 'right',
|
|
247
|
-
width:
|
|
247
|
+
width: 200, // 固定宽度,因为现在有下拉菜单
|
|
248
248
|
render: (_, record) => {
|
|
249
249
|
var _a;
|
|
250
|
-
|
|
250
|
+
const actions = (_a = schema.actions) !== null && _a !== void 0 ? _a : [];
|
|
251
|
+
// 分离基础操作和自定义操作
|
|
252
|
+
const basicActions = actions.filter(action => action.type === 'view' || action.type === 'edit' || action.type === 'delete');
|
|
253
|
+
const customActions = actions.filter(action => action.type === 'custom');
|
|
254
|
+
// 构建下拉菜单项
|
|
255
|
+
const dropdownItems = [
|
|
256
|
+
...customActions.map(action => ({
|
|
257
|
+
key: action.key,
|
|
258
|
+
label: action.label,
|
|
259
|
+
onClick: () => onCustomAction === null || onCustomAction === void 0 ? void 0 : onCustomAction(action.key, record),
|
|
260
|
+
danger: action.danger,
|
|
261
|
+
})),
|
|
262
|
+
...(customActions.length > 0 ? [{
|
|
263
|
+
type: 'divider',
|
|
264
|
+
}] : []),
|
|
265
|
+
{
|
|
266
|
+
key: 'copy-json',
|
|
267
|
+
label: '复制 JSON',
|
|
268
|
+
icon: jsx(CopyOutlined, {}),
|
|
269
|
+
onClick: () => copyJson(record),
|
|
270
|
+
}
|
|
271
|
+
];
|
|
272
|
+
return (jsxs(Space, { size: 4, children: [basicActions.map((action) => {
|
|
251
273
|
var _a, _b, _c;
|
|
252
274
|
if (action.type === 'view') {
|
|
253
275
|
return (jsx(Tooltip, { title: action.label, children: jsx(Button, { type: "link", size: "small", icon: jsx(EyeOutlined, {}), onClick: () => onView(record) }) }, action.key));
|
|
@@ -258,20 +280,20 @@ function DynamicTable({ schema, data, loading, pagination, onView, onEdit, onDel
|
|
|
258
280
|
if (action.type === 'delete') {
|
|
259
281
|
return (jsx(Popconfirm, { title: (_b = (_a = action.confirm) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : '确定删除?', description: (_c = action.confirm) === null || _c === void 0 ? void 0 : _c.content, onConfirm: () => onDelete(record), okText: "\u786E\u5B9A", cancelText: "\u53D6\u6D88", okButtonProps: { danger: true }, children: jsx(Tooltip, { title: action.label, children: jsx(Button, { type: "link", size: "small", danger: true, icon: jsx(DeleteOutlined, {}) }) }) }, action.key));
|
|
260
282
|
}
|
|
261
|
-
return
|
|
262
|
-
}), jsx(Tooltip, { title: "\
|
|
283
|
+
return null;
|
|
284
|
+
}), jsx(Dropdown, { menu: { items: dropdownItems }, trigger: ['click'], placement: "bottomRight", children: jsx(Tooltip, { title: "\u5176\u5B83\u64CD\u4F5C", children: jsx(Button, { type: "link", size: "small", icon: jsx(MoreOutlined, {}) }) }) })] }));
|
|
263
285
|
},
|
|
264
286
|
});
|
|
265
|
-
return (jsx(Table, { rowKey: (
|
|
287
|
+
return (jsx(Table, { rowKey: (_a = schema.rowKey) !== null && _a !== void 0 ? _a : 'id', columns: columns, dataSource: data, loading: loading, scroll: { x: 'max-content' }, pagination: {
|
|
266
288
|
current: pagination.current,
|
|
267
289
|
pageSize: pagination.pageSize,
|
|
268
290
|
total: pagination.total,
|
|
269
291
|
showSizeChanger: true,
|
|
270
|
-
showTotal: ((
|
|
292
|
+
showTotal: ((_b = schema.pagination) === null || _b === void 0 ? void 0 : _b.showTotal)
|
|
271
293
|
? (total) => `共 ${total} 条`
|
|
272
294
|
: undefined,
|
|
273
295
|
onChange: pagination.onChange,
|
|
274
|
-
pageSizeOptions: (
|
|
296
|
+
pageSizeOptions: (_d = (_c = schema.pagination) === null || _c === void 0 ? void 0 : _c.pageSizeOptions) !== null && _d !== void 0 ? _d : [10, 20, 50],
|
|
275
297
|
} }));
|
|
276
298
|
}
|
|
277
299
|
|