@hsu-react/ui 0.0.10 → 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.
- package/README.md +2 -2
- package/es/components/Chart/Bar/Bar3DOpt.d.ts +12 -1
- package/es/components/Chart/Bar/Bar3DOpt.js +27 -7
- package/es/components/Chart/chartUtils/chartTooltipFormatter.js +9 -1
- package/es/components/Panel/ListPanel/ListModalPanel/index.d.ts +2 -0
- package/es/components/Panel/ListPanel/ListModalPanel/index.js +17 -3
- package/es/components/Table/_hooks/useEllipsisTooltip.js +4 -2
- package/es/components/Table/_hooks/useTableColumns.d.ts +1 -0
- package/es/components/Table/_hooks/useTableColumns.js +26 -4
- package/es/components/Table/index.js +2 -1
- package/es/components/TextEllipsis/index.js +32 -14
- package/lib/components/Chart/Bar/Bar3DOpt.d.ts +12 -1
- package/lib/components/Chart/Bar/Bar3DOpt.js +25 -8
- package/lib/components/Chart/chartUtils/chartTooltipFormatter.js +9 -1
- package/lib/components/Panel/ListPanel/ListModalPanel/index.d.ts +2 -0
- package/lib/components/Panel/ListPanel/ListModalPanel/index.js +14 -2
- package/lib/components/Table/_hooks/useEllipsisTooltip.js +3 -1
- package/lib/components/Table/_hooks/useTableColumns.d.ts +1 -0
- package/lib/components/Table/_hooks/useTableColumns.js +22 -4
- package/lib/components/Table/index.js +2 -1
- package/lib/components/TextEllipsis/index.js +27 -14
- package/package.json +1 -1
- package/src/components/Chart/Bar/Bar3DOpt.tsx +32 -8
- package/src/components/Chart/chartUtils/chartTooltipFormatter.ts +9 -3
- package/src/components/Panel/ListPanel/ListModalPanel/index.tsx +17 -1
- package/src/components/Table/_hooks/useEllipsisTooltip.ts +2 -4
- package/src/components/Table/_hooks/useTableColumns.tsx +26 -3
- package/src/components/Table/index.md +2 -0
- package/src/components/Table/index.tsx +1 -0
- package/src/components/TextEllipsis/index.md +1 -1
- package/src/components/TextEllipsis/index.tsx +33 -18
package/README.md
CHANGED
|
@@ -30,10 +30,10 @@ export default function App() {
|
|
|
30
30
|
- `ConfigProvider.permissions` — 当前用户权限码,驱动所有 `hasPermi` 的显隐校验。
|
|
31
31
|
- `ConfigProvider.request` — 注入 HTTP 请求实现(库本身不绑定 HTTP 客户端)。
|
|
32
32
|
|
|
33
|
-
可选引入全局 antd
|
|
33
|
+
可选引入全局 antd 观感覆盖(产物为 scss,宿主项目需具备 scss 编译能力):
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import "@hsu-react/ui/es/styles/antd-overload.
|
|
36
|
+
import "@hsu-react/ui/es/styles/antd-overload.scss";
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
## 开发
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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 (
|
|
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,
|
|
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({
|
|
@@ -25,11 +25,13 @@ var useEllipsisTooltip = function useEllipsisTooltip(tooltipConfig) {
|
|
|
25
25
|
if (column.ellipsis && !column.render && column.dataIndex) {
|
|
26
26
|
return _objectSpread(_objectSpread({}, column), {}, {
|
|
27
27
|
render: function render(text) {
|
|
28
|
-
var _column$
|
|
28
|
+
var _column$ellipsisPosit;
|
|
29
29
|
// 获取实际的显示值
|
|
30
30
|
var displayValue = typeof text === "string" || typeof text === "number" ? text : String(text !== null && text !== void 0 ? text : "");
|
|
31
|
+
|
|
32
|
+
// 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
|
|
33
|
+
// (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
|
|
31
34
|
return /*#__PURE__*/React.createElement(TextEllipsis, {
|
|
32
|
-
width: Math.max(Number((_column$width = column.width) !== null && _column$width !== void 0 ? _column$width : 0), 300) === 300 ? undefined : Math.max(Number((_column$width2 = column.width) !== null && _column$width2 !== void 0 ? _column$width2 : 0), 300),
|
|
33
35
|
tooltipConfig: tooltipConfig,
|
|
34
36
|
children: displayValue,
|
|
35
37
|
ellipsisPosition: (_column$ellipsisPosit = column.ellipsisPosition) !== null && _column$ellipsisPosit !== void 0 ? _column$ellipsisPosit : tooltipConfig === null || tooltipConfig === void 0 ? void 0 : tooltipConfig.ellipsisPosition,
|
|
@@ -31,6 +31,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
|
|
|
31
31
|
bordered?: boolean;
|
|
32
32
|
rowSelection?: TableRowSelection<T>;
|
|
33
33
|
expandable?: TableProps<T>["expandable"];
|
|
34
|
+
tdPadding?: string;
|
|
34
35
|
}
|
|
35
36
|
declare const useTableColumns: <T extends AnyObject>(params: UseTableColumnsParams<T>) => {
|
|
36
37
|
_columns: ColumnsType<T>;
|
|
@@ -26,9 +26,30 @@ import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
26
26
|
var MEASURE_ALIGN_FONT_SIZE = 14;
|
|
27
27
|
// Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
|
|
28
28
|
var MEASURE_ALIGN_BUFFER_PX = 2;
|
|
29
|
-
//
|
|
29
|
+
// 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
|
|
30
30
|
var MEASURE_ALIGN_CELL_PADDING_PX = 16;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* 解析 tdPadding(CSS padding 简写)的左右内边距合计;
|
|
34
|
+
* 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
|
|
35
|
+
*/
|
|
36
|
+
var getCellHorizontalPadding = function getCellHorizontalPadding(tdPadding) {
|
|
37
|
+
if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
38
|
+
var parts = tdPadding.trim().split(/\s+/).map(function (v) {
|
|
39
|
+
return parseFloat(v);
|
|
40
|
+
});
|
|
41
|
+
if (!parts.length || parts.some(function (v) {
|
|
42
|
+
return Number.isNaN(v);
|
|
43
|
+
})) {
|
|
44
|
+
return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
|
|
48
|
+
if (parts.length === 1) return parts[0] * 2;
|
|
49
|
+
if (parts.length === 4) return parts[1] + parts[3];
|
|
50
|
+
return parts[1] * 2;
|
|
51
|
+
};
|
|
52
|
+
|
|
32
53
|
// 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
|
|
33
54
|
var getMeasureAlignWidth = function getMeasureAlignWidth(column, dataSource) {
|
|
34
55
|
if (!(dataSource !== null && dataSource !== void 0 && dataSource.length)) return 0;
|
|
@@ -64,7 +85,8 @@ var useTableColumns = function useTableColumns(params) {
|
|
|
64
85
|
cls = params.cls,
|
|
65
86
|
bordered = params.bordered,
|
|
66
87
|
rowSelection = params.rowSelection,
|
|
67
|
-
expandable = params.expandable
|
|
88
|
+
expandable = params.expandable,
|
|
89
|
+
tdPadding = params.tdPadding;
|
|
68
90
|
var _usePermissions = usePermissions(),
|
|
69
91
|
checkPermission = _usePermissions.checkPermission;
|
|
70
92
|
var _useState = useState(null),
|
|
@@ -132,7 +154,7 @@ var useTableColumns = function useTableColumns(params) {
|
|
|
132
154
|
if (item.measureAlign && typeof item.width === "number") {
|
|
133
155
|
var measured = getMeasureAlignWidth(item, dataSource);
|
|
134
156
|
if (measured > 0) {
|
|
135
|
-
item.width = Math.max(item.width, measured +
|
|
157
|
+
item.width = Math.max(item.width, measured + getCellHorizontalPadding(tdPadding));
|
|
136
158
|
}
|
|
137
159
|
}
|
|
138
160
|
return item;
|
|
@@ -144,7 +166,7 @@ var useTableColumns = function useTableColumns(params) {
|
|
|
144
166
|
});
|
|
145
167
|
});
|
|
146
168
|
return newColumns;
|
|
147
|
-
}, [autoWidth, columns, dataSource, checkPermission, scroll]);
|
|
169
|
+
}, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
|
|
148
170
|
|
|
149
171
|
// 渲染列标题
|
|
150
172
|
var renderColumnTitle = useCallback(function (column) {
|
|
@@ -126,7 +126,8 @@ var Table = function Table(props) {
|
|
|
126
126
|
cls: cls,
|
|
127
127
|
bordered: bordered,
|
|
128
128
|
rowSelection: rowSelection,
|
|
129
|
-
expandable: expandable
|
|
129
|
+
expandable: expandable,
|
|
130
|
+
tdPadding: tdPadding
|
|
130
131
|
}),
|
|
131
132
|
_columns = _useTableColumns._columns,
|
|
132
133
|
renderedColumns = _useTableColumns.renderedColumns,
|
|
@@ -46,6 +46,11 @@ var TextEllipsis = function TextEllipsis(_ref) {
|
|
|
46
46
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
47
47
|
truncatedText = _useState4[0],
|
|
48
48
|
setTruncatedText = _useState4[1];
|
|
49
|
+
// 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
|
|
50
|
+
var _useState5 = useState(0),
|
|
51
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
52
|
+
measuredWidth = _useState6[0],
|
|
53
|
+
setMeasuredWidth = _useState6[1];
|
|
49
54
|
useEffect(function () {
|
|
50
55
|
var element = textRef.current;
|
|
51
56
|
var measureElement = measureRef.current;
|
|
@@ -101,27 +106,40 @@ var TextEllipsis = function TextEllipsis(_ref) {
|
|
|
101
106
|
return result;
|
|
102
107
|
};
|
|
103
108
|
var textContent = getTextContent(children);
|
|
104
|
-
var
|
|
105
|
-
|
|
109
|
+
var update = function update() {
|
|
110
|
+
var isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
|
111
|
+
setIsOverflow(isTextOverflow);
|
|
112
|
+
setMeasuredWidth(element.clientWidth);
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
// 如果是省略前面且文本溢出,需要计算截断后的文本
|
|
115
|
+
if (ellipsisPosition === "start" && isTextOverflow && textContent) {
|
|
116
|
+
var maxWidth = element.clientWidth;
|
|
117
|
+
var truncated = calculateTruncatedText(textContent, maxWidth);
|
|
118
|
+
setTruncatedText(truncated);
|
|
119
|
+
} else {
|
|
120
|
+
setTruncatedText(children);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
update();
|
|
124
|
+
|
|
125
|
+
// 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
|
|
126
|
+
var resizeObserver = new ResizeObserver(update);
|
|
127
|
+
resizeObserver.observe(element);
|
|
128
|
+
return function () {
|
|
129
|
+
return resizeObserver.disconnect();
|
|
130
|
+
};
|
|
115
131
|
}, [children, ellipsisPosition, style]);
|
|
116
132
|
|
|
117
|
-
// 计算 Tooltip
|
|
133
|
+
// 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
|
|
134
|
+
// 实测不到再回退 defaultWidth
|
|
118
135
|
var getTooltipWidth = function getTooltipWidth() {
|
|
119
136
|
var _tooltipConfig$defaul;
|
|
120
137
|
var defaultWidth = (_tooltipConfig$defaul = tooltipConfig === null || tooltipConfig === void 0 ? void 0 : tooltipConfig.defaultWidth) !== null && _tooltipConfig$defaul !== void 0 ? _tooltipConfig$defaul : DEFAULT_TOOLTIP_WIDTH;
|
|
121
|
-
|
|
138
|
+
var dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
|
|
139
|
+
if (!width) return dynamicWidth;
|
|
122
140
|
if (typeof width === "number") return width;
|
|
123
|
-
if (typeof width === "string" && width.includes("%")) return
|
|
124
|
-
return parseInt(width) ||
|
|
141
|
+
if (typeof width === "string" && width.includes("%")) return dynamicWidth;
|
|
142
|
+
return parseInt(width) || dynamicWidth;
|
|
125
143
|
};
|
|
126
144
|
var tooltipWidth = getTooltipWidth();
|
|
127
145
|
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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, {
|
|
@@ -30,8 +30,10 @@ const useEllipsisTooltip = tooltipConfig => {
|
|
|
30
30
|
render: text => {
|
|
31
31
|
// 获取实际的显示值
|
|
32
32
|
const displayValue = typeof text === "string" || typeof text === "number" ? text : String(text ?? "");
|
|
33
|
+
|
|
34
|
+
// 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
|
|
35
|
+
// (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
|
|
33
36
|
return /*#__PURE__*/_react.default.createElement(_TextEllipsis.default, {
|
|
34
|
-
width: Math.max(Number(column.width ?? 0), 300) === 300 ? undefined : Math.max(Number(column.width ?? 0), 300),
|
|
35
37
|
tooltipConfig: tooltipConfig,
|
|
36
38
|
children: displayValue,
|
|
37
39
|
ellipsisPosition: column.ellipsisPosition ?? tooltipConfig?.ellipsisPosition,
|
|
@@ -31,6 +31,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
|
|
|
31
31
|
bordered?: boolean;
|
|
32
32
|
rowSelection?: TableRowSelection<T>;
|
|
33
33
|
expandable?: TableProps<T>["expandable"];
|
|
34
|
+
tdPadding?: string;
|
|
34
35
|
}
|
|
35
36
|
declare const useTableColumns: <T extends AnyObject>(params: UseTableColumnsParams<T>) => {
|
|
36
37
|
_columns: ColumnsType<T>;
|
|
@@ -19,9 +19,26 @@ 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
|
-
//
|
|
22
|
+
// 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
|
|
23
23
|
const MEASURE_ALIGN_CELL_PADDING_PX = 16;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* 解析 tdPadding(CSS padding 简写)的左右内边距合计;
|
|
27
|
+
* 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
|
|
28
|
+
*/
|
|
29
|
+
const getCellHorizontalPadding = tdPadding => {
|
|
30
|
+
if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
31
|
+
const parts = tdPadding.trim().split(/\s+/).map(v => parseFloat(v));
|
|
32
|
+
if (!parts.length || parts.some(v => Number.isNaN(v))) {
|
|
33
|
+
return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
|
|
37
|
+
if (parts.length === 1) return parts[0] * 2;
|
|
38
|
+
if (parts.length === 4) return parts[1] + parts[3];
|
|
39
|
+
return parts[1] * 2;
|
|
40
|
+
};
|
|
41
|
+
|
|
25
42
|
// 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
|
|
26
43
|
const getMeasureAlignWidth = (column, dataSource) => {
|
|
27
44
|
if (!dataSource?.length) return 0;
|
|
@@ -57,7 +74,8 @@ const useTableColumns = params => {
|
|
|
57
74
|
cls,
|
|
58
75
|
bordered,
|
|
59
76
|
rowSelection,
|
|
60
|
-
expandable
|
|
77
|
+
expandable,
|
|
78
|
+
tdPadding
|
|
61
79
|
} = params;
|
|
62
80
|
const {
|
|
63
81
|
checkPermission
|
|
@@ -117,7 +135,7 @@ const useTableColumns = params => {
|
|
|
117
135
|
if (item.measureAlign && typeof item.width === "number") {
|
|
118
136
|
const measured = getMeasureAlignWidth(item, dataSource);
|
|
119
137
|
if (measured > 0) {
|
|
120
|
-
item.width = Math.max(item.width, measured +
|
|
138
|
+
item.width = Math.max(item.width, measured + getCellHorizontalPadding(tdPadding));
|
|
121
139
|
}
|
|
122
140
|
}
|
|
123
141
|
return item;
|
|
@@ -132,7 +150,7 @@ const useTableColumns = params => {
|
|
|
132
150
|
};
|
|
133
151
|
});
|
|
134
152
|
return newColumns;
|
|
135
|
-
}, [autoWidth, columns, dataSource, checkPermission, scroll]);
|
|
153
|
+
}, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
|
|
136
154
|
|
|
137
155
|
// 渲染列标题
|
|
138
156
|
const renderColumnTitle = (0, _react.useCallback)(column => {
|
|
@@ -29,6 +29,8 @@ const TextEllipsis = ({
|
|
|
29
29
|
const measureRef = (0, _react.useRef)(null);
|
|
30
30
|
const [isOverflow, setIsOverflow] = (0, _react.useState)(false);
|
|
31
31
|
const [truncatedText, setTruncatedText] = (0, _react.useState)(children);
|
|
32
|
+
// 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
|
|
33
|
+
const [measuredWidth, setMeasuredWidth] = (0, _react.useState)(0);
|
|
32
34
|
(0, _react.useEffect)(() => {
|
|
33
35
|
const element = textRef.current;
|
|
34
36
|
const measureElement = measureRef.current;
|
|
@@ -84,26 +86,37 @@ const TextEllipsis = ({
|
|
|
84
86
|
return result;
|
|
85
87
|
};
|
|
86
88
|
const textContent = getTextContent(children);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
+
const update = () => {
|
|
90
|
+
const isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
|
91
|
+
setIsOverflow(isTextOverflow);
|
|
92
|
+
setMeasuredWidth(element.clientWidth);
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
// 如果是省略前面且文本溢出,需要计算截断后的文本
|
|
95
|
+
if (ellipsisPosition === "start" && isTextOverflow && textContent) {
|
|
96
|
+
const maxWidth = element.clientWidth;
|
|
97
|
+
const truncated = calculateTruncatedText(textContent, maxWidth);
|
|
98
|
+
setTruncatedText(truncated);
|
|
99
|
+
} else {
|
|
100
|
+
setTruncatedText(children);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
update();
|
|
104
|
+
|
|
105
|
+
// 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
|
|
106
|
+
const resizeObserver = new ResizeObserver(update);
|
|
107
|
+
resizeObserver.observe(element);
|
|
108
|
+
return () => resizeObserver.disconnect();
|
|
98
109
|
}, [children, ellipsisPosition, style]);
|
|
99
110
|
|
|
100
|
-
// 计算 Tooltip
|
|
111
|
+
// 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
|
|
112
|
+
// 实测不到再回退 defaultWidth
|
|
101
113
|
const getTooltipWidth = () => {
|
|
102
114
|
const defaultWidth = tooltipConfig?.defaultWidth ?? DEFAULT_TOOLTIP_WIDTH;
|
|
103
|
-
|
|
115
|
+
const dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
|
|
116
|
+
if (!width) return dynamicWidth;
|
|
104
117
|
if (typeof width === "number") return width;
|
|
105
|
-
if (typeof width === "string" && width.includes("%")) return
|
|
106
|
-
return parseInt(width) ||
|
|
118
|
+
if (typeof width === "string" && width.includes("%")) return dynamicWidth;
|
|
119
|
+
return parseInt(width) || dynamicWidth;
|
|
107
120
|
};
|
|
108
121
|
const tooltipWidth = getTooltipWidth();
|
|
109
122
|
|
package/package.json
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
{
|
|
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,
|
|
@@ -32,11 +32,9 @@ const useEllipsisTooltip = <T extends AnyObject>(
|
|
|
32
32
|
? text
|
|
33
33
|
: String(text ?? "");
|
|
34
34
|
|
|
35
|
+
// 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
|
|
36
|
+
// (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
|
|
35
37
|
return React.createElement(TextEllipsis, {
|
|
36
|
-
width:
|
|
37
|
-
Math.max(Number(column.width ?? 0), 300) === 300
|
|
38
|
-
? undefined
|
|
39
|
-
: Math.max(Number(column.width ?? 0), 300),
|
|
40
38
|
tooltipConfig: tooltipConfig,
|
|
41
39
|
children: displayValue,
|
|
42
40
|
ellipsisPosition:
|
|
@@ -23,9 +23,30 @@ import { PaginationProps } from "../_components/Pagination";
|
|
|
23
23
|
const MEASURE_ALIGN_FONT_SIZE = 14;
|
|
24
24
|
// Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
|
|
25
25
|
const MEASURE_ALIGN_BUFFER_PX = 2;
|
|
26
|
-
//
|
|
26
|
+
// 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
|
|
27
27
|
const MEASURE_ALIGN_CELL_PADDING_PX = 16;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 解析 tdPadding(CSS padding 简写)的左右内边距合计;
|
|
31
|
+
* 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
|
|
32
|
+
*/
|
|
33
|
+
const getCellHorizontalPadding = (tdPadding?: string): number => {
|
|
34
|
+
if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
35
|
+
|
|
36
|
+
const parts = tdPadding
|
|
37
|
+
.trim()
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.map((v) => parseFloat(v));
|
|
40
|
+
if (!parts.length || parts.some((v) => Number.isNaN(v))) {
|
|
41
|
+
return MEASURE_ALIGN_CELL_PADDING_PX;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
|
|
45
|
+
if (parts.length === 1) return parts[0] * 2;
|
|
46
|
+
if (parts.length === 4) return parts[1] + parts[3];
|
|
47
|
+
return parts[1] * 2;
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
// 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
|
|
30
51
|
const getMeasureAlignWidth = <T extends AnyObject>(
|
|
31
52
|
column: ColumnType<T>,
|
|
@@ -65,6 +86,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
|
|
|
65
86
|
bordered?: boolean;
|
|
66
87
|
rowSelection?: TableRowSelection<T>;
|
|
67
88
|
expandable?: TableProps<T>["expandable"];
|
|
89
|
+
tdPadding?: string;
|
|
68
90
|
}
|
|
69
91
|
|
|
70
92
|
const useTableColumns = <T extends AnyObject>(
|
|
@@ -94,6 +116,7 @@ const useTableColumns = <T extends AnyObject>(
|
|
|
94
116
|
bordered,
|
|
95
117
|
rowSelection,
|
|
96
118
|
expandable,
|
|
119
|
+
tdPadding,
|
|
97
120
|
} = params;
|
|
98
121
|
const { checkPermission } = usePermissions();
|
|
99
122
|
const [tableWidth, setTableWidth] = useState<number | null>(null);
|
|
@@ -171,7 +194,7 @@ const useTableColumns = <T extends AnyObject>(
|
|
|
171
194
|
if (measured > 0) {
|
|
172
195
|
item.width = Math.max(
|
|
173
196
|
item.width,
|
|
174
|
-
measured +
|
|
197
|
+
measured + getCellHorizontalPadding(tdPadding)
|
|
175
198
|
);
|
|
176
199
|
}
|
|
177
200
|
}
|
|
@@ -190,7 +213,7 @@ const useTableColumns = <T extends AnyObject>(
|
|
|
190
213
|
});
|
|
191
214
|
|
|
192
215
|
return newColumns;
|
|
193
|
-
}, [autoWidth, columns, dataSource, checkPermission, scroll]);
|
|
216
|
+
}, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
|
|
194
217
|
|
|
195
218
|
// 渲染列标题
|
|
196
219
|
const renderColumnTitle = useCallback(
|
|
@@ -82,6 +82,8 @@ export default () => (
|
|
|
82
82
|
| measureAlign | 开启定宽右对齐(配合列 `align: "center"` 使用) | `boolean` | - |
|
|
83
83
|
| measureText | 测量宽度所用文本;当单元格由 `render` 产出复合/JSX 内容时,用它提供与展示一致的文本(默认取 `dataIndex` 原始值) | `(record) => string` | - |
|
|
84
84
|
|
|
85
|
+
- 列宽自适应:声明的 `width` 只作下限——实际列宽会按「本列最宽内容块的测量宽 + 单元格左右内边距」自动抬升,出现超长值(如 `+3357.52%`)时内容不会被声明宽度截断。
|
|
86
|
+
|
|
85
87
|
> 注意:不适用于 `render` 返回 `{ children, rowSpan }` 这类合并单元格场景。
|
|
86
88
|
|
|
87
89
|
> 另导出 `TableDrag`,用于行拖拽排序场景。
|
|
@@ -37,7 +37,7 @@ export default () => (
|
|
|
37
37
|
| --- | --- | --- | --- |
|
|
38
38
|
| children | 要显示的文本内容 | `ReactNode` | - |
|
|
39
39
|
| width | 容器宽度,用于计算 Tooltip 宽度 | `number \| string` | - |
|
|
40
|
-
| tooltipConfig | Tooltip 配置(可附加 `defaultWidth
|
|
40
|
+
| tooltipConfig | Tooltip 配置(可附加 `defaultWidth`:容器实际宽度实测不到时的兜底宽) | `Omit<TooltipProps, 'title' \| 'children'> & { defaultWidth?: number }` | - |
|
|
41
41
|
| disabled | 是否禁用 Tooltip(即使溢出也不显示) | `boolean` | `false` |
|
|
42
42
|
| ellipsisPosition | 省略号位置,`start` 省略前面,`end` 省略后面 | `'start' \| 'end'` | `'end'` |
|
|
43
43
|
| style | 文本自定义样式 | `React.CSSProperties` | - |
|
|
@@ -45,6 +45,8 @@ const TextEllipsis: React.FC<TextEllipsisProps> = ({
|
|
|
45
45
|
const measureRef = useRef<HTMLSpanElement>(null);
|
|
46
46
|
const [isOverflow, setIsOverflow] = useState(false);
|
|
47
47
|
const [truncatedText, setTruncatedText] = useState<ReactNode>(children);
|
|
48
|
+
// 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
|
|
49
|
+
const [measuredWidth, setMeasuredWidth] = useState(0);
|
|
48
50
|
|
|
49
51
|
useEffect(() => {
|
|
50
52
|
const element = textRef.current;
|
|
@@ -105,29 +107,42 @@ const TextEllipsis: React.FC<TextEllipsisProps> = ({
|
|
|
105
107
|
};
|
|
106
108
|
|
|
107
109
|
const textContent = getTextContent(children);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
|
|
111
|
+
const update = () => {
|
|
112
|
+
const isTextOverflow =
|
|
113
|
+
element.scrollWidth > element.clientWidth ||
|
|
114
|
+
element.scrollHeight > element.clientHeight;
|
|
115
|
+
|
|
116
|
+
setIsOverflow(isTextOverflow);
|
|
117
|
+
setMeasuredWidth(element.clientWidth);
|
|
118
|
+
|
|
119
|
+
// 如果是省略前面且文本溢出,需要计算截断后的文本
|
|
120
|
+
if (ellipsisPosition === "start" && isTextOverflow && textContent) {
|
|
121
|
+
const maxWidth = element.clientWidth;
|
|
122
|
+
const truncated = calculateTruncatedText(textContent, maxWidth);
|
|
123
|
+
setTruncatedText(truncated);
|
|
124
|
+
} else {
|
|
125
|
+
setTruncatedText(children);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
update();
|
|
130
|
+
|
|
131
|
+
// 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
|
|
132
|
+
const resizeObserver = new ResizeObserver(update);
|
|
133
|
+
resizeObserver.observe(element);
|
|
134
|
+
return () => resizeObserver.disconnect();
|
|
122
135
|
}, [children, ellipsisPosition, style]);
|
|
123
136
|
|
|
124
|
-
// 计算 Tooltip
|
|
137
|
+
// 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
|
|
138
|
+
// 实测不到再回退 defaultWidth
|
|
125
139
|
const getTooltipWidth = () => {
|
|
126
140
|
const defaultWidth = tooltipConfig?.defaultWidth ?? DEFAULT_TOOLTIP_WIDTH;
|
|
127
|
-
|
|
141
|
+
const dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
|
|
142
|
+
if (!width) return dynamicWidth;
|
|
128
143
|
if (typeof width === "number") return width;
|
|
129
|
-
if (typeof width === "string" && width.includes("%")) return
|
|
130
|
-
return parseInt(width) ||
|
|
144
|
+
if (typeof width === "string" && width.includes("%")) return dynamicWidth;
|
|
145
|
+
return parseInt(width) || dynamicWidth;
|
|
131
146
|
};
|
|
132
147
|
|
|
133
148
|
const tooltipWidth = getTooltipWidth();
|