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