crud-page-react 0.1.1 → 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 -272
- 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
|
@@ -4,40 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
- 🚀 **零代码配置** - 通过 JSON Schema
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- 🌐 **动态 URL 参数** - 支持任意字段作为 URL 参数 (`:id`, `:orderNo`, `:customerName` 等)
|
|
14
|
-
- ⚡ **自定义操作** - 支持配置自定义按钮操作和 API 调用
|
|
15
|
-
- 📱 **响应式设计** - 适配移动端和桌面端
|
|
16
|
-
- 🎯 **TypeScript 支持** - 完整的类型定义
|
|
7
|
+
- 🚀 **零代码配置** - 通过 JSON Schema 快速生成 CRUD 界面
|
|
8
|
+
- 🎨 **美观易用** - 基于 Ant Design 设计语言
|
|
9
|
+
- 🔧 **高度可定制** - 支持自定义 API、字段配置、操作按钮
|
|
10
|
+
- 📱 **响应式设计** - 自适应各种屏幕尺寸
|
|
11
|
+
- 🔒 **类型安全** - 完整的 TypeScript 类型定义
|
|
12
|
+
- 🎯 **操作分组** - 自定义操作自动折叠到下拉菜单
|
|
17
13
|
|
|
18
14
|
## 安装
|
|
19
15
|
|
|
20
16
|
```bash
|
|
21
17
|
npm install crud-page-react
|
|
22
|
-
# 或
|
|
23
|
-
yarn add crud-page-react
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### 依赖要求
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"react": ">=16.8.0",
|
|
31
|
-
"react-dom": ">=16.8.0",
|
|
32
|
-
"antd": ">=5.0.0",
|
|
33
|
-
"dayjs": ">=1.11.0"
|
|
34
|
-
}
|
|
35
18
|
```
|
|
36
19
|
|
|
37
20
|
## 快速开始
|
|
38
21
|
|
|
39
|
-
### 基础用法
|
|
40
|
-
|
|
41
22
|
```tsx
|
|
42
23
|
import React from 'react';
|
|
43
24
|
import { CrudPage } from 'crud-page-react';
|
|
@@ -47,22 +28,10 @@ const schema: CrudPageSchema = {
|
|
|
47
28
|
id: 'users',
|
|
48
29
|
title: '用户管理',
|
|
49
30
|
api: {
|
|
50
|
-
list: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
create: {
|
|
55
|
-
url: '/api/users',
|
|
56
|
-
method: 'POST'
|
|
57
|
-
},
|
|
58
|
-
update: {
|
|
59
|
-
url: '/api/users/:id',
|
|
60
|
-
method: 'PUT'
|
|
61
|
-
},
|
|
62
|
-
delete: {
|
|
63
|
-
url: '/api/users/:id',
|
|
64
|
-
method: 'DELETE'
|
|
65
|
-
},
|
|
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' },
|
|
66
35
|
},
|
|
67
36
|
fields: [
|
|
68
37
|
{
|
|
@@ -87,260 +56,50 @@ const schema: CrudPageSchema = {
|
|
|
87
56
|
filter: true,
|
|
88
57
|
table: true,
|
|
89
58
|
form: { required: true },
|
|
90
|
-
rules: ['email'],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
key: 'status',
|
|
94
|
-
label: '状态',
|
|
95
|
-
type: 'string',
|
|
96
|
-
widget: 'select',
|
|
97
|
-
config: {
|
|
98
|
-
options: [
|
|
99
|
-
{ label: '启用', value: 'active' },
|
|
100
|
-
{ label: '禁用', value: 'inactive' },
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
filter: true,
|
|
104
|
-
table: true,
|
|
105
|
-
form: true,
|
|
106
59
|
},
|
|
107
60
|
],
|
|
108
61
|
rowKey: 'id',
|
|
109
62
|
};
|
|
110
63
|
|
|
111
64
|
function App() {
|
|
112
|
-
return
|
|
113
|
-
<div>
|
|
114
|
-
<CrudPage schema={schema} />
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
65
|
+
return <CrudPage schema={schema} />;
|
|
117
66
|
}
|
|
118
|
-
|
|
119
|
-
export default App;
|
|
120
67
|
```
|
|
121
68
|
|
|
122
|
-
|
|
69
|
+
## 新特性:操作按钮分组 (v0.1.3)
|
|
123
70
|
|
|
124
|
-
|
|
71
|
+
自定义操作现在会自动折叠到"其它操作"下拉菜单中,保持界面整洁:
|
|
125
72
|
|
|
126
73
|
```tsx
|
|
127
|
-
const
|
|
128
|
-
id: 'orders',
|
|
129
|
-
title: '订单管理',
|
|
130
|
-
api: {
|
|
131
|
-
// 基础配置
|
|
132
|
-
list: {
|
|
133
|
-
url: '/api/orders',
|
|
134
|
-
method: 'GET'
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
// 扩展配置
|
|
138
|
-
create: {
|
|
139
|
-
url: '/api/orders',
|
|
140
|
-
method: 'POST',
|
|
141
|
-
headers: {
|
|
142
|
-
'X-Request-Source': 'admin-panel'
|
|
143
|
-
},
|
|
144
|
-
data: {
|
|
145
|
-
source: 'web',
|
|
146
|
-
createdBy: '{{currentUser}}',
|
|
147
|
-
timestamp: '{{timestamp}}'
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
update: {
|
|
152
|
-
url: '/api/orders/:id',
|
|
153
|
-
method: 'PUT',
|
|
154
|
-
data: {
|
|
155
|
-
updatedBy: '{{currentUser}}',
|
|
156
|
-
updateTime: '{{timestamp}}'
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
delete: {
|
|
161
|
-
url: '/api/orders/:orderNo',
|
|
162
|
-
method: 'DELETE',
|
|
163
|
-
data: {
|
|
164
|
-
deletedBy: '{{currentUser}}',
|
|
165
|
-
deleteReason: '{{deleteReason}}',
|
|
166
|
-
timestamp: '{{timestamp}}'
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
},
|
|
74
|
+
const schema: CrudPageSchema = {
|
|
170
75
|
// ... 其他配置
|
|
171
|
-
};
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 支持的配置选项
|
|
175
|
-
|
|
176
|
-
- **HTTP 方法**:GET、POST、PUT、PATCH、DELETE
|
|
177
|
-
- **自定义请求头**:添加认证、来源标识等
|
|
178
|
-
- **请求体数据**:固定参数和动态参数
|
|
179
|
-
- **模板变量**:`{{fieldName}}` 格式的动态值替换
|
|
180
|
-
- **URL 占位符**:`:fieldName` 格式的动态 URL 构建
|
|
181
|
-
|
|
182
|
-
### 💥 Breaking Changes (v0.1.0)
|
|
183
|
-
|
|
184
|
-
v0.1.0 版本移除了简单字符串配置支持,所有 API 配置必须使用对象格式:
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
// ❌ 不再支持 (v0.0.x)
|
|
188
|
-
api: {
|
|
189
|
-
list: '/api/users',
|
|
190
|
-
create: '/api/users'
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ✅ 必须使用 (v0.1.0+)
|
|
194
|
-
api: {
|
|
195
|
-
list: {
|
|
196
|
-
url: '/api/users',
|
|
197
|
-
method: 'GET'
|
|
198
|
-
},
|
|
199
|
-
create: {
|
|
200
|
-
url: '/api/users',
|
|
201
|
-
method: 'POST'
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## 动态 URL 参数
|
|
207
|
-
|
|
208
|
-
支持在 API 配置中使用任意字段作为 URL 参数:
|
|
209
|
-
|
|
210
|
-
```tsx
|
|
211
|
-
const schema = {
|
|
212
|
-
api: {
|
|
213
|
-
list: '/api/orders',
|
|
214
|
-
create: '/api/orders',
|
|
215
|
-
update: '/api/orders/:id', // 使用 id 字段
|
|
216
|
-
delete: '/api/orders/:orderNo', // 使用 orderNo 字段
|
|
217
|
-
detail: '/api/orders/:id',
|
|
218
|
-
},
|
|
219
76
|
actions: [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
key: 'notify',
|
|
232
|
-
label: '通知客户',
|
|
233
|
-
type: 'custom',
|
|
234
|
-
api: {
|
|
235
|
-
url: '/api/notify/:customerName', // 使用 customerName 字段
|
|
236
|
-
method: 'POST',
|
|
237
|
-
data: {
|
|
238
|
-
message: '您的订单状态已更新'
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
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' },
|
|
242
86
|
],
|
|
243
|
-
// ... 其他配置
|
|
244
87
|
};
|
|
245
88
|
```
|
|
246
89
|
|
|
247
|
-
###
|
|
248
|
-
- `:id` → 记录中的 `id` 字段值
|
|
249
|
-
- `:orderNo` → 记录中的 `orderNo` 字段值
|
|
250
|
-
- `:customerName` → 记录中的 `customerName` 字段值
|
|
251
|
-
- 任意 `:fieldName` → 记录中的 `fieldName` 字段值
|
|
90
|
+
### 操作类型
|
|
252
91
|
|
|
253
|
-
|
|
92
|
+
- **基础操作** (`view`, `edit`, `delete`) - 显示为独立的图标按钮
|
|
93
|
+
- **自定义操作** (`custom`) - 折叠到"其它操作"下拉菜单中
|
|
94
|
+
- **复制功能** - 始终在下拉菜单中提供"复制 JSON"功能
|
|
254
95
|
|
|
255
|
-
|
|
256
|
-
import { CrudPage, ApiRequest } from 'crud-page-react';
|
|
96
|
+
## 文档
|
|
257
97
|
|
|
258
|
-
|
|
259
|
-
const customApiRequest: ApiRequest = async (url, options) => {
|
|
260
|
-
const token = localStorage.getItem('token');
|
|
261
|
-
const response = await fetch(url, {
|
|
262
|
-
headers: {
|
|
263
|
-
'Content-Type': 'application/json',
|
|
264
|
-
'Authorization': token ? `Bearer ${token}` : '',
|
|
265
|
-
...options?.headers,
|
|
266
|
-
},
|
|
267
|
-
...options,
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
if (!response.ok) {
|
|
271
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return response.json();
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// 使用自定义 API 请求
|
|
278
|
-
<CrudPage
|
|
279
|
-
schema={schema}
|
|
280
|
-
apiRequest={customApiRequest}
|
|
281
|
-
/>
|
|
282
|
-
```
|
|
98
|
+
更多详细文档和示例,请查看 [example.md](./example.md)
|
|
283
99
|
|
|
284
|
-
##
|
|
100
|
+
## 更新日志
|
|
285
101
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
289
|
-
|------|------|------|--------|------|
|
|
290
|
-
| schema | CrudPageSchema | ✓ | - | 页面配置 schema |
|
|
291
|
-
| initialData | Record<string, unknown>[] | ✗ | [] | 初始数据,API 失败时作为降级数据 |
|
|
292
|
-
| apiRequest | ApiRequest | ✗ | 内置 fetch | 自定义 API 请求函数 |
|
|
293
|
-
|
|
294
|
-
### ApiRequest
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
interface ApiRequest {
|
|
298
|
-
<T = unknown>(url: string, options?: RequestInit): Promise<T>;
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### CrudPageSchema
|
|
303
|
-
|
|
304
|
-
完整的 schema 配置请参考类型定义文件。
|
|
305
|
-
|
|
306
|
-
## 字段类型支持
|
|
307
|
-
|
|
308
|
-
- `string` - 字符串
|
|
309
|
-
- `number` - 数字
|
|
310
|
-
- `boolean` - 布尔值
|
|
311
|
-
- `date` - 日期
|
|
312
|
-
- `datetime` - 日期时间
|
|
313
|
-
- `array` - 数组
|
|
314
|
-
- `objectArray` - 对象数组
|
|
315
|
-
|
|
316
|
-
## 组件类型支持
|
|
317
|
-
|
|
318
|
-
- `input` - 输入框
|
|
319
|
-
- `textarea` - 文本域
|
|
320
|
-
- `inputNumber` - 数字输入
|
|
321
|
-
- `select` - 下拉选择
|
|
322
|
-
- `multiselect` - 多选下拉
|
|
323
|
-
- `radio` - 单选按钮
|
|
324
|
-
- `checkbox` - 复选框
|
|
325
|
-
- `switch` - 开关
|
|
326
|
-
- `datePicker` - 日期选择
|
|
327
|
-
- `rangePicker` - 日期范围
|
|
328
|
-
- `timePicker` - 时间选择
|
|
329
|
-
- `editableTable` - 可编辑表格
|
|
330
|
-
- `jsonInput` - JSON 编辑器
|
|
331
|
-
|
|
332
|
-
## 开发
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
# 安装依赖
|
|
336
|
-
npm install
|
|
337
|
-
|
|
338
|
-
# 开发模式
|
|
339
|
-
npm run dev
|
|
340
|
-
|
|
341
|
-
# 构建
|
|
342
|
-
npm run build
|
|
343
|
-
```
|
|
102
|
+
查看 [CHANGELOG.md](./CHANGELOG.md) 了解版本更新内容。
|
|
344
103
|
|
|
345
104
|
## 许可证
|
|
346
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
|
|