crud-page-react 0.0.4 → 0.0.6

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.js CHANGED
@@ -819,6 +819,23 @@ function buildUrl(template, record) {
819
819
  return value !== undefined ? String(value) : match;
820
820
  });
821
821
  }
822
+ /** 处理模板数据,支持 {{fieldName}} 格式的变量替换 */
823
+ function processTemplateData(data, record) {
824
+ const result = {};
825
+ for (const [key, value] of Object.entries(data)) {
826
+ if (typeof value === 'string' && value.includes('{{') && value.includes('}}')) {
827
+ // 替换模板变量 {{fieldName}}
828
+ result[key] = value.replace(/\{\{(\w+)\}\}/g, (match, fieldName) => {
829
+ const fieldValue = record[fieldName];
830
+ return fieldValue !== undefined ? String(fieldValue) : match;
831
+ });
832
+ }
833
+ else {
834
+ result[key] = value;
835
+ }
836
+ }
837
+ return result;
838
+ }
822
839
  /** 通用请求封装 */
823
840
  async function apiRequest(url, options) {
824
841
  const res = await fetch(url, Object.assign({ headers: { 'Content-Type': 'application/json' } }, options));
@@ -848,8 +865,8 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
848
865
  const rowKey = schema.rowKey || 'id';
849
866
  // 使用传入的apiRequest或默认的
850
867
  const request = customApiRequest || apiRequest;
851
- // 本地兜底数据(API 失败时使用)
852
- const localDataRef = react.useRef(initialData);
868
+ // 初始数据引用
869
+ const initialDataRef = react.useRef(initialData);
853
870
  const [data, setData] = react.useState([]);
854
871
  const [loading, setLoading] = react.useState(false);
855
872
  const [total, setTotal] = react.useState(0);
@@ -858,33 +875,20 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
858
875
  const [pageSize, setPageSize] = react.useState(((_a = schema.pagination) === null || _a === void 0 ? void 0 : _a.pageSize) || 10);
859
876
  const [modalState, setModalState] = react.useState({ open: false, mode: 'create' });
860
877
  const [messageApi, contextHolder] = antd.message.useMessage();
861
- // ---------- 本地过滤(API 失败时兜底) ----------
862
- const localFilter = react.useCallback((params, p, ps) => {
863
- const filtered = localDataRef.current.filter(row => {
864
- return Object.entries(params).every(([key, val]) => {
865
- if (val === undefined || val === null || val === '')
866
- return true;
867
- if (key.endsWith('_min'))
868
- return Number(row[key.slice(0, -4)]) >= Number(val);
869
- if (key.endsWith('_max'))
870
- return Number(row[key.slice(0, -4)]) <= Number(val);
871
- if (key.endsWith('_start') || key.endsWith('_end'))
872
- return true;
873
- const rowVal = row[key];
874
- if (Array.isArray(rowVal)) {
875
- return Array.isArray(val) ? val.some(v => rowVal.includes(v)) : rowVal.includes(val);
876
- }
877
- if (typeof val === 'string')
878
- return String(rowVal).toLowerCase().includes(val.toLowerCase());
879
- return rowVal === val;
880
- });
881
- });
882
- const start = (p - 1) * ps;
883
- setData(filtered.slice(start, start + ps));
884
- setTotal(filtered.length);
878
+ // ---------- 初始化数据 ----------
879
+ const initializeData = react.useCallback(() => {
880
+ if (initialDataRef.current.length > 0) {
881
+ setData(initialDataRef.current);
882
+ setTotal(initialDataRef.current.length);
883
+ }
885
884
  }, []);
886
885
  // ---------- 获取列表 ----------
887
886
  const fetchList = react.useCallback(async (params = filterParams, p = page, ps = pageSize) => {
887
+ if (!schema.api.list) {
888
+ // 没有配置 API,使用初始数据
889
+ initializeData();
890
+ return;
891
+ }
888
892
  setLoading(true);
889
893
  try {
890
894
  const query = new URLSearchParams({ page: String(p), pageSize: String(ps) });
@@ -897,14 +901,22 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
897
901
  setData(list);
898
902
  setTotal(tot);
899
903
  }
900
- catch (_a) {
901
- // API 不可用 本地演示模式
902
- localFilter(params, p, ps);
904
+ catch (error) {
905
+ console.error('Failed to fetch list:', error);
906
+ messageApi.error('获取数据失败,请检查网络连接或联系管理员');
907
+ // 如果有初始数据,显示初始数据
908
+ if (initialDataRef.current.length > 0) {
909
+ initializeData();
910
+ }
911
+ else {
912
+ setData([]);
913
+ setTotal(0);
914
+ }
903
915
  }
904
916
  finally {
905
917
  setLoading(false);
906
918
  }
907
- }, [request, schema.api.list, filterParams, page, pageSize, localFilter]);
919
+ }, [request, schema.api.list, filterParams, page, pageSize, initializeData, messageApi]);
908
920
  // 初始加载 & 参数变化时重新请求
909
921
  react.useEffect(() => {
910
922
  fetchList(filterParams, page, pageSize);
@@ -922,22 +934,20 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
922
934
  }, []);
923
935
  // ---------- 删除 ----------
924
936
  const handleDelete = react.useCallback(async (record) => {
925
- const id = record[rowKey];
926
- if (schema.api.delete) {
927
- try {
928
- await request(buildUrl(schema.api.delete, record), { method: 'DELETE' });
929
- messageApi.success('删除成功');
930
- fetchList();
931
- return;
932
- }
933
- catch (_a) {
934
- // 降级本地
935
- }
937
+ if (!schema.api.delete) {
938
+ messageApi.error('删除功能未配置');
939
+ return;
940
+ }
941
+ try {
942
+ await request(buildUrl(schema.api.delete, record), { method: 'DELETE' });
943
+ messageApi.success('删除成功');
944
+ fetchList();
945
+ }
946
+ catch (error) {
947
+ console.error('Delete failed:', error);
948
+ messageApi.error('删除失败,请稍后重试');
936
949
  }
937
- localDataRef.current = localDataRef.current.filter(r => r[rowKey] !== id);
938
- localFilter(filterParams, page, pageSize);
939
- messageApi.success('删除成功(演示模式)');
940
- }, [request, schema.api.delete, rowKey, fetchList, localFilter, filterParams, page, pageSize, messageApi]);
950
+ }, [request, schema.api.delete, fetchList, messageApi]);
941
951
  // ---------- 操作列点击 ----------
942
952
  const handleAction = react.useCallback(async (action, record) => {
943
953
  if (action.type === 'view') {
@@ -949,31 +959,55 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
949
959
  else if (action.type === 'delete') {
950
960
  handleDelete(record);
951
961
  }
952
- else if (action.type === 'custom' && action.api) {
962
+ else if (action.type === 'custom') {
953
963
  // 处理自定义 action 的 API 调用
964
+ let apiConfig;
965
+ // 优先使用 apiKey 引用统一配置,向后兼容 api 直接配置
966
+ if (action.apiKey) {
967
+ const apiDef = schema.api[action.apiKey];
968
+ if (typeof apiDef === 'string') {
969
+ // 简单字符串 URL 配置
970
+ apiConfig = {
971
+ url: apiDef,
972
+ method: 'GET',
973
+ responseType: 'json'
974
+ };
975
+ }
976
+ else if (apiDef && typeof apiDef === 'object') {
977
+ // 完整的 API 配置对象
978
+ apiConfig = apiDef;
979
+ }
980
+ }
981
+ else if (action.api) {
982
+ // 向后兼容:使用 action.api 配置
983
+ apiConfig = action.api;
984
+ }
985
+ if (!apiConfig) {
986
+ messageApi.error(`${action.label}未配置 API`);
987
+ return;
988
+ }
954
989
  try {
955
990
  // 构建 URL,动态替换占位符
956
- let url = action.api.url;
957
- // 替换所有 :fieldName 格式的占位符
991
+ let url = apiConfig.url;
958
992
  url = url.replace(/:(\w+)/g, (match, fieldName) => {
959
993
  const value = record[fieldName];
960
994
  return value !== undefined ? String(value) : match;
961
995
  });
962
996
  // 构建请求选项
963
997
  const options = {
964
- method: action.api.method,
965
- headers: Object.assign({ 'Content-Type': 'application/json' }, action.api.headers)
998
+ method: apiConfig.method || 'GET',
999
+ headers: Object.assign({ 'Content-Type': 'application/json' }, apiConfig.headers)
966
1000
  };
967
- // 添加请求体数据
968
- if (action.api.data && ['POST', 'PUT', 'PATCH'].includes(action.api.method)) {
969
- options.body = JSON.stringify(Object.assign(Object.assign({}, action.api.data), {
970
- // 可以添加动态数据
971
- recordId: record[rowKey], timestamp: new Date().toISOString() }));
1001
+ // 处理请求体数据
1002
+ if (apiConfig.data && ['POST', 'PUT', 'PATCH'].includes(apiConfig.method || 'GET')) {
1003
+ // 支持模板变量替换
1004
+ const processedData = processTemplateData(apiConfig.data, record);
1005
+ options.body = JSON.stringify(Object.assign(Object.assign({}, processedData), { recordId: record[rowKey], timestamp: new Date().toISOString() }));
972
1006
  }
973
1007
  // 调用 API
974
1008
  const response = await request(url, options);
975
1009
  // 处理特殊响应类型
976
- if (action.api.responseType === 'blob') {
1010
+ if (apiConfig.responseType === 'blob') {
977
1011
  // 处理文件下载
978
1012
  const blob = new Blob([response]);
979
1013
  const downloadUrl = URL.createObjectURL(blob);
@@ -1007,52 +1041,45 @@ const CrudPage = ({ schema, initialData = [], apiRequest: customApiRequest }) =>
1007
1041
  const handleFormOk = react.useCallback(async (values) => {
1008
1042
  const isCreate = modalState.mode === 'create';
1009
1043
  if (isCreate) {
1010
- if (schema.api.create) {
1011
- try {
1012
- await request(schema.api.create, {
1013
- method: 'POST',
1014
- body: JSON.stringify(values),
1015
- });
1016
- messageApi.success('新增成功');
1017
- setModalState({ open: false, mode: 'create' });
1018
- fetchList();
1019
- return;
1020
- }
1021
- catch (_a) {
1022
- // 降级本地
1023
- }
1044
+ if (!schema.api.create) {
1045
+ messageApi.error('新增功能未配置');
1046
+ return;
1047
+ }
1048
+ try {
1049
+ await request(schema.api.create, {
1050
+ method: 'POST',
1051
+ body: JSON.stringify(values),
1052
+ });
1053
+ messageApi.success('新增成功');
1054
+ setModalState({ open: false, mode: 'create' });
1055
+ fetchList();
1056
+ }
1057
+ catch (error) {
1058
+ console.error('Create failed:', error);
1059
+ messageApi.error('新增失败,请稍后重试');
1024
1060
  }
1025
- const newRecord = Object.assign({ [rowKey]: `local-${Date.now()}` }, values);
1026
- localDataRef.current = [newRecord, ...localDataRef.current];
1027
- localFilter(filterParams, 1, pageSize);
1028
- setPage(1);
1029
- messageApi.success('新增成功(演示模式)');
1030
1061
  }
1031
1062
  else {
1032
- const id = values[rowKey];
1033
- if (schema.api.update) {
1034
- try {
1035
- await request(buildUrl(schema.api.update, values), {
1036
- method: 'PUT',
1037
- body: JSON.stringify(values),
1038
- });
1039
- messageApi.success('编辑成功');
1040
- setModalState({ open: false, mode: 'create' });
1041
- fetchList();
1042
- return;
1043
- }
1044
- catch (_b) {
1045
- // 降级本地
1046
- }
1063
+ if (!schema.api.update) {
1064
+ messageApi.error('编辑功能未配置');
1065
+ return;
1066
+ }
1067
+ try {
1068
+ await request(buildUrl(schema.api.update, values), {
1069
+ method: 'PUT',
1070
+ body: JSON.stringify(values),
1071
+ });
1072
+ messageApi.success('编辑成功');
1073
+ setModalState({ open: false, mode: 'create' });
1074
+ fetchList();
1075
+ }
1076
+ catch (error) {
1077
+ console.error('Update failed:', error);
1078
+ messageApi.error('编辑失败,请稍后重试');
1047
1079
  }
1048
- localDataRef.current = localDataRef.current.map(r => r[rowKey] === id ? Object.assign(Object.assign({}, r), values) : r);
1049
- localFilter(filterParams, page, pageSize);
1050
- messageApi.success('编辑成功(演示模式)');
1051
1080
  }
1052
- setModalState({ open: false, mode: 'create' });
1053
1081
  }, [
1054
- request, modalState.mode, schema.api, rowKey,
1055
- fetchList, localFilter, filterParams, page, pageSize, messageApi,
1082
+ request, modalState.mode, schema.api, fetchList, messageApi,
1056
1083
  ]);
1057
1084
  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: {
1058
1085
  current: page,