@cqsjjb/jjb-react-admin-component 3.3.7 → 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.
- package/ListDataContainer/index.d.ts +9 -1
- package/ListDataContainer/index.js +147 -60
- package/package.json +1 -1
|
@@ -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 {
|
|
26
|
-
* @
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
} : {},
|
|
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(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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(
|
|
194
|
-
|
|
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
|
-
|
|
287
|
+
scroll: tableProps?.scroll ? tableProps.scroll : {
|
|
288
|
+
y: tableScrollY !== undefined ? tableScrollY : tableProps.scroll?.y || undefined
|
|
208
289
|
},
|
|
209
290
|
bordered: true
|
|
210
|
-
},
|
|
291
|
+
}, (() => {
|
|
292
|
+
const {
|
|
293
|
+
scroll,
|
|
294
|
+
...restTableProps
|
|
295
|
+
} = tableProps;
|
|
296
|
+
return restTableProps;
|
|
297
|
+
})()))));
|
|
211
298
|
});
|
|
212
299
|
export default ListDataContainer;
|