@cqsjjb/jjb-react-admin-component 3.3.6 → 3.3.8

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.
@@ -92,7 +92,7 @@ export interface ListDataContainerProps<T extends boolean = false> {
92
92
  /**
93
93
  * 搜索表单配置数组
94
94
  * 描述:由 SearchFormItemConfig 类型的配置项组成,组件内部会根据该数组自动生成搜索表单,
95
- * 不传递则不显示搜索表单
95
+ * 不传递则不显示搜索表单
96
96
  * @type {SearchFormItemConfig[]}
97
97
  * @optional
98
98
  */
@@ -143,6 +143,14 @@ export interface ListDataContainerProps<T extends boolean = false> {
143
143
  ? OverTableProps // ProTable 专属属性(如 search、toolBar 等)
144
144
  : TableProps<any>; // antd 原生 Table 属性(如 bordered、scroll 等)
145
145
 
146
+ /**
147
+ * 搜索表单的自定义属性
148
+ * 描述:传递给SearchForm组件的属性
149
+ * @type {Object}
150
+ * @optional
151
+ */
152
+ searchFormProps?: Object;
153
+
146
154
  /**
147
155
  * 表格类型切换开关
148
156
  * 描述:控制组件内部使用「项目自定义 ProTable」还是「antd 原生 Table」,
@@ -1,48 +1,49 @@
1
1
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
- import React, { useState, useEffect, useCallback, useImperativeHandle, forwardRef } from 'react';
2
+ import React, { useState, useEffect, useCallback, useImperativeHandle, forwardRef, useRef } from 'react';
3
3
  import { Table, Row, Col, Form } from 'antd';
4
4
  import { isEqual } from 'lodash';
5
5
  import SearchForm from '@cqsjjb/jjb-react-admin-component/SearchForm';
6
6
  import ProTable from '@cqsjjb/jjb-react-admin-component/Table';
7
7
 
8
- /**
9
- * @component ListDataContainer
10
- * @description 通用列表数据容器组件,提供搜索表单、数据表格、分页等功能的完整列表页面解决方案
11
- *
12
- * @props {Array} columns - 表格列配置,遵循Ant Design Table组件的columns规范
13
- * @props {Function} fetchDataApi - 数据获取API函数,接收分页和搜索参数,返回包含list和total的对象
14
- * @props {Array} searchFormConfig - 搜索表单配置数组
15
- * @param {React.Component} searchFormConfig[].field - 表单项组件(如ControlWrapper.Select)
16
- * @param {string} searchFormConfig[].name - 表单项字段名
17
- * @param {string} searchFormConfig[].label - 表单项标签
18
- * @param {Object} searchFormConfig[].fieldProps - 传递给字段组件的属性
19
- * @param {Object} searchFormConfig[].itemProps - 传递给Form.Item的属性
20
- * @param {React.ReactNode} searchFormConfig[].content - 字段组件的子元素(如Select.Option列表)
21
- * @param {boolean} searchFormConfig[].required - 是否必填
22
- * @props {string} rowKey - 表格行键值,默认为'id'
23
- * @props {Object} initialPagination - 初始分页配置,包含current、pageSize等分页属性
24
- * @props {Object} tableProps - 传递给Table组件的属性
25
- * @props {boolean} proTable - 是否使用ProTable组件,默认为false
26
- * @example
27
- * // 基础用法
28
- * <ListDataContainer
29
- * columns={tableColumns}
30
- * fetchDataApi={getCourseList}
31
- * searchFormConfig={[
32
- * {
33
- * field: Select,
34
- * name: 'status',
35
- * label: '状态',
36
- * }
37
- * {
38
- * field: Input,
39
- * name: 'name',
40
- * label: '名称',
41
- * required: true,
42
- * }
43
- * ]}
44
- * />
45
- *
8
+ /**
9
+ * @component ListDataContainer
10
+ * @description 通用列表数据容器组件,提供搜索表单、数据表格、分页等功能的完整列表页面解决方案
11
+ *
12
+ * @props {Array} columns - 表格列配置,遵循Ant Design Table组件的columns规范
13
+ * @props {Function} fetchDataApi - 数据获取API函数,接收分页和搜索参数,返回包含list和total的对象
14
+ * @props {Array} searchFormConfig - 搜索表单配置数组
15
+ * @param {React.Component} searchFormConfig[].field - 表单项组件(如ControlWrapper.Select)
16
+ * @param {string} searchFormConfig[].name - 表单项字段名
17
+ * @param {string} searchFormConfig[].label - 表单项标签
18
+ * @param {Object} searchFormConfig[].fieldProps - 传递给字段组件的属性
19
+ * @param {Object} searchFormConfig[].itemProps - 传递给Form.Item的属性
20
+ * @param {React.ReactNode} searchFormConfig[].content - 字段组件的子元素(如Select.Option列表)
21
+ * @param {boolean} searchFormConfig[].required - 是否必填
22
+ * @props {string} rowKey - 表格行键值,默认为'id'
23
+ * @props {Object} initialPagination - 初始分页配置,包含current、pageSize等分页属性
24
+ * @props {Object} tableProps - 传递给Table组件的属性
25
+ * @props {Object} searchFormProps - 传递给SearchForm组件的属性
26
+ * @props {boolean} proTable - 是否使用ProTable组件,默认为false
27
+ * @example
28
+ * // 基础用法
29
+ * <ListDataContainer
30
+ * columns={tableColumns}
31
+ * fetchDataApi={getCourseList}
32
+ * searchFormConfig={[
33
+ * {
34
+ * field: Select,
35
+ * name: 'status',
36
+ * label: '状态',
37
+ * }
38
+ * {
39
+ * field: Input,
40
+ * name: 'name',
41
+ * label: '名称',
42
+ * required: true,
43
+ * }
44
+ * ]}
45
+ * />
46
+ *
46
47
  */
47
48
 
48
49
  const SELECT_COMPONENTS = ['Select', 'TreeSelect', 'Cascader', 'DatePicker', 'TimePicker', 'Checkbox', 'Radio', 'Switch', 'Slider', 'Upload'];
@@ -59,29 +60,42 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
59
60
  showQuickJumper: true
60
61
  },
61
62
  tableProps = {},
63
+ searchFormProps = {},
62
64
  proTable = false,
63
65
  onRest = () => {}
64
66
  }, ref) => {
65
- const [form] = Form.useForm();
66
67
  const [tableData, setTableData] = useState([]);
67
68
  const [loading, setLoading] = useState(false);
68
69
  const [pagination, setPagination] = useState(initialPagination);
69
70
  const [totalCount, setTotalCount] = useState(0);
71
+ const [tableScrollY, setTableScrollY] = useState(undefined);
72
+ const form = useRef(null);
73
+ const searchFormRef = useRef(null);
74
+ const containerRef = useRef(null);
70
75
  const getComponentName = Component => {
71
76
  return Component.displayName || Component.constructor.name;
72
77
  };
73
78
 
74
- /**
75
- * 数据加载与刷新方法
76
- * @param {Object} [overrideParams={}] - 可选,用于覆盖内部参数的键值对
77
- * 若传入,会覆盖同名的表单参数和分页参数(如page、pageSize或搜索字段)
79
+ /**
80
+ * 数据加载与刷新方法
81
+ * @param {Object} [overrideParams={}] - 可选,用于覆盖内部参数的键值对
82
+ * 若传入,会覆盖同名的表单参数和分页参数(如page、pageSize或搜索字段)
78
83
  */
79
84
  const loadDataSource = useCallback(async (overrideParams = {}) => {
80
85
  if (!fetchDataApi) return;
81
86
  setLoading(true);
82
87
  try {
88
+ if (overrideParams.pageIndex && overrideParams.pageSize) {
89
+ setPagination(prev => ({
90
+ ...prev,
91
+ pageIndex: overrideParams.pageIndex,
92
+ pageSize: overrideParams.pageSize
93
+ }));
94
+ }
95
+
83
96
  // 1. 获取组件内部的表单参数
84
- const formValues = await form.validateFields();
97
+ const formValues = await form.current.validateFields();
98
+ console.warn("formValues", formValues);
85
99
 
86
100
  // 2. 构造基础参数:分页参数 + 表单参数
87
101
  const baseParams = {
@@ -107,7 +121,7 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
107
121
  } finally {
108
122
  setLoading(false);
109
123
  }
110
- }, [fetchDataApi, pagination, form]);
124
+ }, [fetchDataApi, pagination, form, searchFormConfig]);
111
125
 
112
126
  // 暴露刷新方法给父组件
113
127
  useImperativeHandle(ref, () => ({
@@ -127,11 +141,11 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
127
141
  };
128
142
  const handleReset = () => {
129
143
  // 清空表单
130
- form.setFieldsValue(searchFormConfig.reduce((obj, item) => {
144
+ form.current.setFieldsValue(searchFormConfig.reduce((obj, item) => {
131
145
  obj[item.name] = undefined;
132
146
  return obj;
133
147
  }, {}));
134
- onRest && onRest(form);
148
+ onRest && onRest(form.current);
135
149
  // 重置分页并使用内部默认参数查询
136
150
  setPagination(initialPagination);
137
151
  loadDataSource();
@@ -150,6 +164,59 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
150
164
  useEffect(() => {
151
165
  loadDataSource();
152
166
  }, []);
167
+
168
+ // 动态计算表格滚动高度
169
+ useEffect(() => {
170
+ const calculateScrollY = () => {
171
+ if (!containerRef.current || !searchFormRef.current) return;
172
+ const containerHeight = containerRef.current.clientHeight;
173
+ const searchFormHeight = searchFormRef.current.offsetHeight;
174
+ const gap = 16; // gap between searchForm and table
175
+ const paginationHeight = 64; // 分页器高度(包含margin)
176
+
177
+ // 计算表格可用高度 = 容器高度 - 搜索表单高度 - gap - 分页器高度 - 间隔
178
+ const availableHeight = containerHeight - searchFormHeight - gap - paginationHeight - 12;
179
+
180
+ // 确保最小高度为 100px
181
+ if (availableHeight > 100) {
182
+ setTableScrollY(availableHeight);
183
+ } else {
184
+ setTableScrollY(100);
185
+ }
186
+ };
187
+
188
+ // 延迟计算,确保 DOM 渲染完成
189
+ const timer = setTimeout(() => {
190
+ calculateScrollY();
191
+ }, 100);
192
+
193
+ // 使用 ResizeObserver 监听搜索表单高度变化
194
+ let resizeObserver = null;
195
+ if (typeof ResizeObserver !== 'undefined') {
196
+ resizeObserver = new ResizeObserver(() => {
197
+ // 使用 requestAnimationFrame 确保在下一帧计算
198
+ requestAnimationFrame(() => {
199
+ calculateScrollY();
200
+ });
201
+ });
202
+ if (searchFormRef.current) {
203
+ resizeObserver.observe(searchFormRef.current);
204
+ }
205
+ if (containerRef.current) {
206
+ resizeObserver.observe(containerRef.current);
207
+ }
208
+ }
209
+
210
+ // 监听窗口大小变化
211
+ window.addEventListener('resize', calculateScrollY);
212
+ return () => {
213
+ clearTimeout(timer);
214
+ if (resizeObserver) {
215
+ resizeObserver.disconnect();
216
+ }
217
+ window.removeEventListener('resize', calculateScrollY);
218
+ };
219
+ }, [searchFormConfig]);
153
220
  const generateFormItems = () => {
154
221
  return searchFormConfig.map((item, index) => {
155
222
  const {
@@ -172,26 +239,40 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
172
239
  required: true,
173
240
  message: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
174
241
  }]
175
- } : {}, itemProps, restProps), /*#__PURE__*/React.createElement(FieldComponent, _extends({
242
+ } : {}, {
243
+ style: {
244
+ marginBottom: 0
245
+ }
246
+ }, itemProps, restProps), /*#__PURE__*/React.createElement(FieldComponent, _extends({
176
247
  allowClear: true,
177
248
  placeholder: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
178
249
  }, fieldProps), content || null));
179
250
  }).filter(Boolean);
180
251
  };
181
252
  const TableComponent = proTable ? ProTable : Table;
182
- return /*#__PURE__*/React.createElement(Row, {
183
- gutter: [16, 24]
184
- }, /*#__PURE__*/React.createElement(Col, {
185
- span: 24
186
- }, /*#__PURE__*/React.createElement(SearchForm, {
187
- expand: true,
253
+ return /*#__PURE__*/React.createElement("div", {
254
+ ref: containerRef,
255
+ style: {
256
+ display: 'flex',
257
+ flexDirection: 'column',
258
+ gap: 16,
259
+ height: '100%',
260
+ overflow: 'hidden'
261
+ }
262
+ }, /*#__PURE__*/React.createElement("div", {
263
+ ref: searchFormRef
264
+ }, /*#__PURE__*/React.createElement(SearchForm, _extends({
265
+ defaultExpand: true,
188
266
  form: form,
189
267
  loading: loading,
190
268
  formLine: generateFormItems(),
191
269
  onReset: handleReset,
192
270
  onFinish: handleFinish
193
- })), /*#__PURE__*/React.createElement(Col, {
194
- span: 24
271
+ }, searchFormProps))), /*#__PURE__*/React.createElement("div", {
272
+ style: {
273
+ flex: 1,
274
+ overflow: 'hidden'
275
+ }
195
276
  }, /*#__PURE__*/React.createElement(TableComponent, _extends({
196
277
  columns: columns,
197
278
  dataSource: tableData,
@@ -203,10 +284,16 @@ const ListDataContainer = /*#__PURE__*/forwardRef(({
203
284
  total: totalCount
204
285
  },
205
286
  onChange: handlePaginationChange,
206
- scroll: {
207
- x: 'max-content'
287
+ scroll: tableProps?.scroll ? tableProps.scroll : {
288
+ y: tableScrollY !== undefined ? tableScrollY : tableProps.scroll?.y || undefined
208
289
  },
209
290
  bordered: true
210
- }, tableProps))));
291
+ }, (() => {
292
+ const {
293
+ scroll,
294
+ ...restTableProps
295
+ } = tableProps;
296
+ return restTableProps;
297
+ })()))));
211
298
  });
212
299
  export default ListDataContainer;
@@ -22,7 +22,7 @@
22
22
  .@{com-prefix-cls}-layout-container-tools {
23
23
  margin-top: 0;
24
24
  margin-bottom: 0;
25
- padding: 0 20px 0 20px;
25
+ padding: 0 20px 16px 20px;
26
26
  }
27
27
 
28
28
  .@{com-prefix-cls}-layout-container-content {
@@ -44,4 +44,4 @@
44
44
  padding: 20px;
45
45
  border-top: 1px #f0f0f0 solid;
46
46
  }
47
- }
47
+ }
@@ -11,6 +11,8 @@ export interface SearchFormProps {
11
11
  expand?: boolean;
12
12
  // 内部默认展开状态,仅在 expand 未传入时生效 默认-false
13
13
  defaultExpand?: boolean;
14
+ // 列数 默认-3
15
+ colSize?: number;
14
16
  // 查询 loading 状态 默认-false
15
17
  loading?: boolean;
16
18
  // 表单行节点数组 默认-[]
@@ -29,4 +31,4 @@ export interface SearchFormProps {
29
31
 
30
32
  declare const SearchForm: React.FC<SearchFormProps>;
31
33
 
32
- export default SearchForm;
34
+ export default SearchForm;
@@ -1,4 +1,3 @@
1
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
1
  import React, { useState, useRef, useEffect } from 'react';
3
2
  import { Form, Button, Space, Row, Col } from 'antd';
4
3
  import { SearchOutlined, DoubleRightOutlined, UndoOutlined } from '@ant-design/icons';
@@ -9,6 +8,7 @@ const SearchForm = ({
9
8
  // 受控
10
9
  defaultExpand = false,
11
10
  // 内部默认展开
11
+ colSize = 3,
12
12
  loading = false,
13
13
  formLine = [],
14
14
  initialValues = {},
@@ -17,45 +17,14 @@ const SearchForm = ({
17
17
  onFinish = () => {},
18
18
  onExpand
19
19
  }) => {
20
- // 如果外部没有传入 form,使用 Form.useForm() 创建实例
21
- const [internalFormInstance] = Form.useForm();
22
- const internalFormRef = useRef(internalFormInstance);
23
-
24
- // 判断外部传入的是 ref 还是 form 实例
25
- // ref 对象有 current 属性,form 实例没有
26
- const isExternalRef = externalForm && 'current' in externalForm;
27
-
28
- // 警告:ref 用法将在后续版本中移除
29
- useEffect(() => {
30
- if (isExternalRef) {
31
- console.warn('[SearchForm] 警告:通过 ref 传入 form 的方式已废弃,将在后续版本中移除。' + '请使用 Form.useForm() 创建 form 实例并直接传入,例如:\n' + ' const [form] = Form.useForm();\n' + ' <SearchForm form={form} />');
32
- }
33
- }, [isExternalRef]);
34
-
35
- // 获取实际的 form 实例(兼容 ref 和直接传入实例两种情况)
36
- const getFormInstance = () => {
37
- if (externalForm) {
38
- return isExternalRef ? externalForm.current : externalForm;
39
- }
40
- return internalFormInstance;
41
- };
42
-
43
- // 兼容性处理:用于 form.current 访问方式的 ref
44
- const formRef = externalForm && isExternalRef ? externalForm : internalFormRef;
45
-
46
- // 更新内部 ref,确保 formRef.current 始终指向正确的实例
47
- useEffect(() => {
48
- if (!isExternalRef && externalForm) {
49
- // 如果外部传入的是实例,将其赋值给内部 ref 的 current
50
- internalFormRef.current = externalForm;
51
- }
52
- }, [externalForm, isExternalRef]);
20
+ const internalForm = useRef();
21
+ const form = externalForm || internalForm;
53
22
  const [internalOpen, setInternalOpen] = useState(defaultExpand);
54
23
  useEffect(() => {
55
24
  if (onRef) {
56
- onRef(formRef);
25
+ onRef(form);
57
26
  }
58
- }, [formRef, onRef]);
27
+ }, [form, onRef]);
59
28
  const isControlled = expand !== undefined;
60
29
  const isOpen = isControlled ? expand : internalOpen;
61
30
  const handleToggle = () => {
@@ -78,11 +47,10 @@ const SearchForm = ({
78
47
  icon: /*#__PURE__*/React.createElement(UndoOutlined, null),
79
48
  loading: loading,
80
49
  onClick: () => {
81
- const formInstance = getFormInstance();
82
- formInstance.resetFields();
83
- onReset(formInstance.getFieldsValue());
50
+ form.current.resetFields();
51
+ onReset(form.current.getFieldsValue());
84
52
  }
85
- }, "\u91CD\u7F6E"), formLine.length > 2 && /*#__PURE__*/React.createElement(Button, {
53
+ }, "\u91CD\u7F6E"), formLine.length / colSize > 1 && /*#__PURE__*/React.createElement(Button, {
86
54
  icon: /*#__PURE__*/React.createElement(DoubleRightOutlined, {
87
55
  style: {
88
56
  transform: isOpen ? 'rotate(-90deg)' : 'rotate(90deg)'
@@ -90,70 +58,38 @@ const SearchForm = ({
90
58
  }),
91
59
  onClick: handleToggle
92
60
  }, isOpen ? '收起' : '展开'));
93
-
94
- /**
95
- * 处理 formLine,确保所有 Form.Item 都有 noStyle 属性
96
- * @param {ReactNode[]} nodes - 表单节点数组
97
- * @returns {ReactNode[]} - 处理后的节点数组
98
- */
99
- const processFormLine = nodes => {
100
- return nodes.map(node => {
101
- // 检查是否是 Form.Item 组件
102
- if (/*#__PURE__*/React.isValidElement(node) && node.type === Form.Item) {
103
- // 如果没有 noStyle 属性,则克隆并添加
104
- if (!node.props.noStyle) {
105
- return /*#__PURE__*/React.cloneElement(node, {
106
- noStyle: true
107
- });
108
- }
109
- }
110
- return node;
111
- });
112
- };
113
61
  const groupsList = () => {
114
- // 先处理 formLine,确保所有 Form.Item 都有 noStyle
115
- const processedFormLine = processFormLine(formLine);
116
- const arr = processedFormLine.map(node => ({
62
+ const arr = formLine.map(node => ({
117
63
  show: true,
118
64
  node
119
65
  }));
120
- if (isOpen || arr.length <= 2) {
66
+ if (isOpen || arr.length <= 3) {
121
67
  return arr;
122
68
  }
123
- return arr.map((item, index) => index > 1 ? {
69
+ return arr.map((item, index) => index > 2 ? {
124
70
  ...item,
125
71
  show: false
126
72
  } : item);
127
73
  };
128
74
  const formItemList = groupsList();
129
75
  if (formItemList.length === 0) return null;
130
- return /*#__PURE__*/React.createElement(Form, _extends({}, externalForm && isExternalRef ? {
131
- ref: externalForm
132
- } : {
133
- form: externalForm || internalFormInstance
134
- }, {
76
+ return /*#__PURE__*/React.createElement(Form, {
77
+ ref: form,
135
78
  style: style,
136
79
  initialValues: initialValues,
137
80
  onFinish: values => onFinish(values)
138
- }), /*#__PURE__*/React.createElement(Row, {
81
+ }, /*#__PURE__*/React.createElement(Row, {
139
82
  gutter: [16, 16]
140
83
  }, formItemList.map((item, index) => /*#__PURE__*/React.createElement(Col, {
141
84
  key: index,
142
- span: 8,
85
+ span: 24 / colSize,
143
86
  style: {
144
87
  display: item.show ? 'block' : 'none'
145
88
  }
146
89
  }, item.node)), /*#__PURE__*/React.createElement(Col, {
147
- span: 8,
148
- style: {
149
- display: 'flex',
150
- alignItems: 'flex-start'
151
- }
90
+ span: 24 / colSize
152
91
  }, /*#__PURE__*/React.createElement(Form.Item, {
153
- noStyle: true,
154
- style: {
155
- marginBottom: 0
156
- }
92
+ noStyle: true
157
93
  }, getButtons()))));
158
94
  };
159
95
  export default SearchForm;
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import { tools } from '@cqsjjb/jjb-common-lib';
3
+ import { MoreOutlined } from '@ant-design/icons';
3
4
  import { Dropdown, Space } from 'antd';
4
5
  export default function TableAction({
5
6
  maximum = 3,
6
7
  children,
7
8
  space,
9
+ icon: Icon = MoreOutlined,
8
10
  placement = 'bottomRight',
9
11
  // 下拉菜单位置,默认 bottomRight
10
12
  trigger = ['hover'] // 下拉触发方式,默认 hover
@@ -24,5 +26,5 @@ export default function TableAction({
24
26
  },
25
27
  placement: placement,
26
28
  trigger: trigger
27
- }, /*#__PURE__*/React.createElement("a", null, "\u66F4\u591A")));
29
+ }, /*#__PURE__*/React.createElement("a", null, /*#__PURE__*/React.createElement(Icon, null))));
28
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cqsjjb/jjb-react-admin-component",
3
- "version": "3.3.6",
3
+ "version": "3.3.8",
4
4
  "description": "jjb-react-admin-组件库@new",
5
5
  "main": "index.js",
6
6
  "author": "jjb-front-team",