crud-page-react 0.0.2 → 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.d.ts +10 -0
- package/dist/index.esm.js +132 -90
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +132 -90
- package/dist/index.js.map +1 -1
- package/dist/types/schema.d.ts +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -812,9 +812,12 @@ function DynamicForm({ schema, mode, visible, initialValues, onSubmit, onCancel,
|
|
|
812
812
|
}
|
|
813
813
|
|
|
814
814
|
const { Title } = antd.Typography;
|
|
815
|
-
/**
|
|
816
|
-
function buildUrl(template,
|
|
817
|
-
return template.replace(/:
|
|
815
|
+
/** 动态替换 URL 模板中的占位符 */
|
|
816
|
+
function buildUrl(template, record) {
|
|
817
|
+
return template.replace(/:(\w+)/g, (match, fieldName) => {
|
|
818
|
+
const value = record[fieldName];
|
|
819
|
+
return value !== undefined ? String(value) : match;
|
|
820
|
+
});
|
|
818
821
|
}
|
|
819
822
|
/** 通用请求封装 */
|
|
820
823
|
async function apiRequest(url, options) {
|
|
@@ -845,8 +848,8 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
845
848
|
const rowKey = schema.rowKey || 'id';
|
|
846
849
|
// 使用传入的apiRequest或默认的
|
|
847
850
|
const request = customApiRequest || apiRequest;
|
|
848
|
-
//
|
|
849
|
-
const
|
|
851
|
+
// 初始数据引用
|
|
852
|
+
const initialDataRef = react.useRef(initialData);
|
|
850
853
|
const [data, setData] = react.useState([]);
|
|
851
854
|
const [loading, setLoading] = react.useState(false);
|
|
852
855
|
const [total, setTotal] = react.useState(0);
|
|
@@ -855,33 +858,20 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
855
858
|
const [pageSize, setPageSize] = react.useState(((_a = schema.pagination) === null || _a === void 0 ? void 0 : _a.pageSize) || 10);
|
|
856
859
|
const [modalState, setModalState] = react.useState({ open: false, mode: 'create' });
|
|
857
860
|
const [messageApi, contextHolder] = antd.message.useMessage();
|
|
858
|
-
// ----------
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (key.endsWith('_min'))
|
|
865
|
-
return Number(row[key.slice(0, -4)]) >= Number(val);
|
|
866
|
-
if (key.endsWith('_max'))
|
|
867
|
-
return Number(row[key.slice(0, -4)]) <= Number(val);
|
|
868
|
-
if (key.endsWith('_start') || key.endsWith('_end'))
|
|
869
|
-
return true;
|
|
870
|
-
const rowVal = row[key];
|
|
871
|
-
if (Array.isArray(rowVal)) {
|
|
872
|
-
return Array.isArray(val) ? val.some(v => rowVal.includes(v)) : rowVal.includes(val);
|
|
873
|
-
}
|
|
874
|
-
if (typeof val === 'string')
|
|
875
|
-
return String(rowVal).toLowerCase().includes(val.toLowerCase());
|
|
876
|
-
return rowVal === val;
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
const start = (p - 1) * ps;
|
|
880
|
-
setData(filtered.slice(start, start + ps));
|
|
881
|
-
setTotal(filtered.length);
|
|
861
|
+
// ---------- 初始化数据 ----------
|
|
862
|
+
const initializeData = react.useCallback(() => {
|
|
863
|
+
if (initialDataRef.current.length > 0) {
|
|
864
|
+
setData(initialDataRef.current);
|
|
865
|
+
setTotal(initialDataRef.current.length);
|
|
866
|
+
}
|
|
882
867
|
}, []);
|
|
883
868
|
// ---------- 获取列表 ----------
|
|
884
869
|
const fetchList = react.useCallback(async (params = filterParams, p = page, ps = pageSize) => {
|
|
870
|
+
if (!schema.api.list) {
|
|
871
|
+
// 没有配置 API,使用初始数据
|
|
872
|
+
initializeData();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
885
875
|
setLoading(true);
|
|
886
876
|
try {
|
|
887
877
|
const query = new URLSearchParams({ page: String(p), pageSize: String(ps) });
|
|
@@ -894,14 +884,22 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
894
884
|
setData(list);
|
|
895
885
|
setTotal(tot);
|
|
896
886
|
}
|
|
897
|
-
catch (
|
|
898
|
-
|
|
899
|
-
|
|
887
|
+
catch (error) {
|
|
888
|
+
console.error('Failed to fetch list:', error);
|
|
889
|
+
messageApi.error('获取数据失败,请检查网络连接或联系管理员');
|
|
890
|
+
// 如果有初始数据,显示初始数据
|
|
891
|
+
if (initialDataRef.current.length > 0) {
|
|
892
|
+
initializeData();
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
setData([]);
|
|
896
|
+
setTotal(0);
|
|
897
|
+
}
|
|
900
898
|
}
|
|
901
899
|
finally {
|
|
902
900
|
setLoading(false);
|
|
903
901
|
}
|
|
904
|
-
}, [request, schema.api.list, filterParams, page, pageSize,
|
|
902
|
+
}, [request, schema.api.list, filterParams, page, pageSize, initializeData, messageApi]);
|
|
905
903
|
// 初始加载 & 参数变化时重新请求
|
|
906
904
|
react.useEffect(() => {
|
|
907
905
|
fetchList(filterParams, page, pageSize);
|
|
@@ -919,24 +917,22 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
919
917
|
}, []);
|
|
920
918
|
// ---------- 删除 ----------
|
|
921
919
|
const handleDelete = react.useCallback(async (record) => {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
catch (_a) {
|
|
931
|
-
// 降级本地
|
|
932
|
-
}
|
|
920
|
+
if (!schema.api.delete) {
|
|
921
|
+
messageApi.error('删除功能未配置');
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
await request(buildUrl(schema.api.delete, record), { method: 'DELETE' });
|
|
926
|
+
messageApi.success('删除成功');
|
|
927
|
+
fetchList();
|
|
933
928
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
929
|
+
catch (error) {
|
|
930
|
+
console.error('Delete failed:', error);
|
|
931
|
+
messageApi.error('删除失败,请稍后重试');
|
|
932
|
+
}
|
|
933
|
+
}, [request, schema.api.delete, fetchList, messageApi]);
|
|
938
934
|
// ---------- 操作列点击 ----------
|
|
939
|
-
const handleAction = react.useCallback((action, record) => {
|
|
935
|
+
const handleAction = react.useCallback(async (action, record) => {
|
|
940
936
|
if (action.type === 'view') {
|
|
941
937
|
setModalState({ open: true, mode: 'view', record });
|
|
942
938
|
}
|
|
@@ -946,57 +942,103 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
946
942
|
else if (action.type === 'delete') {
|
|
947
943
|
handleDelete(record);
|
|
948
944
|
}
|
|
949
|
-
|
|
945
|
+
else if (action.type === 'custom' && action.api) {
|
|
946
|
+
// 处理自定义 action 的 API 调用
|
|
947
|
+
try {
|
|
948
|
+
// 构建 URL,动态替换占位符
|
|
949
|
+
let url = action.api.url;
|
|
950
|
+
// 替换所有 :fieldName 格式的占位符
|
|
951
|
+
url = url.replace(/:(\w+)/g, (match, fieldName) => {
|
|
952
|
+
const value = record[fieldName];
|
|
953
|
+
return value !== undefined ? String(value) : match;
|
|
954
|
+
});
|
|
955
|
+
// 构建请求选项
|
|
956
|
+
const options = {
|
|
957
|
+
method: action.api.method,
|
|
958
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, action.api.headers)
|
|
959
|
+
};
|
|
960
|
+
// 添加请求体数据
|
|
961
|
+
if (action.api.data && ['POST', 'PUT', 'PATCH'].includes(action.api.method)) {
|
|
962
|
+
options.body = JSON.stringify(Object.assign(Object.assign({}, action.api.data), {
|
|
963
|
+
// 可以添加动态数据
|
|
964
|
+
recordId: record[rowKey], timestamp: new Date().toISOString() }));
|
|
965
|
+
}
|
|
966
|
+
// 调用 API
|
|
967
|
+
const response = await request(url, options);
|
|
968
|
+
// 处理特殊响应类型
|
|
969
|
+
if (action.api.responseType === 'blob') {
|
|
970
|
+
// 处理文件下载
|
|
971
|
+
const blob = new Blob([response]);
|
|
972
|
+
const downloadUrl = URL.createObjectURL(blob);
|
|
973
|
+
const a = document.createElement('a');
|
|
974
|
+
a.href = downloadUrl;
|
|
975
|
+
a.download = `${action.key}.pdf`;
|
|
976
|
+
a.click();
|
|
977
|
+
URL.revokeObjectURL(downloadUrl);
|
|
978
|
+
messageApi.success(`${action.label}成功`);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
// 处理 JSON 响应
|
|
982
|
+
const result = response;
|
|
983
|
+
if (result.success !== false) {
|
|
984
|
+
messageApi.success(`${action.label}成功`);
|
|
985
|
+
// 刷新数据
|
|
986
|
+
await fetchList();
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
messageApi.error(result.message || `${action.label}失败`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
catch (error) {
|
|
994
|
+
console.error(`Action ${action.key} failed:`, error);
|
|
995
|
+
messageApi.error(`${action.label}失败`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}, [handleDelete, rowKey, request, messageApi, fetchList]);
|
|
950
999
|
// ---------- 新增 / 编辑提交 ----------
|
|
951
1000
|
const handleFormOk = react.useCallback(async (values) => {
|
|
952
1001
|
const isCreate = modalState.mode === 'create';
|
|
953
1002
|
if (isCreate) {
|
|
954
|
-
if (schema.api.create) {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1003
|
+
if (!schema.api.create) {
|
|
1004
|
+
messageApi.error('新增功能未配置');
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
try {
|
|
1008
|
+
await request(schema.api.create, {
|
|
1009
|
+
method: 'POST',
|
|
1010
|
+
body: JSON.stringify(values),
|
|
1011
|
+
});
|
|
1012
|
+
messageApi.success('新增成功');
|
|
1013
|
+
setModalState({ open: false, mode: 'create' });
|
|
1014
|
+
fetchList();
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
console.error('Create failed:', error);
|
|
1018
|
+
messageApi.error('新增失败,请稍后重试');
|
|
968
1019
|
}
|
|
969
|
-
const newRecord = Object.assign({ [rowKey]: `local-${Date.now()}` }, values);
|
|
970
|
-
localDataRef.current = [newRecord, ...localDataRef.current];
|
|
971
|
-
localFilter(filterParams, 1, pageSize);
|
|
972
|
-
setPage(1);
|
|
973
|
-
messageApi.success('新增成功(演示模式)');
|
|
974
1020
|
}
|
|
975
1021
|
else {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1022
|
+
if (!schema.api.update) {
|
|
1023
|
+
messageApi.error('编辑功能未配置');
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
await request(buildUrl(schema.api.update, values), {
|
|
1028
|
+
method: 'PUT',
|
|
1029
|
+
body: JSON.stringify(values),
|
|
1030
|
+
});
|
|
1031
|
+
messageApi.success('编辑成功');
|
|
1032
|
+
setModalState({ open: false, mode: 'create' });
|
|
1033
|
+
fetchList();
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
console.error('Update failed:', error);
|
|
1037
|
+
messageApi.error('编辑失败,请稍后重试');
|
|
991
1038
|
}
|
|
992
|
-
localDataRef.current = localDataRef.current.map(r => r[rowKey] === id ? Object.assign(Object.assign({}, r), values) : r);
|
|
993
|
-
localFilter(filterParams, page, pageSize);
|
|
994
|
-
messageApi.success('编辑成功(演示模式)');
|
|
995
1039
|
}
|
|
996
|
-
setModalState({ open: false, mode: 'create' });
|
|
997
1040
|
}, [
|
|
998
|
-
request, modalState.mode, schema.api,
|
|
999
|
-
fetchList, localFilter, filterParams, page, pageSize, messageApi,
|
|
1041
|
+
request, modalState.mode, schema.api, fetchList, messageApi,
|
|
1000
1042
|
]);
|
|
1001
1043
|
return (jsxRuntime.jsxs("div", { style: { padding: 24, background: '#f5f6fa', minHeight: '100vh' }, children: [contextHolder, jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }, children: [jsxRuntime.jsx(Title, { level: 4, style: { margin: 0 }, children: schema.title }), schema.api.create && (jsxRuntime.jsx(antd.Button, { type: "primary", icon: jsxRuntime.jsx(icons.PlusOutlined, {}), onClick: () => setModalState({ open: true, mode: 'create', record: undefined }), children: schema.createButtonLabel || '新增' }))] }), jsxRuntime.jsx(DynamicFilter, { schema: schema, onSearch: handleSearch, onReset: () => handleSearch({}) }), jsxRuntime.jsx(DynamicTable, { schema: schema, data: data, loading: loading, pagination: {
|
|
1002
1044
|
current: page,
|