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/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
- /** 替换 URL 模板中的 :id */
816
- function buildUrl(template, id) {
817
- return template.replace(/:id/, String(id));
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
- // 本地兜底数据(API 失败时使用)
849
- const localDataRef = react.useRef(initialData);
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
- // ---------- 本地过滤(API 失败时兜底) ----------
859
- const localFilter = react.useCallback((params, p, ps) => {
860
- const filtered = localDataRef.current.filter(row => {
861
- return Object.entries(params).every(([key, val]) => {
862
- if (val === undefined || val === null || val === '')
863
- return true;
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 (_a) {
898
- // API 不可用 本地演示模式
899
- localFilter(params, p, ps);
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, localFilter]);
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
- const id = record[rowKey];
923
- if (schema.api.delete) {
924
- try {
925
- await request(buildUrl(schema.api.delete, id), { method: 'DELETE' });
926
- messageApi.success('删除成功');
927
- fetchList();
928
- return;
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
- localDataRef.current = localDataRef.current.filter(r => r[rowKey] !== id);
935
- localFilter(filterParams, page, pageSize);
936
- messageApi.success('删除成功(演示模式)');
937
- }, [request, schema.api.delete, rowKey, fetchList, localFilter, filterParams, page, pageSize, messageApi]);
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
- }, [handleDelete]);
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
- try {
956
- await request(schema.api.create, {
957
- method: 'POST',
958
- body: JSON.stringify(values),
959
- });
960
- messageApi.success('新增成功');
961
- setModalState({ open: false, mode: 'create' });
962
- fetchList();
963
- return;
964
- }
965
- catch (_a) {
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
- const id = values[rowKey];
977
- if (schema.api.update) {
978
- try {
979
- await request(buildUrl(schema.api.update, id), {
980
- method: 'PUT',
981
- body: JSON.stringify(values),
982
- });
983
- messageApi.success('编辑成功');
984
- setModalState({ open: false, mode: 'create' });
985
- fetchList();
986
- return;
987
- }
988
- catch (_b) {
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, rowKey,
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,