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 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 配置即可生成完整 CRUD 界面
16
- - 📝 **丰富的表单组件** - 支持 15+ 种表单控件类型
17
- - 🔍 **智能筛选** - 自动生成筛选器,支持范围查询
18
- - 📊 **灵活表格** - 可配置列宽、排序、固定列等
19
- - 🎨 **Raw JSON 模式** - 支持原始 JSON 编辑和查看
20
- - 🔌 **自定义 API** - 支持自定义 API 请求函数和动态 URL 参数
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
- url: '/api/users',
60
- method: 'GET'
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
- ### 扩展 API 配置
69
+ ## 新特性:操作按钮分组 (v0.1.3)
131
70
 
132
- v0.1.0+ 版本要求使用完整的 API 配置对象,支持自定义 HTTP 方法、请求头、请求体数据和模板变量:
71
+ 自定义操作现在会自动折叠到"其它操作"下拉菜单中,保持界面整洁:
133
72
 
134
73
  ```tsx
135
- const advancedSchema: CrudPageSchema = {
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
- key: 'track',
230
- label: '物流跟踪',
231
- type: 'custom',
232
- api: {
233
- url: '/api/tracking/:orderNo', // 使用 orderNo 字段
234
- method: 'GET',
235
- responseType: 'json'
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
- ### CrudPageSchema
311
-
312
- 完整的 schema 配置请参考类型定义文件。
313
-
314
- ## 字段类型支持
90
+ ### 操作类型
315
91
 
316
- - `string` - 字符串
317
- - `number` - 数字
318
- - `boolean` - 布尔值
319
- - `date` - 日期
320
- - `datetime` - 日期时间
321
- - `array` - 数组
322
- - `objectArray` - 对象数组
92
+ - **基础操作** (`view`, `edit`, `delete`) - 显示为独立的图标按钮
93
+ - **自定义操作** (`custom`) - 折叠到"其它操作"下拉菜单中
94
+ - **复制功能** - 始终在下拉菜单中提供"复制 JSON"功能
323
95
 
324
- ## 组件类型支持
96
+ ## 文档
325
97
 
326
- - `input` - 输入框
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
- ```bash
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, CopyOutlined, FormOutlined, CodeOutlined, PlusOutlined } from '@ant-design/icons';
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, _e, _f;
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: ((_b = (_a = schema.actions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) * 60 + 60,
247
+ width: 200, // 固定宽度,因为现在有下拉菜单
248
248
  render: (_, record) => {
249
249
  var _a;
250
- return (jsxs(Space, { size: 4, children: [((_a = schema.actions) !== null && _a !== void 0 ? _a : []).map((action) => {
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 (jsx(Tooltip, { title: action.label, children: jsx(Button, { type: "link", size: "small", onClick: () => onCustomAction === null || onCustomAction === void 0 ? void 0 : onCustomAction(action.key, record), children: action.label }) }, action.key));
262
- }), jsx(Tooltip, { title: "\u590D\u5236 JSON", children: jsx(Button, { type: "link", size: "small", icon: jsx(CopyOutlined, {}), onClick: () => copyJson(record) }) })] }));
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: (_c = schema.rowKey) !== null && _c !== void 0 ? _c : 'id', columns: columns, dataSource: data, loading: loading, scroll: { x: 'max-content' }, pagination: {
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: ((_d = schema.pagination) === null || _d === void 0 ? void 0 : _d.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: (_f = (_e = schema.pagination) === null || _e === void 0 ? void 0 : _e.pageSizeOptions) !== null && _f !== void 0 ? _f : [10, 20, 50],
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