crud-page-react 0.0.5 → 0.0.7
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/dist/index.d.ts +7 -5
- package/dist/index.esm.js +146 -31
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +146 -31
- package/dist/index.js.map +1 -1
- package/dist/types/schema.d.ts +7 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -812,12 +812,40 @@ function DynamicForm({ schema, mode, visible, initialValues, onSubmit, onCancel,
|
|
|
812
812
|
}
|
|
813
813
|
|
|
814
814
|
const { Title } = antd.Typography;
|
|
815
|
-
/**
|
|
816
|
-
function
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
815
|
+
/** 处理模板数据,支持 {{fieldName}} 格式的变量替换 */
|
|
816
|
+
function processTemplateData(data, record) {
|
|
817
|
+
const result = {};
|
|
818
|
+
for (const [key, value] of Object.entries(data)) {
|
|
819
|
+
if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) {
|
|
820
|
+
// 替换模板变量 {{fieldName}}
|
|
821
|
+
result[key] = value.replace(/\{\{(\w+)\}\}/g, (match, fieldName) => {
|
|
822
|
+
const fieldValue = record[fieldName];
|
|
823
|
+
return fieldValue !== undefined ? String(fieldValue) : match;
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
result[key] = value;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
/** 解析 API 配置,支持字符串和对象两种格式 */
|
|
833
|
+
function parseApiConfig(apiDef) {
|
|
834
|
+
if (!apiDef)
|
|
835
|
+
return undefined;
|
|
836
|
+
if (typeof apiDef === 'string') {
|
|
837
|
+
// 简单字符串 URL 配置
|
|
838
|
+
return {
|
|
839
|
+
url: apiDef,
|
|
840
|
+
method: 'GET',
|
|
841
|
+
responseType: 'json'
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
else if (typeof apiDef === 'object') {
|
|
845
|
+
// 完整的 API 配置对象
|
|
846
|
+
return apiDef;
|
|
847
|
+
}
|
|
848
|
+
return undefined;
|
|
821
849
|
}
|
|
822
850
|
/** 通用请求封装 */
|
|
823
851
|
async function apiRequest(url, options) {
|
|
@@ -867,19 +895,39 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
867
895
|
}, []);
|
|
868
896
|
// ---------- 获取列表 ----------
|
|
869
897
|
const fetchList = react.useCallback(async (params = filterParams, p = page, ps = pageSize) => {
|
|
870
|
-
|
|
898
|
+
const listApiConfig = parseApiConfig(schema.api.list);
|
|
899
|
+
if (!listApiConfig) {
|
|
871
900
|
// 没有配置 API,使用初始数据
|
|
872
901
|
initializeData();
|
|
873
902
|
return;
|
|
874
903
|
}
|
|
875
904
|
setLoading(true);
|
|
876
905
|
try {
|
|
906
|
+
// 构建查询参数
|
|
877
907
|
const query = new URLSearchParams({ page: String(p), pageSize: String(ps) });
|
|
878
908
|
Object.entries(params).forEach(([k, v]) => {
|
|
879
909
|
if (v !== undefined && v !== null && v !== '')
|
|
880
910
|
query.set(k, String(v));
|
|
881
911
|
});
|
|
882
|
-
|
|
912
|
+
// 构建请求选项
|
|
913
|
+
const options = {
|
|
914
|
+
method: listApiConfig.method || 'GET',
|
|
915
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, listApiConfig.headers)
|
|
916
|
+
};
|
|
917
|
+
// 如果是 POST 请求,将查询参数放到请求体中
|
|
918
|
+
let url = listApiConfig.url;
|
|
919
|
+
if (listApiConfig.method === 'POST') {
|
|
920
|
+
const queryParams = {};
|
|
921
|
+
query.forEach((value, key) => {
|
|
922
|
+
queryParams[key] = value;
|
|
923
|
+
});
|
|
924
|
+
const requestData = Object.assign(Object.assign({}, queryParams), listApiConfig.data);
|
|
925
|
+
options.body = JSON.stringify(requestData);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
url = `${url}?${query}`;
|
|
929
|
+
}
|
|
930
|
+
const json = await request(url, options);
|
|
883
931
|
const { list, total: tot } = extractListResponse(json);
|
|
884
932
|
setData(list);
|
|
885
933
|
setTotal(tot);
|
|
@@ -917,12 +965,29 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
917
965
|
}, []);
|
|
918
966
|
// ---------- 删除 ----------
|
|
919
967
|
const handleDelete = react.useCallback(async (record) => {
|
|
920
|
-
|
|
968
|
+
const deleteApiConfig = parseApiConfig(schema.api.delete);
|
|
969
|
+
if (!deleteApiConfig) {
|
|
921
970
|
messageApi.error('删除功能未配置');
|
|
922
971
|
return;
|
|
923
972
|
}
|
|
924
973
|
try {
|
|
925
|
-
|
|
974
|
+
// 构建 URL,动态替换占位符
|
|
975
|
+
let url = deleteApiConfig.url;
|
|
976
|
+
url = url.replace(/:(\w+)/g, (match, fieldName) => {
|
|
977
|
+
const value = record[fieldName];
|
|
978
|
+
return value !== undefined ? String(value) : match;
|
|
979
|
+
});
|
|
980
|
+
// 构建请求选项
|
|
981
|
+
const options = {
|
|
982
|
+
method: deleteApiConfig.method || 'DELETE',
|
|
983
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, deleteApiConfig.headers)
|
|
984
|
+
};
|
|
985
|
+
// 处理请求体数据
|
|
986
|
+
if (deleteApiConfig.data && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(deleteApiConfig.method || 'DELETE')) {
|
|
987
|
+
const processedData = processTemplateData(deleteApiConfig.data, record);
|
|
988
|
+
options.body = JSON.stringify(Object.assign(Object.assign({}, processedData), { recordId: record[rowKey], timestamp: new Date().toISOString() }));
|
|
989
|
+
}
|
|
990
|
+
await request(url, options);
|
|
926
991
|
messageApi.success('删除成功');
|
|
927
992
|
fetchList();
|
|
928
993
|
}
|
|
@@ -930,7 +995,7 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
930
995
|
console.error('Delete failed:', error);
|
|
931
996
|
messageApi.error('删除失败,请稍后重试');
|
|
932
997
|
}
|
|
933
|
-
}, [request, schema.api.delete, fetchList, messageApi]);
|
|
998
|
+
}, [request, schema.api.delete, fetchList, messageApi, rowKey]);
|
|
934
999
|
// ---------- 操作列点击 ----------
|
|
935
1000
|
const handleAction = react.useCallback(async (action, record) => {
|
|
936
1001
|
if (action.type === 'view') {
|
|
@@ -942,31 +1007,55 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
942
1007
|
else if (action.type === 'delete') {
|
|
943
1008
|
handleDelete(record);
|
|
944
1009
|
}
|
|
945
|
-
else if (action.type === 'custom'
|
|
1010
|
+
else if (action.type === 'custom') {
|
|
946
1011
|
// 处理自定义 action 的 API 调用
|
|
1012
|
+
let apiConfig;
|
|
1013
|
+
// 优先使用 apiKey 引用统一配置,向后兼容 api 直接配置
|
|
1014
|
+
if (action.apiKey) {
|
|
1015
|
+
const apiDef = schema.api[action.apiKey];
|
|
1016
|
+
if (typeof apiDef === 'string') {
|
|
1017
|
+
// 简单字符串 URL 配置
|
|
1018
|
+
apiConfig = {
|
|
1019
|
+
url: apiDef,
|
|
1020
|
+
method: 'GET',
|
|
1021
|
+
responseType: 'json'
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
else if (apiDef && typeof apiDef === 'object') {
|
|
1025
|
+
// 完整的 API 配置对象
|
|
1026
|
+
apiConfig = apiDef;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else if (action.api) {
|
|
1030
|
+
// 向后兼容:使用 action.api 配置
|
|
1031
|
+
apiConfig = action.api;
|
|
1032
|
+
}
|
|
1033
|
+
if (!apiConfig) {
|
|
1034
|
+
messageApi.error(`${action.label}未配置 API`);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
947
1037
|
try {
|
|
948
1038
|
// 构建 URL,动态替换占位符
|
|
949
|
-
let url =
|
|
950
|
-
// 替换所有 :fieldName 格式的占位符
|
|
1039
|
+
let url = apiConfig.url;
|
|
951
1040
|
url = url.replace(/:(\w+)/g, (match, fieldName) => {
|
|
952
1041
|
const value = record[fieldName];
|
|
953
1042
|
return value !== undefined ? String(value) : match;
|
|
954
1043
|
});
|
|
955
1044
|
// 构建请求选项
|
|
956
1045
|
const options = {
|
|
957
|
-
method:
|
|
958
|
-
headers: Object.assign({ 'Content-Type': 'application/json' },
|
|
1046
|
+
method: apiConfig.method || 'GET',
|
|
1047
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, apiConfig.headers)
|
|
959
1048
|
};
|
|
960
|
-
//
|
|
961
|
-
if (
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1049
|
+
// 处理请求体数据
|
|
1050
|
+
if (apiConfig.data && ['POST', 'PUT', 'PATCH'].includes(apiConfig.method || 'GET')) {
|
|
1051
|
+
// 支持模板变量替换
|
|
1052
|
+
const processedData = processTemplateData(apiConfig.data, record);
|
|
1053
|
+
options.body = JSON.stringify(Object.assign(Object.assign({}, processedData), { recordId: record[rowKey], timestamp: new Date().toISOString() }));
|
|
965
1054
|
}
|
|
966
1055
|
// 调用 API
|
|
967
1056
|
const response = await request(url, options);
|
|
968
1057
|
// 处理特殊响应类型
|
|
969
|
-
if (
|
|
1058
|
+
if (apiConfig.responseType === 'blob') {
|
|
970
1059
|
// 处理文件下载
|
|
971
1060
|
const blob = new Blob([response]);
|
|
972
1061
|
const downloadUrl = URL.createObjectURL(blob);
|
|
@@ -1000,15 +1089,25 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
1000
1089
|
const handleFormOk = react.useCallback(async (values) => {
|
|
1001
1090
|
const isCreate = modalState.mode === 'create';
|
|
1002
1091
|
if (isCreate) {
|
|
1003
|
-
|
|
1092
|
+
const createApiConfig = parseApiConfig(schema.api.create);
|
|
1093
|
+
if (!createApiConfig) {
|
|
1004
1094
|
messageApi.error('新增功能未配置');
|
|
1005
1095
|
return;
|
|
1006
1096
|
}
|
|
1007
1097
|
try {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1098
|
+
// 构建请求选项
|
|
1099
|
+
const options = {
|
|
1100
|
+
method: createApiConfig.method || 'POST',
|
|
1101
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, createApiConfig.headers)
|
|
1102
|
+
};
|
|
1103
|
+
// 处理请求体数据
|
|
1104
|
+
let requestData = Object.assign({}, values);
|
|
1105
|
+
if (createApiConfig.data) {
|
|
1106
|
+
const processedData = processTemplateData(createApiConfig.data, values);
|
|
1107
|
+
requestData = Object.assign(Object.assign(Object.assign({}, requestData), processedData), { timestamp: new Date().toISOString() });
|
|
1108
|
+
}
|
|
1109
|
+
options.body = JSON.stringify(requestData);
|
|
1110
|
+
await request(createApiConfig.url, options);
|
|
1012
1111
|
messageApi.success('新增成功');
|
|
1013
1112
|
setModalState({ open: false, mode: 'create' });
|
|
1014
1113
|
fetchList();
|
|
@@ -1019,15 +1118,31 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
|
|
|
1019
1118
|
}
|
|
1020
1119
|
}
|
|
1021
1120
|
else {
|
|
1022
|
-
|
|
1121
|
+
const updateApiConfig = parseApiConfig(schema.api.update);
|
|
1122
|
+
if (!updateApiConfig) {
|
|
1023
1123
|
messageApi.error('编辑功能未配置');
|
|
1024
1124
|
return;
|
|
1025
1125
|
}
|
|
1026
1126
|
try {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1127
|
+
// 构建 URL,动态替换占位符
|
|
1128
|
+
let url = updateApiConfig.url;
|
|
1129
|
+
url = url.replace(/:(\w+)/g, (match, fieldName) => {
|
|
1130
|
+
const value = values[fieldName];
|
|
1131
|
+
return value !== undefined ? String(value) : match;
|
|
1030
1132
|
});
|
|
1133
|
+
// 构建请求选项
|
|
1134
|
+
const options = {
|
|
1135
|
+
method: updateApiConfig.method || 'PUT',
|
|
1136
|
+
headers: Object.assign({ 'Content-Type': 'application/json' }, updateApiConfig.headers)
|
|
1137
|
+
};
|
|
1138
|
+
// 处理请求体数据
|
|
1139
|
+
let requestData = Object.assign({}, values);
|
|
1140
|
+
if (updateApiConfig.data) {
|
|
1141
|
+
const processedData = processTemplateData(updateApiConfig.data, values);
|
|
1142
|
+
requestData = Object.assign(Object.assign(Object.assign({}, requestData), processedData), { timestamp: new Date().toISOString() });
|
|
1143
|
+
}
|
|
1144
|
+
options.body = JSON.stringify(requestData);
|
|
1145
|
+
await request(url, options);
|
|
1031
1146
|
messageApi.success('编辑成功');
|
|
1032
1147
|
setModalState({ open: false, mode: 'create' });
|
|
1033
1148
|
fetchList();
|