n20-common-lib 3.0.22 → 3.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +4 -3
  2. package/src/assets/css/drag-list.scss +22 -14
  3. package/src/assets/css/normalize.scss +6 -0
  4. package/src/assets/css/show-column.scss +72 -14
  5. package/src/assets/css/table copy.scss +234 -0
  6. package/src/assets/css/table.scss +159 -13
  7. package/src/assets/realUrl.js +2 -1
  8. package/src/components/AdvancedFilter/index.vue +1 -1
  9. package/src/components/ApprovalButtons/showOtherAttrNew.vue +1 -1
  10. package/src/components/ApprovalRecord/index.vue +5 -5
  11. package/src/components/DragList/index.vue +33 -12
  12. package/src/components/Empty/img/searchNoData.png +0 -0
  13. package/src/components/HandlingAdvice/index.vue +1 -0
  14. package/src/components/ShowColumn/index copy.vue +18 -33
  15. package/src/components/ShowColumn/index.vue +244 -59
  16. package/src/components/TablePro/index copy.vue +462 -0
  17. package/src/components/TablePro/index.js +9 -2
  18. package/src/components/TablePro/index.vue +542 -28
  19. package/src/components/TableSetSize/index copy.vue +69 -0
  20. package/src/components/TableSetSize/index.vue +10 -13
  21. package/src/plugins/Print/print-js/src/js/print.js +0 -6
  22. package/theme/blue.css +3 -0
  23. package/theme/cctcRed.css +3 -0
  24. package/theme/fonts/SIMSUN.5e0c362c.ttf +0 -0
  25. package/theme/fonts/element-icons.535877f5.woff +0 -0
  26. package/theme/fonts/element-icons.732389de.ttf +0 -0
  27. package/theme/fonts/iconfont.09d221ee.woff +0 -0
  28. package/theme/fonts/iconfont.1c4bfacc.ttf +0 -0
  29. package/theme/fonts/iconfont.a6f34dc7.woff2 +0 -0
  30. package/theme/fonts/iconfont.f4c32765.ttf +0 -0
  31. package/theme/green.css +3 -0
  32. package/theme/lightBlue.css +3 -0
  33. package/theme/mapleLeafRed.css +3 -0
  34. package/theme/orange.css +3 -0
  35. package/theme/purple.css +3 -0
  36. package/theme/red.css +3 -0
  37. package/theme/yellow.css +3 -0
  38. package/src/components/ShowColumn/index copy 2.vue +0 -545
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <div style="height: 100%; position: relative">
2
+ <div class="table-pro-wrapper" style="height: 100%; position: relative">
3
+ <!-- 骨架屏 - 仅覆盖内容区域,不覆盖表头 -->
4
+ <div v-if="loading" class="table-pro-skeleton">
5
+ <div class="skeleton-row" v-for="n in skeletonRows" :key="'skeleton-' + n">
6
+ <div class="skeleton-cell skeleton-checkbox"></div>
7
+ <div class="skeleton-cell" v-for="col in skeletonCols" :key="'col-' + col"></div>
8
+ </div>
9
+ </div>
3
10
  <vxe-table
4
11
  ref="vxeTable"
5
12
  :key="colsKey"
@@ -32,8 +39,6 @@
32
39
  multiple: false,
33
40
  remote: true,
34
41
  trigger: 'cell',
35
- iconAsc: 'n20-icon-daoxu',
36
- iconDesc: 'n20-icon-shunxu',
37
42
  ...$attrs.sortConfig,
38
43
  ...$attrs['sort-config']
39
44
  }"
@@ -42,12 +47,16 @@
42
47
  iconNone: 'n20-icon-xiala-moren',
43
48
  iconMatch: 'n20-icon-xiala-moren'
44
49
  }"
50
+ :merge-cells="mergeCells"
45
51
  v-bind="Object.assign({ size: size }, $attrs, sizeBind)"
46
52
  v-on="$listeners"
47
53
  @sort-change="(val) => customSortMethod(val)"
48
54
  @filter-change="filterChange"
49
55
  @checkbox-change="handleSelectionChange"
50
56
  @cell-click="handleCellClick"
57
+ @cell-mouseenter="handleCellMouseEnter"
58
+ @cell-mouseleave="handleCellMouseLeave"
59
+ @scroll="handleTableScroll"
51
60
  >
52
61
  <template v-for="(item, i) in _columns">
53
62
  <slot v-if="item.slotName" :name="item.slotName" :column="item"></slot>
@@ -77,16 +86,26 @@
77
86
  >
78
87
  <template #header="{ $table, checked, indeterminate, disabled }">
79
88
  <span class="custom-checkbox" @click.stop="toggleAll($table, disabled)">
80
- <el-checkbox v-if="indeterminate" :key="key" :indeterminate="indeterminate" :disabled="disabled" />
81
- <el-checkbox v-else-if="checked" :key="key" :value="checked" :disabled="disabled" />
82
- <el-checkbox v-else :key="key" :value="checked" :disabled="disabled" />
89
+ <el-checkbox
90
+ v-if="indeterminate"
91
+ :key="key + '_header_indeterminate'"
92
+ :indeterminate="indeterminate"
93
+ :disabled="disabled"
94
+ />
95
+ <el-checkbox v-else-if="checked" :key="key + '_header_checked'" :value="checked" :disabled="disabled" />
96
+ <el-checkbox v-else :key="key + '_header_unchecked'" :value="checked" :disabled="disabled" />
83
97
  </span>
84
98
  </template>
85
99
  <template #checkbox="{ $table, row, checked, indeterminate, disabled }">
86
100
  <span class="custom-checkbox" @click.stop="toggleChecked($table, row, disabled)">
87
- <el-checkbox v-if="indeterminate" :key="key" :indeterminate="indeterminate" :disabled="disabled" />
88
- <el-checkbox v-else-if="checked" :key="key" :value="checked" :disabled="disabled" />
89
- <el-checkbox v-else :key="key" :value="checked" :disabled="disabled" />
101
+ <el-checkbox
102
+ v-if="indeterminate"
103
+ :key="key + '-row-indeterminate'"
104
+ :indeterminate="indeterminate"
105
+ :disabled="disabled"
106
+ />
107
+ <el-checkbox v-else-if="checked" :key="key + '-row-checked'" :value="checked" :disabled="disabled" />
108
+ <el-checkbox v-else :key="key + '-row-unchecked'" :value="checked" :disabled="disabled" />
90
109
  </span>
91
110
  </template>
92
111
  </vxe-column>
@@ -109,7 +128,7 @@
109
128
  </vxe-colgroup>
110
129
  <vxe-column
111
130
  v-else
112
- :key="'vxe-table-' + i"
131
+ :key="'vxe-table-base__column' + i"
113
132
  :class-name="`${item.wrap && `vxe-table-custom-wrap`} ${item.bold && `font-w600`}`"
114
133
  :formatter="item.formatter ? item.formatter : 'formatName'"
115
134
  :filters="item.filters"
@@ -117,15 +136,105 @@
117
136
  :title="item.label"
118
137
  :field="item.prop"
119
138
  v-bind="item"
120
- />
139
+ >
140
+ <template #header="{ column }">
141
+ <div
142
+ class="flex-box flex-v flex-c"
143
+ @mouseenter="hoverHeaderProp = item.prop"
144
+ @mouseleave="hoverHeaderProp = null"
145
+ >
146
+ <slot name="header" :column="column">{{ column.title }}</slot>
147
+ <i
148
+ v-if="item.tooltip"
149
+ class="n20-icon-xinxitishi vxe-table--column__icon m-l-ss"
150
+ v-title="item.tooltip"
151
+ ></i>
152
+ <!-- 已固定列(fixed='left'):hover 时显示 lock 图标,点击取消固定 -->
153
+ <i
154
+ v-if="item.fixed === 'left'"
155
+ class="iconfont icon-lock vxe-table--column__icon m-l-ss pointer color-primary"
156
+ @click.stop="unlockColumn(item)"
157
+ ></i>
158
+ <!-- 未固定列(且非静态列):hover 时显示 unlock 图标,点击设为固定 -->
159
+ <i
160
+ v-if="!item.fixed && !item.static"
161
+ v-show="hoverHeaderProp === item.prop"
162
+ class="iconfont icon-unlock vxe-table--column__icon m-l-ss pointer"
163
+ @click.stop="lockColumn(item)"
164
+ ></i>
165
+ </div>
166
+ </template>
167
+ </vxe-column>
121
168
  </template>
169
+ <vxe-column
170
+ fixed="right"
171
+ header-class-name="fixed-column__static"
172
+ :min-width="showColumn && showSetsize ? 80 : 50"
173
+ >
174
+ <div slot="header">
175
+ <i
176
+ v-if="showColumn"
177
+ v-title="$lc('设置显示列')"
178
+ class="iconfont icon-system-solution pointer"
179
+ @click="$emit('visible-column')"
180
+ ></i>
181
+ <tableSetSize v-if="showSetsize" :size="sizeC" v-bind="$attrs" @update:size="sizeUp" @resize="sizeSet" />
182
+ </div>
183
+ </vxe-column>
122
184
  <template #empty>
123
185
  <slot name="empty">
124
186
  <empty type="empty" :content="$lc('暂无数据')" :height="200" :width="200" />
125
187
  </slot>
126
188
  </template>
127
189
  </vxe-table>
128
- <tableSetSize v-if="showSetsize" :size="sizeC" v-bind="$attrs" @update:size="sizeUp" @resize="sizeSet" />
190
+ <!-- 操作列的悬浮按钮组 -->
191
+ <transition name="hover-btns-fade">
192
+ <div
193
+ v-if="showHoverBtns && visibleHoverBtns.length > 0"
194
+ class="table-hover-btns"
195
+ :style="hoverBtnsStyle"
196
+ @mouseenter="handleBtnGroupEnter"
197
+ @mouseleave="handleBtnGroupLeave"
198
+ >
199
+ <div class="hover-btns-wrapper">
200
+ <el-button
201
+ v-for="(btn, idx) in displayHoverBtns"
202
+ :key="'display-' + idx"
203
+ :type="btn.type || 'primary'"
204
+ :size="btn.size || 'mini'"
205
+ :icon="btn.icon"
206
+ :disabled="btn.disabled"
207
+ :class="[btn.class]"
208
+ @click.stop="handleHoverBtnClick(btn, $event)"
209
+ >
210
+ {{ btn.label }}
211
+ </el-button>
212
+ <!-- 更多按钮下拉菜单 -->
213
+ <el-dropdown
214
+ v-if="moreHoverBtns.length > 0"
215
+ class="hover-btns-more"
216
+ @command="handleMoreBtnCommand"
217
+ @visible-change="handleDropdownVisibleChange"
218
+ >
219
+ <el-button type="primary" size="mini">
220
+ {{ $lc('更多') }}<i class="el-icon-arrow-down el-icon--right"></i>
221
+ </el-button>
222
+ <el-dropdown-menu slot="dropdown">
223
+ <el-dropdown-item
224
+ v-for="(btn, idx) in moreHoverBtns"
225
+ :key="'more-' + idx"
226
+ :command="btn"
227
+ :disabled="btn.disabled"
228
+ >
229
+ <span :class="btn.type === 'danger' ? 'color-danger' : ''">
230
+ {{ btn.label }}
231
+ </span>
232
+ </el-dropdown-item>
233
+ </el-dropdown-menu>
234
+ </el-dropdown>
235
+ </div>
236
+ </div>
237
+ </transition>
129
238
  </div>
130
239
  </template>
131
240
 
@@ -133,7 +242,7 @@
133
242
  import empty from '../Empty'
134
243
  import tableSetSize from '../TableSetSize/index.vue'
135
244
  import { $lc } from '../../utils/i18n/index.js'
136
- import XEUtils from 'xe-utils'
245
+
137
246
  const renderer = {
138
247
  name: 'renderer',
139
248
  props: {
@@ -197,6 +306,10 @@ export default {
197
306
  return forbidSelect
198
307
  }
199
308
  },
309
+ showColumn: {
310
+ type: Boolean,
311
+ default: true
312
+ },
200
313
  // 数据更新时是否自动清空已选
201
314
  clearSelect: {
202
315
  type: Boolean,
@@ -210,6 +323,63 @@ export default {
210
323
  isAutoWidth: {
211
324
  type: Boolean,
212
325
  default: true
326
+ },
327
+ // 合并单元格配置,格式: [{ row: 0, col: 0, rowspan: 2, colspan: 0 }]
328
+ mergeCells: {
329
+ type: Array,
330
+ default: () => []
331
+ },
332
+ // 全选时是否排除被合并的行(从属行)
333
+ excludeMergedRows: {
334
+ type: Boolean,
335
+ default: false
336
+ },
337
+ // 是否显示骨架屏加载状态
338
+ loading: {
339
+ type: Boolean,
340
+ default: false
341
+ },
342
+ // 骨架屏行数
343
+ skeletonRows: {
344
+ type: Number,
345
+ default: 10
346
+ },
347
+ // 骨架屏列数
348
+ skeletonCols: {
349
+ type: Number,
350
+ default: 6
351
+ },
352
+ // 悬浮按钮列表配置
353
+ // 格式: [{ command: 'edit', label: '编辑', type: 'text', isHas: 'edit', click: (row) => {} }]
354
+ btnList: {
355
+ type: Array,
356
+ default: () => []
357
+ },
358
+ // 权限检查函数,返回当前行允许显示的按钮 key 列表
359
+ // 格式: (row, btns) => ['edit', 'delete'] 或 true(显示所有)或 false(不显示任何)
360
+ hoverBtnsPermission: {
361
+ type: Function,
362
+ default: null
363
+ },
364
+ // 悬浮按钮组是否显示分隔符
365
+ hoverBtnsDivider: {
366
+ type: Boolean,
367
+ default: true
368
+ },
369
+ // 悬浮按钮组显示延迟(毫秒)
370
+ hoverBtnsDelay: {
371
+ type: Number,
372
+ default: 50
373
+ },
374
+ // 悬浮按钮组隐藏延迟(毫秒)
375
+ hoverBtnsHideDelay: {
376
+ type: Number,
377
+ default: 150
378
+ },
379
+ // 悬浮按钮组最大显示数量,超过则收起到"更多"下拉菜单
380
+ hoverBtnsMaxShow: {
381
+ type: Number,
382
+ default: 4
213
383
  }
214
384
  },
215
385
  data() {
@@ -218,15 +388,61 @@ export default {
218
388
  colsKey: 0,
219
389
  sizeC: localStorage.getItem('table-size') || _this.size,
220
390
  sizeBind: undefined,
221
- key: 0
391
+ setTableSize: false,
392
+ key: 0,
393
+ // 悬浮按钮组相关状态
394
+ hoverRowData: null, // 当前悬停行数据
395
+ hoverRowIndex: -1, // 当前悬停行索引
396
+ showHoverBtns: false, // 是否显示悬浮按钮组
397
+ hoverBtnsPosition: { top: 0, left: 0, width: 0 }, // 按钮组位置
398
+ hoverShowTimer: null, // 显示延迟定时器
399
+ hoverHideTimer: null, // 隐藏延迟定时器
400
+ isHoverOnBtnGroup: false, // 鼠标是否在按钮组上
401
+ isDropdownVisible: false, // "更多"下拉菜单是否展开
402
+ hoverHeaderProp: null // 当前悬停表头列的 prop
222
403
  }
223
404
  },
224
405
  computed: {
225
- _columns() {
226
- if (this.isAutoWidth) {
227
- return this.calcColumnWidth(this.columns)
228
- } else {
229
- return this.columns
406
+ _columns: {
407
+ get() {
408
+ if (this.isAutoWidth) {
409
+ return this.calcColumnWidth(this.columns)
410
+ } else {
411
+ return this.columns
412
+ }
413
+ },
414
+ set(val) {
415
+ return val
416
+ }
417
+ },
418
+ // 计算当前行可见的悬浮按钮
419
+ visibleHoverBtns() {
420
+ if (!this.hoverRowData || !this.btnList || this.btnList.length === 0) {
421
+ return []
422
+ }
423
+ return this.btnList.filter((btn) => this.hasBtn(btn.isHas, this.hoverRowData))
424
+ },
425
+ // 直接显示的按钮(不超过阈值)
426
+ displayHoverBtns() {
427
+ if (this.visibleHoverBtns.length > this.hoverBtnsMaxShow) {
428
+ return this.visibleHoverBtns.slice(0, this.hoverBtnsMaxShow - 1)
429
+ }
430
+ return this.visibleHoverBtns
431
+ },
432
+ // 收起到"更多"下拉菜单的按钮
433
+ moreHoverBtns() {
434
+ if (this.visibleHoverBtns.length > this.hoverBtnsMaxShow) {
435
+ return this.visibleHoverBtns.slice(this.hoverBtnsMaxShow - 1)
436
+ }
437
+ return []
438
+ },
439
+
440
+ // 悬浮按钮组样式
441
+ hoverBtnsStyle() {
442
+ return {
443
+ top: `${this.hoverBtnsPosition.top}px`,
444
+ right: 0, // 固定在右侧设置列按钮左边
445
+ height: `${this.hoverBtnsPosition.height}px`
230
446
  }
231
447
  }
232
448
  },
@@ -248,16 +464,84 @@ export default {
248
464
  activated() {
249
465
  this.$refs.vxeTable.loadData(this.data)
250
466
  },
251
- mounted() {},
467
+ beforeDestroy() {
468
+ // 清理定时器
469
+ this.clearHoverTimers()
470
+ },
252
471
  methods: {
253
- // 全选
472
+ // 锁定列:将该列 fixed 设为 'left',直接修改 prop 对象并刷新表格
473
+ lockColumn(item) {
474
+ if (item.fixed || item.static) return
475
+ this.$set(item, 'fixed', 'left')
476
+ // 手动触发 colsKey 刷新,让 vxe-table 重新渲染列配置
477
+ this.colsKey = this.colsKey + 1
478
+ },
479
+ // 解锁列:移除 fixed 属性,将该列恢复为普通列
480
+ unlockColumn(item) {
481
+ if (item.fixed !== 'left') return
482
+ this.$delete(item, 'fixed')
483
+ // 手动触发 colsKey 刷新,让 vxe-table 重新渲染列配置
484
+ this.colsKey = this.colsKey + 1
485
+ },
486
+ // 判断按鈕是否显示
487
+ hasBtn(isHas, row) {
488
+ if (isHas === undefined || isHas === null) {
489
+ return true
490
+ } else if (typeof isHas === 'boolean') {
491
+ return isHas
492
+ } else if (typeof isHas === 'string' || Array.isArray(isHas)) {
493
+ return this.$has(isHas)
494
+ } else if (typeof isHas === 'function') {
495
+ return isHas(row)
496
+ }
497
+ },
498
+ // 全选/反选
254
499
  toggleAllSelection() {
255
- if (this.$refs.vxeTable) {
256
- this.$refs.vxeTable.setAllCheckboxRow(true)
257
- // 得手动触发
258
- this.handleSelectionChange()
500
+ if (!this.$refs.vxeTable) return
501
+ const allData = this.data || []
502
+ // 获取合并行索引集合
503
+ const mergedIndexes =
504
+ this.excludeMergedRows && this.mergeCells && this.mergeCells.length > 0 ? this.getMergedRowIndexes() : new Set()
505
+ // 筛选出可选择的行(排除合并从属行 + 排除禁止选择的行)
506
+ const selectableRows = allData.filter((row, index) => {
507
+ if (mergedIndexes.has(index)) return false
508
+ if (typeof this.forbidSelect === 'function' && this.forbidSelect({ row }) === true) return false
509
+ return true
510
+ })
511
+ // 判断当前可选行是否已全部选中
512
+ const checkedRows = this.$refs.vxeTable.getCheckboxRecords(false)
513
+ const checkedSet = new Set(checkedRows)
514
+ const isAllChecked = selectableRows.length > 0 && selectableRows.every((row) => checkedSet.has(row))
515
+ if (isAllChecked) {
516
+ // 已全选,则取消可选行的选中状态
517
+ selectableRows.forEach((row) => {
518
+ this.$refs.vxeTable.setCheckboxRow(row, false)
519
+ })
520
+ } else {
521
+ // 未全选,则选中所有可选行
522
+ selectableRows.forEach((row) => {
523
+ this.$refs.vxeTable.setCheckboxRow(row, true)
524
+ })
259
525
  }
526
+ // 得手动触发
527
+ this.handleSelectionChange()
260
528
  },
529
+ // 获取被合并的从属行索引集合
530
+ getMergedRowIndexes() {
531
+ const mergedIndexes = new Set()
532
+ if (this.mergeCells && this.mergeCells.length > 0) {
533
+ this.mergeCells.forEach((cell) => {
534
+ if (cell.rowspan > 1) {
535
+ // 从 row+1 到 row+rowspan-1 都是被合并的从属行
536
+ for (let i = 1; i < cell.rowspan; i++) {
537
+ mergedIndexes.add(cell.row + i)
538
+ }
539
+ }
540
+ })
541
+ }
542
+ return mergedIndexes
543
+ },
544
+
261
545
  // 清空选择
262
546
  clearSelection() {
263
547
  if (this.$refs.vxeTable) {
@@ -272,11 +556,54 @@ export default {
272
556
  // 得手动触发
273
557
  this.handleSelectionChange()
274
558
  },
559
+ // 获取被合并的从属行索引集合
560
+ getMergedRowIndexes() {
561
+ const mergedIndexes = new Set()
562
+ if (this.mergeCells && this.mergeCells.length > 0) {
563
+ this.mergeCells.forEach((cell) => {
564
+ if (cell.rowspan > 1) {
565
+ // 从 row+1 到 row+rowspan-1 都是被合并的从属行
566
+ for (let i = 1; i < cell.rowspan; i++) {
567
+ mergedIndexes.add(cell.row + i)
568
+ }
569
+ }
570
+ })
571
+ }
572
+ return mergedIndexes
573
+ },
275
574
  toggleAll($table, disabled) {
276
575
  if (disabled) {
277
576
  return false
278
577
  }
279
- $table.toggleAllCheckboxRow()
578
+ // 如果配置了排除合并行,则手动处理非合并行的选中状态
579
+ if (this.excludeMergedRows && this.mergeCells && this.mergeCells.length > 0) {
580
+ const mergedIndexes = this.getMergedRowIndexes()
581
+ const allData = this.data || []
582
+ // 筛选出可选择的行(排除合并从属行 + 排除禁止选择的行)
583
+ const selectableRows = allData.filter((row, index) => {
584
+ if (mergedIndexes.has(index)) return false
585
+ if (typeof this.forbidSelect === 'function' && this.forbidSelect({ row }) === true) return false
586
+ return true
587
+ })
588
+ // 判断当前可选行是否已全部选中
589
+ const checkedRows = $table.getCheckboxRecords(false)
590
+ const checkedSet = new Set(checkedRows)
591
+ const isAllChecked = selectableRows.length > 0 && selectableRows.every((row) => checkedSet.has(row))
592
+ if (isAllChecked) {
593
+ // 已全选,则取消可选行的选中状态
594
+ selectableRows.forEach((row) => {
595
+ $table.setCheckboxRow(row, false)
596
+ })
597
+ } else {
598
+ // 未全选,则选中所有可选行
599
+ selectableRows.forEach((row) => {
600
+ $table.setCheckboxRow(row, true)
601
+ })
602
+ }
603
+ } else {
604
+ // 默认行为:全选/取消全选所有行
605
+ $table.toggleAllCheckboxRow()
606
+ }
280
607
  this.key++
281
608
  this.handleSelectionChange()
282
609
  },
@@ -329,9 +656,10 @@ export default {
329
656
  },
330
657
  // row当前单次勾选的哪一行数据 包含checked字段
331
658
  handleSelectionChange(row = '') {
332
- const val = this.$refs.vxeTable.getCheckboxRecords()
659
+ const val = this.$refs.vxeTable.getCheckboxRecords(false)
333
660
  // 支持跨页勾选
334
661
  const val1 = this.$refs.vxeTable.getCheckboxReserveRecords()
662
+
335
663
  this.$emit('selection-change-method', [...val, ...val1], row)
336
664
  },
337
665
  // 点击行勾选/取消勾选
@@ -392,6 +720,193 @@ export default {
392
720
  this.sizeBind = el
393
721
  },
394
722
  // 计算列宽
723
+ // ========== 悬浮按钮组相关方法 ==========
724
+ // 清理所有定时器
725
+ clearHoverTimers() {
726
+ if (this.hoverShowTimer) {
727
+ clearTimeout(this.hoverShowTimer)
728
+ this.hoverShowTimer = null
729
+ }
730
+ if (this.hoverHideTimer) {
731
+ clearTimeout(this.hoverHideTimer)
732
+ this.hoverHideTimer = null
733
+ }
734
+ },
735
+ // 鼠标进入单元格事件(防抖处理)
736
+ handleCellMouseEnter({ row, rowIndex, $rowIndex, $event }) {
737
+ // 如果没有配置悬浮按钮,不处理
738
+ if (!this.btnList || this.btnList.length === 0) {
739
+ return
740
+ }
741
+ // 清理隐藏定时器
742
+ if (this.hoverHideTimer) {
743
+ clearTimeout(this.hoverHideTimer)
744
+ this.hoverHideTimer = null
745
+ }
746
+ // 如果是同一行,不重复处理
747
+ if (this.hoverRowIndex === rowIndex && this.showHoverBtns) {
748
+ return
749
+ }
750
+ // 清理之前的显示定时器
751
+ if (this.hoverShowTimer) {
752
+ clearTimeout(this.hoverShowTimer)
753
+ }
754
+ // 延迟显示(防抖)
755
+ this.hoverShowTimer = setTimeout(() => {
756
+ this.updateHoverRow(row, rowIndex, $event)
757
+ }, this.hoverBtnsDelay)
758
+ },
759
+ // 鼠标离开单元格事件(防抖处理)
760
+ handleCellMouseLeave({ row, rowIndex, $event }) {
761
+ // 如果没有配置悬浮按钮,不处理
762
+ if (!this.btnList || this.btnList.length === 0) {
763
+ return
764
+ }
765
+ // 清理显示定时器
766
+ if (this.hoverShowTimer) {
767
+ clearTimeout(this.hoverShowTimer)
768
+ this.hoverShowTimer = null
769
+ }
770
+ // 延迟隐藏(给鼠标移动到按钮组的时间)
771
+ this.hoverHideTimer = setTimeout(() => {
772
+ if (!this.isHoverOnBtnGroup) {
773
+ this.hideHoverBtns()
774
+ }
775
+ }, this.hoverBtnsHideDelay)
776
+ },
777
+ // 更新悬停行信息并显示按钮组
778
+ updateHoverRow(row, rowIndex, $event) {
779
+ this.hoverRowData = row
780
+ this.hoverRowIndex = rowIndex
781
+ // 计算按钮组位置
782
+ this.calculateBtnPosition($event)
783
+ // 显示按钮组
784
+ this.showHoverBtns = true
785
+ // 触发事件
786
+ this.$emit('row-hover-enter', { row, rowIndex })
787
+ },
788
+ // 计算按钮组位置
789
+ calculateBtnPosition($event) {
790
+ const tableEl = this.$refs.vxeTable?.$el
791
+ if (!tableEl) return
792
+ // 获取当前行的 DOM 元素
793
+ const rowEl = $event?.target?.closest('tr.vxe-body--row')
794
+ if (!rowEl) return
795
+ // 获取表格容器的位置信息
796
+ const tableRect = tableEl.getBoundingClientRect()
797
+ const rowRect = rowEl.getBoundingClientRect()
798
+ // 计算相对于表格容器的位置
799
+ this.hoverBtnsPosition = {
800
+ top: rowRect.top - tableRect.top,
801
+ height: rowRect.height
802
+ }
803
+ },
804
+ // 隐藏悬浮按钮组
805
+ hideHoverBtns() {
806
+ // 如果下拉菜单正在展开,不隐藏
807
+ if (this.isDropdownVisible) {
808
+ return
809
+ }
810
+ // 移除当前行的 hover 类名
811
+ this.removeRowHoverClass()
812
+ this.showHoverBtns = false
813
+ this.hoverRowData = null
814
+ this.hoverRowIndex = -1
815
+ this.isHoverOnBtnGroup = false
816
+ // 触发事件
817
+ this.$emit('row-hover-leave')
818
+ },
819
+ // 鼠标进入按钮组
820
+ handleBtnGroupEnter() {
821
+ this.isHoverOnBtnGroup = true
822
+ // 清理隐藏定时器
823
+ if (this.hoverHideTimer) {
824
+ clearTimeout(this.hoverHideTimer)
825
+ this.hoverHideTimer = null
826
+ }
827
+ // 给当前行添加 hover 类名
828
+ this.addRowHoverClass()
829
+ },
830
+ // 鼠标离开按钮组
831
+ handleBtnGroupLeave() {
832
+ this.isHoverOnBtnGroup = false
833
+ // 如果下拉菜单正在展开,不隐藏按钮组
834
+ if (this.isDropdownVisible) {
835
+ return
836
+ }
837
+ // 移除当前行的 hover 类名
838
+ this.removeRowHoverClass()
839
+ // 延迟隐藏
840
+ this.hoverHideTimer = setTimeout(() => {
841
+ // 再次检查下拉菜单状态
842
+ if (!this.isDropdownVisible) {
843
+ this.hideHoverBtns()
844
+ }
845
+ }, this.hoverBtnsHideDelay)
846
+ },
847
+ // 处理下拉菜单显示/隐藏状态变化
848
+ handleDropdownVisibleChange(visible) {
849
+ this.isDropdownVisible = visible
850
+ if (!visible) {
851
+ // 下拉菜单关闭后,延迟检查是否需要隐藏按钮组
852
+ this.hoverHideTimer = setTimeout(() => {
853
+ if (!this.isHoverOnBtnGroup && !this.isDropdownVisible) {
854
+ this.hideHoverBtns()
855
+ }
856
+ }, this.hoverBtnsHideDelay)
857
+ }
858
+ },
859
+ // 给当前悬停行添加 hover 类名
860
+ addRowHoverClass() {
861
+ const tableEl = this.$refs.vxeTable?.$el
862
+ if (!tableEl || this.hoverRowIndex < 0) return
863
+ // 查找当前行的 tr 元素
864
+ const rows = tableEl.querySelectorAll('tr.vxe-body--row')
865
+ rows.forEach((row, index) => {
866
+ if (index === this.hoverRowIndex) {
867
+ row.classList.add('row--hover')
868
+ }
869
+ })
870
+ },
871
+ // 移除当前悬停行的 hover 类名
872
+ removeRowHoverClass() {
873
+ const tableEl = this.$refs.vxeTable?.$el
874
+ if (!tableEl) return
875
+ // 移除所有行的 hover 类名
876
+ const rows = tableEl.querySelectorAll('tr.vxe-body--row.row--hover')
877
+ rows.forEach((row) => {
878
+ row.classList.remove('row--hover')
879
+ })
880
+ },
881
+ // 处理悬浮按钮点击
882
+ handleHoverBtnClick(btn, $event) {
883
+ if (btn.disabled) return
884
+
885
+ if (typeof btn.command === 'function') {
886
+ btn.command(this.hoverRowData, this.hoverRowIndex, $event)
887
+ } else if (typeof btn.command === 'string') {
888
+ this.$emit(btn.command, this.hoverRowData, this.hoverRowIndex, $event)
889
+ }
890
+ // 触发事件
891
+ this.$emit('hover-btn-click', {
892
+ btn,
893
+ row: this.hoverRowData,
894
+ rowIndex: this.hoverRowIndex,
895
+ $event
896
+ })
897
+ },
898
+ // 处理"更多"下拉菜单命令
899
+ handleMoreBtnCommand(btn) {
900
+ if (!btn || btn.disabled) return
901
+ this.handleHoverBtnClick(btn, null)
902
+ },
903
+ // 表格滚动时隐藏按钮组
904
+ handleTableScroll() {
905
+ if (this.showHoverBtns) {
906
+ this.hideHoverBtns()
907
+ }
908
+ },
909
+ // ========== 列宽计算相关方法 ==========
395
910
  calcColumnWidth(columns) {
396
911
  columns = columns.map((item) => {
397
912
  return {
@@ -409,7 +924,6 @@ export default {
409
924
  return columns
410
925
  }
411
926
  const { width: windowWidth } = wrapperEl.getBoundingClientRect()
412
- console.log(windowWidth, 'windowWidth')
413
927
  // 定义每个字符的平均宽度(像素)
414
928
  const CHAR_WIDTH = 20 // 根据实际字体大小调整
415
929
  const PADDING = 20 // 左右padding