crud-page-react 0.0.4 → 0.0.5
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 +50 -1
- package/dist/index.esm.js +71 -85
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +71 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
- 🔍 **智能筛选** - 自动生成筛选器,支持范围查询
|
|
10
10
|
- 📊 **灵活表格** - 可配置列宽、排序、固定列等
|
|
11
11
|
- 🎨 **Raw JSON 模式** - 支持原始 JSON 编辑和查看
|
|
12
|
-
- 🔌 **自定义 API** - 支持自定义 API
|
|
12
|
+
- 🔌 **自定义 API** - 支持自定义 API 请求函数和动态 URL 参数
|
|
13
|
+
- 🌐 **动态 URL 参数** - 支持任意字段作为 URL 参数 (`:id`, `:orderNo`, `:customerName` 等)
|
|
14
|
+
- ⚡ **自定义操作** - 支持配置自定义按钮操作和 API 调用
|
|
13
15
|
- 📱 **响应式设计** - 适配移动端和桌面端
|
|
14
16
|
- 🎯 **TypeScript 支持** - 完整的类型定义
|
|
15
17
|
|
|
@@ -103,6 +105,53 @@ function App() {
|
|
|
103
105
|
export default App;
|
|
104
106
|
```
|
|
105
107
|
|
|
108
|
+
## 动态 URL 参数
|
|
109
|
+
|
|
110
|
+
支持在 API 配置中使用任意字段作为 URL 参数:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
const schema = {
|
|
114
|
+
api: {
|
|
115
|
+
list: '/api/orders',
|
|
116
|
+
create: '/api/orders',
|
|
117
|
+
update: '/api/orders/:id', // 使用 id 字段
|
|
118
|
+
delete: '/api/orders/:orderNo', // 使用 orderNo 字段
|
|
119
|
+
detail: '/api/orders/:id',
|
|
120
|
+
},
|
|
121
|
+
actions: [
|
|
122
|
+
{
|
|
123
|
+
key: 'track',
|
|
124
|
+
label: '物流跟踪',
|
|
125
|
+
type: 'custom',
|
|
126
|
+
api: {
|
|
127
|
+
url: '/api/tracking/:orderNo', // 使用 orderNo 字段
|
|
128
|
+
method: 'GET',
|
|
129
|
+
responseType: 'json'
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: 'notify',
|
|
134
|
+
label: '通知客户',
|
|
135
|
+
type: 'custom',
|
|
136
|
+
api: {
|
|
137
|
+
url: '/api/notify/:customerName', // 使用 customerName 字段
|
|
138
|
+
method: 'POST',
|
|
139
|
+
data: {
|
|
140
|
+
message: '您的订单状态已更新'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
// ... 其他配置
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 支持的占位符格式
|
|
150
|
+
- `:id` → 记录中的 `id` 字段值
|
|
151
|
+
- `:orderNo` → 记录中的 `orderNo` 字段值
|
|
152
|
+
- `:customerName` → 记录中的 `customerName` 字段值
|
|
153
|
+
- 任意 `:fieldName` → 记录中的 `fieldName` 字段值
|
|
154
|
+
|
|
106
155
|
## 自定义 API 请求
|
|
107
156
|
|
|
108
157
|
```tsx
|
package/dist/index.esm.js
CHANGED
|
@@ -846,8 +846,8 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
846
846
|
const rowKey = schema.rowKey || 'id';
|
|
847
847
|
// 使用传入的apiRequest或默认的
|
|
848
848
|
const request = customApiRequest || apiRequest;
|
|
849
|
-
//
|
|
850
|
-
const
|
|
849
|
+
// 初始数据引用
|
|
850
|
+
const initialDataRef = useRef(initialData);
|
|
851
851
|
const [data, setData] = useState([]);
|
|
852
852
|
const [loading, setLoading] = useState(false);
|
|
853
853
|
const [total, setTotal] = useState(0);
|
|
@@ -856,33 +856,20 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
856
856
|
const [pageSize, setPageSize] = useState(((_a = schema.pagination) === null || _a === void 0 ? void 0 : _a.pageSize) || 10);
|
|
857
857
|
const [modalState, setModalState] = useState({ open: false, mode: 'create' });
|
|
858
858
|
const [messageApi, contextHolder] = message.useMessage();
|
|
859
|
-
// ----------
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (key.endsWith('_min'))
|
|
866
|
-
return Number(row[key.slice(0, -4)]) >= Number(val);
|
|
867
|
-
if (key.endsWith('_max'))
|
|
868
|
-
return Number(row[key.slice(0, -4)]) <= Number(val);
|
|
869
|
-
if (key.endsWith('_start') || key.endsWith('_end'))
|
|
870
|
-
return true;
|
|
871
|
-
const rowVal = row[key];
|
|
872
|
-
if (Array.isArray(rowVal)) {
|
|
873
|
-
return Array.isArray(val) ? val.some(v => rowVal.includes(v)) : rowVal.includes(val);
|
|
874
|
-
}
|
|
875
|
-
if (typeof val === 'string')
|
|
876
|
-
return String(rowVal).toLowerCase().includes(val.toLowerCase());
|
|
877
|
-
return rowVal === val;
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
const start = (p - 1) * ps;
|
|
881
|
-
setData(filtered.slice(start, start + ps));
|
|
882
|
-
setTotal(filtered.length);
|
|
859
|
+
// ---------- 初始化数据 ----------
|
|
860
|
+
const initializeData = useCallback(() => {
|
|
861
|
+
if (initialDataRef.current.length > 0) {
|
|
862
|
+
setData(initialDataRef.current);
|
|
863
|
+
setTotal(initialDataRef.current.length);
|
|
864
|
+
}
|
|
883
865
|
}, []);
|
|
884
866
|
// ---------- 获取列表 ----------
|
|
885
867
|
const fetchList = useCallback(async (params = filterParams, p = page, ps = pageSize) => {
|
|
868
|
+
if (!schema.api.list) {
|
|
869
|
+
// 没有配置 API,使用初始数据
|
|
870
|
+
initializeData();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
886
873
|
setLoading(true);
|
|
887
874
|
try {
|
|
888
875
|
const query = new URLSearchParams({ page: String(p), pageSize: String(ps) });
|
|
@@ -895,14 +882,22 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
895
882
|
setData(list);
|
|
896
883
|
setTotal(tot);
|
|
897
884
|
}
|
|
898
|
-
catch (
|
|
899
|
-
|
|
900
|
-
|
|
885
|
+
catch (error) {
|
|
886
|
+
console.error('Failed to fetch list:', error);
|
|
887
|
+
messageApi.error('获取数据失败,请检查网络连接或联系管理员');
|
|
888
|
+
// 如果有初始数据,显示初始数据
|
|
889
|
+
if (initialDataRef.current.length > 0) {
|
|
890
|
+
initializeData();
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
setData([]);
|
|
894
|
+
setTotal(0);
|
|
895
|
+
}
|
|
901
896
|
}
|
|
902
897
|
finally {
|
|
903
898
|
setLoading(false);
|
|
904
899
|
}
|
|
905
|
-
}, [request, schema.api.list, filterParams, page, pageSize,
|
|
900
|
+
}, [request, schema.api.list, filterParams, page, pageSize, initializeData, messageApi]);
|
|
906
901
|
// 初始加载 & 参数变化时重新请求
|
|
907
902
|
useEffect(() => {
|
|
908
903
|
fetchList(filterParams, page, pageSize);
|
|
@@ -920,22 +915,20 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
920
915
|
}, []);
|
|
921
916
|
// ---------- 删除 ----------
|
|
922
917
|
const handleDelete = useCallback(async (record) => {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
918
|
+
if (!schema.api.delete) {
|
|
919
|
+
messageApi.error('删除功能未配置');
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
try {
|
|
923
|
+
await request(buildUrl(schema.api.delete, record), { method: 'DELETE' });
|
|
924
|
+
messageApi.success('删除成功');
|
|
925
|
+
fetchList();
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
console.error('Delete failed:', error);
|
|
929
|
+
messageApi.error('删除失败,请稍后重试');
|
|
934
930
|
}
|
|
935
|
-
|
|
936
|
-
localFilter(filterParams, page, pageSize);
|
|
937
|
-
messageApi.success('删除成功(演示模式)');
|
|
938
|
-
}, [request, schema.api.delete, rowKey, fetchList, localFilter, filterParams, page, pageSize, messageApi]);
|
|
931
|
+
}, [request, schema.api.delete, fetchList, messageApi]);
|
|
939
932
|
// ---------- 操作列点击 ----------
|
|
940
933
|
const handleAction = useCallback(async (action, record) => {
|
|
941
934
|
if (action.type === 'view') {
|
|
@@ -1005,52 +998,45 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
1005
998
|
const handleFormOk = useCallback(async (values) => {
|
|
1006
999
|
const isCreate = modalState.mode === 'create';
|
|
1007
1000
|
if (isCreate) {
|
|
1008
|
-
if (schema.api.create) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1001
|
+
if (!schema.api.create) {
|
|
1002
|
+
messageApi.error('新增功能未配置');
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
await request(schema.api.create, {
|
|
1007
|
+
method: 'POST',
|
|
1008
|
+
body: JSON.stringify(values),
|
|
1009
|
+
});
|
|
1010
|
+
messageApi.success('新增成功');
|
|
1011
|
+
setModalState({ open: false, mode: 'create' });
|
|
1012
|
+
fetchList();
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
console.error('Create failed:', error);
|
|
1016
|
+
messageApi.error('新增失败,请稍后重试');
|
|
1022
1017
|
}
|
|
1023
|
-
const newRecord = Object.assign({ [rowKey]: `local-${Date.now()}` }, values);
|
|
1024
|
-
localDataRef.current = [newRecord, ...localDataRef.current];
|
|
1025
|
-
localFilter(filterParams, 1, pageSize);
|
|
1026
|
-
setPage(1);
|
|
1027
|
-
messageApi.success('新增成功(演示模式)');
|
|
1028
1018
|
}
|
|
1029
1019
|
else {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1020
|
+
if (!schema.api.update) {
|
|
1021
|
+
messageApi.error('编辑功能未配置');
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
await request(buildUrl(schema.api.update, values), {
|
|
1026
|
+
method: 'PUT',
|
|
1027
|
+
body: JSON.stringify(values),
|
|
1028
|
+
});
|
|
1029
|
+
messageApi.success('编辑成功');
|
|
1030
|
+
setModalState({ open: false, mode: 'create' });
|
|
1031
|
+
fetchList();
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
console.error('Update failed:', error);
|
|
1035
|
+
messageApi.error('编辑失败,请稍后重试');
|
|
1045
1036
|
}
|
|
1046
|
-
localDataRef.current = localDataRef.current.map(r => r[rowKey] === id ? Object.assign(Object.assign({}, r), values) : r);
|
|
1047
|
-
localFilter(filterParams, page, pageSize);
|
|
1048
|
-
messageApi.success('编辑成功(演示模式)');
|
|
1049
1037
|
}
|
|
1050
|
-
setModalState({ open: false, mode: 'create' });
|
|
1051
1038
|
}, [
|
|
1052
|
-
request, modalState.mode, schema.api,
|
|
1053
|
-
fetchList, localFilter, filterParams, page, pageSize, messageApi,
|
|
1039
|
+
request, modalState.mode, schema.api, fetchList, messageApi,
|
|
1054
1040
|
]);
|
|
1055
1041
|
return (jsxs("div", { style: { padding: 24, background: '#f5f6fa', minHeight: '100vh' }, children: [contextHolder, jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }, children: [jsx(Title, { level: 4, style: { margin: 0 }, children: schema.title }), schema.api.create && (jsx(Button, { type: "primary", icon: jsx(PlusOutlined, {}), onClick: () => setModalState({ open: true, mode: 'create', record: undefined }), children: schema.createButtonLabel || '新增' }))] }), jsx(DynamicFilter, { schema: schema, onSearch: handleSearch, onReset: () => handleSearch({}) }), jsx(DynamicTable, { schema: schema, data: data, loading: loading, pagination: {
|
|
1056
1042
|
current: page,
|