@hsu-react/ui 0.0.8 → 0.0.10

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 (126) hide show
  1. package/es/components/Chart/Bar/index.d.ts +5 -1
  2. package/es/components/Chart/Bar/index.js +84 -9
  3. package/es/components/Chart/Line/index.d.ts +5 -1
  4. package/es/components/Chart/Line/index.js +84 -9
  5. package/es/components/Chart/_utils/autoSmooth.d.ts +2 -0
  6. package/es/components/Chart/_utils/autoSmooth.js +12 -2
  7. package/es/components/Chart/_utils/cartesian.d.ts +11 -0
  8. package/es/components/Chart/_utils/cartesian.js +19 -0
  9. package/es/components/Chart/chartUtils/chartTooltipFormatter.d.ts +2 -0
  10. package/es/components/Chart/chartUtils/chartTooltipFormatter.js +13 -3
  11. package/es/components/Chart/chartUtils/index.d.ts +5 -2
  12. package/es/components/Chart/chartUtils/index.js +3 -1
  13. package/es/components/Chart/chartUtils/useDataZoomWindow.d.ts +19 -0
  14. package/es/components/Chart/chartUtils/useDataZoomWindow.js +37 -0
  15. package/es/components/Chart/chartUtils/useLegendSelection.d.ts +14 -0
  16. package/es/components/Chart/chartUtils/useLegendSelection.js +35 -0
  17. package/es/components/FormItem/ItemContainer/index.d.ts +2 -0
  18. package/es/components/FormItem/ItemContainer/index.js +4 -15
  19. package/es/components/Modal/index.module.scss +1 -0
  20. package/es/components/Panel/ListPanel/ListModalPanel/index.js +33 -5
  21. package/es/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  22. package/es/components/Panel/ListPanel/index.module.scss +11 -12
  23. package/es/components/Search/{_components → SearchAdvanced/_components}/AdvancedFiltersDrawer/index.d.ts +3 -3
  24. package/es/components/Search/{_components → SearchAdvanced/_components}/AdvancedFiltersDrawer/index.js +3 -3
  25. package/es/components/Search/SearchAdvanced/index.js +26 -148
  26. package/es/components/Search/SearchCard/index.js +1 -1
  27. package/es/components/Search/{_components → SearchCollapsible/_components}/CollapseToggle/index.js +2 -2
  28. package/es/components/Search/SearchCollapsible/index.js +17 -138
  29. package/es/components/Search/SearchWithFilter/index.js +4 -143
  30. package/es/components/Search/SearchWithMore/index.js +11 -142
  31. package/es/components/Search/_components/FilterDropdown/index.js +5 -6
  32. package/es/components/Search/_components/SearchBase/index.d.ts +32 -0
  33. package/es/components/Search/_components/SearchBase/index.js +185 -0
  34. package/es/components/Search/_hooks/useSearchCommon.js +5 -11
  35. package/es/components/Search/_hooks/useSearchExpand.js +4 -13
  36. package/es/components/Search/_utils/calculateButtonGroupAndColumnWidth.js +3 -10
  37. package/es/components/Search/_utils/calculateVisibleItems.js +4 -5
  38. package/es/components/Search/_utils/index.d.ts +2 -0
  39. package/es/components/Search/_utils/index.js +3 -1
  40. package/es/components/Search/_utils/measureButtonGroupWidth.d.ts +6 -0
  41. package/es/components/Search/_utils/measureButtonGroupWidth.js +18 -0
  42. package/es/components/Search/_utils/shouldDisplayExtraItem.d.ts +11 -0
  43. package/es/components/Search/_utils/shouldDisplayExtraItem.js +13 -0
  44. package/es/components/Search/index.d.ts +4 -0
  45. package/es/components/Search/index.js +6 -133
  46. package/es/components/Table/_hooks/useTableColumns.js +11 -0
  47. package/lib/components/Chart/Bar/index.d.ts +5 -1
  48. package/lib/components/Chart/Bar/index.js +68 -4
  49. package/lib/components/Chart/Line/index.d.ts +5 -1
  50. package/lib/components/Chart/Line/index.js +68 -4
  51. package/lib/components/Chart/_utils/autoSmooth.d.ts +2 -0
  52. package/lib/components/Chart/_utils/autoSmooth.js +11 -1
  53. package/lib/components/Chart/_utils/cartesian.d.ts +11 -0
  54. package/lib/components/Chart/_utils/cartesian.js +21 -1
  55. package/lib/components/Chart/chartUtils/chartTooltipFormatter.d.ts +2 -0
  56. package/lib/components/Chart/chartUtils/chartTooltipFormatter.js +5 -3
  57. package/lib/components/Chart/chartUtils/index.d.ts +5 -2
  58. package/lib/components/Chart/chartUtils/index.js +14 -0
  59. package/lib/components/Chart/chartUtils/useDataZoomWindow.d.ts +19 -0
  60. package/lib/components/Chart/chartUtils/useDataZoomWindow.js +31 -0
  61. package/lib/components/Chart/chartUtils/useLegendSelection.d.ts +14 -0
  62. package/lib/components/Chart/chartUtils/useLegendSelection.js +26 -0
  63. package/lib/components/FormItem/ItemContainer/index.d.ts +2 -0
  64. package/lib/components/FormItem/ItemContainer/index.js +2 -5
  65. package/lib/components/Modal/index.module.scss +1 -0
  66. package/lib/components/Panel/ListPanel/ListModalPanel/index.js +21 -2
  67. package/lib/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  68. package/lib/components/Panel/ListPanel/index.module.scss +11 -12
  69. package/lib/components/Search/{_components → SearchAdvanced/_components}/AdvancedFiltersDrawer/index.d.ts +3 -3
  70. package/lib/components/Search/{_components → SearchAdvanced/_components}/AdvancedFiltersDrawer/index.js +3 -3
  71. package/lib/components/Search/SearchAdvanced/index.js +19 -134
  72. package/lib/components/Search/SearchCard/index.js +1 -1
  73. package/lib/components/Search/{_components → SearchCollapsible/_components}/CollapseToggle/index.js +2 -2
  74. package/lib/components/Search/SearchCollapsible/index.js +12 -124
  75. package/lib/components/Search/SearchWithFilter/index.js +5 -129
  76. package/lib/components/Search/SearchWithMore/index.js +7 -126
  77. package/lib/components/Search/_components/FilterDropdown/index.js +5 -6
  78. package/lib/components/Search/_components/SearchBase/index.d.ts +32 -0
  79. package/lib/components/Search/_components/SearchBase/index.js +169 -0
  80. package/lib/components/Search/_hooks/useSearchCommon.js +4 -6
  81. package/lib/components/Search/_hooks/useSearchExpand.js +2 -13
  82. package/lib/components/Search/_utils/calculateButtonGroupAndColumnWidth.js +2 -10
  83. package/lib/components/Search/_utils/calculateVisibleItems.js +3 -5
  84. package/lib/components/Search/_utils/index.d.ts +2 -0
  85. package/lib/components/Search/_utils/index.js +15 -1
  86. package/lib/components/Search/_utils/measureButtonGroupWidth.d.ts +6 -0
  87. package/lib/components/Search/_utils/measureButtonGroupWidth.js +24 -0
  88. package/lib/components/Search/_utils/shouldDisplayExtraItem.d.ts +11 -0
  89. package/lib/components/Search/_utils/shouldDisplayExtraItem.js +19 -0
  90. package/lib/components/Search/index.d.ts +4 -0
  91. package/lib/components/Search/index.js +8 -119
  92. package/lib/components/Table/_hooks/useTableColumns.js +11 -0
  93. package/package.json +1 -1
  94. package/src/components/Chart/Bar/index.tsx +132 -2
  95. package/src/components/Chart/Line/index.tsx +132 -2
  96. package/src/components/Chart/_utils/autoSmooth.ts +181 -164
  97. package/src/components/Chart/_utils/cartesian.ts +28 -0
  98. package/src/components/Chart/chartUtils/chartTooltipFormatter.ts +11 -2
  99. package/src/components/Chart/chartUtils/index.ts +53 -37
  100. package/src/components/Chart/chartUtils/useDataZoomWindow.ts +61 -0
  101. package/src/components/Chart/chartUtils/useLegendSelection.ts +35 -0
  102. package/src/components/FormItem/ItemContainer/index.tsx +4 -7
  103. package/src/components/Modal/index.module.scss +1 -0
  104. package/src/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  105. package/src/components/Panel/ListPanel/ListModalPanel/index.tsx +261 -233
  106. package/src/components/Panel/ListPanel/index.module.scss +11 -12
  107. package/src/components/Search/{_components → SearchAdvanced/_components}/AdvancedFiltersDrawer/index.tsx +6 -6
  108. package/src/components/Search/SearchAdvanced/index.tsx +25 -164
  109. package/src/components/Search/SearchCard/index.tsx +1 -1
  110. package/src/components/Search/{_components → SearchCollapsible/_components}/CollapseToggle/index.tsx +2 -2
  111. package/src/components/Search/SearchCollapsible/index.tsx +13 -162
  112. package/src/components/Search/SearchWithFilter/index.tsx +2 -164
  113. package/src/components/Search/SearchWithMore/index.tsx +9 -161
  114. package/src/components/Search/_components/FilterDropdown/index.tsx +5 -6
  115. package/src/components/Search/_components/SearchBase/index.tsx +224 -0
  116. package/src/components/Search/_hooks/useSearchCommon.ts +10 -13
  117. package/src/components/Search/_hooks/useSearchExpand.ts +3 -13
  118. package/src/components/Search/_utils/calculateButtonGroupAndColumnWidth.ts +2 -10
  119. package/src/components/Search/_utils/calculateVisibleItems.ts +10 -11
  120. package/src/components/Search/_utils/index.ts +2 -0
  121. package/src/components/Search/_utils/measureButtonGroupWidth.ts +23 -0
  122. package/src/components/Search/_utils/shouldDisplayExtraItem.ts +23 -0
  123. package/src/components/Search/index.tsx +7 -153
  124. package/src/components/Table/_hooks/useTableColumns.tsx +17 -0
  125. /package/es/components/Search/{_components → SearchCollapsible/_components}/CollapseToggle/index.d.ts +0 -0
  126. /package/lib/components/Search/{_components → SearchCollapsible/_components}/CollapseToggle/index.d.ts +0 -0
@@ -4,137 +4,26 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _antd = require("antd");
8
- var _FormItem = _interopRequireDefault(require("../FormItem"));
9
- var _react = _interopRequireWildcard(require("react"));
10
- var _classnames = _interopRequireDefault(require("classnames"));
11
- var _indexModule = _interopRequireDefault(require("./index.module.scss"));
12
- var _hooks = require("./_hooks");
7
+ var _react = _interopRequireDefault(require("react"));
13
8
  var _SearchCard = _interopRequireDefault(require("./SearchCard"));
14
9
  var _SearchAdvanced = _interopRequireDefault(require("./SearchAdvanced"));
15
10
  var _SearchCollapsible = _interopRequireDefault(require("./SearchCollapsible"));
16
11
  var _SearchWithFilter = _interopRequireDefault(require("./SearchWithFilter"));
17
12
  var _SearchWithMore = _interopRequireDefault(require("./SearchWithMore"));
18
- var _ButtonGroup = require("./_components/ButtonGroup");
19
- var _SearchButtons = require("./_components/SearchButtons");
20
- var _ExpandButton = require("./_components/ExpandButton");
13
+ var _SearchBase = _interopRequireDefault(require("./_components/SearchBase"));
21
14
  var _jsxRuntime = require("react/jsx-runtime");
22
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
23
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
24
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
16
  // 导出所有可用的 Search 模式键名
26
17
 
27
18
  // 导出 Search 模式映射类型
28
19
 
20
+ /**
21
+ * 基础 Search 组件
22
+ * - 标准的搜索表单布局,自适应列数与展开/收起
23
+ */
29
24
  const Search = props => {
30
- const {
31
- searchItems = [],
32
- moreSearchItems = [],
33
- className,
34
- onSearch,
35
- onReset,
36
- externalForm,
37
- hasPermi,
38
- columnNum = 4,
39
- beforeButtonGroup,
40
- affterButtonGroup,
41
- searchData,
42
- minLabelWidth,
43
- defaultExpanded = false,
44
- onExpandChange,
45
- autoAdaptWidth = true,
46
- baseWidth,
47
- onValuesChange,
48
- searchDisabled = false,
49
- showAllSearchItems = false,
50
- searchText,
51
- resetText,
52
- columnOffsetWidth = 0
53
- } = props;
54
- const [form] = _antd.Form.useForm(externalForm);
55
- const {
56
- containerRef,
57
- buttonGroupRef,
58
- cls,
59
- getLabelWidth,
60
- currentSearchItems,
61
- totalColumnNum,
62
- expand,
63
- toggleExpand,
64
- showExpandButton,
65
- onSearchClick,
66
- onResetClick,
67
- shouldRender,
68
- permitted
69
- } = (0, _hooks.useSearchCommon)({
70
- form,
71
- searchItems,
72
- moreSearchItems,
73
- searchData,
74
- hasPermi,
75
- beforeButtonGroup,
76
- affterButtonGroup,
77
- columnNum,
78
- autoAdaptWidth,
79
- defaultExpanded,
80
- onExpandChange,
81
- showAllSearchItems,
82
- minLabelWidth,
83
- baseWidth,
84
- onSearch,
85
- onReset,
86
- columnOffsetWidth
87
- });
88
- if (!shouldRender) {
89
- return null;
90
- }
91
- return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
92
- ref: containerRef,
93
- className: (0, _classnames.default)(_indexModule.default.Search, className),
94
- style: {
95
- "--column-number": totalColumnNum,
96
- "--column-offset-width": `${columnOffsetWidth}px`
97
- },
98
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
99
- className: (0, _classnames.default)(_indexModule.default.searchOption, _indexModule.default[expand.toString()]),
100
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_antd.Form, {
101
- form: form,
102
- className: (0, _classnames.default)(_indexModule.default.option, cls),
103
- onValuesChange: onValuesChange,
104
- children: [permitted && currentSearchItems?.map((item, idx) => {
105
- const visibleCurrentItems = currentSearchItems.filter(i => i.visible !== false);
106
- const columnIndex = idx % totalColumnNum;
107
- const sameColumnItems = visibleCurrentItems.filter((_, _idx) => _idx % totalColumnNum === columnIndex);
108
- return /*#__PURE__*/(0, _react.createElement)(_FormItem.default, {
109
- ...item,
110
- key: item.name,
111
- className: (0, _classnames.default)(_indexModule.default.item, item.className, {
112
- [_indexModule.default.unFill]: item.width !== undefined
113
- }),
114
- labelWidth: item.width !== undefined ? undefined : getLabelWidth(sameColumnItems, undefined, minLabelWidth)
115
- });
116
- }), (beforeButtonGroup?.length || affterButtonGroup?.length || permitted) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ButtonGroup.ButtonGroup, {
117
- beforeButtonGroup: beforeButtonGroup,
118
- affterButtonGroup: affterButtonGroup,
119
- expandButton: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ExpandButton.ExpandButton, {
120
- expand: expand,
121
- toggleExpand: toggleExpand,
122
- advancedFilters: false,
123
- showExpandButton: showExpandButton,
124
- showAllSearchItems: showAllSearchItems || !!moreSearchItems?.length
125
- }),
126
- permitted: permitted,
127
- ref: buttonGroupRef,
128
- children: currentSearchItems.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_SearchButtons.SearchButtons, {
129
- onSearch: onSearchClick,
130
- onReset: onResetClick,
131
- searchDisabled: searchDisabled,
132
- searchText: searchText,
133
- resetText: resetText
134
- })
135
- })]
136
- })
137
- })
25
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SearchBase.default, {
26
+ ...props
138
27
  });
139
28
  };
140
29
  Search.Card = _SearchCard.default;
@@ -19,6 +19,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
19
19
  const MEASURE_ALIGN_FONT_SIZE = 14;
20
20
  // Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
21
21
  const MEASURE_ALIGN_BUFFER_PX = 2;
22
+ // 单元格左右内边距合计(--td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
23
+ const MEASURE_ALIGN_CELL_PADDING_PX = 16;
22
24
 
23
25
  // 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
24
26
  const getMeasureAlignWidth = (column, dataSource) => {
@@ -109,6 +111,15 @@ const useTableColumns = params => {
109
111
  }, 0) ?? 0 : 0;
110
112
  item.width = Math.max(titleWidth, dataIndexWidth) + 32;
111
113
  }
114
+
115
+ // measureAlign 列:列宽须容得下最宽行的定宽内容块 + 单元格左右内边距,
116
+ // 否则出现超长值(如 +3357.52%)时内容会被列宽截断;声明的 width 只作下限。
117
+ if (item.measureAlign && typeof item.width === "number") {
118
+ const measured = getMeasureAlignWidth(item, dataSource);
119
+ if (measured > 0) {
120
+ item.width = Math.max(item.width, measured + MEASURE_ALIGN_CELL_PADDING_PX);
121
+ }
122
+ }
112
123
  return item;
113
124
  })?.map(item => {
114
125
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsu-react/ui",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "一套基于 React + Ant Design 的中后台业务组件库",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,7 +16,9 @@ import {
16
16
  ChartScrollConfig,
17
17
  createDefaultCategoryXAxis,
18
18
  createDefaultValueYAxis,
19
+ DataZoomIndexWindow,
19
20
  getSliderShow,
21
+ percentWindowToIndexWindow,
20
22
  resolveScrollConfig,
21
23
  } from "../_utils/cartesian";
22
24
 
@@ -27,6 +29,10 @@ export interface ChartBarProps extends ChartCommonProps {
27
29
  legendData?: string[];
28
30
  scrollConfig?: ChartScrollConfig;
29
31
  onClick?: (event: echarts.ECElementEvent) => void;
32
+ /** 图例点选变化回调,参数为当前各系列选中状态(配合 legend.selected 可实现坐标轴重算) */
33
+ onLegendSelectChanged?: (selected: Record<string, boolean>) => void;
34
+ /** dataZoom 可见窗口变化回调(滑块/滚轮/自动滚动/初始同步均触发),参数为当前可见的 x 轴索引区间(配合 useDataZoomWindow 可按展示部分重算坐标轴) */
35
+ onDataZoomWindowChanged?: (window: DataZoomIndexWindow) => void;
30
36
  /** 是否启用图例自动滚动,默认 false */
31
37
  enableLegendAutoScroll?: boolean;
32
38
  /** 图例每页可见数量,默认 8 */
@@ -55,6 +61,8 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
55
61
  insideDataZoom,
56
62
  sliderDataZoom,
57
63
  onClick,
64
+ onLegendSelectChanged,
65
+ onDataZoomWindowChanged,
58
66
  enableLegendAutoScroll = false,
59
67
  legendVisibleCount = 8,
60
68
  legendScrollInterval = 1500,
@@ -63,6 +71,7 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
63
71
  const chartRef = useRef<HTMLDivElement>(null);
64
72
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
65
73
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
74
+ const prevXAxisKeyRef = useRef<string | null>(null);
66
75
  const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
67
76
  null,
68
77
  );
@@ -268,8 +277,46 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
268
277
  chartInstanceRef.current = chart;
269
278
  }
270
279
 
271
- // 设置配置
280
+ // 设置配置:notMerge 会把 dataZoom 窗口重置回初始位置,
281
+ // x 轴数据未变化时(如坐标轴重算/图例点选触发的重渲染)先记录当前窗口,设置后恢复
282
+ const xAxisKey = xAxisData?.join("\u0001") ?? "";
283
+ const prevZoomRanges =
284
+ prevXAxisKeyRef.current === xAxisKey
285
+ ? ((chart.getOption() as ChartsOption | undefined)?.dataZoom as
286
+ | Array<{ start?: number; end?: number }>
287
+ | undefined)
288
+ : undefined;
289
+ prevXAxisKeyRef.current = xAxisKey;
272
290
  chart.setOption(chartOption as ChartOptionType, true);
291
+ const nextZooms = (chartOption as ChartsOption).dataZoom;
292
+ if (
293
+ prevZoomRanges?.length &&
294
+ Array.isArray(nextZooms) &&
295
+ nextZooms.length === prevZoomRanges.length
296
+ ) {
297
+ chart.setOption({
298
+ dataZoom: prevZoomRanges.map((z) => ({ start: z.start, end: z.end })),
299
+ });
300
+ }
301
+
302
+ // 向调用方同步当前可见窗口(初始渲染与恢复后),用于按展示部分重算坐标轴
303
+ if (onDataZoomWindowChanged) {
304
+ const zooms = (chart.getOption() as ChartsOption | undefined)
305
+ ?.dataZoom as Array<{ start?: number; end?: number }> | undefined;
306
+ const zoom = zooms?.[0];
307
+ const totalLen = xAxisData?.length ?? 0;
308
+ if (typeof zoom?.start === "number" && typeof zoom?.end === "number") {
309
+ onDataZoomWindowChanged(
310
+ percentWindowToIndexWindow(zoom.start, zoom.end, totalLen),
311
+ );
312
+ } else {
313
+ // 无 dataZoom(如数据量不足未启用滚动)时窗口即全量,同步一次避免残留旧窗口
314
+ onDataZoomWindowChanged({
315
+ startIndex: 0,
316
+ endIndex: Math.max(0, totalLen - 1),
317
+ });
318
+ }
319
+ }
273
320
 
274
321
  // 添加 resize 监听
275
322
  window.addEventListener("resize", handleResize);
@@ -285,6 +332,59 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
285
332
  chartInstanceRef.current?.on("click", onClick);
286
333
  }
287
334
 
335
+ // 图例点选事件
336
+ const handleLegendSelectChanged = (params: unknown) => {
337
+ onLegendSelectChanged?.(
338
+ (params as { selected: Record<string, boolean> }).selected,
339
+ );
340
+ };
341
+ if (onLegendSelectChanged) {
342
+ chartInstanceRef.current?.on(
343
+ "legendselectchanged",
344
+ handleLegendSelectChanged,
345
+ );
346
+ }
347
+
348
+ // dataZoom 原生交互(slider 拖动 / inside 缩放)→ 同步可见窗口
349
+ const handleDataZoom = (params: unknown) => {
350
+ const raw = params as {
351
+ start?: number;
352
+ end?: number;
353
+ startValue?: number;
354
+ endValue?: number;
355
+ batch?: Array<{
356
+ start?: number;
357
+ end?: number;
358
+ startValue?: number;
359
+ endValue?: number;
360
+ }>;
361
+ };
362
+ const info = raw?.batch?.[0] ?? raw;
363
+ if (
364
+ typeof info?.startValue === "number" &&
365
+ typeof info?.endValue === "number"
366
+ ) {
367
+ onDataZoomWindowChanged?.({
368
+ startIndex: Math.max(0, Math.round(info.startValue)),
369
+ endIndex: Math.max(0, Math.round(info.endValue)),
370
+ });
371
+ } else if (
372
+ typeof info?.start === "number" &&
373
+ typeof info?.end === "number"
374
+ ) {
375
+ onDataZoomWindowChanged?.(
376
+ percentWindowToIndexWindow(
377
+ info.start,
378
+ info.end,
379
+ xAxisData?.length ?? 0,
380
+ ),
381
+ );
382
+ }
383
+ };
384
+ if (onDataZoomWindowChanged) {
385
+ chartInstanceRef.current?.on("datazoom", handleDataZoom);
386
+ }
387
+
288
388
  // 图例自动滚动(参考 Pie 组件)
289
389
  const opt = chartOption as ChartsOption;
290
390
  const legendDataLen = Array.isArray(opt.legend)
@@ -317,6 +417,17 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
317
417
  chartInstanceRef.current?.off("click", onClick);
318
418
  }
319
419
 
420
+ if (onLegendSelectChanged) {
421
+ chartInstanceRef.current?.off(
422
+ "legendselectchanged",
423
+ handleLegendSelectChanged,
424
+ );
425
+ }
426
+
427
+ if (onDataZoomWindowChanged) {
428
+ chartInstanceRef.current?.off("datazoom", handleDataZoom);
429
+ }
430
+
320
431
  if (legendScrollRef.current) {
321
432
  legendScrollRef.current.dispose();
322
433
  legendScrollRef.current = null;
@@ -326,6 +437,9 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
326
437
  chartOption,
327
438
  handleResize,
328
439
  onClick,
440
+ onLegendSelectChanged,
441
+ onDataZoomWindowChanged,
442
+ xAxisData,
329
443
  enableLegendAutoScroll,
330
444
  legendVisibleCount,
331
445
  legendScrollInterval,
@@ -358,10 +472,26 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
358
472
  startIndex: normalizedScroll.startIndex,
359
473
  autoPlay: normalizedScroll.autoScroll,
360
474
  enableWheelScroll,
475
+ onWindowChange: onDataZoomWindowChanged
476
+ ? (startPercent, endPercent) =>
477
+ onDataZoomWindowChanged(
478
+ percentWindowToIndexWindow(
479
+ startPercent,
480
+ endPercent,
481
+ xAxisData.length,
482
+ ),
483
+ )
484
+ : undefined,
361
485
  });
362
486
 
363
487
  return dispose;
364
- }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData]);
488
+ }, [
489
+ dataZoom,
490
+ normalizedScroll,
491
+ sliderDataZoom,
492
+ xAxisData,
493
+ onDataZoomWindowChanged,
494
+ ]);
365
495
 
366
496
  // 组件卸载时清理资源
367
497
  useEffect(() => {
@@ -16,7 +16,9 @@ import {
16
16
  ChartScrollConfig,
17
17
  createDefaultCategoryXAxis,
18
18
  createDefaultValueYAxis,
19
+ DataZoomIndexWindow,
19
20
  getSliderShow,
21
+ percentWindowToIndexWindow,
20
22
  resolveScrollConfig,
21
23
  } from "../_utils/cartesian";
22
24
 
@@ -28,6 +30,10 @@ export interface ChartLineProps extends ChartCommonProps {
28
30
  scrollConfig?: ChartScrollConfig;
29
31
  yAxisNameOffsets?: number[];
30
32
  onClick?: (event: echarts.ECElementEvent) => void;
33
+ /** 图例点选变化回调,参数为当前各系列选中状态(配合 legend.selected 可实现坐标轴重算) */
34
+ onLegendSelectChanged?: (selected: Record<string, boolean>) => void;
35
+ /** dataZoom 可见窗口变化回调(滑块/滚轮/自动滚动/初始同步均触发),参数为当前可见的 x 轴索引区间(配合 useDataZoomWindow 可按展示部分重算坐标轴) */
36
+ onDataZoomWindowChanged?: (window: DataZoomIndexWindow) => void;
31
37
  /** 是否启用图例自动滚动,默认 false */
32
38
  enableLegendAutoScroll?: boolean;
33
39
  /** 图例每页可见数量,默认 8 */
@@ -56,6 +62,8 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
56
62
  insideDataZoom,
57
63
  sliderDataZoom,
58
64
  onClick,
65
+ onLegendSelectChanged,
66
+ onDataZoomWindowChanged,
59
67
  enableLegendAutoScroll = false,
60
68
  legendVisibleCount = 8,
61
69
  legendScrollInterval = 1500,
@@ -64,6 +72,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
64
72
  const chartRef = useRef<HTMLDivElement>(null);
65
73
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
66
74
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
75
+ const prevXAxisKeyRef = useRef<string | null>(null);
67
76
  const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
68
77
  null,
69
78
  );
@@ -265,8 +274,46 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
265
274
  chartInstanceRef.current = chart;
266
275
  }
267
276
 
268
- // 设置配置
277
+ // 设置配置:notMerge 会把 dataZoom 窗口重置回初始位置,
278
+ // x 轴数据未变化时(如坐标轴重算/图例点选触发的重渲染)先记录当前窗口,设置后恢复
279
+ const xAxisKey = xAxisData?.join("\u0001") ?? "";
280
+ const prevZoomRanges =
281
+ prevXAxisKeyRef.current === xAxisKey
282
+ ? ((chart.getOption() as ChartsOption | undefined)?.dataZoom as
283
+ | Array<{ start?: number; end?: number }>
284
+ | undefined)
285
+ : undefined;
286
+ prevXAxisKeyRef.current = xAxisKey;
269
287
  chart.setOption(chartOption as ChartOptionType, true);
288
+ const nextZooms = (chartOption as ChartsOption).dataZoom;
289
+ if (
290
+ prevZoomRanges?.length &&
291
+ Array.isArray(nextZooms) &&
292
+ nextZooms.length === prevZoomRanges.length
293
+ ) {
294
+ chart.setOption({
295
+ dataZoom: prevZoomRanges.map((z) => ({ start: z.start, end: z.end })),
296
+ });
297
+ }
298
+
299
+ // 向调用方同步当前可见窗口(初始渲染与恢复后),用于按展示部分重算坐标轴
300
+ if (onDataZoomWindowChanged) {
301
+ const zooms = (chart.getOption() as ChartsOption | undefined)
302
+ ?.dataZoom as Array<{ start?: number; end?: number }> | undefined;
303
+ const zoom = zooms?.[0];
304
+ const totalLen = xAxisData?.length ?? 0;
305
+ if (typeof zoom?.start === "number" && typeof zoom?.end === "number") {
306
+ onDataZoomWindowChanged(
307
+ percentWindowToIndexWindow(zoom.start, zoom.end, totalLen),
308
+ );
309
+ } else {
310
+ // 无 dataZoom(如数据量不足未启用滚动)时窗口即全量,同步一次避免残留旧窗口
311
+ onDataZoomWindowChanged({
312
+ startIndex: 0,
313
+ endIndex: Math.max(0, totalLen - 1),
314
+ });
315
+ }
316
+ }
270
317
 
271
318
  // 添加 resize 监听
272
319
  window.addEventListener("resize", handleResize);
@@ -282,6 +329,59 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
282
329
  chartInstanceRef.current?.on("click", onClick);
283
330
  }
284
331
 
332
+ // 图例点选事件
333
+ const handleLegendSelectChanged = (params: unknown) => {
334
+ onLegendSelectChanged?.(
335
+ (params as { selected: Record<string, boolean> }).selected,
336
+ );
337
+ };
338
+ if (onLegendSelectChanged) {
339
+ chartInstanceRef.current?.on(
340
+ "legendselectchanged",
341
+ handleLegendSelectChanged,
342
+ );
343
+ }
344
+
345
+ // dataZoom 原生交互(slider 拖动 / inside 缩放)→ 同步可见窗口
346
+ const handleDataZoom = (params: unknown) => {
347
+ const raw = params as {
348
+ start?: number;
349
+ end?: number;
350
+ startValue?: number;
351
+ endValue?: number;
352
+ batch?: Array<{
353
+ start?: number;
354
+ end?: number;
355
+ startValue?: number;
356
+ endValue?: number;
357
+ }>;
358
+ };
359
+ const info = raw?.batch?.[0] ?? raw;
360
+ if (
361
+ typeof info?.startValue === "number" &&
362
+ typeof info?.endValue === "number"
363
+ ) {
364
+ onDataZoomWindowChanged?.({
365
+ startIndex: Math.max(0, Math.round(info.startValue)),
366
+ endIndex: Math.max(0, Math.round(info.endValue)),
367
+ });
368
+ } else if (
369
+ typeof info?.start === "number" &&
370
+ typeof info?.end === "number"
371
+ ) {
372
+ onDataZoomWindowChanged?.(
373
+ percentWindowToIndexWindow(
374
+ info.start,
375
+ info.end,
376
+ xAxisData?.length ?? 0,
377
+ ),
378
+ );
379
+ }
380
+ };
381
+ if (onDataZoomWindowChanged) {
382
+ chartInstanceRef.current?.on("datazoom", handleDataZoom);
383
+ }
384
+
285
385
  // 图例自动滚动(与 Pie/Bar 一致)
286
386
  const opt = chartOption as ChartsOption;
287
387
  const legendDataLen = Array.isArray(opt.legend)
@@ -314,6 +414,17 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
314
414
  chartInstanceRef.current?.off("click", onClick);
315
415
  }
316
416
 
417
+ if (onLegendSelectChanged) {
418
+ chartInstanceRef.current?.off(
419
+ "legendselectchanged",
420
+ handleLegendSelectChanged,
421
+ );
422
+ }
423
+
424
+ if (onDataZoomWindowChanged) {
425
+ chartInstanceRef.current?.off("datazoom", handleDataZoom);
426
+ }
427
+
317
428
  if (legendScrollRef.current) {
318
429
  legendScrollRef.current.dispose();
319
430
  legendScrollRef.current = null;
@@ -323,6 +434,9 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
323
434
  chartOption,
324
435
  handleResize,
325
436
  onClick,
437
+ onLegendSelectChanged,
438
+ onDataZoomWindowChanged,
439
+ xAxisData,
326
440
  enableLegendAutoScroll,
327
441
  legendVisibleCount,
328
442
  legendScrollInterval,
@@ -355,10 +469,26 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
355
469
  startIndex: normalizedScroll.startIndex,
356
470
  autoPlay: normalizedScroll.autoScroll,
357
471
  enableWheelScroll,
472
+ onWindowChange: onDataZoomWindowChanged
473
+ ? (startPercent, endPercent) =>
474
+ onDataZoomWindowChanged(
475
+ percentWindowToIndexWindow(
476
+ startPercent,
477
+ endPercent,
478
+ xAxisData.length,
479
+ ),
480
+ )
481
+ : undefined,
358
482
  });
359
483
 
360
484
  return dispose;
361
- }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData]);
485
+ }, [
486
+ dataZoom,
487
+ normalizedScroll,
488
+ sliderDataZoom,
489
+ xAxisData,
490
+ onDataZoomWindowChanged,
491
+ ]);
362
492
 
363
493
  // 组件卸载时清理资源
364
494
  useEffect(() => {