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 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
- /** 动态替换 URL 模板中的占位符 */
814
- function buildUrl(template, record) {
815
- return template.replace(/:(\w+)/g, (match, fieldName) => {
816
- const value = record[fieldName];
817
- return value !== undefined ? String(value) : match;
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
- if (!schema.api.list) {
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
- const json = await request(`${schema.api.list}?${query}`);
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
- if (!schema.api.delete) {
966
+ const deleteApiConfig = parseApiConfig(schema.api.delete);
967
+ if (!deleteApiConfig) {
919
968
  messageApi.error('删除功能未配置');
920
969
  return;
921
970
  }
922
971
  try {
923
- await request(buildUrl(schema.api.delete, record), { method: 'DELETE' });
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' && action.api) {
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 = action.api.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: action.api.method,
956
- headers: Object.assign({ 'Content-Type': 'application/json' }, action.api.headers)
1044
+ method: apiConfig.method || 'GET',
1045
+ headers: Object.assign({ 'Content-Type': 'application/json' }, apiConfig.headers)
957
1046
  };
958
- // 添加请求体数据
959
- if (action.api.data && ['POST', 'PUT', 'PATCH'].includes(action.api.method)) {
960
- options.body = JSON.stringify(Object.assign(Object.assign({}, action.api.data), {
961
- // 可以添加动态数据
962
- recordId: record[rowKey], timestamp: new Date().toISOString() }));
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 (action.api.responseType === 'blob') {
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
- if (!schema.api.create) {
1090
+ const createApiConfig = parseApiConfig(schema.api.create);
1091
+ if (!createApiConfig) {
1002
1092
  messageApi.error('新增功能未配置');
1003
1093
  return;
1004
1094
  }
1005
1095
  try {
1006
- await request(schema.api.create, {
1007
- method: 'POST',
1008
- body: JSON.stringify(values),
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
- if (!schema.api.update) {
1119
+ const updateApiConfig = parseApiConfig(schema.api.update);
1120
+ if (!updateApiConfig) {
1021
1121
  messageApi.error('编辑功能未配置');
1022
1122
  return;
1023
1123
  }
1024
1124
  try {
1025
- await request(buildUrl(schema.api.update, values), {
1026
- method: 'PUT',
1027
- body: JSON.stringify(values),
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();