@hsu-react/ui 0.0.11 → 0.0.12

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.
@@ -1,11 +1,22 @@
1
1
  import * as echarts from "echarts";
2
2
  import { ChartOptionType } from "..";
3
+ export interface Bar3DSeriesOptions {
4
+ /** 同组 3D 柱系列总数(图例隐藏的不计入),用于整组相对类目刻度居中,默认 1 */
5
+ seriesCount?: number;
6
+ /** 当前系列在同组内的序号(图例隐藏的不占位),默认取 params.seriesIndex */
7
+ seriesOrder?: number;
8
+ /** 堆叠模式:同一类目上各系列纵向叠放(共用同一根柱) */
9
+ stack?: boolean;
10
+ /** 堆叠基底:每个数据点下方已累计的值,由调用方按可见系列累加 */
11
+ stackBase?: number[];
12
+ }
3
13
  /**
4
14
  * 3D柱状图series生成
5
15
  * @param _colors 颜色
6
16
  * @param barGap 柱间距,必须大于等于柱宽
7
17
  * @param barWidth 柱宽
18
+ * @param options 分组居中 / 堆叠配置
8
19
  * @returns
9
20
  */
10
- declare const bar3DSeries: (_colors: echarts.Color, barGap?: number, barWidth?: number) => echarts.SeriesOption & ChartOptionType;
21
+ declare const bar3DSeries: (_colors: echarts.Color, barGap?: number, barWidth?: number, options?: Bar3DSeriesOptions) => echarts.SeriesOption & ChartOptionType;
11
22
  export default bar3DSeries;
@@ -83,22 +83,37 @@ var CubeTop = echarts.graphic.extendShape({
83
83
  echarts.graphic.registerShape("CubeFront", CubeFront);
84
84
  echarts.graphic.registerShape("CubeRight", CubeRight);
85
85
  echarts.graphic.registerShape("CubeTop", CubeTop);
86
-
87
86
  /**
88
87
  * 3D柱状图series生成
89
88
  * @param _colors 颜色
90
89
  * @param barGap 柱间距,必须大于等于柱宽
91
90
  * @param barWidth 柱宽
91
+ * @param options 分组居中 / 堆叠配置
92
92
  * @returns
93
93
  */
94
94
  var bar3DSeries = function bar3DSeries(_colors) {
95
95
  var barGap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 25;
96
96
  var barWidth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 20;
97
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
98
+ var _options$seriesCount = options.seriesCount,
99
+ seriesCount = _options$seriesCount === void 0 ? 1 : _options$seriesCount,
100
+ seriesOrder = options.seriesOrder,
101
+ _options$stack = options.stack,
102
+ stack = _options$stack === void 0 ? false : _options$stack,
103
+ stackBase = options.stackBase;
104
+ // 单根柱的视觉足迹为 [x - barWidth, x + depthOffset](depthOffset 为 3D 深度)
105
+ var depthOffset = barWidth * 8 / 20;
106
+ // 整组(堆叠时只有一根柱)相对类目刻度的居中补偿
107
+ var groupSpan = stack ? 0 : Math.max(0, seriesCount - 1) * barGap;
108
+ var centerOffset = (barWidth - depthOffset - groupSpan) / 2;
97
109
  var series = {
98
110
  type: "custom",
99
111
  renderItem: function renderItem(params, api) {
112
+ var _stackBase$dataIndex;
100
113
  var _params$seriesIndex = params.seriesIndex,
101
- seriesIndex = _params$seriesIndex === void 0 ? 0 : _params$seriesIndex;
114
+ seriesIndex = _params$seriesIndex === void 0 ? 0 : _params$seriesIndex,
115
+ _params$dataIndex = params.dataIndex,
116
+ dataIndex = _params$dataIndex === void 0 ? 0 : _params$dataIndex;
102
117
  var cubeLeftStyle = _colors !== null && _colors !== void 0 ? _colors : new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
103
118
  offset: 0,
104
119
  color: colors[0]
@@ -123,14 +138,19 @@ var bar3DSeries = function bar3DSeries(_colors) {
123
138
  var _ref = [api.value(0), api.value(1)],
124
139
  value0 = _ref[0],
125
140
  value1 = _ref[1];
126
- var location = api.coord([value0, value1]);
127
- var xAxisTranslation = seriesIndex * barGap;
128
141
  if (value1 === 0) {
129
142
  return {
130
143
  type: "group",
131
144
  children: []
132
145
  };
133
146
  }
147
+
148
+ // 堆叠时从基底(下方各系列累计值)画到基底+自身值;非堆叠从 0 画起
149
+ var stackBottom = stack ? (_stackBase$dataIndex = stackBase === null || stackBase === void 0 ? void 0 : stackBase[dataIndex]) !== null && _stackBase$dataIndex !== void 0 ? _stackBase$dataIndex : 0 : 0;
150
+ var location = api.coord([value0, stackBottom + Number(value1)]);
151
+ var bottomPoint = api.coord([value0, stackBottom]);
152
+ var order = seriesOrder !== null && seriesOrder !== void 0 ? seriesOrder : seriesIndex;
153
+ var xAxisTranslation = (stack ? 0 : order * barGap) + centerOffset;
134
154
  return {
135
155
  type: "group",
136
156
  children: [{
@@ -141,7 +161,7 @@ var bar3DSeries = function bar3DSeries(_colors) {
141
161
  yValue: value1,
142
162
  x: location[0],
143
163
  y: location[1],
144
- xAxisPoint: api.coord([value0, 0]),
164
+ xAxisPoint: bottomPoint,
145
165
  xAxisTranslation: xAxisTranslation,
146
166
  barWidth: barWidth
147
167
  },
@@ -156,7 +176,7 @@ var bar3DSeries = function bar3DSeries(_colors) {
156
176
  yValue: value1,
157
177
  x: location[0],
158
178
  y: location[1],
159
- xAxisPoint: api.coord([value0, 0]),
179
+ xAxisPoint: bottomPoint,
160
180
  xAxisTranslation: xAxisTranslation,
161
181
  barWidth: barWidth
162
182
  },
@@ -171,7 +191,7 @@ var bar3DSeries = function bar3DSeries(_colors) {
171
191
  yValue: value1,
172
192
  x: location[0],
173
193
  y: location[1],
174
- xAxisPoint: api.coord([value0, 0]),
194
+ xAxisPoint: bottomPoint,
175
195
  xAxisTranslation: xAxisTranslation,
176
196
  barWidth: barWidth
177
197
  },
@@ -24,8 +24,16 @@ var chartTooltipFormatter = function chartTooltipFormatter(props) {
24
24
  var item = params[0];
25
25
  return "\n <div style=\"margin: 0;line-height:1;\">\n <div style=\"margin: 0;line-height:1;\">\n ".concat(hideMarker ? "" : "".concat(item === null || item === void 0 ? void 0 : item.marker), "\n <span style=\"font-size:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.fontSize, "px;color:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.color, ";font-weight:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.fontWeight, ";margin-left:2px\">").concat(item === null || item === void 0 ? void 0 : item.name, "</span>\n <span style=\"float:right;margin-left:20px;font-size:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.fontSize, "px;color:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.color, ";font-weight:").concat(textStyle === null || textStyle === void 0 ? void 0 : textStyle.fontWeight, "\">").concat(item === null || item === void 0 ? void 0 : item.value).concat(unit ? " ".concat(unit) : "", "</span>\n <div style=\"clear:both\"></div>\n </div>\n <div style=\"clear:both\"></div>\n </div>\n ");
26
26
  }
27
+
28
+ // value 可能是数值、拼了单位的字符串("123次")或带千分位("1,234"),排序前统一还原成数值
29
+ var toSortValue = function toSortValue(v) {
30
+ if (typeof v === "number") return v;
31
+ if (Array.isArray(v)) return toSortValue(v[v.length - 1]);
32
+ var n = parseFloat(String(v !== null && v !== void 0 ? v : "").replace(/,/g, ""));
33
+ return Number.isNaN(n) ? 0 : n;
34
+ };
27
35
  var sortedParams = sortDescending ? _toConsumableArray(params).sort(function (a, b) {
28
- return (Number(b.value) || 0) - (Number(a.value) || 0);
36
+ return toSortValue(b.value) - toSortValue(a.value);
29
37
  }) : params;
30
38
  var items = sortedParams === null || sortedParams === void 0 ? void 0 : sortedParams.map(function (item, idx) {
31
39
  var _item$value;
@@ -13,6 +13,8 @@ interface TableTools {
13
13
  columnMgt?: IColumnMgt;
14
14
  }
15
15
  export interface ListModalPanelTabelProps extends Omit<TableProps, "title"> {
16
+ /** 透传给内部 Table 的 key,用于在异构列集切换时强制重挂载表格 */
17
+ key?: React.Key;
16
18
  title?: string;
17
19
  buttonGroup?: ButtonProps[];
18
20
  tabBarProps?: TabBarProps;
@@ -1,6 +1,6 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
2
  var _excluded = ["modalClassName", "className", "searchProps", "searchMode", "tableProps", "hasPermi", "extraContent"],
3
- _excluded2 = ["title", "buttonGroup", "className", "columns", "tabBarProps", "tableTools", "tableContainerClassName", "tips", "otherTool"];
3
+ _excluded2 = ["title", "buttonGroup", "className", "columns", "tabBarProps", "tableTools", "tableContainerClassName", "tips", "otherTool", "key"];
4
4
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
5
5
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
6
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
@@ -54,6 +54,7 @@ var ListModalPanel = function ListModalPanel(props) {
54
54
  tableContainerClassName = tableProps.tableContainerClassName,
55
55
  tips = tableProps.tips,
56
56
  otherTool = tableProps.otherTool,
57
+ tableInstanceKey = tableProps.key,
57
58
  tableConfig = _objectWithoutProperties(tableProps, _excluded2);
58
59
  var columnMgt = tableTools.columnMgt;
59
60
  var _usePermissions = usePermissions(hasPermi),
@@ -70,6 +71,12 @@ var ListModalPanel = function ListModalPanel(props) {
70
71
  _useState6 = _slicedToArray(_useState5, 2),
71
72
  isFullscreen = _useState6[0],
72
73
  setIsFullscreen = _useState6[1];
74
+ // 弹窗打开动画是否已完成:Search 的列数/展开等 DOM 测量要等弹窗完全展开
75
+ // 才能取到准确尺寸(zoom 动画只变 transform,不触发 ResizeObserver)
76
+ var _useState7 = useState(false),
77
+ _useState8 = _slicedToArray(_useState7, 2),
78
+ modalReady = _useState8[0],
79
+ setModalReady = _useState8[1];
73
80
  useEffect(function () {
74
81
  if (modalConfig.open) {
75
82
  setIsFullscreen(false);
@@ -133,6 +140,11 @@ var ListModalPanel = function ListModalPanel(props) {
133
140
  }
134
141
  return /*#__PURE__*/_jsxs(_Fragment, {
135
142
  children: [/*#__PURE__*/_jsx(Modal, _objectSpread(_objectSpread(_objectSpread({}, modalConfig), {}, {
143
+ afterOpenChange: function afterOpenChange(open) {
144
+ var _modalConfig$afterOpe;
145
+ setModalReady(open);
146
+ (_modalConfig$afterOpe = modalConfig.afterOpenChange) === null || _modalConfig$afterOpe === void 0 || _modalConfig$afterOpe.call(modalConfig, open);
147
+ },
136
148
  width: isFullscreen ? "100vw" : (_modalConfig$width = modalConfig.width) !== null && _modalConfig$width !== void 0 ? _modalConfig$width : 1400
137
149
  }, isFullscreen ? {
138
150
  full: true
@@ -150,7 +162,9 @@ var ListModalPanel = function ListModalPanel(props) {
150
162
  }].concat(_toConsumableArray((_modalConfig$titleBut = modalConfig.titleButtonGroup) !== null && _modalConfig$titleBut !== void 0 ? _modalConfig$titleBut : [])),
151
163
  children: /*#__PURE__*/_jsxs("div", {
152
164
  className: classNames(styles.ListModalPanel, className),
153
- children: [extraContent, SearchComponent, /*#__PURE__*/_jsxs("div", {
165
+ children: [extraContent, /*#__PURE__*/React.cloneElement(SearchComponent, {
166
+ key: modalReady ? "modal-ready" : "modal-pending"
167
+ }), /*#__PURE__*/_jsxs("div", {
154
168
  className: classNames(styles.tableContainer, tableContainerClassName),
155
169
  children: [/*#__PURE__*/_jsx(ToolBar, {
156
170
  columns: columns,
@@ -170,7 +184,7 @@ var ListModalPanel = function ListModalPanel(props) {
170
184
  fillPanel: true
171
185
  }, tableConfig), {}, {
172
186
  columns: _columns
173
- })))]
187
+ })), tableInstanceKey)]
174
188
  })]
175
189
  })
176
190
  })), /*#__PURE__*/_jsx(ColumnMgt, _objectSpread({
@@ -1,11 +1,22 @@
1
1
  import * as echarts from "echarts";
2
2
  import { ChartOptionType } from "..";
3
+ export interface Bar3DSeriesOptions {
4
+ /** 同组 3D 柱系列总数(图例隐藏的不计入),用于整组相对类目刻度居中,默认 1 */
5
+ seriesCount?: number;
6
+ /** 当前系列在同组内的序号(图例隐藏的不占位),默认取 params.seriesIndex */
7
+ seriesOrder?: number;
8
+ /** 堆叠模式:同一类目上各系列纵向叠放(共用同一根柱) */
9
+ stack?: boolean;
10
+ /** 堆叠基底:每个数据点下方已累计的值,由调用方按可见系列累加 */
11
+ stackBase?: number[];
12
+ }
3
13
  /**
4
14
  * 3D柱状图series生成
5
15
  * @param _colors 颜色
6
16
  * @param barGap 柱间距,必须大于等于柱宽
7
17
  * @param barWidth 柱宽
18
+ * @param options 分组居中 / 堆叠配置
8
19
  * @returns
9
20
  */
10
- declare const bar3DSeries: (_colors: echarts.Color, barGap?: number, barWidth?: number) => echarts.SeriesOption & ChartOptionType;
21
+ declare const bar3DSeries: (_colors: echarts.Color, barGap?: number, barWidth?: number, options?: Bar3DSeriesOptions) => echarts.SeriesOption & ChartOptionType;
11
22
  export default bar3DSeries;
@@ -91,20 +91,32 @@ const CubeTop = echarts.graphic.extendShape({
91
91
  echarts.graphic.registerShape("CubeFront", CubeFront);
92
92
  echarts.graphic.registerShape("CubeRight", CubeRight);
93
93
  echarts.graphic.registerShape("CubeTop", CubeTop);
94
-
95
94
  /**
96
95
  * 3D柱状图series生成
97
96
  * @param _colors 颜色
98
97
  * @param barGap 柱间距,必须大于等于柱宽
99
98
  * @param barWidth 柱宽
99
+ * @param options 分组居中 / 堆叠配置
100
100
  * @returns
101
101
  */
102
- const bar3DSeries = (_colors, barGap = 25, barWidth = 20) => {
102
+ const bar3DSeries = (_colors, barGap = 25, barWidth = 20, options = {}) => {
103
+ const {
104
+ seriesCount = 1,
105
+ seriesOrder,
106
+ stack = false,
107
+ stackBase
108
+ } = options;
109
+ // 单根柱的视觉足迹为 [x - barWidth, x + depthOffset](depthOffset 为 3D 深度)
110
+ const depthOffset = barWidth * 8 / 20;
111
+ // 整组(堆叠时只有一根柱)相对类目刻度的居中补偿
112
+ const groupSpan = stack ? 0 : Math.max(0, seriesCount - 1) * barGap;
113
+ const centerOffset = (barWidth - depthOffset - groupSpan) / 2;
103
114
  const series = {
104
115
  type: "custom",
105
116
  renderItem: (params, api) => {
106
117
  const {
107
- seriesIndex = 0
118
+ seriesIndex = 0,
119
+ dataIndex = 0
108
120
  } = params;
109
121
  const cubeLeftStyle = _colors ?? new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
110
122
  offset: 0,
@@ -128,14 +140,19 @@ const bar3DSeries = (_colors, barGap = 25, barWidth = 20) => {
128
140
  color: colors[0]
129
141
  }]);
130
142
  const [value0, value1] = [api.value(0), api.value(1)];
131
- const location = api.coord([value0, value1]);
132
- const xAxisTranslation = seriesIndex * barGap;
133
143
  if (value1 === 0) {
134
144
  return {
135
145
  type: "group",
136
146
  children: []
137
147
  };
138
148
  }
149
+
150
+ // 堆叠时从基底(下方各系列累计值)画到基底+自身值;非堆叠从 0 画起
151
+ const stackBottom = stack ? stackBase?.[dataIndex] ?? 0 : 0;
152
+ const location = api.coord([value0, stackBottom + Number(value1)]);
153
+ const bottomPoint = api.coord([value0, stackBottom]);
154
+ const order = seriesOrder ?? seriesIndex;
155
+ const xAxisTranslation = (stack ? 0 : order * barGap) + centerOffset;
139
156
  return {
140
157
  type: "group",
141
158
  children: [{
@@ -146,7 +163,7 @@ const bar3DSeries = (_colors, barGap = 25, barWidth = 20) => {
146
163
  yValue: value1,
147
164
  x: location[0],
148
165
  y: location[1],
149
- xAxisPoint: api.coord([value0, 0]),
166
+ xAxisPoint: bottomPoint,
150
167
  xAxisTranslation,
151
168
  barWidth
152
169
  },
@@ -161,7 +178,7 @@ const bar3DSeries = (_colors, barGap = 25, barWidth = 20) => {
161
178
  yValue: value1,
162
179
  x: location[0],
163
180
  y: location[1],
164
- xAxisPoint: api.coord([value0, 0]),
181
+ xAxisPoint: bottomPoint,
165
182
  xAxisTranslation,
166
183
  barWidth
167
184
  },
@@ -176,7 +193,7 @@ const bar3DSeries = (_colors, barGap = 25, barWidth = 20) => {
176
193
  yValue: value1,
177
194
  x: location[0],
178
195
  y: location[1],
179
- xAxisPoint: api.coord([value0, 0]),
196
+ xAxisPoint: bottomPoint,
180
197
  xAxisTranslation,
181
198
  barWidth
182
199
  },
@@ -44,7 +44,15 @@ const chartTooltipFormatter = props => {
44
44
  </div>
45
45
  `;
46
46
  }
47
- const sortedParams = sortDescending ? [...params].sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)) : params;
47
+
48
+ // value 可能是数值、拼了单位的字符串("123次")或带千分位("1,234"),排序前统一还原成数值
49
+ const toSortValue = v => {
50
+ if (typeof v === "number") return v;
51
+ if (Array.isArray(v)) return toSortValue(v[v.length - 1]);
52
+ const n = parseFloat(String(v ?? "").replace(/,/g, ""));
53
+ return Number.isNaN(n) ? 0 : n;
54
+ };
55
+ const sortedParams = sortDescending ? [...params].sort((a, b) => toSortValue(b.value) - toSortValue(a.value)) : params;
48
56
  const items = sortedParams?.map((item, idx) => {
49
57
  const value = item.value ?? "";
50
58
  return `
@@ -13,6 +13,8 @@ interface TableTools {
13
13
  columnMgt?: IColumnMgt;
14
14
  }
15
15
  export interface ListModalPanelTabelProps extends Omit<TableProps, "title"> {
16
+ /** 透传给内部 Table 的 key,用于在异构列集切换时强制重挂载表格 */
17
+ key?: React.Key;
16
18
  title?: string;
17
19
  buttonGroup?: ButtonProps[];
18
20
  tabBarProps?: TabBarProps;
@@ -40,6 +40,9 @@ const ListModalPanel = props => {
40
40
  tableContainerClassName,
41
41
  tips,
42
42
  otherTool,
43
+ // 异构列集切换时用于强制重挂载表格的 key;显式提取,避免随 ...tableConfig
44
+ // 展开进 JSX 触发 React "key prop being spread" 警告
45
+ key: tableInstanceKey,
43
46
  ...tableConfig
44
47
  } = tableProps;
45
48
  const {
@@ -51,6 +54,9 @@ const ListModalPanel = props => {
51
54
  const [_columns, setColumns] = (0, _react.useState)(columns);
52
55
  const [showColumnMgt, setShowColumnMgt] = (0, _react.useState)(false);
53
56
  const [isFullscreen, setIsFullscreen] = (0, _react.useState)(false);
57
+ // 弹窗打开动画是否已完成:Search 的列数/展开等 DOM 测量要等弹窗完全展开
58
+ // 才能取到准确尺寸(zoom 动画只变 transform,不触发 ResizeObserver)
59
+ const [modalReady, setModalReady] = (0, _react.useState)(false);
54
60
  (0, _react.useEffect)(() => {
55
61
  if (modalConfig.open) {
56
62
  setIsFullscreen(false);
@@ -117,6 +123,10 @@ const ListModalPanel = props => {
117
123
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
118
124
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Modal.default, {
119
125
  ...modalConfig,
126
+ afterOpenChange: open => {
127
+ setModalReady(open);
128
+ modalConfig.afterOpenChange?.(open);
129
+ },
120
130
  width: isFullscreen ? "100vw" : modalConfig.width ?? 1400,
121
131
  ...(isFullscreen ? {
122
132
  full: true
@@ -132,7 +142,9 @@ const ListModalPanel = props => {
132
142
  }, ...(modalConfig.titleButtonGroup ?? [])],
133
143
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
134
144
  className: (0, _classnames.default)(_indexModule.default.ListModalPanel, className),
135
- children: [extraContent, SearchComponent, /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
145
+ children: [extraContent, /*#__PURE__*/_react.default.cloneElement(SearchComponent, {
146
+ key: modalReady ? "modal-ready" : "modal-pending"
147
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
136
148
  className: (0, _classnames.default)(_indexModule.default.tableContainer, tableContainerClassName),
137
149
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ToolBar.default, {
138
150
  columns: columns,
@@ -151,7 +163,7 @@ const ListModalPanel = props => {
151
163
  fillPanel: true,
152
164
  ...tableConfig,
153
165
  columns: _columns
154
- })]
166
+ }, tableInstanceKey)]
155
167
  })]
156
168
  })
157
169
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ColumnMgt.default, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsu-react/ui",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "一套基于 React + Ant Design 的中后台业务组件库",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -102,25 +102,45 @@ echarts.graphic.registerShape("CubeFront", CubeFront);
102
102
  echarts.graphic.registerShape("CubeRight", CubeRight);
103
103
  echarts.graphic.registerShape("CubeTop", CubeTop);
104
104
 
105
+ export interface Bar3DSeriesOptions {
106
+ /** 同组 3D 柱系列总数(图例隐藏的不计入),用于整组相对类目刻度居中,默认 1 */
107
+ seriesCount?: number;
108
+ /** 当前系列在同组内的序号(图例隐藏的不占位),默认取 params.seriesIndex */
109
+ seriesOrder?: number;
110
+ /** 堆叠模式:同一类目上各系列纵向叠放(共用同一根柱) */
111
+ stack?: boolean;
112
+ /** 堆叠基底:每个数据点下方已累计的值,由调用方按可见系列累加 */
113
+ stackBase?: number[];
114
+ }
115
+
105
116
  /**
106
117
  * 3D柱状图series生成
107
118
  * @param _colors 颜色
108
119
  * @param barGap 柱间距,必须大于等于柱宽
109
120
  * @param barWidth 柱宽
121
+ * @param options 分组居中 / 堆叠配置
110
122
  * @returns
111
123
  */
112
124
  const bar3DSeries = (
113
125
  _colors: echarts.Color,
114
126
  barGap: number = 25,
115
127
  barWidth: number = 20,
128
+ options: Bar3DSeriesOptions = {},
116
129
  ): echarts.SeriesOption & ChartOptionType => {
130
+ const { seriesCount = 1, seriesOrder, stack = false, stackBase } = options;
131
+ // 单根柱的视觉足迹为 [x - barWidth, x + depthOffset](depthOffset 为 3D 深度)
132
+ const depthOffset = (barWidth * 8) / 20;
133
+ // 整组(堆叠时只有一根柱)相对类目刻度的居中补偿
134
+ const groupSpan = stack ? 0 : Math.max(0, seriesCount - 1) * barGap;
135
+ const centerOffset = (barWidth - depthOffset - groupSpan) / 2;
136
+
117
137
  const series = {
118
138
  type: "custom",
119
139
  renderItem: (
120
140
  params: echarts.CustomSeriesRenderItemParams,
121
141
  api: echarts.CustomSeriesRenderItemAPI,
122
142
  ) => {
123
- const { seriesIndex = 0 } = params;
143
+ const { seriesIndex = 0, dataIndex = 0 } = params;
124
144
 
125
145
  const cubeLeftStyle =
126
146
  _colors ??
@@ -161,14 +181,18 @@ const bar3DSeries = (
161
181
 
162
182
  const [value0, value1] = [api.value!(0), api.value!(1)];
163
183
 
164
- const location = api.coord!([value0, value1]);
165
-
166
- const xAxisTranslation = seriesIndex * barGap;
167
-
168
184
  if (value1 === 0) {
169
185
  return { type: "group", children: [] };
170
186
  }
171
187
 
188
+ // 堆叠时从基底(下方各系列累计值)画到基底+自身值;非堆叠从 0 画起
189
+ const stackBottom = stack ? (stackBase?.[dataIndex] ?? 0) : 0;
190
+ const location = api.coord!([value0, stackBottom + Number(value1)]);
191
+ const bottomPoint = api.coord!([value0, stackBottom]);
192
+
193
+ const order = seriesOrder ?? seriesIndex;
194
+ const xAxisTranslation = (stack ? 0 : order * barGap) + centerOffset;
195
+
172
196
  return {
173
197
  type: "group",
174
198
  children: [
@@ -180,7 +204,7 @@ const bar3DSeries = (
180
204
  yValue: value1,
181
205
  x: location[0],
182
206
  y: location[1],
183
- xAxisPoint: api.coord!([value0, 0]),
207
+ xAxisPoint: bottomPoint,
184
208
  xAxisTranslation,
185
209
  barWidth,
186
210
  },
@@ -196,7 +220,7 @@ const bar3DSeries = (
196
220
  yValue: value1,
197
221
  x: location[0],
198
222
  y: location[1],
199
- xAxisPoint: api.coord!([value0, 0]),
223
+ xAxisPoint: bottomPoint,
200
224
  xAxisTranslation,
201
225
  barWidth,
202
226
  },
@@ -212,7 +236,7 @@ const bar3DSeries = (
212
236
  yValue: value1,
213
237
  x: location[0],
214
238
  y: location[1],
215
- xAxisPoint: api.coord!([value0, 0]),
239
+ xAxisPoint: bottomPoint,
216
240
  xAxisTranslation,
217
241
  barWidth,
218
242
  },
@@ -80,10 +80,16 @@ const chartTooltipFormatter = (props: ChartTooltipFormatterProps) => {
80
80
  `;
81
81
  }
82
82
 
83
+ // value 可能是数值、拼了单位的字符串("123次")或带千分位("1,234"),排序前统一还原成数值
84
+ const toSortValue = (v: unknown): number => {
85
+ if (typeof v === "number") return v;
86
+ if (Array.isArray(v)) return toSortValue(v[v.length - 1]);
87
+ const n = parseFloat(String(v ?? "").replace(/,/g, ""));
88
+ return Number.isNaN(n) ? 0 : n;
89
+ };
90
+
83
91
  const sortedParams = sortDescending
84
- ? [...params].sort(
85
- (a, b) => (Number(b.value) || 0) - (Number(a.value) || 0),
86
- )
92
+ ? [...params].sort((a, b) => toSortValue(b.value) - toSortValue(a.value))
87
93
  : params;
88
94
 
89
95
  const items = sortedParams
@@ -30,6 +30,8 @@ interface TableTools {
30
30
  }
31
31
 
32
32
  export interface ListModalPanelTabelProps extends Omit<TableProps, "title"> {
33
+ /** 透传给内部 Table 的 key,用于在异构列集切换时强制重挂载表格 */
34
+ key?: React.Key;
33
35
  title?: string;
34
36
  buttonGroup?: ButtonProps[];
35
37
  tabBarProps?: TabBarProps;
@@ -97,6 +99,9 @@ const ListModalPanel: React.FC<ListModalPanelProps<SearchModeKeys>> = (
97
99
  tableContainerClassName,
98
100
  tips,
99
101
  otherTool,
102
+ // 异构列集切换时用于强制重挂载表格的 key;显式提取,避免随 ...tableConfig
103
+ // 展开进 JSX 触发 React "key prop being spread" 警告
104
+ key: tableInstanceKey,
100
105
  ...tableConfig
101
106
  } = tableProps;
102
107
  const { columnMgt } = tableTools;
@@ -104,6 +109,9 @@ const ListModalPanel: React.FC<ListModalPanelProps<SearchModeKeys>> = (
104
109
  const [_columns, setColumns] = useState<ColumnsType | undefined>(columns);
105
110
  const [showColumnMgt, setShowColumnMgt] = useState<boolean>(false);
106
111
  const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
112
+ // 弹窗打开动画是否已完成:Search 的列数/展开等 DOM 测量要等弹窗完全展开
113
+ // 才能取到准确尺寸(zoom 动画只变 transform,不触发 ResizeObserver)
114
+ const [modalReady, setModalReady] = useState<boolean>(false);
107
115
 
108
116
  useEffect(() => {
109
117
  if (modalConfig.open) {
@@ -193,6 +201,10 @@ const ListModalPanel: React.FC<ListModalPanelProps<SearchModeKeys>> = (
193
201
  <>
194
202
  <Modal
195
203
  {...modalConfig}
204
+ afterOpenChange={(open) => {
205
+ setModalReady(open);
206
+ modalConfig.afterOpenChange?.(open);
207
+ }}
196
208
  width={isFullscreen ? "100vw" : (modalConfig.width ?? 1400)}
197
209
  {...(isFullscreen ? { full: true } : {})}
198
210
  className={classNames(styles.modal, modalClassName, {
@@ -214,7 +226,10 @@ const ListModalPanel: React.FC<ListModalPanelProps<SearchModeKeys>> = (
214
226
  >
215
227
  <div className={classNames(styles.ListModalPanel, className)}>
216
228
  {extraContent}
217
- {SearchComponent}
229
+ {/* 动画完成后重挂载 Search,使其在弹窗完全展开后重新初测 DOM 尺寸 */}
230
+ {React.cloneElement(SearchComponent, {
231
+ key: modalReady ? "modal-ready" : "modal-pending",
232
+ })}
218
233
  <div
219
234
  className={classNames(
220
235
  styles.tableContainer,
@@ -232,6 +247,7 @@ const ListModalPanel: React.FC<ListModalPanelProps<SearchModeKeys>> = (
232
247
  otherTool={otherTool}
233
248
  />
234
249
  <Table
250
+ key={tableInstanceKey}
235
251
  className={`${styles.table} ${tableClassName ?? ""}`}
236
252
  {...{
237
253
  scroll: true,