@creekjs/web-components 1.0.0

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.
Files changed (73) hide show
  1. package/.fatherrc.ts +13 -0
  2. package/.turbo/daemon/deec863de02760ed-turbo.log.2024-11-20 +0 -0
  3. package/README.md +1026 -0
  4. package/dist/bg-center/index.js +28 -0
  5. package/dist/creek-config-provider/CreekConfigContext.js +2 -0
  6. package/dist/creek-config-provider/index.js +14 -0
  7. package/dist/creek-hooks/index.js +1 -0
  8. package/dist/creek-hooks/useViewportHeight.js +147 -0
  9. package/dist/creek-icon/index.js +37 -0
  10. package/dist/creek-keep-alive/index.js +20 -0
  11. package/dist/creek-layout/CollapseButton.js +59 -0
  12. package/dist/creek-layout/Exception/NotFound.js +13 -0
  13. package/dist/creek-layout/Exception/NotFoundPage.js +5 -0
  14. package/dist/creek-layout/Exception/index.js +8 -0
  15. package/dist/creek-layout/HeaderContent/FullScreen.js +50 -0
  16. package/dist/creek-layout/HeaderContent/UserInfo.js +58 -0
  17. package/dist/creek-layout/HeaderContent/index.js +33 -0
  18. package/dist/creek-layout/index.js +86 -0
  19. package/dist/creek-loading/index.js +52 -0
  20. package/dist/creek-search/CreekSearch.js +51 -0
  21. package/dist/creek-search/CreekSearchContext.js +546 -0
  22. package/dist/creek-search/CreekSearchFilterDisplay.js +97 -0
  23. package/dist/creek-search/CreekSearchInput.js +96 -0
  24. package/dist/creek-search/CreekSearchValueSelector.js +422 -0
  25. package/dist/creek-search/index.js +5 -0
  26. package/dist/creek-search/type.js +1 -0
  27. package/dist/creek-table/SearchTable.js +121 -0
  28. package/dist/creek-table/TableOptionRender.js +65 -0
  29. package/dist/creek-table/TableViewContent.js +45 -0
  30. package/dist/creek-table/hooks/index.js +3 -0
  31. package/dist/creek-table/hooks/useAdaptiveToolBar.js +48 -0
  32. package/dist/creek-table/hooks/useAutoAddFilterToColumns.js +93 -0
  33. package/dist/creek-table/hooks/useElementDistance.js +58 -0
  34. package/dist/creek-table/index.js +25 -0
  35. package/dist/creek-table/toolBarRender.js +36 -0
  36. package/dist/creek-table/type.js +1 -0
  37. package/dist/index.js +7 -0
  38. package/package.json +19 -0
  39. package/src/bg-center/index.tsx +26 -0
  40. package/src/creek-config-provider/CreekConfigContext.tsx +7 -0
  41. package/src/creek-config-provider/index.tsx +12 -0
  42. package/src/creek-hooks/index.ts +1 -0
  43. package/src/creek-hooks/useViewportHeight.tsx +154 -0
  44. package/src/creek-icon/index.tsx +34 -0
  45. package/src/creek-keep-alive/index.tsx +11 -0
  46. package/src/creek-layout/CollapseButton.tsx +66 -0
  47. package/src/creek-layout/Exception/NotFound.tsx +12 -0
  48. package/src/creek-layout/Exception/NotFoundPage.tsx +4 -0
  49. package/src/creek-layout/Exception/index.tsx +10 -0
  50. package/src/creek-layout/HeaderContent/FullScreen.tsx +46 -0
  51. package/src/creek-layout/HeaderContent/UserInfo.tsx +54 -0
  52. package/src/creek-layout/HeaderContent/index.tsx +27 -0
  53. package/src/creek-layout/index.tsx +98 -0
  54. package/src/creek-loading/index.tsx +35 -0
  55. package/src/creek-search/CreekSearch.tsx +59 -0
  56. package/src/creek-search/CreekSearchContext.tsx +593 -0
  57. package/src/creek-search/CreekSearchFilterDisplay.tsx +84 -0
  58. package/src/creek-search/CreekSearchInput.tsx +75 -0
  59. package/src/creek-search/CreekSearchValueSelector.tsx +324 -0
  60. package/src/creek-search/index.tsx +5 -0
  61. package/src/creek-search/type.ts +9 -0
  62. package/src/creek-table/SearchTable.tsx +115 -0
  63. package/src/creek-table/TableOptionRender.tsx +57 -0
  64. package/src/creek-table/TableViewContent.tsx +44 -0
  65. package/src/creek-table/hooks/index.ts +4 -0
  66. package/src/creek-table/hooks/useAdaptiveToolBar.tsx +45 -0
  67. package/src/creek-table/hooks/useAutoAddFilterToColumns.tsx +90 -0
  68. package/src/creek-table/hooks/useElementDistance.tsx +64 -0
  69. package/src/creek-table/index.tsx +16 -0
  70. package/src/creek-table/toolBarRender.tsx +28 -0
  71. package/src/creek-table/type.ts +21 -0
  72. package/src/index.tsx +8 -0
  73. package/tsconfig.json +12 -0
@@ -0,0 +1,75 @@
1
+ import { SearchOutlined } from '@ant-design/icons';
2
+ import { AutoComplete, Input, Popover } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import classNames from 'classnames';
5
+
6
+ import { useSearchContext } from './CreekSearchContext';
7
+ import { CreekSearchValueSelector } from './CreekSearchValueSelector';
8
+
9
+ const useStyles = createStyles(({ token, prefixCls }) => {
10
+
11
+ return {
12
+ searchWrapper: {
13
+ width: 350,
14
+ position: 'relative',
15
+ },
16
+ autoCompleteContainer: {
17
+ width: '100%',
18
+ },
19
+ searchInput: {
20
+ [`& .${prefixCls}-input`]: { fontSize: token.fontSize },
21
+ [`& .${prefixCls}-input-prefix`]: { color: token.colorTextTertiary },
22
+ },
23
+ popoverContent: {
24
+ [`& .${prefixCls}-popover-inner`]: { padding: 0 },
25
+ },
26
+ };
27
+ });
28
+
29
+ // 搜索输入框组件
30
+ const SearchInput = () => {
31
+ const { styles } = useStyles();
32
+ const { searchValue, filterOptions, inputRef, setSearchValue, handleSearch, handleSelectColumn } = useSearchContext();
33
+
34
+ // AutoComplete 选项
35
+ const autoCompleteOptions = filterOptions?.map((option) => ({
36
+ value: option.dataIndex,
37
+ label: option.title,
38
+ }));
39
+
40
+ return (
41
+ <AutoComplete ref={inputRef} className={styles.autoCompleteContainer} options={autoCompleteOptions} onSearch={setSearchValue} onSelect={handleSelectColumn} value={searchValue} allowClear>
42
+ <Input placeholder="添加筛选条件" prefix={<SearchOutlined />} onPressEnter={(e) => handleSearch((e.target as HTMLInputElement).value)} className={styles.searchInput} />
43
+ </AutoComplete>
44
+ );
45
+ };
46
+
47
+ export type CreekSearchInputProps = {
48
+ className?: string;
49
+ };
50
+ // 主搜索输入组件
51
+ export const CreekSearchInput = (props: CreekSearchInputProps) => {
52
+ const { className } = props;
53
+ const { styles } = useStyles();
54
+ const { showValueSelector, cancelValueSelector } = useSearchContext();
55
+
56
+ return (
57
+ <div className={classNames(styles.searchWrapper, className)}>
58
+ <Popover
59
+ content={<CreekSearchValueSelector />}
60
+ trigger="click"
61
+ arrow={false}
62
+ open={showValueSelector}
63
+ onOpenChange={(visible) => {
64
+ if (!visible) {
65
+ cancelValueSelector();
66
+ }
67
+ }}
68
+ overlayClassName={styles.popoverContent}
69
+ placement="bottomLeft"
70
+ >
71
+ <SearchInput />
72
+ </Popover>
73
+ </div>
74
+ );
75
+ };
@@ -0,0 +1,324 @@
1
+ import { ParamsType } from '@ant-design/pro-components';
2
+ import { Button, Checkbox, DatePicker, Input, InputNumber, Radio, Select, Space, Switch, TimePicker } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import _ from 'lodash';
5
+ import { useEffect, useMemo } from 'react';
6
+
7
+ import { useSearchContext } from './CreekSearchContext';
8
+
9
+ const { RangePicker } = DatePicker;
10
+ const { RangePicker: TimeRangePicker } = TimePicker;
11
+
12
+ // 组件渲染器接口
13
+ interface ComponentRendererProps {
14
+ column: any;
15
+ value: any;
16
+ onChange: (value: any) => void;
17
+ className?: string;
18
+ }
19
+
20
+ // 各类型组件渲染器
21
+ const ComponentRenderer = ({ column, value, onChange, className }: ComponentRendererProps) => {
22
+ const { getValueTypeConfig, getColumnOptions, hasOptions } = useSearchContext();
23
+
24
+ const { valueType, fieldProps = {} } = column;
25
+
26
+ const config = getValueTypeConfig(valueType);
27
+ const baseStyle = { width: '100%' };
28
+ const mergedProps = { ...fieldProps, style: { ...baseStyle, ...fieldProps.style } };
29
+
30
+ // 如果有选项配置,优先使用选项组件
31
+ if (hasOptions(column)) {
32
+ const options = getColumnOptions(column);
33
+
34
+ switch (config.componentType) {
35
+ case 'radio':
36
+ const radioOptions = options.map((option) => ({
37
+ ...option,
38
+ }));
39
+
40
+ return <Radio.Group {...mergedProps} className={className} value={value} onChange={(e) => onChange(e.target.value)} options={radioOptions} />;
41
+
42
+ case 'checkbox':
43
+ const currentValue = value || [];
44
+ const allValues = options.map((opt) => opt.value);
45
+ const checkAll = allValues.length === currentValue.length && allValues.length > 0;
46
+ const indeterminate = currentValue.length > 0 && currentValue.length < allValues.length;
47
+
48
+ const handleCheckboxChange = (checkedValues: any[]) => {
49
+ onChange(checkedValues);
50
+ };
51
+
52
+ const onCheckAllChange = (e: any) => {
53
+ onChange(e.target.checked ? allValues : []);
54
+ };
55
+
56
+ return (
57
+ <div className={className}>
58
+ <Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll} style={{ marginBottom: 4 }}>
59
+ 全部
60
+ </Checkbox>
61
+ <Checkbox.Group {...mergedProps} value={currentValue} onChange={handleCheckboxChange} options={options} />
62
+ </div>
63
+ );
64
+
65
+ case 'select':
66
+ default:
67
+ return <Select {...mergedProps} placeholder="请选择" value={value} onChange={onChange} options={options} showSearch allowClear />;
68
+ }
69
+ }
70
+
71
+ // 根据组件类型渲染对应组件
72
+ switch (config.componentType) {
73
+ case 'input':
74
+ return <Input {...mergedProps} placeholder={fieldProps.placeholder || '请输入值'} value={value} onChange={(e) => onChange(e.target.value)} />;
75
+
76
+ case 'number':
77
+ return <InputNumber {...mergedProps} placeholder={fieldProps.placeholder || '请输入数字'} value={value} onChange={onChange} />;
78
+
79
+ case 'select':
80
+ // 处理switch类型的特殊情况
81
+ if (valueType === 'switch') {
82
+ return <Select {...mergedProps} placeholder="请选择" value={value} onChange={onChange} allowClear />;
83
+ }
84
+ return <Select {...mergedProps} placeholder="请选择" value={value} onChange={onChange} allowClear />;
85
+
86
+ case 'switch':
87
+ return <Switch {...mergedProps} checked={value} onChange={onChange} />;
88
+
89
+ case 'date':
90
+ const dateProps = {
91
+ ...mergedProps,
92
+ placeholder: fieldProps.placeholder || '请选择日期',
93
+ value: value,
94
+ onChange: onChange,
95
+ };
96
+
97
+ // 根据具体的日期类型渲染不同的组件
98
+ switch (valueType) {
99
+ case 'dateTime':
100
+ return <DatePicker {...dateProps} showTime />;
101
+ case 'dateMonth':
102
+ return <DatePicker {...dateProps} picker="month" />;
103
+ case 'dateWeek':
104
+ return <DatePicker {...dateProps} picker="week" />;
105
+ case 'dateQuarter':
106
+ return <DatePicker {...dateProps} picker="quarter" />;
107
+ case 'dateYear':
108
+ return <DatePicker {...dateProps} picker="year" />;
109
+ case 'date':
110
+ default:
111
+ return <DatePicker {...dateProps} />;
112
+ }
113
+
114
+ case 'dateRange':
115
+ const rangeProps = {
116
+ ...mergedProps,
117
+ placeholder: fieldProps.placeholder || ['开始日期', '结束日期'],
118
+ value: value,
119
+ onChange: onChange,
120
+ };
121
+
122
+ // 根据具体的日期范围类型渲染不同的组件
123
+ switch (valueType) {
124
+ case 'dateTimeRange':
125
+ return <RangePicker {...rangeProps} showTime />;
126
+ case 'dateMonthRange':
127
+ return <RangePicker {...rangeProps} picker="month" />;
128
+ case 'dateWeekRange':
129
+ return <RangePicker {...rangeProps} picker="week" />;
130
+ case 'dateQuarterRange':
131
+ return <RangePicker {...rangeProps} picker="quarter" />;
132
+ case 'dateYearRange':
133
+ return <RangePicker {...rangeProps} picker="year" />;
134
+ case 'dateRange':
135
+ default:
136
+ return <RangePicker {...rangeProps} />;
137
+ }
138
+
139
+ case 'time':
140
+ return <TimePicker {...mergedProps} placeholder={fieldProps.placeholder || '请选择时间'} value={value} onChange={onChange} />;
141
+
142
+ case 'timeRange':
143
+ return <TimeRangePicker {...mergedProps} placeholder={fieldProps.placeholder || ['开始时间', '结束时间']} value={value} onChange={onChange} />;
144
+
145
+ case 'radio':
146
+ const iswitch = valueType === 'switch';
147
+ const radioOptions = [
148
+ { label: '开', value: true },
149
+ { label: '关', value: false },
150
+ ];
151
+ const _radioOptions = iswitch ? radioOptions : mergedProps.options;
152
+
153
+ // 如果valueType是switch,使用RadioButton样式
154
+ return <Radio.Group {...mergedProps} options={_radioOptions} className={className} value={value} onChange={(e) => onChange(e.target.value)} />;
155
+
156
+ case 'checkbox':
157
+ return <Checkbox.Group {...mergedProps} className={className} value={value} onChange={onChange} />;
158
+
159
+ default:
160
+ return <Input {...mergedProps} placeholder="请输入值" value={value} onChange={(e) => onChange(e.target.value)} />;
161
+ }
162
+ };
163
+
164
+ // 样式定义
165
+ const useStyles = createStyles(({ token, prefixCls }) => {
166
+ const selectedBgColor = '#F5F7F9';
167
+
168
+ // 公共选项样式
169
+ const optionBase = {
170
+ width: '100%',
171
+ padding: 8,
172
+ borderRadius: 2,
173
+ marginBottom: 4,
174
+ marginInlineEnd: 0,
175
+ '&:hover': { backgroundColor: selectedBgColor },
176
+ };
177
+
178
+ return {
179
+ valueSelectorContent: {
180
+ padding: 8,
181
+ borderBottom: `1px solid ${token.colorBorderSecondary}`,
182
+
183
+ // 复用公共样式
184
+ [`& .${prefixCls}-checkbox-wrapper, & .${prefixCls}-radio-wrapper`]: optionBase,
185
+
186
+ // checked 状态
187
+ [`& .${prefixCls}-checkbox-wrapper-checked, & .${prefixCls}-radio-wrapper-checked`]: {
188
+ backgroundColor: selectedBgColor,
189
+ },
190
+ },
191
+
192
+ valueSelectorActions: {
193
+ display: 'flex',
194
+ justifyContent: 'flex-end',
195
+ gap: token.marginXS,
196
+ padding: 8,
197
+ },
198
+ verticalStyle: {
199
+ display: 'flex',
200
+ flexDirection: 'column',
201
+ },
202
+
203
+ // 不同宽度样式
204
+ smallSelector: { width: 80 },
205
+ narrowSelector: { width: 150 },
206
+ mediumSelector: { width: 280 },
207
+ wideSelector: { width: 350 },
208
+ };
209
+ });
210
+
211
+ export type CreekSearchValueSelectorProps<T extends ParamsType, U extends ParamsType> = {
212
+ onConfirm?: () => void;
213
+ };
214
+
215
+ // 根据不同类型获取初始值
216
+ const getInitialValue = (componentType: string) => {
217
+ switch (componentType) {
218
+ case 'checkbox':
219
+ return [];
220
+ case 'switch':
221
+ return false;
222
+ case 'number':
223
+ return undefined; // InputNumber 使用 undefined 而不是 null
224
+ default:
225
+ return null;
226
+ }
227
+ };
228
+
229
+ // 值选择器组件
230
+ export const CreekSearchValueSelector = <T extends ParamsType, U extends ParamsType>(props: CreekSearchValueSelectorProps<T, U>) => {
231
+ const { styles } = useStyles();
232
+ const searchContext = useSearchContext();
233
+
234
+ const { selectedColumn, tempValue, setTempValue, confirmAddFilter, getValueTypeConfig, filters } = searchContext;
235
+ const { onConfirm } = props;
236
+
237
+ const config = getValueTypeConfig(selectedColumn?.valueType);
238
+
239
+ // 获取当前过滤器的值
240
+ const currentFilterValue = useMemo(() => {
241
+ const currentFilter = filters.find((item) => item.dataIndex === selectedColumn?.dataIndex);
242
+ return currentFilter?.value;
243
+ }, [filters, selectedColumn]);
244
+
245
+ // 计算实际显示的值
246
+ const actualValue = useMemo(() => {
247
+ // 如果 tempValue 已设置(不是 null 和 undefined),优先使用 tempValue
248
+ if (tempValue !== null && tempValue !== undefined) {
249
+ return tempValue;
250
+ }
251
+ // 否则使用当前过滤器的值,如果也没有则使用初始值
252
+ return currentFilterValue ?? getInitialValue(config.componentType);
253
+ }, [tempValue, currentFilterValue, selectedColumn?.valueType, config.componentType]);
254
+
255
+ // 当选择的列发生变化时,重置 tempValue 为当前过滤器的值
256
+ useEffect(() => {
257
+ if (selectedColumn) {
258
+ const currentFilter = filters.find((item) => item.dataIndex === selectedColumn.dataIndex);
259
+ if (currentFilter?.value !== undefined) {
260
+ setTempValue(currentFilter.value);
261
+ } else {
262
+ // 如果没有现有的过滤器值,设置为初始值
263
+ setTempValue(getInitialValue(config.componentType));
264
+ }
265
+ }
266
+ }, [selectedColumn?.dataIndex]); // 只在 selectedColumn.dataIndex 变化时触发
267
+
268
+ // 根据组件类型获取合适的宽度
269
+ const getPopoverWidth = () => {
270
+ switch (config.componentType) {
271
+ case 'select':
272
+ case 'radio':
273
+ case 'checkbox':
274
+ case 'switch':
275
+ return styles.narrowSelector;
276
+ case 'number':
277
+ return styles.mediumSelector;
278
+ case 'date':
279
+ case 'dateRange':
280
+ case 'time':
281
+ case 'timeRange':
282
+ default:
283
+ return styles.wideSelector;
284
+ }
285
+ };
286
+
287
+ // 重置值的处理
288
+ const handleReset = () => {
289
+ const initialValue = getInitialValue(config.componentType);
290
+ setTempValue(initialValue);
291
+ };
292
+
293
+ // 处理值的变化
294
+ const handleValueChange = (value: any) => {
295
+ setTempValue(value);
296
+ };
297
+
298
+ return selectedColumn ? (
299
+ <div className={getPopoverWidth()}>
300
+ <div className={styles.valueSelectorContent}>
301
+ <ComponentRenderer column={selectedColumn} value={actualValue} onChange={handleValueChange} className={['radio', 'checkbox'].includes(config.componentType) ? styles.verticalStyle : ''} />
302
+ </div>
303
+ <div className={styles.valueSelectorActions}>
304
+ <Space size="small">
305
+ <Button size="small" onClick={handleReset}>
306
+ 重置
307
+ </Button>
308
+ <Button
309
+ type="primary"
310
+ size="small"
311
+ onClick={() => {
312
+ confirmAddFilter();
313
+ if (_.isFunction(onConfirm)) {
314
+ onConfirm();
315
+ }
316
+ }}
317
+ >
318
+ 确定
319
+ </Button>
320
+ </Space>
321
+ </div>
322
+ </div>
323
+ ) : null;
324
+ };
@@ -0,0 +1,5 @@
1
+ export * from './CreekSearch';
2
+ export * from './CreekSearchContext';
3
+ export * from './CreekSearchFilterDisplay';
4
+ export * from './CreekSearchInput';
5
+ export * from './CreekSearchValueSelector';
@@ -0,0 +1,9 @@
1
+ export type CreekSearchAddFilterOption = {
2
+ value: any;
3
+ displayText?: string;
4
+ };
5
+
6
+ export type CreekSearchFilter = CreekSearchAddFilterOption & {
7
+ dataIndex: string;
8
+ title: string;
9
+ };
@@ -0,0 +1,115 @@
1
+ import { ParamsType, ProTable } from '@ant-design/pro-components';
2
+ import { createStyles } from 'antd-style';
3
+ import classnames from 'classnames';
4
+ import _ from 'lodash';
5
+ import { useMemo, useRef } from 'react';
6
+
7
+ import { CreekFilterDisplay, CreekSearchInput, useSearchContext } from '../creek-search';
8
+
9
+ import { useAdaptiveToolBar, useAutoAddFilterToColumns } from './hooks';
10
+ import { TableOptionRender } from './TableOptionRender';
11
+ import { TableViewContent } from './TableViewContent';
12
+ import { toolBarRender } from './toolBarRender';
13
+ import { CreekTableProps } from './type';
14
+
15
+ const useStyles = createStyles(({ prefixCls }) => {
16
+ return {
17
+ 'creek-table-container': {
18
+ [`.${prefixCls}-pagination`]: {
19
+ [`.${prefixCls}-pagination-total-text`]: {
20
+ flex: `1`,
21
+ },
22
+ },
23
+ },
24
+ };
25
+ });
26
+
27
+ // 独立的 ProTable 组件
28
+ export const SearchProTable = <T extends ParamsType, U extends ParamsType, ValueType = 'text'>(props: CreekTableProps<T, U, ValueType>) => {
29
+ const {
30
+ columns,
31
+ params,
32
+ prefixCls = 'ant',
33
+ autoAddFilterForColumn = true,
34
+ className,
35
+ optionsRender,
36
+ tableViewRender,
37
+ pagination,
38
+ pageFixedBottom = true,
39
+ pageFixedBottomConfig,
40
+ ...restProps
41
+ } = props;
42
+
43
+ const searchContext = useSearchContext();
44
+ const { filters, filtersToParams } = searchContext;
45
+
46
+ const proTableRef = useRef(null);
47
+
48
+ // 使用自定义 hook 处理列的筛选功能(包含状态管理)
49
+ const { columnsWithFilter } = useAutoAddFilterToColumns({
50
+ columns,
51
+ autoAddFilterForColumn,
52
+ });
53
+
54
+ // 使用自定义 hook 处理工具栏的自适应功能
55
+ const { shouldCollapse } = useAdaptiveToolBar({
56
+ containerRef: proTableRef,
57
+ prefixCls,
58
+ });
59
+
60
+ const { styles } = useStyles();
61
+
62
+ // 获取搜素的参数
63
+ const finalParams = useMemo(() => {
64
+ let result = filtersToParams(filters) as U;
65
+ return result;
66
+ }, [filters]);
67
+
68
+ return (
69
+ <div ref={proTableRef}>
70
+ <ProTable<T, U, ValueType>
71
+ {...restProps}
72
+ className={classnames(styles['creek-table-container'], className)}
73
+ columns={columnsWithFilter}
74
+ toolbar={{
75
+ ...restProps.toolbar,
76
+ }}
77
+ params={{
78
+ ...params,
79
+ ...finalParams,
80
+ }}
81
+ search={false}
82
+ pagination={{
83
+ showTotal: (total) => <span>共 {total} 条</span>,
84
+ showSizeChanger: true,
85
+ ...pagination,
86
+ }}
87
+ optionsRender={(defaultProps, dom) => {
88
+ return _.isFunction(optionsRender)
89
+ ? optionsRender(defaultProps, dom)
90
+ : restProps?.options
91
+ ? [<TableOptionRender key="option" defaultDom={dom} importConfig={restProps?.options?.importConfig} exportConfig={restProps?.options?.exportConfig} />]
92
+ : [];
93
+ }}
94
+ toolBarRender={() => {
95
+ return toolBarRender({ shouldCollapse, restProps });
96
+ }}
97
+ // 在 headerTitle 中只放搜索输入框
98
+ headerTitle={<CreekSearchInput />}
99
+ // 在表格内容区上方显示筛选条件
100
+ tableViewRender={(defaultProps, defaultDom) => {
101
+ return _.isFunction(tableViewRender) ? (
102
+ <>
103
+ <CreekFilterDisplay />
104
+ {tableViewRender(defaultProps, defaultDom)}
105
+ </>
106
+ ) : (
107
+ <TableViewContent<T, U, ValueType> pageFixedBottom={pageFixedBottom} pageFixedBottomConfig={pageFixedBottomConfig} prefixCls={prefixCls}>
108
+ {defaultDom}
109
+ </TableViewContent>
110
+ );
111
+ }}
112
+ />
113
+ </div>
114
+ );
115
+ };
@@ -0,0 +1,57 @@
1
+ import { ExportOutlined, ImportOutlined } from '@ant-design/icons';
2
+ import { ParamsType } from '@ant-design/pro-components';
3
+ import { Space, Tooltip } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+
6
+ import { OptionRenderCustom } from './type';
7
+
8
+ const useStyles = createStyles(({ prefixCls, token }) => {
9
+ return {
10
+ 'table-option-render-item': {
11
+ border: `1px solid ${token.colorBorder}`,
12
+ borderRadius: token.borderRadius,
13
+ padding: '8px',
14
+ cursor: 'pointer',
15
+ },
16
+ };
17
+ });
18
+
19
+ export type TableOptionRenderProps<T extends ParamsType, U extends ParamsType, ValueType = 'text'> = {
20
+ defaultDom?: React.ReactNode[];
21
+ importConfig?: OptionRenderCustom;
22
+ exportConfig?: OptionRenderCustom;
23
+ };
24
+
25
+ export const TableOptionRender = <T extends ParamsType, U extends ParamsType, ValueType = 'text'>(props: TableOptionRenderProps<T, U, ValueType>) => {
26
+ const { defaultDom, importConfig, exportConfig } = props;
27
+
28
+ const { styles } = useStyles();
29
+
30
+ const [reload, dis, setting] = defaultDom || [];
31
+ return (
32
+ <Space size={8}>
33
+ {importConfig && (
34
+ <span
35
+ className={styles['table-option-render-item']}
36
+ onClick={() => {
37
+ importConfig.onClick?.();
38
+ }}
39
+ >
40
+ <Tooltip title={importConfig.text || '导入'}>{importConfig.icon || <ImportOutlined />}</Tooltip>
41
+ </span>
42
+ )}
43
+ {exportConfig && (
44
+ <span
45
+ className={styles['table-option-render-item']}
46
+ onClick={() => {
47
+ exportConfig.onClick?.();
48
+ }}
49
+ >
50
+ <Tooltip title={exportConfig.text || '导出'}>{exportConfig.icon || <ExportOutlined />}</Tooltip>
51
+ </span>
52
+ )}
53
+ <span className={styles['table-option-render-item']}>{reload}</span>
54
+ <span className={styles['table-option-render-item']}>{setting}</span>
55
+ </Space>
56
+ );
57
+ };
@@ -0,0 +1,44 @@
1
+ import { ParamsType } from '@ant-design/pro-components';
2
+
3
+ import { useDebounceFn, useDeepCompareEffect } from 'ahooks';
4
+
5
+ import { useViewportHeight } from '../creek-hooks';
6
+ import { CreekFilterDisplay } from '../creek-search';
7
+ import { CreekTableProps } from './type';
8
+
9
+ export type CreekTableViewRender<T extends ParamsType, U extends ParamsType, ValueType = 'text'> = CreekTableProps<T, U, ValueType>['tableViewRender'];
10
+
11
+ // 独立的 TableViewWrapper 组件 - 包含所有表格视图相关逻辑
12
+ export const TableViewContent = <T extends ParamsType, U extends ParamsType, ValueType = 'text'>(props: CreekTableProps<T, U, ValueType>) => {
13
+ const { prefixCls, pageFixedBottomConfig, pageFixedBottom, children } = props;
14
+
15
+ const { containerRef, viewPortHeight } = useViewportHeight({
16
+ isObserverParent: true,
17
+ });
18
+
19
+ // 设置antd内容区的高度,使得分页永远在底部
20
+ const { run: setAntdTableContentHeight } = useDebounceFn((mainHeight: number) => {
21
+ const antdTableContentElement = containerRef.current?.querySelector(`.${prefixCls}-table`);
22
+ const antdPaginationElement = containerRef.current?.querySelector(`.${prefixCls}-pagination`);
23
+ if (antdTableContentElement) {
24
+ const paginationHeight = antdPaginationElement?.clientHeight || 0;
25
+ const bottomFix = pageFixedBottomConfig?.bottomFix || 8;
26
+ const tableContentHeight = mainHeight - paginationHeight - 16 - bottomFix;
27
+ antdTableContentElement.setAttribute('style', `height: ${tableContentHeight}px`);
28
+ }
29
+ });
30
+
31
+ useDeepCompareEffect(() => {
32
+ if (pageFixedBottom) {
33
+ setAntdTableContentHeight(viewPortHeight ?? 0);
34
+ }
35
+ }, [viewPortHeight, pageFixedBottom, setAntdTableContentHeight]);
36
+
37
+ // 默认渲染逻辑
38
+ return (
39
+ <>
40
+ <CreekFilterDisplay />
41
+ <div ref={containerRef}>{children}</div>
42
+ </>
43
+ );
44
+ };
@@ -0,0 +1,4 @@
1
+ export * from './useAdaptiveToolBar';
2
+ export * from './useAutoAddFilterToColumns';
3
+ export * from './useElementDistance';
4
+
@@ -0,0 +1,45 @@
1
+ import { MutableRefObject, useEffect, useRef, useState } from 'react';
2
+ import { useElementDistance } from './useElementDistance';
3
+
4
+ export const useAdaptiveToolBar = (options: {
5
+ containerRef: MutableRefObject<HTMLElement | null>;
6
+ prefixCls: string;
7
+ minLeftDistance?: number;
8
+ hysteresis?: number; // 滞回差值
9
+ }) => {
10
+ const element1Ref = useRef<HTMLElement>(null) as MutableRefObject<HTMLElement | null>;
11
+ const element2Ref = useRef<HTMLElement>(null) as MutableRefObject<HTMLElement | null>;
12
+ const [shouldCollapse, setShouldCollapse] = useState(false);
13
+
14
+ const { containerRef, prefixCls, minLeftDistance = 48, hysteresis = 36 } = options;
15
+
16
+ useEffect(() => {
17
+ element1Ref.current = containerRef?.current?.querySelector(`.${prefixCls}-pro-table-list-toolbar-left`) || null;
18
+ element2Ref.current = containerRef?.current?.querySelector(`.${prefixCls}-pro-table-list-toolbar-right`)?.querySelector('div') || null;
19
+ });
20
+
21
+ const distance = useElementDistance(element1Ref, element2Ref);
22
+
23
+ // 方案一:使用滞回逻辑防止震荡
24
+ useEffect(() => {
25
+ if (!distance?.x) return;
26
+
27
+ const currentDistance = distance.x;
28
+
29
+ if (shouldCollapse) {
30
+ // 当前是折叠状态,需要距离足够大才展开
31
+ if (currentDistance > minLeftDistance + hysteresis) {
32
+ setShouldCollapse(false);
33
+ }
34
+ } else {
35
+ // 当前是展开状态,距离小于阈值时折叠
36
+ if (currentDistance < minLeftDistance) {
37
+ setShouldCollapse(true);
38
+ }
39
+ }
40
+ }, [distance?.x, minLeftDistance, hysteresis, shouldCollapse]);
41
+
42
+ return {
43
+ shouldCollapse,
44
+ };
45
+ };