n20-common-lib 3.3.16 → 3.3.18
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/package.json +1 -1
- package/src/assets/css/v3/table.scss +2 -0
- package/src/components/Table/index.vue +4 -1
- package/src/components/TableOperateColumn/index.vue +129 -3
- package/src/components/TablePro/index.vue +7 -0
- package/src/components/TableProOperateColumn/index.vue +135 -1
- package/src/components/v3/TablePro/index.vue +138 -2
- package/style/index.css +1 -1
- package/theme/blue.css +1 -1
- package/theme/cctcRed.css +1 -1
- package/theme/green.css +1 -1
- package/theme/lightBlue.css +1 -1
- package/theme/orange.css +1 -1
- package/theme/purple.css +1 -1
- package/theme/red.css +1 -1
- package/theme/yellow.css +1 -1
package/package.json
CHANGED
|
@@ -265,6 +265,7 @@
|
|
|
265
265
|
.table-hover-btns {
|
|
266
266
|
position: absolute;
|
|
267
267
|
right: 0;
|
|
268
|
+
width: max-content;
|
|
268
269
|
display: flex;
|
|
269
270
|
align-items: center;
|
|
270
271
|
justify-content: flex-end;
|
|
@@ -274,6 +275,7 @@
|
|
|
274
275
|
|
|
275
276
|
.hover-btns-wrapper {
|
|
276
277
|
display: flex;
|
|
278
|
+
width: max-content;
|
|
277
279
|
align-items: center;
|
|
278
280
|
gap: 4px;
|
|
279
281
|
white-space: nowrap;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-table-column v-if="!bigData" v-bind="
|
|
2
|
+
<el-table-column v-if="!bigData" v-bind="resolvedColumnAttrs">
|
|
3
3
|
<OperateBtns slot-scope="{ row, $index }" :btn-list="btnList" :row="row" @command="(c) => $emit(c, row, $index)" />
|
|
4
4
|
</el-table-column>
|
|
5
|
-
<ux-table-column v-else :field="$attrs.prop" :title="$attrs.label" v-bind="
|
|
5
|
+
<ux-table-column v-else :field="$attrs.prop" :title="$attrs.label" v-bind="resolvedColumnAttrs">
|
|
6
6
|
<OperateBtns slot-scope="{ row, $index }" :btn-list="btnList" :row="row" @command="(c) => $emit(c, row, $index)" />
|
|
7
7
|
</ux-table-column>
|
|
8
8
|
</template>
|
|
@@ -10,17 +10,143 @@
|
|
|
10
10
|
<script>
|
|
11
11
|
// import { $lc } from '../../utils/i18n/index'
|
|
12
12
|
import OperateBtns from './OperateBtns.vue'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_OPERATE_COLUMN_MIN_WIDTH = 80
|
|
15
|
+
|
|
13
16
|
export default {
|
|
14
17
|
name: 'TableOperateColumn',
|
|
15
18
|
components: {
|
|
16
19
|
OperateBtns
|
|
17
20
|
},
|
|
18
|
-
inject: {
|
|
21
|
+
inject: {
|
|
22
|
+
bigData: { default: false },
|
|
23
|
+
n20TableContext: { default: null }
|
|
24
|
+
},
|
|
19
25
|
props: {
|
|
20
26
|
btnList: {
|
|
21
27
|
type: Array,
|
|
22
28
|
default: () => []
|
|
23
29
|
}
|
|
30
|
+
},
|
|
31
|
+
computed: {
|
|
32
|
+
resolvedColumnAttrs() {
|
|
33
|
+
const attrs = { ...this.$attrs }
|
|
34
|
+
const hasWidth =
|
|
35
|
+
Object.prototype.hasOwnProperty.call(attrs, 'width') &&
|
|
36
|
+
attrs.width !== null &&
|
|
37
|
+
attrs.width !== undefined &&
|
|
38
|
+
attrs.width !== ''
|
|
39
|
+
if (hasWidth) {
|
|
40
|
+
return attrs
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const minWidthKey = ['minWidth', 'min-width'].find((key) => {
|
|
44
|
+
const value = attrs[key]
|
|
45
|
+
return Object.prototype.hasOwnProperty.call(attrs, key) && value !== null && value !== undefined && value !== ''
|
|
46
|
+
})
|
|
47
|
+
const calculatedWidth = this.calcOperateColumnWidth()
|
|
48
|
+
if (minWidthKey) {
|
|
49
|
+
const minWidth = this.parsePixelWidth(attrs[minWidthKey])
|
|
50
|
+
if (minWidth !== null) {
|
|
51
|
+
attrs[minWidthKey] = Math.max(calculatedWidth, minWidth)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// 避免 Element/UX Table 将大量剩余空间分配给操作列。
|
|
55
|
+
attrs.width = calculatedWidth
|
|
56
|
+
}
|
|
57
|
+
return attrs
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
methods: {
|
|
61
|
+
measureOperateText(text) {
|
|
62
|
+
const normalizedText = text === undefined || text === null ? '' : String(text)
|
|
63
|
+
if (!normalizedText) return 0
|
|
64
|
+
|
|
65
|
+
const fallbackWidth = Array.from(normalizedText).reduce(
|
|
66
|
+
(width, char) => width + (/[⺀-鿿]/.test(char) ? 14 : 8),
|
|
67
|
+
0
|
|
68
|
+
)
|
|
69
|
+
if (typeof document === 'undefined') return fallbackWidth
|
|
70
|
+
|
|
71
|
+
const canvas = document.createElement('canvas')
|
|
72
|
+
const context = canvas.getContext('2d')
|
|
73
|
+
if (!context) return fallbackWidth
|
|
74
|
+
|
|
75
|
+
context.font = '14px PingFang SC, Microsoft YaHei, sans-serif'
|
|
76
|
+
return Math.max(context.measureText(normalizedText).width, fallbackWidth)
|
|
77
|
+
},
|
|
78
|
+
parsePixelWidth(value) {
|
|
79
|
+
if (typeof value === 'number') {
|
|
80
|
+
return Number.isFinite(value) && value >= 0 ? value : null
|
|
81
|
+
}
|
|
82
|
+
if (typeof value !== 'string') return null
|
|
83
|
+
|
|
84
|
+
const normalizedValue = value.trim()
|
|
85
|
+
if (!/^\d+(\.\d+)?(px)?$/.test(normalizedValue)) return null
|
|
86
|
+
const parsedValue = Number.parseFloat(normalizedValue)
|
|
87
|
+
return Number.isFinite(parsedValue) ? parsedValue : null
|
|
88
|
+
},
|
|
89
|
+
calcOperateColumnWidth() {
|
|
90
|
+
const CELL_HORIZONTAL_PADDING = this.bigData ? 20 : 16
|
|
91
|
+
// OperateBtns 使用行内元素,除 6px margin 外还会产生约 4px 的模板空白。
|
|
92
|
+
const BUTTON_GAP = 10
|
|
93
|
+
const CHILDREN_ARROW_WIDTH = 20
|
|
94
|
+
const MORE_BUTTON_WIDTH = 16
|
|
95
|
+
const buttons = (this.btnList || []).filter((button) => button && button.isHas !== false)
|
|
96
|
+
const getButtonWidth = (button) => {
|
|
97
|
+
let width = this.measureOperateText(button.label)
|
|
98
|
+
if (button.children && button.children.length > 0) {
|
|
99
|
+
width += CHILDREN_ARROW_WIDTH
|
|
100
|
+
}
|
|
101
|
+
return width
|
|
102
|
+
}
|
|
103
|
+
const buttonWidths = buttons.map(getButtonWidth)
|
|
104
|
+
|
|
105
|
+
if (buttonWidths.length === 0) {
|
|
106
|
+
return DEFAULT_OPERATE_COLUMN_MIN_WIDTH
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const calcLayoutWidth = (inlineWidths, hasMoreButton = false) => {
|
|
110
|
+
const itemCount = inlineWidths.length + (hasMoreButton ? 1 : 0)
|
|
111
|
+
const contentWidth = inlineWidths.reduce((total, width) => total + width, 0)
|
|
112
|
+
const moreButtonWidth = hasMoreButton ? MORE_BUTTON_WIDTH : 0
|
|
113
|
+
const gapWidth = Math.max(itemCount - 1, 0) * BUTTON_GAP
|
|
114
|
+
return contentWidth + moreButtonWidth + gapWidth + CELL_HORIZONTAL_PADDING
|
|
115
|
+
}
|
|
116
|
+
const rows = this.n20TableContext?.getData?.() || []
|
|
117
|
+
if (rows.length > 0) {
|
|
118
|
+
const maxRowWidth = rows.reduce((maxWidth, row) => {
|
|
119
|
+
const visibleButtonWidths = buttons
|
|
120
|
+
.filter((button) => this.hasButton(button.isHas, row))
|
|
121
|
+
.map(getButtonWidth)
|
|
122
|
+
const rowWidth = visibleButtonWidths.length > 3
|
|
123
|
+
? calcLayoutWidth(visibleButtonWidths.slice(0, 2), true)
|
|
124
|
+
: calcLayoutWidth(visibleButtonWidths)
|
|
125
|
+
return Math.max(maxWidth, rowWidth)
|
|
126
|
+
}, 0)
|
|
127
|
+
return Math.max(DEFAULT_OPERATE_COLUMN_MIN_WIDTH, Math.ceil(maxRowWidth))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sortedButtonWidths = [...buttonWidths].sort((a, b) => b - a)
|
|
131
|
+
const directButtonsWidth = calcLayoutWidth(sortedButtonWidths.slice(0, 3))
|
|
132
|
+
const collapsedButtonsWidth =
|
|
133
|
+
sortedButtonWidths.length > 3 ? calcLayoutWidth(sortedButtonWidths.slice(0, 2), true) : 0
|
|
134
|
+
|
|
135
|
+
return Math.max(
|
|
136
|
+
DEFAULT_OPERATE_COLUMN_MIN_WIDTH,
|
|
137
|
+
Math.ceil(directButtonsWidth),
|
|
138
|
+
Math.ceil(collapsedButtonsWidth)
|
|
139
|
+
)
|
|
140
|
+
},
|
|
141
|
+
hasButton(isHas, row) {
|
|
142
|
+
if (isHas === undefined || isHas === null) return true
|
|
143
|
+
if (typeof isHas === 'boolean') return isHas
|
|
144
|
+
if (typeof isHas === 'function') return isHas(row)
|
|
145
|
+
if (typeof isHas === 'string' || Array.isArray(isHas)) {
|
|
146
|
+
return typeof this.$has === 'function' ? this.$has(isHas) : true
|
|
147
|
+
}
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
24
150
|
}
|
|
25
151
|
}
|
|
26
152
|
</script>
|
|
@@ -1,19 +1,153 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<vxe-column :field="$attrs.prop" :title="$attrs.label" v-bind="
|
|
2
|
+
<vxe-column :field="$attrs.prop" :title="$attrs.label" v-bind="resolvedColumnAttrs">
|
|
3
3
|
<OperateBtns slot-scope="{ row, $index }" :btn-list="btnList" :row="row" @command="(c) => $emit(c, row, $index)" />
|
|
4
4
|
</vxe-column>
|
|
5
5
|
</template>
|
|
6
6
|
<script>
|
|
7
7
|
import OperateBtns from './OperateBtns.vue'
|
|
8
8
|
|
|
9
|
+
const DEFAULT_OPERATE_COLUMN_WIDTH = 180
|
|
10
|
+
|
|
9
11
|
export default {
|
|
10
12
|
name: 'TableProOperateColumn',
|
|
11
13
|
components: { OperateBtns },
|
|
14
|
+
inject: {
|
|
15
|
+
n20TableProContext: {
|
|
16
|
+
default: null
|
|
17
|
+
}
|
|
18
|
+
},
|
|
12
19
|
props: {
|
|
13
20
|
btnList: {
|
|
14
21
|
type: Array,
|
|
15
22
|
default: () => []
|
|
16
23
|
}
|
|
24
|
+
},
|
|
25
|
+
computed: {
|
|
26
|
+
resolvedColumnAttrs() {
|
|
27
|
+
const attrs = { ...this.$attrs }
|
|
28
|
+
const hasWidth =
|
|
29
|
+
Object.prototype.hasOwnProperty.call(attrs, 'width') &&
|
|
30
|
+
attrs.width !== null &&
|
|
31
|
+
attrs.width !== undefined &&
|
|
32
|
+
attrs.width !== ''
|
|
33
|
+
if (hasWidth) {
|
|
34
|
+
return attrs
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const minWidthKey = ['minWidth', 'min-width'].find((key) => {
|
|
38
|
+
const value = attrs[key]
|
|
39
|
+
return Object.prototype.hasOwnProperty.call(attrs, key) && value !== null && value !== undefined && value !== ''
|
|
40
|
+
})
|
|
41
|
+
const calculatedWidth = this.calcOperateColumnWidth()
|
|
42
|
+
if (minWidthKey) {
|
|
43
|
+
const minWidth = this.parsePixelWidth(attrs[minWidthKey])
|
|
44
|
+
if (minWidth !== null) {
|
|
45
|
+
attrs[minWidthKey] = Math.max(calculatedWidth, minWidth)
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
attrs.width = calculatedWidth
|
|
49
|
+
}
|
|
50
|
+
return attrs
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
methods: {
|
|
54
|
+
measureOperateText(text) {
|
|
55
|
+
const normalizedText = text === undefined || text === null ? '' : String(text)
|
|
56
|
+
if (!normalizedText) return 0
|
|
57
|
+
|
|
58
|
+
const fallbackWidth = Array.from(normalizedText).reduce(
|
|
59
|
+
(width, char) => width + (/[⺀-鿿]/.test(char) ? 14 : 8),
|
|
60
|
+
0
|
|
61
|
+
)
|
|
62
|
+
if (typeof document === 'undefined') return fallbackWidth
|
|
63
|
+
|
|
64
|
+
const canvas = document.createElement('canvas')
|
|
65
|
+
const context = canvas.getContext('2d')
|
|
66
|
+
if (!context) return fallbackWidth
|
|
67
|
+
|
|
68
|
+
context.font = '14px PingFang SC, Microsoft YaHei, sans-serif'
|
|
69
|
+
return Math.max(context.measureText(normalizedText).width, fallbackWidth)
|
|
70
|
+
},
|
|
71
|
+
parsePixelWidth(value) {
|
|
72
|
+
if (typeof value === 'number') {
|
|
73
|
+
return Number.isFinite(value) && value >= 0 ? value : null
|
|
74
|
+
}
|
|
75
|
+
if (typeof value !== 'string') return null
|
|
76
|
+
|
|
77
|
+
const normalizedValue = value.trim()
|
|
78
|
+
if (!/^\d+(\.\d+)?(px)?$/.test(normalizedValue)) return null
|
|
79
|
+
const parsedValue = Number.parseFloat(normalizedValue)
|
|
80
|
+
return Number.isFinite(parsedValue) ? parsedValue : null
|
|
81
|
+
},
|
|
82
|
+
calcOperateColumnWidth() {
|
|
83
|
+
const CELL_HORIZONTAL_PADDING = 14
|
|
84
|
+
const BUTTON_GAP = 6
|
|
85
|
+
const ICON_AND_GAP_WIDTH = 23
|
|
86
|
+
const CHILDREN_ARROW_WIDTH = 20
|
|
87
|
+
const ICON_ONLY_BUTTON_WIDTH = 28
|
|
88
|
+
const MORE_BUTTON_WIDTH = 29
|
|
89
|
+
const buttons = (this.btnList || []).filter((button) => button && button.isHas !== false)
|
|
90
|
+
const getButtonWidth = (button) => {
|
|
91
|
+
if (button.iconOnly && button.icon) {
|
|
92
|
+
return ICON_ONLY_BUTTON_WIDTH
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let width = this.measureOperateText(button.label)
|
|
96
|
+
if (button.icon) {
|
|
97
|
+
width += ICON_AND_GAP_WIDTH
|
|
98
|
+
}
|
|
99
|
+
if (button.children && button.children.length > 0) {
|
|
100
|
+
width += CHILDREN_ARROW_WIDTH
|
|
101
|
+
}
|
|
102
|
+
return width
|
|
103
|
+
}
|
|
104
|
+
const buttonWidths = buttons.map(getButtonWidth)
|
|
105
|
+
|
|
106
|
+
if (buttonWidths.length === 0) {
|
|
107
|
+
return DEFAULT_OPERATE_COLUMN_WIDTH
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const calcLayoutWidth = (inlineWidths, hasMoreButton = false) => {
|
|
111
|
+
const itemCount = inlineWidths.length + (hasMoreButton ? 1 : 0)
|
|
112
|
+
const contentWidth = inlineWidths.reduce((total, width) => total + width, 0)
|
|
113
|
+
const moreButtonWidth = hasMoreButton ? MORE_BUTTON_WIDTH : 0
|
|
114
|
+
const gapWidth = Math.max(itemCount - 1, 0) * BUTTON_GAP
|
|
115
|
+
return contentWidth + moreButtonWidth + gapWidth + CELL_HORIZONTAL_PADDING
|
|
116
|
+
}
|
|
117
|
+
const rows = this.n20TableProContext?.getData?.() || []
|
|
118
|
+
if (rows.length > 0) {
|
|
119
|
+
const maxRowWidth = rows.reduce((maxWidth, row) => {
|
|
120
|
+
const visibleButtonWidths = buttons
|
|
121
|
+
.filter((button) => this.hasButton(button.isHas, row))
|
|
122
|
+
.map(getButtonWidth)
|
|
123
|
+
const rowWidth = visibleButtonWidths.length > 3
|
|
124
|
+
? calcLayoutWidth(visibleButtonWidths.slice(0, 2), true)
|
|
125
|
+
: calcLayoutWidth(visibleButtonWidths)
|
|
126
|
+
return Math.max(maxWidth, rowWidth)
|
|
127
|
+
}, 0)
|
|
128
|
+
return Math.max(DEFAULT_OPERATE_COLUMN_WIDTH, Math.ceil(maxRowWidth))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sortedButtonWidths = [...buttonWidths].sort((a, b) => b - a)
|
|
132
|
+
const directButtonsWidth = calcLayoutWidth(sortedButtonWidths.slice(0, 3))
|
|
133
|
+
const collapsedButtonsWidth =
|
|
134
|
+
sortedButtonWidths.length > 3 ? calcLayoutWidth(sortedButtonWidths.slice(0, 2), true) : 0
|
|
135
|
+
|
|
136
|
+
return Math.max(
|
|
137
|
+
DEFAULT_OPERATE_COLUMN_WIDTH,
|
|
138
|
+
Math.ceil(directButtonsWidth),
|
|
139
|
+
Math.ceil(collapsedButtonsWidth)
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
hasButton(isHas, row) {
|
|
143
|
+
if (isHas === undefined || isHas === null) return true
|
|
144
|
+
if (typeof isHas === 'boolean') return isHas
|
|
145
|
+
if (typeof isHas === 'function') return isHas(row)
|
|
146
|
+
if (typeof isHas === 'string' || Array.isArray(isHas)) {
|
|
147
|
+
return typeof this.$has === 'function' ? this.$has(isHas) : true
|
|
148
|
+
}
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
17
151
|
}
|
|
18
152
|
}
|
|
19
153
|
</script>
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
v-if="isOperateFixed && btnList && btnList.length > 0"
|
|
288
288
|
:title="$lc('操作')"
|
|
289
289
|
field="__operate__"
|
|
290
|
-
:width="
|
|
290
|
+
:width="computedOperateColumnWidth"
|
|
291
291
|
fixed="right"
|
|
292
292
|
align="center"
|
|
293
293
|
>
|
|
@@ -452,8 +452,10 @@ import empty from '../../Empty'
|
|
|
452
452
|
import tableShowColumn from '../../ShowColumn/index.vue'
|
|
453
453
|
import { saveTransform } from '../../ShowColumn/index.vue'
|
|
454
454
|
import tableOperate from '../../TableOperate/index.vue'
|
|
455
|
-
import OperateBtns from '../../TableProOperateColumn/OperateBtns.vue'
|
|
456
455
|
import { isTableCellInteractiveTarget } from '../../TablePro/cellClick'
|
|
456
|
+
import OperateBtns from '../../TableProOperateColumn/OperateBtns.vue'
|
|
457
|
+
|
|
458
|
+
const DEFAULT_OPERATE_COLUMN_WIDTH = 180
|
|
457
459
|
|
|
458
460
|
const renderer = {
|
|
459
461
|
name: 'renderer',
|
|
@@ -627,10 +629,16 @@ export default {
|
|
|
627
629
|
type: Function,
|
|
628
630
|
default: null
|
|
629
631
|
},
|
|
632
|
+
// 固定操作列宽度;不传时按各行实际可见按钮的最大宽度自动计算
|
|
630
633
|
operateColumnWidth: {
|
|
631
634
|
type: Number,
|
|
632
635
|
default: 180
|
|
633
636
|
},
|
|
637
|
+
// 自动计算操作列宽度的下限;显式传入 operateColumnWidth 时不生效
|
|
638
|
+
operateColumnMinWidth: {
|
|
639
|
+
type: [Number, String],
|
|
640
|
+
default: 180
|
|
641
|
+
},
|
|
634
642
|
// 异步获取操作按钮函数,(row) => Promise<Array<BtnItem>>
|
|
635
643
|
getOperateBtns: {
|
|
636
644
|
type: Function,
|
|
@@ -722,6 +730,19 @@ export default {
|
|
|
722
730
|
const activeCount = [this.showColumn, this.showSetsize, hasExpand].filter(Boolean).length
|
|
723
731
|
return activeCount === 3 ? 112 : 76
|
|
724
732
|
},
|
|
733
|
+
// 操作列宽度:显式传入时优先,否则按按钮内容自动计算
|
|
734
|
+
computedOperateColumnWidth() {
|
|
735
|
+
const hasCustomWidth = Object.prototype.hasOwnProperty.call(this.$options.propsData || {}, 'operateColumnWidth')
|
|
736
|
+
const hasCustomWidthValue =
|
|
737
|
+
this.operateColumnWidth !== null && this.operateColumnWidth !== undefined && this.operateColumnWidth !== ''
|
|
738
|
+
const customWidth = hasCustomWidthValue ? Number(this.operateColumnWidth) : NaN
|
|
739
|
+
if (hasCustomWidth && Number.isFinite(customWidth) && customWidth >= 0) {
|
|
740
|
+
return customWidth
|
|
741
|
+
}
|
|
742
|
+
const calculatedWidth = this.calcOperateColumnWidth()
|
|
743
|
+
const minWidth = this.parseOperateColumnPixelWidth(this.operateColumnMinWidth)
|
|
744
|
+
return minWidth === null ? calculatedWidth : Math.max(calculatedWidth, minWidth)
|
|
745
|
+
},
|
|
725
746
|
// 骨架屏定位样式,根据表头实际高度动态计算
|
|
726
747
|
skeletonStyle() {
|
|
727
748
|
return {
|
|
@@ -1385,6 +1406,121 @@ export default {
|
|
|
1385
1406
|
return checkedSet.has(args.row) ? 'v3-n20-table-pro__row-checked' : ''
|
|
1386
1407
|
},
|
|
1387
1408
|
|
|
1409
|
+
// 测量操作按钮文案宽度,优先使用当前页面字体,服务端或 canvas 不可用时使用保守估算
|
|
1410
|
+
measureOperateText(text) {
|
|
1411
|
+
const normalizedText = text === undefined || text === null ? '' : String(text)
|
|
1412
|
+
if (!normalizedText) return 0
|
|
1413
|
+
|
|
1414
|
+
const fallbackWidth = Array.from(normalizedText).reduce(
|
|
1415
|
+
(width, char) => width + (/[⺀-鿿]/.test(char) ? 14 : 8),
|
|
1416
|
+
0
|
|
1417
|
+
)
|
|
1418
|
+
if (typeof document === 'undefined') return fallbackWidth
|
|
1419
|
+
|
|
1420
|
+
const canvas = document.createElement('canvas')
|
|
1421
|
+
const context = canvas.getContext('2d')
|
|
1422
|
+
if (!context) return fallbackWidth
|
|
1423
|
+
|
|
1424
|
+
const tableEl = this.$refs.vxeTable?.$el
|
|
1425
|
+
const textEl = tableEl?.querySelector('.table-operate-btns__wrapper .el-link')
|
|
1426
|
+
if (textEl && typeof window !== 'undefined' && window.getComputedStyle) {
|
|
1427
|
+
const style = window.getComputedStyle(textEl)
|
|
1428
|
+
context.font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily}`
|
|
1429
|
+
} else {
|
|
1430
|
+
context.font = '14px PingFang SC, Microsoft YaHei, sans-serif'
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
return Math.max(context.measureText(normalizedText).width, fallbackWidth)
|
|
1434
|
+
},
|
|
1435
|
+
|
|
1436
|
+
parseOperateColumnPixelWidth(value) {
|
|
1437
|
+
if (typeof value === 'number') {
|
|
1438
|
+
return Number.isFinite(value) && value >= 0 ? value : null
|
|
1439
|
+
}
|
|
1440
|
+
if (typeof value !== 'string') return null
|
|
1441
|
+
|
|
1442
|
+
const normalizedValue = value.trim()
|
|
1443
|
+
if (!/^\d+(\.\d+)?(px)?$/.test(normalizedValue)) return null
|
|
1444
|
+
const parsedValue = Number.parseFloat(normalizedValue)
|
|
1445
|
+
return Number.isFinite(parsedValue) ? parsedValue : null
|
|
1446
|
+
},
|
|
1447
|
+
|
|
1448
|
+
// 固定操作列按当前数据中每行实际可见的按钮布局取最大宽度
|
|
1449
|
+
calcOperateColumnWidth() {
|
|
1450
|
+
const CELL_HORIZONTAL_PADDING = 24
|
|
1451
|
+
const BUTTON_GAP = 12
|
|
1452
|
+
const ICON_AND_GAP_WIDTH = 20
|
|
1453
|
+
const CHILDREN_ARROW_WIDTH = 14
|
|
1454
|
+
const ICON_ONLY_BUTTON_WIDTH = 28
|
|
1455
|
+
const MORE_BUTTON_WIDTH = 40
|
|
1456
|
+
const buttons = (this.btnList || []).filter(Boolean)
|
|
1457
|
+
const buttonWidthMap = new Map()
|
|
1458
|
+
buttons.forEach((button) => {
|
|
1459
|
+
if (button.iconOnly && button.icon) {
|
|
1460
|
+
buttonWidthMap.set(button, ICON_ONLY_BUTTON_WIDTH)
|
|
1461
|
+
return
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
let width = this.measureOperateText(button.label)
|
|
1465
|
+
if (button.icon) {
|
|
1466
|
+
width += ICON_AND_GAP_WIDTH
|
|
1467
|
+
}
|
|
1468
|
+
if (button.children && button.children.length > 0) {
|
|
1469
|
+
width += CHILDREN_ARROW_WIDTH
|
|
1470
|
+
}
|
|
1471
|
+
buttonWidthMap.set(button, width)
|
|
1472
|
+
})
|
|
1473
|
+
|
|
1474
|
+
if (buttons.length === 0) {
|
|
1475
|
+
return DEFAULT_OPERATE_COLUMN_WIDTH
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
const calcLayoutWidth = (inlineWidths, hasMoreButton = false) => {
|
|
1479
|
+
const itemCount = inlineWidths.length + (hasMoreButton ? 1 : 0)
|
|
1480
|
+
const buttonWidth = inlineWidths.reduce((total, width) => total + width, 0)
|
|
1481
|
+
const moreButtonWidth = hasMoreButton ? MORE_BUTTON_WIDTH : 0
|
|
1482
|
+
const gapWidth = Math.max(itemCount - 1, 0) * BUTTON_GAP
|
|
1483
|
+
return buttonWidth + moreButtonWidth + gapWidth + CELL_HORIZONTAL_PADDING
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const calcButtonsWidth = (visibleButtons) => {
|
|
1487
|
+
const hasMoreButton = visibleButtons.length > 3 || !!this.getOperateBtns
|
|
1488
|
+
const inlineButtons = visibleButtons.length > 3 ? visibleButtons.slice(0, 2) : visibleButtons
|
|
1489
|
+
return calcLayoutWidth(
|
|
1490
|
+
inlineButtons.map((button) => buttonWidthMap.get(button) || 0),
|
|
1491
|
+
hasMoreButton
|
|
1492
|
+
)
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// 直接根据 data 计算所有行,可覆盖 isHas(row) 导致的 1/2/3/更多按钮差异,且不受虚拟滚动影响。
|
|
1496
|
+
const rows = Array.isArray(this.data) ? this.data : []
|
|
1497
|
+
const maxRowWidth = rows.reduce((maxWidth, row) => {
|
|
1498
|
+
const visibleButtons = buttons.filter((button) => this.hasBtn(button.isHas, row))
|
|
1499
|
+
return Math.max(maxWidth, calcButtonsWidth(visibleButtons))
|
|
1500
|
+
}, 0)
|
|
1501
|
+
|
|
1502
|
+
if (maxRowWidth > 0) {
|
|
1503
|
+
return Math.max(DEFAULT_OPERATE_COLUMN_WIDTH, Math.ceil(maxRowWidth))
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// 数据尚未加载时按所有可能布局保守估算,避免首次渲染回落到过窄列宽。
|
|
1507
|
+
const sortedButtonWidths = buttons
|
|
1508
|
+
.filter((button) => button.isHas !== false)
|
|
1509
|
+
.map((button) => buttonWidthMap.get(button) || 0)
|
|
1510
|
+
.sort((a, b) => b - a)
|
|
1511
|
+
const directButtonsWidth = calcLayoutWidth(sortedButtonWidths.slice(0, 3))
|
|
1512
|
+
const collapsedButtonsWidth =
|
|
1513
|
+
sortedButtonWidths.length > 3 ? calcLayoutWidth(sortedButtonWidths.slice(0, 2), true) : 0
|
|
1514
|
+
const dynamicButtonsWidth = this.getOperateBtns ? calcLayoutWidth(sortedButtonWidths.slice(0, 3), true) : 0
|
|
1515
|
+
|
|
1516
|
+
return Math.max(
|
|
1517
|
+
DEFAULT_OPERATE_COLUMN_WIDTH,
|
|
1518
|
+
Math.ceil(directButtonsWidth),
|
|
1519
|
+
Math.ceil(collapsedButtonsWidth),
|
|
1520
|
+
Math.ceil(dynamicButtonsWidth)
|
|
1521
|
+
)
|
|
1522
|
+
},
|
|
1523
|
+
|
|
1388
1524
|
calcColumnWidth(columns) {
|
|
1389
1525
|
// 常量配置
|
|
1390
1526
|
const CHAR_WIDTH = 20 // 每个字符的平均宽度(像素)
|