sone-ui-component-3.2.4 2.1.61 → 2.1.63
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/lib/sone-ui.common.js +4398 -677
- package/lib/sone-ui.common.js.map +1 -1
- package/lib/sone-ui.css +1 -1
- package/lib/sone-ui.umd.js +4398 -677
- package/lib/sone-ui.umd.js.map +1 -1
- package/lib/sone-ui.umd.min.js +18 -3
- package/lib/sone-ui.umd.min.js.map +1 -1
- package/package.json +2 -2
- package/packages/normalTable/src/mainNew.vue +400 -189
- package/packages/normalTable/src/virtual/el-table-virtual-column-formatter.vue +14 -0
- package/packages/normalTable/src/virtual/el-table-virtual-column.vue +416 -0
- package/packages/normalTable/src/virtual/el-table-virtual-scroll.vue +1548 -0
- package/packages/normalTable/src/virtual/util.js +147 -0
- package/src/index.js +1 -1
|
@@ -0,0 +1,1548 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="el-table-virtual-scroll"
|
|
4
|
+
:class="[
|
|
5
|
+
isExpanding ? 'is-expanding' : '',
|
|
6
|
+
isHideAppend ? 'hide-append' : '',
|
|
7
|
+
scrollPosition ? `is-scrolling-${scrollPosition}` : '',
|
|
8
|
+
hasFixedRight ? 'has-custom-fixed-right' : '']">
|
|
9
|
+
<slot v-bind="{ headerCellFixedStyle, cellFixedStyle }"></slot>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import throttle from 'lodash/throttle'
|
|
15
|
+
import {
|
|
16
|
+
isScroller,
|
|
17
|
+
getParentScroller,
|
|
18
|
+
getScrollTop,
|
|
19
|
+
getOffsetHeight,
|
|
20
|
+
scrollToY,
|
|
21
|
+
isEmpty,
|
|
22
|
+
setMousewheelSlow,
|
|
23
|
+
orderBy,
|
|
24
|
+
getColumnById
|
|
25
|
+
} from './util'
|
|
26
|
+
|
|
27
|
+
// 表格body class名称
|
|
28
|
+
const TableBodyClassNames = [
|
|
29
|
+
'.el-table__body-wrapper', // 主表格容器
|
|
30
|
+
'.el-table__fixed-right .el-table__fixed-body-wrapper', // 右固定表格容器
|
|
31
|
+
'.el-table__fixed .el-table__fixed-body-wrapper' // 左固定表格容器
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
name: 'el-table-virtual-scroll',
|
|
36
|
+
props: {
|
|
37
|
+
// 总数据
|
|
38
|
+
data: {
|
|
39
|
+
type: Array,
|
|
40
|
+
required: true
|
|
41
|
+
},
|
|
42
|
+
// 每一行的预估高度
|
|
43
|
+
itemSize: {
|
|
44
|
+
type: Number,
|
|
45
|
+
default: 60
|
|
46
|
+
},
|
|
47
|
+
// 指定滚动容器
|
|
48
|
+
scrollBox: {
|
|
49
|
+
type: String
|
|
50
|
+
},
|
|
51
|
+
// 顶部和底部缓冲区域,值越大显示表格的行数越多
|
|
52
|
+
buffer: {
|
|
53
|
+
type: Number,
|
|
54
|
+
default: 200
|
|
55
|
+
},
|
|
56
|
+
// key值,data数据中的唯一id
|
|
57
|
+
keyProp: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: 'id'
|
|
60
|
+
},
|
|
61
|
+
// 滚动事件的节流时间
|
|
62
|
+
throttleTime: {
|
|
63
|
+
type: Number,
|
|
64
|
+
default: 16
|
|
65
|
+
},
|
|
66
|
+
// 是否获取表格行动态高度
|
|
67
|
+
dynamic: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: true
|
|
70
|
+
},
|
|
71
|
+
// 是否开启虚拟滚动
|
|
72
|
+
virtualized: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: true
|
|
75
|
+
},
|
|
76
|
+
// 表格行合并时,合并在一起的行返回相同的key值
|
|
77
|
+
rowSpanKey: {
|
|
78
|
+
type: Function
|
|
79
|
+
},
|
|
80
|
+
warn: {
|
|
81
|
+
type: Boolean,
|
|
82
|
+
default: true
|
|
83
|
+
},
|
|
84
|
+
// 禁用虚拟滚动
|
|
85
|
+
disabled: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
default: false
|
|
88
|
+
},
|
|
89
|
+
// 支持自定义选中数据的排序规则,传入false则可保留列表的排序规则,默认是按照选中顺序排序
|
|
90
|
+
selectionSort: {
|
|
91
|
+
type: [Function, Boolean],
|
|
92
|
+
default: true
|
|
93
|
+
},
|
|
94
|
+
// 获取el-table组件,默认 virtual-scroll 组件的第一个子组件
|
|
95
|
+
getElTable: {
|
|
96
|
+
type: Function,
|
|
97
|
+
default: function () {
|
|
98
|
+
return this.$children[0]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
keepScroll: {
|
|
102
|
+
type: Boolean,
|
|
103
|
+
default: true
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
provide () {
|
|
107
|
+
return {
|
|
108
|
+
virtualScroll: this
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
data () {
|
|
112
|
+
return {
|
|
113
|
+
sizes: {}, // 尺寸映射(依赖响应式)
|
|
114
|
+
start: 0, // 渲染列表开始索引
|
|
115
|
+
end: undefined, // 渲染列表结束索引
|
|
116
|
+
curRow: null, // 表格单选:选中的行
|
|
117
|
+
oldSelection: [], // 表格多选:选中的行
|
|
118
|
+
isExpanding: false, // 列是否正在展开
|
|
119
|
+
columnVms: [], // virtual-column 组件实例
|
|
120
|
+
isHideAppend: false, // 是否隐藏append
|
|
121
|
+
scrollPosition: '', // x轴滚动位置(左、中、右)
|
|
122
|
+
hasFixedRight: false, // 是否有固定右边的列
|
|
123
|
+
listData: [], // 未筛选为data源数据,筛选后则为筛选后的数据
|
|
124
|
+
isTree: false // 是否自定义树形表格
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
computed: {
|
|
128
|
+
// 计算出每个item(的key值)到滚动容器顶部的距离
|
|
129
|
+
offsetMap ({ keyProp, itemSize, sizes, listData }) {
|
|
130
|
+
if (!this.dynamic) return {}
|
|
131
|
+
|
|
132
|
+
const res = {}
|
|
133
|
+
let total = 0
|
|
134
|
+
for (let i = 0; i < listData.length; i++) {
|
|
135
|
+
const key = listData[i][keyProp]
|
|
136
|
+
if (typeof key === 'undefined') {
|
|
137
|
+
this.warn && console.warn(`data[${i}][${keyProp}] 为 undefined,请确保 keyProp 对应的值不为undefined`)
|
|
138
|
+
}
|
|
139
|
+
res[key] = total
|
|
140
|
+
|
|
141
|
+
const curSize = sizes[key]
|
|
142
|
+
const size = typeof curSize === 'number' ? curSize : itemSize
|
|
143
|
+
total += size
|
|
144
|
+
}
|
|
145
|
+
return res
|
|
146
|
+
},
|
|
147
|
+
// 树节点的 children 映射,通过响应式关联起来,那么children中添加、删除节点会触发treeMap computed,从而监听treeMap更新视图【注:children 添加删除不会触发data watch,data只是浅监听】
|
|
148
|
+
treeMap ({ data, keyProp, treeProps, isTree }) {
|
|
149
|
+
if (!isTree || !treeProps) return
|
|
150
|
+
const res = {}
|
|
151
|
+
const { children } = treeProps
|
|
152
|
+
const traverse = (nodes) => {
|
|
153
|
+
nodes.forEach(node => {
|
|
154
|
+
const key = node[keyProp]
|
|
155
|
+
if (typeof key !== 'undefined' && node[children]) {
|
|
156
|
+
res[key] = node[children]
|
|
157
|
+
traverse(node[children])
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 开始遍历树结构
|
|
163
|
+
traverse(data)
|
|
164
|
+
return res
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
methods: {
|
|
168
|
+
// 初始化数据
|
|
169
|
+
initData () {
|
|
170
|
+
this.destory() // 销毁,防止多次调用
|
|
171
|
+
// 可视范围内显示数据
|
|
172
|
+
this.renderData = []
|
|
173
|
+
// 页面可视范围顶端、底部
|
|
174
|
+
this.top = undefined
|
|
175
|
+
this.bottom = undefined
|
|
176
|
+
// 截取页面可视范围内显示数据的开始和结尾索引
|
|
177
|
+
this.start = 0
|
|
178
|
+
this.end = undefined
|
|
179
|
+
// 是否是表格内部滚动
|
|
180
|
+
this.isInnerScroll = false
|
|
181
|
+
// 高亮的行
|
|
182
|
+
this.highlightRow = null
|
|
183
|
+
// 滚动位置
|
|
184
|
+
this.scrollPos = [0, 0]
|
|
185
|
+
// 触发scroll
|
|
186
|
+
this.triggleScroll = false
|
|
187
|
+
// 多选:记录多选选项的顺序
|
|
188
|
+
this.checkOrder = 0
|
|
189
|
+
|
|
190
|
+
// 验证ElTable组件
|
|
191
|
+
this.elTable = this.getElTable()
|
|
192
|
+
if (!this.elTable || this.elTable.$options.name !== 'ElTable') {
|
|
193
|
+
throw new Error('未找到 <el-table> 组件. 请确保 <el-table> 组件在虚拟组件内,且 getElTable 方法能获取到正确的 <el-table> 组件!')
|
|
194
|
+
}
|
|
195
|
+
if (!this.elTable.rowKey) {
|
|
196
|
+
this.warn && console.warn('[el-table-virtual-scroll]: 建议设置 <el-table> 组件的 rowKey 属性')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.scroller = this.getScroller()
|
|
200
|
+
this.observeElTable()
|
|
201
|
+
|
|
202
|
+
// 监听事件
|
|
203
|
+
this.onScroll = !this.throttleTime ? this.handleScroll : throttle(this.handleScroll, this.throttleTime)
|
|
204
|
+
this.scroller.addEventListener('scroll', this.onScroll)
|
|
205
|
+
window.addEventListener('resize', this.onScroll)
|
|
206
|
+
|
|
207
|
+
// 兼容
|
|
208
|
+
this.hackTableExpand() // 兼容表格展开行
|
|
209
|
+
this.hackTableHeaderDrag() // 兼容表格头拖拽
|
|
210
|
+
this.hackTableSort() // 兼容表格排序
|
|
211
|
+
this.hackTableFilter() // 兼容表格筛选
|
|
212
|
+
this.hackRowHighlight() // 兼容单选
|
|
213
|
+
this.hackSelection() // 兼容多选
|
|
214
|
+
this.hackCustomTree() // 兼容树形表格
|
|
215
|
+
this.bindTableDestory() // 绑定表格销毁事件
|
|
216
|
+
|
|
217
|
+
// 设置listData,首次updateTreeData会在node上添加$v_tree属性,触发data watch,从而触发 onSortChange,所以defaultSort就无需再次触发
|
|
218
|
+
this.treeProps = this.elTable.treeProps || { children: 'children', hasChildren: 'hasChildren' }
|
|
219
|
+
// 此处兼容 default-sort 属性
|
|
220
|
+
if (this.elTable.defaultSort) {
|
|
221
|
+
this.$nextTick(() => { // 此处使用nextTick是因为 el-tale的sortingColumn排序数据还没设置好,得等一会
|
|
222
|
+
this.onSortChange() // onSortChange 会触发updateTreeData
|
|
223
|
+
})
|
|
224
|
+
} else {
|
|
225
|
+
this.updateTreeData()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 初次执行 (固定高度的表格布局好后,会触发 bodyHeight 更改(已手动监听,位于 unWatch2代码处),从而触发 onScroll,所以无需手动执行onScroll)
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
!this.triggleScroll && this.onScroll()
|
|
231
|
+
}, 100)
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
// 滚轮滚动速度减缓,减少快速滚动白屏
|
|
235
|
+
// slowNum - 减速的值,值越大,滚动越慢
|
|
236
|
+
slowOnMousewheel (slowNum = 1, scroller = this.scroller) {
|
|
237
|
+
this.removeMousewheelEvent && this.removeMousewheelEvent()
|
|
238
|
+
this.removeMousewheelEvent = null
|
|
239
|
+
|
|
240
|
+
if (!slowNum) return
|
|
241
|
+
this.removeMousewheelEvent = setMousewheelSlow(scroller, slowNum)
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// 获取滚动元素
|
|
245
|
+
getScroller () {
|
|
246
|
+
let el
|
|
247
|
+
if (this.scrollBox) {
|
|
248
|
+
if (this.scrollBox === 'window' || this.scrollBox === window) return window
|
|
249
|
+
|
|
250
|
+
el = document.querySelector(this.scrollBox)
|
|
251
|
+
if (!el) throw new Error(` scrollBox prop: '${this.scrollBox}' is not a valid selector`)
|
|
252
|
+
if (!isScroller(el)) console.warn(`Warning! scrollBox prop: '${this.scrollBox}' is not a scroll element`)
|
|
253
|
+
return el
|
|
254
|
+
}
|
|
255
|
+
// 如果表格是固定高度,则获取表格内的滚动节点,否则获取父层滚动节点
|
|
256
|
+
if (this.elTable && (this.elTable.height || this.elTable.maxHeight || this.elTable.height === 0 || this.elTable.maxHeight === 0)) {
|
|
257
|
+
this.isInnerScroll = true
|
|
258
|
+
return this.$el.querySelector('.el-table__body-wrapper')
|
|
259
|
+
} else {
|
|
260
|
+
return getParentScroller(this.$el)
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// 设置表格到滚动容器的距离
|
|
265
|
+
getToTop () {
|
|
266
|
+
if (this.isInnerScroll) {
|
|
267
|
+
return 0
|
|
268
|
+
} else {
|
|
269
|
+
return this.$el.getBoundingClientRect().top - (this.scroller === window ? 0 : this.scroller.getBoundingClientRect().top) + getScrollTop(this.scroller)
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// 处理滚动事件
|
|
274
|
+
handleScroll (shouldUpdate = true) {
|
|
275
|
+
if (this.disabled) return
|
|
276
|
+
if (!this.scroller) return
|
|
277
|
+
this.triggleScroll = true
|
|
278
|
+
|
|
279
|
+
// 【修复】如果使用v-show 进行切换表格会特别卡顿 #30;
|
|
280
|
+
// 【原因】v-show为false时,表格内滚动容器的高度为auto,没有滚动条限制,虚拟滚动计算渲染全部内容
|
|
281
|
+
if (this.isInnerScroll && !this.scroller.style.height && !this.scroller.style.maxHeight) return
|
|
282
|
+
|
|
283
|
+
// 如果组件失活,则不再执行handleScroll;否则外部容器滚动情况下记录的scrollTop会是0
|
|
284
|
+
if (this.isDeactivated) return
|
|
285
|
+
// 记录scrollPos
|
|
286
|
+
// 需要判断表格没有隐藏(修复表格隐藏状态下更新绑定数组长度,显示后滚动条位置异常 #67)
|
|
287
|
+
if (this.isInnerScroll && this.elTable.layout.bodyHeight) {
|
|
288
|
+
this.scrollPos[0] = this.scroller.scrollTop
|
|
289
|
+
this.scrollPos[1] = this.scroller.scrollLeft
|
|
290
|
+
}
|
|
291
|
+
if (!this.virtualized) return
|
|
292
|
+
|
|
293
|
+
this.removeHoverRows()
|
|
294
|
+
// 更新当前尺寸(高度)
|
|
295
|
+
this.updateSizes()
|
|
296
|
+
// 计算renderData
|
|
297
|
+
this.calcRenderData()
|
|
298
|
+
// 计算位置
|
|
299
|
+
this.calcPosition()
|
|
300
|
+
shouldUpdate && this.updatePosition()
|
|
301
|
+
// 触发事件
|
|
302
|
+
this.$emit('change', this.renderData, this.start, this.end)
|
|
303
|
+
// 同步表格行高亮
|
|
304
|
+
this.syncRowsHighlight()
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// 移除多个hover-row
|
|
308
|
+
removeHoverRows () {
|
|
309
|
+
const hoverRows = this.$el.querySelectorAll('.el-table__row.hover-row')
|
|
310
|
+
if (hoverRows.length > 1) {
|
|
311
|
+
Array.from(hoverRows).forEach((row) => {
|
|
312
|
+
row.classList.remove('hover-row')
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
// 更新尺寸(高度)
|
|
318
|
+
updateSizes () {
|
|
319
|
+
if (!this.dynamic) return
|
|
320
|
+
let rows = this.$el.querySelectorAll('.el-table__body > tbody > .el-table__row')
|
|
321
|
+
|
|
322
|
+
// 处理树形表格(修复树结构懒加载 如果有hasChildren=false的行 行可视区域高度异常 #45)
|
|
323
|
+
const isTree = this.elTable.lazy
|
|
324
|
+
const isVTree = this.isTree // 自定义树(非el-table的树)
|
|
325
|
+
const noFirstLevelReg = /el-table__row--level-[1-9]\d*/ // 匹配树形表格非一级行
|
|
326
|
+
if (!isVTree && isTree) {
|
|
327
|
+
// 筛选出树形表格的一级行,一级行className含有el-table__row--level-0或者不存在层级className
|
|
328
|
+
rows = Array.from(this.$el.querySelectorAll('.el-table__body > tbody > .el-table__row')).filter(row => {
|
|
329
|
+
return !noFirstLevelReg.test(row.className)
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
Array.from(rows).forEach((row, index) => {
|
|
334
|
+
const item = this.renderData[index]
|
|
335
|
+
if (!item) return
|
|
336
|
+
|
|
337
|
+
// 计算表格行的高度
|
|
338
|
+
let offsetHeight = row.offsetHeight
|
|
339
|
+
// 表格行如果有扩展行,需要加上扩展内容的高度
|
|
340
|
+
if (!isTree && !isVTree && row.classList.contains('expanded')) {
|
|
341
|
+
offsetHeight += row.nextSibling.offsetHeight
|
|
342
|
+
}
|
|
343
|
+
// 表格行如果有子孙节点,需要加上子孙节点的高度
|
|
344
|
+
if (isTree) {
|
|
345
|
+
let next = row.nextSibling
|
|
346
|
+
while (next && next.tagName === 'TR' && noFirstLevelReg.test(next.className)) {
|
|
347
|
+
offsetHeight += next.offsetHeight
|
|
348
|
+
next = next.nextSibling
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const key = item[this.keyProp]
|
|
353
|
+
if (offsetHeight && this.sizes[key] !== offsetHeight) {
|
|
354
|
+
this.$set(this.sizes, key, offsetHeight)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// 获取某条数据offsetTop
|
|
360
|
+
getItemOffsetTop (index) {
|
|
361
|
+
if (!this.dynamic) {
|
|
362
|
+
return this.itemSize * index
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const item = this.listData[index]
|
|
366
|
+
if (item) {
|
|
367
|
+
return this.offsetMap[item[this.keyProp]] || 0
|
|
368
|
+
}
|
|
369
|
+
return 0
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
// 获取某条数据的尺寸
|
|
373
|
+
getItemSize (index) {
|
|
374
|
+
if (index <= -1) return 0
|
|
375
|
+
const item = this.listData[index]
|
|
376
|
+
if (item) {
|
|
377
|
+
const key = item[this.keyProp]
|
|
378
|
+
return this.sizes[key] || this.itemSize
|
|
379
|
+
}
|
|
380
|
+
return this.itemSize
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
// 计算只在视图上渲染的数据
|
|
384
|
+
calcRenderData () {
|
|
385
|
+
const { scroller, listData, buffer } = this
|
|
386
|
+
// 计算可视范围顶部、底部
|
|
387
|
+
const toTop = this.getToTop() // 表格到滚动容器的距离
|
|
388
|
+
const top = getScrollTop(scroller) - buffer - toTop
|
|
389
|
+
const bottom = getScrollTop(scroller) + getOffsetHeight(scroller) + buffer - toTop
|
|
390
|
+
|
|
391
|
+
let start
|
|
392
|
+
let end
|
|
393
|
+
if (!this.dynamic) {
|
|
394
|
+
start = top <= 0 ? 0 : Math.floor(top / this.itemSize)
|
|
395
|
+
end = bottom <= 0 ? 0 : Math.ceil(bottom / this.itemSize)
|
|
396
|
+
} else {
|
|
397
|
+
// 二分法计算可视范围内的开始的第一个内容
|
|
398
|
+
let l = 0
|
|
399
|
+
let r = listData.length - 1
|
|
400
|
+
let mid = 0
|
|
401
|
+
while (l <= r) {
|
|
402
|
+
mid = Math.floor((l + r) / 2)
|
|
403
|
+
const midVal = this.getItemOffsetTop(mid)
|
|
404
|
+
if (midVal < top) {
|
|
405
|
+
const midNextVal = this.getItemOffsetTop(mid + 1)
|
|
406
|
+
if (midNextVal > top) break
|
|
407
|
+
l = mid + 1
|
|
408
|
+
} else {
|
|
409
|
+
r = mid - 1
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
start = mid
|
|
413
|
+
|
|
414
|
+
// 二分法计算可视范围内的结束的最后一个内容
|
|
415
|
+
l = start
|
|
416
|
+
r = listData.length - 1
|
|
417
|
+
mid = 0
|
|
418
|
+
while (l <= r) {
|
|
419
|
+
mid = Math.floor((l + r) / 2)
|
|
420
|
+
const midVal = this.getItemOffsetTop(mid)
|
|
421
|
+
if (midVal >= bottom) {
|
|
422
|
+
const midNextVal = this.getItemOffsetTop(mid - 1)
|
|
423
|
+
if (midNextVal < bottom) break
|
|
424
|
+
r = mid - 1
|
|
425
|
+
} else {
|
|
426
|
+
l = mid + 1
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
end = mid
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (this.isRowSpan()) {
|
|
433
|
+
// 计算包含合并行的开始结束区间(⚠️注意:合并行不支持使用斑马纹,因为不能100%确定合并行的开始行是偶数,可能会向上找一直到第一行,导致渲染非常多行,浪费性能)
|
|
434
|
+
[start, end] = this.calcRenderSpanData(start, end)
|
|
435
|
+
} else {
|
|
436
|
+
// 开始索引始终保持偶数,如果为奇数,则加1使其保持偶数【确保表格行的偶数数一致,不会导致斑马纹乱序显示】
|
|
437
|
+
if (start % 2) start = start - 1
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
this.top = top
|
|
441
|
+
this.bottom = bottom
|
|
442
|
+
this.start = start
|
|
443
|
+
this.end = end
|
|
444
|
+
this.renderData = listData.slice(start, end + 1)
|
|
445
|
+
if (this.start === 0 && this.end > 30 && this.end === this.listData.length - 1) {
|
|
446
|
+
this.warn && console.warn('[el-table-virtual-scroll] 表格数据全部渲染,渲染数量为:' + this.listData.length)
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// 是否是合并行
|
|
451
|
+
isRowSpan () {
|
|
452
|
+
return typeof this.rowSpanKey === 'function'
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
// 如果存在合并行的情况,渲染的数据范围扩大到包含合并行
|
|
456
|
+
calcRenderSpanData (start, end) {
|
|
457
|
+
// 从开始节点向上查找是否有合并行
|
|
458
|
+
let prevKey
|
|
459
|
+
while (start > 0) {
|
|
460
|
+
const curRow = this.listData[start]
|
|
461
|
+
const curkey = this.rowSpanKey(curRow, start)
|
|
462
|
+
// 如果不存在key,说明当前行不属于合并行
|
|
463
|
+
if (isEmpty(curkey)) break
|
|
464
|
+
|
|
465
|
+
// 如果当前行与后面一行的key不相同,说明则当前行不属于合并行,从后一行开始截断
|
|
466
|
+
if (!isEmpty(prevKey) && prevKey !== curkey) {
|
|
467
|
+
start++
|
|
468
|
+
break
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
prevKey = curkey
|
|
472
|
+
start--
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 从末端节点向下查找是否有合并行
|
|
476
|
+
const len = this.listData.length
|
|
477
|
+
prevKey = undefined
|
|
478
|
+
while (end < len) {
|
|
479
|
+
const curRow = this.listData[end]
|
|
480
|
+
const curkey = this.rowSpanKey(curRow, end)
|
|
481
|
+
// 如果不存在key,说明当前行不属于合并行
|
|
482
|
+
if (!curkey) break
|
|
483
|
+
|
|
484
|
+
// 如果当前行与前面一行的key不相同,说明则当前行不属于合并行,从前一行开始截断
|
|
485
|
+
if (prevKey && prevKey !== curkey) {
|
|
486
|
+
end--
|
|
487
|
+
break
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
prevKey = curkey
|
|
491
|
+
end++
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return [start, end]
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
// 计算位置
|
|
498
|
+
calcPosition () {
|
|
499
|
+
const last = this.listData.length - 1
|
|
500
|
+
// 计算内容总高度
|
|
501
|
+
const wrapHeight = this.getItemOffsetTop(last) + this.getItemSize(last)
|
|
502
|
+
// 计算当前滚动位置需要撑起的高度
|
|
503
|
+
const offsetTop = this.getItemOffsetTop(this.start)
|
|
504
|
+
|
|
505
|
+
let tableWrapEl
|
|
506
|
+
// 设置dom位置
|
|
507
|
+
TableBodyClassNames.forEach((className, index) => {
|
|
508
|
+
const el = this.$el.querySelector(className)
|
|
509
|
+
if (!el) return
|
|
510
|
+
|
|
511
|
+
// 创建wrapEl、innerEl
|
|
512
|
+
if (!el.wrapEl) {
|
|
513
|
+
const wrapEl = document.createElement('div')
|
|
514
|
+
const innerEl = document.createElement('div')
|
|
515
|
+
wrapEl.appendChild(innerEl)
|
|
516
|
+
innerEl.appendChild(el.children[0])
|
|
517
|
+
el.insertBefore(wrapEl, el.firstChild)
|
|
518
|
+
el.wrapEl = wrapEl
|
|
519
|
+
el.innerEl = innerEl
|
|
520
|
+
|
|
521
|
+
// 修复 fixed 动态切换时,固定列不显示(scrollTop位置和非固定列滚动位置不一致导致的,需要同步scrollTop的值)
|
|
522
|
+
if (index > 0 && tableWrapEl) {
|
|
523
|
+
this.$nextTick(() => {
|
|
524
|
+
el.scrollTop = tableWrapEl.scrollTop
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
index === 0 && (tableWrapEl = el) // 记录非固定列的dom
|
|
529
|
+
|
|
530
|
+
if (el.wrapEl) {
|
|
531
|
+
// 设置高度
|
|
532
|
+
el.wrapEl.style.height = wrapHeight + 'px'
|
|
533
|
+
// 设置transform撑起高度
|
|
534
|
+
el.innerEl.style.transform = `translateY(${offsetTop}px)`
|
|
535
|
+
// 设置paddingTop撑起高度
|
|
536
|
+
// el.innerEl.style.paddingTop = `${offsetTop}px`
|
|
537
|
+
}
|
|
538
|
+
})
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
// 监听el-table
|
|
542
|
+
observeElTable () {
|
|
543
|
+
// 监听滚动位置
|
|
544
|
+
const unWatch1 = this.$watch(
|
|
545
|
+
() => [this.elTable.scrollPosition, this.elTable.layout.scrollX],
|
|
546
|
+
([pos, scrollX], [oldPos, oldScrollX] = []) => {
|
|
547
|
+
// 修复自定义固定列 所有列宽总宽度小于表格宽度时 固定列样式有问题 #65
|
|
548
|
+
this.scrollPosition = this.elTable.layout.scrollX ? pos : 'none'
|
|
549
|
+
|
|
550
|
+
// 修复element-ui原有bug:当窗口缩放时,x轴滚动条从无到到有,且x轴已滚动到最右侧,右侧固定列
|
|
551
|
+
if (scrollX && !oldScrollX) {
|
|
552
|
+
this.elTable.syncPostion && this.elTable.syncPostion()
|
|
553
|
+
}
|
|
554
|
+
}, { immediate: true })
|
|
555
|
+
|
|
556
|
+
// 监听表格滚动高度变化(切换v-show时更新)
|
|
557
|
+
const unWatch2 = this.$watch(() => this.elTable.layout.bodyHeight, (val) => {
|
|
558
|
+
val > 0 && this.restoreScroll()
|
|
559
|
+
val > 0 && this.onScroll()
|
|
560
|
+
})
|
|
561
|
+
this.unWatchs = [unWatch1, unWatch2]
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
// 执行update方法更新虚拟滚动,且每次nextTick只能执行一次【在数据大于100条开启虚拟滚动时,由于监听了data、virtualized会连续触发两次update方法:第一次update时,(updateSize)计算尺寸里的渲染数据(renderData)与表格行的dom是一一对应,之后会改变渲染数据(renderData)的值;而第二次执行update时,renderData改变了,而表格行dom未改变,导致renderData与dom不一一对应,从而位置计算错误,最终渲染的数据对应不上。因此使用每次nextTick只能执行一次来避免bug发生】
|
|
565
|
+
doUpdate () {
|
|
566
|
+
if (this.hasDoUpdate) return // nextTick内已经执行过一次就不执行
|
|
567
|
+
if (!this.scroller) return // scroller不存在说明未初始化完成,不执行
|
|
568
|
+
|
|
569
|
+
// 启动虚拟滚动的瞬间,需要暂时隐藏el-table__append-wrapper里的内容,不然会导致滚动位置一直到append的内容处
|
|
570
|
+
this.isHideAppend = true
|
|
571
|
+
this.onScroll()
|
|
572
|
+
this.hasDoUpdate = true
|
|
573
|
+
this.$nextTick(() => {
|
|
574
|
+
this.hasDoUpdate = false
|
|
575
|
+
this.isHideAppend = false
|
|
576
|
+
})
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
// 空闲时更新位置(触发时间:滚动停止后等待10ms执行)
|
|
580
|
+
// 场景:固定表格fixed变化时、扩展行展开滑动时需要更新
|
|
581
|
+
updatePosition () {
|
|
582
|
+
this.timer && clearTimeout(this.timer)
|
|
583
|
+
this.timer = setTimeout(() => {
|
|
584
|
+
this.timer && clearTimeout(this.timer)
|
|
585
|
+
// 传入false,避免一直循环调用
|
|
586
|
+
this.handleScroll(false)
|
|
587
|
+
}, this.throttleTime + 10)
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
// 渲染全部数据
|
|
591
|
+
renderAllData () {
|
|
592
|
+
this.renderData = this.listData
|
|
593
|
+
this.$emit('change', this.listData, 0, this.listData.length - 1)
|
|
594
|
+
console.log('renderAllData', this.listData)
|
|
595
|
+
|
|
596
|
+
this.$nextTick(() => {
|
|
597
|
+
// 清除撑起的高度和位置
|
|
598
|
+
TableBodyClassNames.forEach(className => {
|
|
599
|
+
const el = this.$el.querySelector(className)
|
|
600
|
+
if (!el) return
|
|
601
|
+
|
|
602
|
+
if (el.wrapEl) {
|
|
603
|
+
// 设置高度
|
|
604
|
+
el.wrapEl.style.height = 'auto'
|
|
605
|
+
// 设置transform撑起高度
|
|
606
|
+
el.innerEl.style.transform = `translateY(${0}px)`
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
})
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
// 恢复滚动位置(仅支持表格内部滚动)
|
|
613
|
+
restoreScroll () {
|
|
614
|
+
if (!this.scroller || !this.isInnerScroll) return
|
|
615
|
+
this.scroller.scrollLeft = this.keepScroll ? this.scrollPos[1] : 0
|
|
616
|
+
this.scroller.scrollTop = this.keepScroll ? this.scrollPos[0] : 0
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
// 【外部调用】更新
|
|
620
|
+
update () {
|
|
621
|
+
if (this.isTree) {
|
|
622
|
+
this.onFilterChange && this.onFilterChange()
|
|
623
|
+
}
|
|
624
|
+
// console.log('update')
|
|
625
|
+
this.handleScroll()
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
// 【外部调用】滚动到第几行
|
|
629
|
+
// (不太精确:滚动到第n行时,如果周围的表格行计算出真实高度后会更新高度,导致内容坍塌或撑起)
|
|
630
|
+
// offsetY - 偏移量
|
|
631
|
+
scrollTo (index, offsetY = 0, stop = false) {
|
|
632
|
+
const item = this.listData[index]
|
|
633
|
+
if (item && this.scroller) {
|
|
634
|
+
this.updateSizes()
|
|
635
|
+
this.calcRenderData()
|
|
636
|
+
|
|
637
|
+
this.$nextTick(() => {
|
|
638
|
+
const offsetTop = this.getItemOffsetTop(index) - offsetY
|
|
639
|
+
scrollToY(this.scroller, offsetTop)
|
|
640
|
+
|
|
641
|
+
// 调用两次scrollTo,第一次滚动时,如果表格行初次渲染高度发生变化时,会导致滚动位置有偏差,此时需要第二次执行滚动,确保滚动位置无误
|
|
642
|
+
if (!stop) {
|
|
643
|
+
setTimeout(() => {
|
|
644
|
+
this.scrollTo(index, offsetY, true)
|
|
645
|
+
}, 50)
|
|
646
|
+
}
|
|
647
|
+
})
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
|
|
651
|
+
// 【外部调用】滚动到对应的行
|
|
652
|
+
scrollToRow (row, offsetY = 0) {
|
|
653
|
+
const index = this.listData.findIndex((item) => item === row || item[this.keyProp] === row[this.keyProp])
|
|
654
|
+
this.scrollTo(index, offsetY)
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
// 【外部调用】重置 (没用废弃)
|
|
658
|
+
reset () {
|
|
659
|
+
this.sizes = {}
|
|
660
|
+
this.scrollTo(0, 0, false)
|
|
661
|
+
},
|
|
662
|
+
|
|
663
|
+
// 销毁
|
|
664
|
+
destory () {
|
|
665
|
+
if (this.scroller) {
|
|
666
|
+
this.scroller.removeEventListener('scroll', this.onScroll)
|
|
667
|
+
window.removeEventListener('resize', this.onScroll)
|
|
668
|
+
}
|
|
669
|
+
if (this.unWatchs) {
|
|
670
|
+
this.unWatchs.forEach(unWatch => unWatch())
|
|
671
|
+
}
|
|
672
|
+
if (this.removeMousewheelEvent) {
|
|
673
|
+
this.removeMousewheelEvent()
|
|
674
|
+
}
|
|
675
|
+
this.oldSelection = []
|
|
676
|
+
this.elTable = null
|
|
677
|
+
this.scroller = null
|
|
678
|
+
this.unWatchs = []
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
// 【VirtualColumn调用】获取列表全部数据】
|
|
682
|
+
// origin - 源数据,非筛选后的数据
|
|
683
|
+
getData (origin = true) {
|
|
684
|
+
return this.list || (origin ? this.data : this.listData)
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
// 【VirtualColumn调用】添加virtual-column实例
|
|
688
|
+
addColumn (vm) {
|
|
689
|
+
this.columnVms.push(vm)
|
|
690
|
+
},
|
|
691
|
+
|
|
692
|
+
// 【VirtualColumn调用】移除virtual-column实例
|
|
693
|
+
removeColumn (vm) {
|
|
694
|
+
this.columnVms = this.columnVms.filter(item => item !== vm)
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
// 【多选】选中所有列
|
|
698
|
+
checkAll (val, rows = this.listData, byUser = false) {
|
|
699
|
+
const removedRows = []
|
|
700
|
+
rows.forEach(row => {
|
|
701
|
+
if (row.$v_checked) {
|
|
702
|
+
removedRows.push(row)
|
|
703
|
+
}
|
|
704
|
+
if (row.$v_checked !== val) {
|
|
705
|
+
this.$set(row, '$v_checked', val)
|
|
706
|
+
this.$set(row, '$v_checkedOrder', val ? this.checkOrder++ : undefined)
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
const selection = this.emitSelectionChange(removedRows)
|
|
710
|
+
|
|
711
|
+
if (byUser) { // 当用户手动勾选全选 Checkbox 时触发的事件
|
|
712
|
+
this.$emit('select-all', selection, val)
|
|
713
|
+
this.elTable.$emit('select-all', selection, val)
|
|
714
|
+
}
|
|
715
|
+
if (val === false) {
|
|
716
|
+
this.checkOrder = 0 // 取消全选,则重置checkOrder
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
// 【多选】选中某一列
|
|
721
|
+
checkRow (row, val, emit = true, byUser = false) {
|
|
722
|
+
if (row.$v_checked === val) return
|
|
723
|
+
|
|
724
|
+
this.$set(row, '$v_checked', val)
|
|
725
|
+
this.$set(row, '$v_checkedOrder', val ? this.checkOrder++ : undefined)
|
|
726
|
+
if (emit) {
|
|
727
|
+
const selection = this.emitSelectionChange(val ? [] : [row])
|
|
728
|
+
if (byUser) { // 当用户手动勾选数据行的 Checkbox 时触发的事件
|
|
729
|
+
this.$emit('select', selection, row, val)
|
|
730
|
+
this.elTable.$emit('select', selection, row, val)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
// 【多选】兼容表格clearSelection方法
|
|
736
|
+
clearSelection () {
|
|
737
|
+
// 清除旧的选中项
|
|
738
|
+
this.oldSelection.forEach(row => {
|
|
739
|
+
this.$set(row, '$v_checked', false)
|
|
740
|
+
})
|
|
741
|
+
this.oldSelection = []
|
|
742
|
+
|
|
743
|
+
// 清除所有选中项
|
|
744
|
+
this.checkAll(false)
|
|
745
|
+
this.columnVms.forEach(vm => vm.syncCheckStatus())
|
|
746
|
+
},
|
|
747
|
+
|
|
748
|
+
// 【多选】兼容表格toggleRowSelection方法
|
|
749
|
+
toggleRowSelection (row, selected) {
|
|
750
|
+
if (!Array.isArray(row)) {
|
|
751
|
+
row = [row]
|
|
752
|
+
}
|
|
753
|
+
this.toggleRowsSelection(row, selected)
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
// 【扩展多选】表格切换多个row选中状态
|
|
757
|
+
toggleRowsSelection (rows, selected) {
|
|
758
|
+
// reserve-selection 模式用到的变量
|
|
759
|
+
const oldSelectedMap = {} // 保留值map(旧的选中值)
|
|
760
|
+
const curSelectedMap = {} // 当前选中值map
|
|
761
|
+
let toDeleteMap = null // 需删除值map
|
|
762
|
+
const isReserve = this.isReserveSelection()
|
|
763
|
+
if (isReserve) {
|
|
764
|
+
this.oldSelection.forEach(row => {
|
|
765
|
+
oldSelectedMap[row[this.keyProp]] = true
|
|
766
|
+
})
|
|
767
|
+
this.data.forEach(row => {
|
|
768
|
+
curSelectedMap[row[this.keyProp]] = true
|
|
769
|
+
})
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const removedRows = []
|
|
773
|
+
rows.forEach(row => {
|
|
774
|
+
const val = typeof selected === 'boolean' ? selected : !row.$v_checked
|
|
775
|
+
!val && removedRows.push(row)
|
|
776
|
+
this.$set(row, '$v_checked', val)
|
|
777
|
+
this.$set(row, '$v_checkedOrder', val ? this.checkOrder++ : undefined)
|
|
778
|
+
if (!isReserve) return
|
|
779
|
+
|
|
780
|
+
/* 处理reserve-selection 模式 */
|
|
781
|
+
// 如果row在保留值oldSelection里,且取消选中,则需要将它移除
|
|
782
|
+
const key = row[this.keyProp]
|
|
783
|
+
if (key in oldSelectedMap && !val) {
|
|
784
|
+
if (!toDeleteMap) toDeleteMap = {}
|
|
785
|
+
toDeleteMap[key] = true
|
|
786
|
+
}
|
|
787
|
+
// 如果row不在当前列表里,且为选中,则需要添加在保留值oldSelection里
|
|
788
|
+
if (!(key in curSelectedMap) && val) {
|
|
789
|
+
this.oldSelection.push(row)
|
|
790
|
+
}
|
|
791
|
+
})
|
|
792
|
+
if (toDeleteMap) {
|
|
793
|
+
this.oldSelection = this.oldSelection.filter(
|
|
794
|
+
row => !(row[this.keyProp] in toDeleteMap)
|
|
795
|
+
)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
this.emitSelectionChange(removedRows)
|
|
799
|
+
this.columnVms.forEach(vm => vm.syncCheckStatus())
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
// 【多选】兼容表格selection-change事件
|
|
803
|
+
emitSelectionChange (removedRows) {
|
|
804
|
+
const isReserve = this.isReserveSelection() // 是否保留旧的值
|
|
805
|
+
const selection = isReserve ? [...this.oldSelection] : []
|
|
806
|
+
this.data.forEach(row => {
|
|
807
|
+
if (row.$v_checked) {
|
|
808
|
+
selection.push(row)
|
|
809
|
+
}
|
|
810
|
+
})
|
|
811
|
+
this.sortSelection(selection)
|
|
812
|
+
this.$emit('selection-change', selection, removedRows)
|
|
813
|
+
this.elTable.$emit('selection-change', selection, removedRows)
|
|
814
|
+
// 对于 reserve-selection 模式,oldSelection始终保留旧的选中值,不保留当前选中值
|
|
815
|
+
// 对于 非 reserve-selection 模式,oldSelection始终保留当前选中值
|
|
816
|
+
if (!isReserve) {
|
|
817
|
+
this.oldSelection = [...selection]
|
|
818
|
+
}
|
|
819
|
+
return selection
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
// 【多选】兼容表格 reserve-selection,存储上次选中的值
|
|
823
|
+
updateSelectionByRowKey (data, oldData = []) {
|
|
824
|
+
if (!this.elTable) return
|
|
825
|
+
|
|
826
|
+
// 将旧的选中值存储到oldSelection中
|
|
827
|
+
oldData.forEach(row => {
|
|
828
|
+
if (row.$v_checked) {
|
|
829
|
+
this.oldSelection.push(row)
|
|
830
|
+
}
|
|
831
|
+
})
|
|
832
|
+
const selectedMap = {}
|
|
833
|
+
this.oldSelection.forEach(row => {
|
|
834
|
+
selectedMap[row[this.keyProp]] = true
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const usedMap = {} // 当前选中值的map
|
|
838
|
+
data.forEach(row => {
|
|
839
|
+
const key = row[this.keyProp]
|
|
840
|
+
if (key in selectedMap) {
|
|
841
|
+
this.$set(row, '$v_checked', true)
|
|
842
|
+
usedMap[key] = true
|
|
843
|
+
}
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
// 从oldSelection中移除当前选中的值
|
|
847
|
+
this.oldSelection = this.oldSelection.filter(row => !(row[this.keyProp] in usedMap))
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
// 【多选】多选列是否设置了 reserve-selection
|
|
851
|
+
isReserveSelection () {
|
|
852
|
+
return this.columnVms.some(vm => vm.reserveSelection && vm.isSelection())
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
// 获取选中值
|
|
856
|
+
getSelection () {
|
|
857
|
+
if (this.isReserveSelection()) {
|
|
858
|
+
const curSelection = this.data.filter(row => row.$v_checked)
|
|
859
|
+
return [...this.oldSelection, ...curSelection]
|
|
860
|
+
} else {
|
|
861
|
+
return this.oldSelection
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
// 【多选】更新多选的值
|
|
866
|
+
updateSelectionData (data, oldData) {
|
|
867
|
+
this.syncSelectionStatus()
|
|
868
|
+
|
|
869
|
+
if (data !== oldData) {
|
|
870
|
+
if (this.oldSelection.length > 0) {
|
|
871
|
+
// 修复多选select-change事件在表格数据更新后未触发,导致旧数据未清除 #100
|
|
872
|
+
this.$emit('selection-change', [], [...this.oldSelection])
|
|
873
|
+
this.elTable.$emit('selection-change', [], [...this.oldSelection])
|
|
874
|
+
this.oldSelection = []
|
|
875
|
+
}
|
|
876
|
+
return
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// 新的选中项
|
|
880
|
+
const selection = this.data.filter(row => row.$v_checked)
|
|
881
|
+
this.sortSelection(selection)
|
|
882
|
+
// 新的选中项key map
|
|
883
|
+
const selectionKeyMap = selection.reduce((map, dataItem) => {
|
|
884
|
+
map[dataItem[this.keyProp]] = true
|
|
885
|
+
return map
|
|
886
|
+
}, {})
|
|
887
|
+
// 移除的项
|
|
888
|
+
const removedRows = this.oldSelection.reduce((rows, row) => {
|
|
889
|
+
if (!(row[this.keyProp] in selectionKeyMap)) rows.push(row)
|
|
890
|
+
return rows
|
|
891
|
+
}, [])
|
|
892
|
+
// 手动删除选中项、新旧项不一致(正常不会发生),触发selection-change事件
|
|
893
|
+
if (removedRows.length || selection.length !== this.oldSelection.length) {
|
|
894
|
+
this.$emit('selection-change', selection, removedRows)
|
|
895
|
+
this.elTable.$emit('selection-change', selection, removedRows)
|
|
896
|
+
this.oldSelection = [...selection]
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
|
|
900
|
+
// 【多选】多选排序
|
|
901
|
+
sortSelection (selection) {
|
|
902
|
+
if (!this.selectionSort) return
|
|
903
|
+
if (typeof this.selectionSort === 'function') {
|
|
904
|
+
selection.sort((a, b) => this.selectionSort(a, b))
|
|
905
|
+
} else {
|
|
906
|
+
selection.sort((a, b) => a.$v_checkedOrder - b.$v_checkedOrder)
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
// 【多选】同步多选状态
|
|
911
|
+
syncSelectionStatus () {
|
|
912
|
+
const selectionVm = this.columnVms.find(vm => vm.isSelection())
|
|
913
|
+
if (selectionVm) {
|
|
914
|
+
selectionVm.syncCheckStatus()
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
|
|
918
|
+
// 【radio单选】设置选中行
|
|
919
|
+
setCurrentRow (row) {
|
|
920
|
+
this.curRow = row
|
|
921
|
+
this.$emit('current-change', row)
|
|
922
|
+
this.elTable.$emit('current-change', row)
|
|
923
|
+
},
|
|
924
|
+
|
|
925
|
+
// 【单选高亮】兼容行高亮
|
|
926
|
+
hackRowHighlight () {
|
|
927
|
+
// 兼容el-table的setCurrentRow:重写setCurrentRow方法
|
|
928
|
+
if (this.elTable.setCurrentRow.virtual) {
|
|
929
|
+
const originSetCurrentRow = this.elTable.setCurrentRow.bind(this.elTable) // 原方法
|
|
930
|
+
const setCurrentRow = (row) => { // 重写setCurrentRow
|
|
931
|
+
this.elTable.store.states.currentRow = this.highlightRow // 同步表格行高亮的值
|
|
932
|
+
if (this.highlightRow !== row) this.highlightRow = row // 同步highlightRow的值
|
|
933
|
+
originSetCurrentRow(row) // 执行原方法
|
|
934
|
+
}
|
|
935
|
+
this.elTable.setCurrentRow = setCurrentRow
|
|
936
|
+
setCurrentRow.virtual = true
|
|
937
|
+
}
|
|
938
|
+
// 兼容el-table的currentRowKey属性
|
|
939
|
+
const unWatch = this.$watch(() => this.elTable.currentRowKey, (val) => {
|
|
940
|
+
if (this.elTable.rowKey) {
|
|
941
|
+
const targetRow = this.listData.find(row => val === row[this.elTable.rowKey])
|
|
942
|
+
this.highlightRow = targetRow
|
|
943
|
+
}
|
|
944
|
+
}, { immediate: true })
|
|
945
|
+
this.unWatchs.push(unWatch)
|
|
946
|
+
|
|
947
|
+
// 监听高亮的事件
|
|
948
|
+
const onCurrentChange = (row) => {
|
|
949
|
+
this.highlightRow = row
|
|
950
|
+
}
|
|
951
|
+
this.elTable.$on('current-change', onCurrentChange)
|
|
952
|
+
this.unWatchs.push(() => {
|
|
953
|
+
this.elTable.$off('current-change', onCurrentChange)
|
|
954
|
+
})
|
|
955
|
+
},
|
|
956
|
+
|
|
957
|
+
// 【单选高亮】同步表格行高亮的值
|
|
958
|
+
syncRowsHighlight () {
|
|
959
|
+
if (!this.elTable.highlightCurrentRow) return
|
|
960
|
+
// 必须使用nextTick,不然值同步不上
|
|
961
|
+
this.$nextTick(() => {
|
|
962
|
+
this.elTable.store.states.currentRow = this.highlightRow
|
|
963
|
+
})
|
|
964
|
+
},
|
|
965
|
+
|
|
966
|
+
// 使用自定义的树形表格;需禁用原来的树(将children字段改为空)
|
|
967
|
+
useCustomSelection () {
|
|
968
|
+
this.isSelection = true
|
|
969
|
+
},
|
|
970
|
+
|
|
971
|
+
// 【多选高亮】兼容多选
|
|
972
|
+
hackSelection () {
|
|
973
|
+
// 兼容选中行高亮
|
|
974
|
+
const onFixedColumnsChange = () => {
|
|
975
|
+
if (!this.elTable) return
|
|
976
|
+
const tableBodyVm = this.elTable.$children.filter(vm => {
|
|
977
|
+
return vm.$options.name === 'ElTableBody'
|
|
978
|
+
})
|
|
979
|
+
tableBodyVm.forEach(vm => {
|
|
980
|
+
if (vm.getRowClass.virtual) return
|
|
981
|
+
// 重写 table-body组件的 getRowClass 方法
|
|
982
|
+
// 支持多选选中时高亮
|
|
983
|
+
const originGetRowClass = vm.getRowClass.bind(this.elTable)
|
|
984
|
+
vm.getRowClass = (row, rowIndex) => {
|
|
985
|
+
const classes = originGetRowClass(row, rowIndex)
|
|
986
|
+
if (this.elTable.highlightSelectionRow && row.$v_checked) {
|
|
987
|
+
classes.push('selection-row')
|
|
988
|
+
}
|
|
989
|
+
return classes
|
|
990
|
+
}
|
|
991
|
+
vm.getRowClass.virtual = true
|
|
992
|
+
})
|
|
993
|
+
}
|
|
994
|
+
const unWatch = this.$watch(
|
|
995
|
+
() => [this.elTable.fixedColumns, this.elTable.rightFixedColumns],
|
|
996
|
+
onFixedColumnsChange,
|
|
997
|
+
{ immediate: true }
|
|
998
|
+
)
|
|
999
|
+
this.unWatchs.push(unWatch)
|
|
1000
|
+
|
|
1001
|
+
// 兼容多选 toggleRowSelection 方法
|
|
1002
|
+
this.elTable.toggleRowSelection = (row, selected) => {
|
|
1003
|
+
this.toggleRowSelection(row, selected)
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
this.elTable.clearSelection = () => {
|
|
1007
|
+
this.clearSelection()
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// 【多选】兼容表格 toggleAllSelection 方法
|
|
1011
|
+
this.elTable.toggleAllSelection = () => {
|
|
1012
|
+
const selectionVm = this.columnVms.find(vm => vm.isSelection())
|
|
1013
|
+
if (selectionVm) {
|
|
1014
|
+
selectionVm.onCheckAllRows(!selectionVm.isCheckedAll)
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
|
|
1019
|
+
// 监听表格header-dragend事件
|
|
1020
|
+
hackTableHeaderDrag () {
|
|
1021
|
+
const onHeaderDragend = () => {
|
|
1022
|
+
// 设置状态,用于自定义固定列
|
|
1023
|
+
this.hasHeadDrag = true
|
|
1024
|
+
setTimeout(() => {
|
|
1025
|
+
// #50 修复el-table原bug: 刷新布局,列放大缩小让高度变大,导致布局错乱
|
|
1026
|
+
this.elTable.doLayout()
|
|
1027
|
+
})
|
|
1028
|
+
// 修复某一行内容很多时,将该行宽度拖拽成很宽,内容坍塌导致空白行(需要立即更新,因为要获取新行变化的高度)
|
|
1029
|
+
this.update()
|
|
1030
|
+
}
|
|
1031
|
+
this.elTable.$on('header-dragend', onHeaderDragend)
|
|
1032
|
+
this.unWatchs.push(() => {
|
|
1033
|
+
this.elTable.$off('header-dragend', onHeaderDragend)
|
|
1034
|
+
})
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
// 【展开行】使用扩展行
|
|
1038
|
+
useExpandTable () {
|
|
1039
|
+
this.isExpandType = true
|
|
1040
|
+
},
|
|
1041
|
+
|
|
1042
|
+
// 【展开行】监听表格expand-change事件
|
|
1043
|
+
hackTableExpand () {
|
|
1044
|
+
if (!this.isExpandType) return
|
|
1045
|
+
const { store } = this.elTable
|
|
1046
|
+
|
|
1047
|
+
// 判断表格行是否渲染扩展行
|
|
1048
|
+
store.isRowExpanded = (row) => {
|
|
1049
|
+
return row.$v_expanded
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// expandRowKeys 变化时候,设置展开的行
|
|
1053
|
+
store.setExpandRowKeys = () => {
|
|
1054
|
+
const { expandRowKeys } = this.elTable
|
|
1055
|
+
this.listData.forEach(row => {
|
|
1056
|
+
this.$set(row, '$v_expanded', expandRowKeys.includes(row[this.elTable.rowKey]))
|
|
1057
|
+
})
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// 表格data变化时,更新展开的行
|
|
1061
|
+
store.updateExpandRows = () => {
|
|
1062
|
+
const { defaultExpandAll } = this.elTable
|
|
1063
|
+
if (defaultExpandAll) {
|
|
1064
|
+
// 当设置了默认展开所有行时,直接设置所有行展开
|
|
1065
|
+
this.listData.forEach(row => {
|
|
1066
|
+
this.$set(row, '$v_expanded', true)
|
|
1067
|
+
})
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// 外部调动方法 toggleRowExpansion
|
|
1072
|
+
store.toggleRowExpansion = (row, expanded) => {
|
|
1073
|
+
const val = typeof expanded === 'boolean' ? expanded : !row.$v_expanded
|
|
1074
|
+
if (val === Boolean(row.$v_expanded)) return
|
|
1075
|
+
this.$set(row, '$v_expanded', val)
|
|
1076
|
+
// 暂时不做排序,没必要
|
|
1077
|
+
this.elTable.$emit('expand-change', row, this.listData.filter(row => row.$v_expanded))
|
|
1078
|
+
|
|
1079
|
+
if (this.updateExpandeding) return
|
|
1080
|
+
this.updateExpandeding = true
|
|
1081
|
+
this.$nextTick(() => {
|
|
1082
|
+
this.updateExpandeding = false
|
|
1083
|
+
this.update()
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// 重写expandRows的indexOf,使其能获取正确的展开状态
|
|
1088
|
+
store.states.expandRows.indexOf = (row) => {
|
|
1089
|
+
if (row) {
|
|
1090
|
+
return row.$v_expanded || -1
|
|
1091
|
+
}
|
|
1092
|
+
return -1
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
1095
|
+
|
|
1096
|
+
// 【展开行/树】切换某一行的展开状态
|
|
1097
|
+
toggleRowExpansion (row, expanded) {
|
|
1098
|
+
this.elTable.toggleRowExpansion(row, expanded)
|
|
1099
|
+
},
|
|
1100
|
+
|
|
1101
|
+
// 【自定义固定列】设置固定左右样式
|
|
1102
|
+
headerCellFixedStyle (data) {
|
|
1103
|
+
return this.cellFixedStyle(data, true)
|
|
1104
|
+
},
|
|
1105
|
+
|
|
1106
|
+
// 【自定义固定列】设置固定左右样式
|
|
1107
|
+
cellFixedStyle ({ column }, isHeader = false) {
|
|
1108
|
+
const elTable = this.getElTable()
|
|
1109
|
+
if (!elTable) return
|
|
1110
|
+
// 右边固定列头部需要加上滚动条宽度-gutterWidth
|
|
1111
|
+
const { gutterWidth: _gutterWidth, scrollY, bodyWidth } = elTable.layout
|
|
1112
|
+
const gutterWidth = isHeader && scrollY ? _gutterWidth : 0
|
|
1113
|
+
// 计算固定样式(当列宽度变化时重新计算,其余直接使用缓存值fixedMap)
|
|
1114
|
+
if (!this.fixedMap || this._isScrollY !== scrollY || this._bodyWidth !== bodyWidth || this.hasHeadDrag) {
|
|
1115
|
+
if (this.hasHeadDrag) this.hasHeadDrag = false
|
|
1116
|
+
this._isScrollY = scrollY
|
|
1117
|
+
this._bodyWidth = bodyWidth
|
|
1118
|
+
this.fixedMap = {}
|
|
1119
|
+
this.totalLeft = 0 // 左边固定定位累加值
|
|
1120
|
+
this.totalRight = 0 // 右边固定定位累加值
|
|
1121
|
+
|
|
1122
|
+
const columns = elTable.columns
|
|
1123
|
+
const rightColumns = [] // 右边固定列集合
|
|
1124
|
+
let lastLeftColumn // 左边固定列的最后一列
|
|
1125
|
+
let firstRightColumn // 右边固定列的第一列
|
|
1126
|
+
for (let i = 0; i < columns.length; i++) {
|
|
1127
|
+
const column = columns[i]
|
|
1128
|
+
const isLeft = column.className && column.className.includes('virtual-column__fixed-left')
|
|
1129
|
+
const isRight = column.className && column.className.includes('virtual-column__fixed-right')
|
|
1130
|
+
|
|
1131
|
+
if (!isLeft && !isRight) continue
|
|
1132
|
+
// 设置左边固定列定位样式
|
|
1133
|
+
if (isLeft) {
|
|
1134
|
+
lastLeftColumn = column
|
|
1135
|
+
this.fixedMap[column.id] = {
|
|
1136
|
+
left: this.totalLeft
|
|
1137
|
+
}
|
|
1138
|
+
this.totalLeft += column.realWidth || column.width
|
|
1139
|
+
}
|
|
1140
|
+
// 收集右边固定列
|
|
1141
|
+
if (isRight) {
|
|
1142
|
+
if (!firstRightColumn) firstRightColumn = column
|
|
1143
|
+
rightColumns.push(column)
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
// 设置固定列阴影classname
|
|
1147
|
+
const leftClass = ' is-last-column'
|
|
1148
|
+
const rightClass = ' is-first-column'
|
|
1149
|
+
|
|
1150
|
+
// 设置左边、右边固定列class
|
|
1151
|
+
if (lastLeftColumn && !lastLeftColumn.className.includes(leftClass)) lastLeftColumn.className += leftClass
|
|
1152
|
+
if (firstRightColumn && !firstRightColumn.className.includes(rightClass)) firstRightColumn.className += rightClass
|
|
1153
|
+
|
|
1154
|
+
// 设置右边固定列定位样式(从右往左依次)
|
|
1155
|
+
this.hasFixedRight = rightColumns.length > 0
|
|
1156
|
+
rightColumns.reverse().forEach(column => {
|
|
1157
|
+
this.fixedMap[column.id] = {
|
|
1158
|
+
right: this.totalRight
|
|
1159
|
+
}
|
|
1160
|
+
this.totalRight += column.realWidth || column.width
|
|
1161
|
+
})
|
|
1162
|
+
}
|
|
1163
|
+
const style = this.fixedMap[column.id]
|
|
1164
|
+
if (!style) return
|
|
1165
|
+
const isFixedRight = 'right' in style
|
|
1166
|
+
const curStyle = isFixedRight ? { right: style.right + gutterWidth + 'px' } : { left: style.left + 'px' }
|
|
1167
|
+
|
|
1168
|
+
// 修复 #89 使用 <virtual-column> vfixed, 表尾合计行对应的列没有固定
|
|
1169
|
+
if (elTable.showSummary) {
|
|
1170
|
+
this.$nextTick(() => {
|
|
1171
|
+
const footTh = this.$el.querySelector(`.el-table__footer-wrapper .${column.id}`)
|
|
1172
|
+
if (footTh) {
|
|
1173
|
+
if (curStyle.left) footTh.style.left = curStyle.left
|
|
1174
|
+
if (curStyle.right) footTh.style.right = curStyle.right
|
|
1175
|
+
}
|
|
1176
|
+
})
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return curStyle
|
|
1180
|
+
},
|
|
1181
|
+
|
|
1182
|
+
// 【自定义固定列】更新表头布局
|
|
1183
|
+
doHeaderLayout () {
|
|
1184
|
+
if (!this.elTable) return
|
|
1185
|
+
this.fixedMap = null
|
|
1186
|
+
this.elTable.$refs.tableHeader.$forceUpdate()
|
|
1187
|
+
},
|
|
1188
|
+
|
|
1189
|
+
// 绑定排序事件
|
|
1190
|
+
hackTableSort () {
|
|
1191
|
+
this.onSortChange = () => {
|
|
1192
|
+
if (!this.elTable) return
|
|
1193
|
+
const states = this.elTable.store.states
|
|
1194
|
+
const { sortingColumn } = states
|
|
1195
|
+
const data = this.filterData || this.data // 优先使用过滤后的数据进行排序
|
|
1196
|
+
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
|
|
1197
|
+
this.updateTreeData(data)
|
|
1198
|
+
} else {
|
|
1199
|
+
const listData = orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy)
|
|
1200
|
+
this.updateTreeData(listData)
|
|
1201
|
+
}
|
|
1202
|
+
// 触发更新
|
|
1203
|
+
this.doUpdate()
|
|
1204
|
+
if (!this.virtualized) {
|
|
1205
|
+
this.renderAllData()
|
|
1206
|
+
}
|
|
1207
|
+
this.$nextTick(() => {
|
|
1208
|
+
this.syncSelectionStatus()
|
|
1209
|
+
})
|
|
1210
|
+
}
|
|
1211
|
+
this.elTable.$on('sort-change', this.onSortChange)
|
|
1212
|
+
this.unWatchs.push(() => {
|
|
1213
|
+
this.elTable.$off('sort-change', this.onSortChange)
|
|
1214
|
+
})
|
|
1215
|
+
|
|
1216
|
+
// clearSort不会触发sort-change,需要重写clearSort方法,手动触发
|
|
1217
|
+
const originClearSort = this.elTable.clearSort.bind(this.elTable)
|
|
1218
|
+
this.elTable.clearSort = (...rest) => {
|
|
1219
|
+
originClearSort(...rest)
|
|
1220
|
+
this.onSortChange()
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
|
|
1224
|
+
// 绑定筛选事件
|
|
1225
|
+
hackTableFilter () {
|
|
1226
|
+
// 拦截el-table内部对树的筛选
|
|
1227
|
+
if (this.isTree) {
|
|
1228
|
+
this.$nextTick(() => {
|
|
1229
|
+
this.elTable.store.execQuery = () => {
|
|
1230
|
+
this.elTable.store.states.data = [...this.elTable.data]
|
|
1231
|
+
}
|
|
1232
|
+
})
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
this.onFilterChange = () => {
|
|
1236
|
+
if (!this.elTable) return
|
|
1237
|
+
const states = this.elTable.store.states
|
|
1238
|
+
const { filters } = states
|
|
1239
|
+
|
|
1240
|
+
// 使用原数据进行数据过滤
|
|
1241
|
+
let data = this.data
|
|
1242
|
+
Object.keys(filters).forEach((columnId) => {
|
|
1243
|
+
const values = states.filters[columnId]
|
|
1244
|
+
if (!values || values.length === 0) return
|
|
1245
|
+
const column = getColumnById(states, columnId)
|
|
1246
|
+
if (column && column.filterMethod) {
|
|
1247
|
+
data = data.filter((row) => {
|
|
1248
|
+
return values.some(value => column.filterMethod.call(null, value, row, column))
|
|
1249
|
+
})
|
|
1250
|
+
}
|
|
1251
|
+
})
|
|
1252
|
+
|
|
1253
|
+
// 过滤完之后,手动执行排序
|
|
1254
|
+
const hasFilter = this.data !== data
|
|
1255
|
+
this.filterData = hasFilter ? data : null
|
|
1256
|
+
this.onSortChange()
|
|
1257
|
+
}
|
|
1258
|
+
this.elTable.$on('filter-change', this.onFilterChange)
|
|
1259
|
+
this.unWatchs.push(() => {
|
|
1260
|
+
this.elTable.$off('filter-change', this.onFilterChange)
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1263
|
+
// clearFilter不会触发 filter-change,需要重写 clearFilter 方法,手动触发
|
|
1264
|
+
const originClearFilter = this.elTable.clearFilter.bind(this.elTable)
|
|
1265
|
+
this.elTable.clearFilter = (...rest) => {
|
|
1266
|
+
originClearFilter(...rest)
|
|
1267
|
+
this.onFilterChange()
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// init filters
|
|
1271
|
+
// 此处兼容列的 filtered-value 属性
|
|
1272
|
+
const states = this.elTable.store.states
|
|
1273
|
+
states.columns.forEach(column => {
|
|
1274
|
+
if (column.filteredValue && column.filteredValue.length) {
|
|
1275
|
+
this.onFilterChange()
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1278
|
+
},
|
|
1279
|
+
|
|
1280
|
+
// 使用自定义的树形表格;需禁用原来的树(将children字段改为空)
|
|
1281
|
+
// 场景:row包含children会被当做的树结构,与 virtual-column 的树结构有冲突,所以需要禁用原来的
|
|
1282
|
+
useCustomTree (isTry = true) {
|
|
1283
|
+
this.isTree = true
|
|
1284
|
+
const elTable = this.getElTable()
|
|
1285
|
+
if (elTable) {
|
|
1286
|
+
const states = elTable.store.states
|
|
1287
|
+
states.childrenColumnName = '' // 拦截原来树形表格
|
|
1288
|
+
states.lazyColumnIdentifier = '' // 拦截原来树形懒加载表格
|
|
1289
|
+
elTable.store.updateTreeData = () => {}
|
|
1290
|
+
} else if (isTry) {
|
|
1291
|
+
this.$nextTick(() => {
|
|
1292
|
+
this.useCustomTree(false)
|
|
1293
|
+
})
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
|
|
1297
|
+
// 兼容自定义树形表格
|
|
1298
|
+
hackCustomTree () {
|
|
1299
|
+
if (!this.isTree) return
|
|
1300
|
+
// 兼容 el-table 的 expand-row-keys属性
|
|
1301
|
+
const unWatch = this.$watch(() => this.elTable.expandRowKeys, () => {
|
|
1302
|
+
this.expandRowKeysChanged = true
|
|
1303
|
+
this.update()
|
|
1304
|
+
this.expandRowKeysChanged = false
|
|
1305
|
+
})
|
|
1306
|
+
this.unWatchs.push(unWatch)
|
|
1307
|
+
|
|
1308
|
+
// 兼容 el-table 的 toggleRowExpansion
|
|
1309
|
+
this.elTable.toggleRowExpansion = (row, expanded) => {
|
|
1310
|
+
const treeState = row.$v_tree
|
|
1311
|
+
if (!treeState) return
|
|
1312
|
+
|
|
1313
|
+
// 展开状态取反
|
|
1314
|
+
if (typeof expanded === 'undefined') {
|
|
1315
|
+
expanded = !treeState.expanded
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// 状态一致返回
|
|
1319
|
+
if (treeState.expanded === expanded) return
|
|
1320
|
+
treeState.expanded = expanded
|
|
1321
|
+
|
|
1322
|
+
// 防止多次触发update
|
|
1323
|
+
if (this.togglingRowExpansion) return
|
|
1324
|
+
|
|
1325
|
+
this.togglingRowExpansion = true
|
|
1326
|
+
this.$nextTick(() => {
|
|
1327
|
+
this.togglingRowExpansion = false
|
|
1328
|
+
this.update()
|
|
1329
|
+
this.columnVms.forEach(vm => vm.isTree && vm.renderTreeNode())
|
|
1330
|
+
})
|
|
1331
|
+
}
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1334
|
+
// 更新树:将树结构平铺,将展开的节点筛出来,同时对新节点插入$v_tree 记录树节点状态数据
|
|
1335
|
+
updateTreeData (data = this.data) {
|
|
1336
|
+
if (!this.isTree || !this.treeProps) {
|
|
1337
|
+
this.listData = data
|
|
1338
|
+
return
|
|
1339
|
+
}
|
|
1340
|
+
const res = []
|
|
1341
|
+
const { children = 'children' } = this.treeProps
|
|
1342
|
+
const { defaultExpandAll, expandRowKeys, rowKey, indent } = this.elTable
|
|
1343
|
+
|
|
1344
|
+
const getExpanded = (key) => {
|
|
1345
|
+
if (!key) return false
|
|
1346
|
+
return defaultExpandAll || (expandRowKeys && expandRowKeys.indexOf(key) !== -1) || false
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const traverse = (nodes, parent, level = 0, display = true) => {
|
|
1350
|
+
nodes.forEach(node => {
|
|
1351
|
+
if (display) {
|
|
1352
|
+
res.push(node)
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
let treeState = node.$v_tree || {}
|
|
1356
|
+
if (!node.$v_tree) {
|
|
1357
|
+
// 如果是新节点,添加 $v_tree($v_tree不设置成响应式,$v_tree值的变更可能会触发data变化,data watch会触发,这是不可控的,会导致updateTreeData多触发一次)
|
|
1358
|
+
node.$v_tree = treeState = {
|
|
1359
|
+
parent,
|
|
1360
|
+
level,
|
|
1361
|
+
expanded: getExpanded(node[rowKey]),
|
|
1362
|
+
loaded: false,
|
|
1363
|
+
loading: false,
|
|
1364
|
+
indent: (level - 1) * indent
|
|
1365
|
+
}
|
|
1366
|
+
} else {
|
|
1367
|
+
// 如果是旧节点
|
|
1368
|
+
// expandRowKeys更改时,旧的节点数据的 expanded(是否展开)以getExpanded方法为准
|
|
1369
|
+
if (this.expandRowKeysChanged) {
|
|
1370
|
+
treeState.expanded = getExpanded(node[rowKey])
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (Array.isArray(node[children])) {
|
|
1375
|
+
// 父节点显示且展开状态时,才显示子节点
|
|
1376
|
+
const childDisplay = display && treeState.expanded
|
|
1377
|
+
traverse(node[children], node, level + 1, childDisplay)
|
|
1378
|
+
}
|
|
1379
|
+
})
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// 开始遍历树结构
|
|
1383
|
+
traverse(data)
|
|
1384
|
+
this.listData = res
|
|
1385
|
+
// console.log('this.isTree', this.listData, this.listData.map(i => i.id))
|
|
1386
|
+
},
|
|
1387
|
+
|
|
1388
|
+
/*
|
|
1389
|
+
* 【扩展树形表格方法】获取子节点
|
|
1390
|
+
*/
|
|
1391
|
+
getChildNodes (row) {
|
|
1392
|
+
const { children = 'children' } = this.treeProps
|
|
1393
|
+
return row[children] || []
|
|
1394
|
+
},
|
|
1395
|
+
|
|
1396
|
+
// 【扩展树形表格方法】获取父节点
|
|
1397
|
+
getParentNode (row) {
|
|
1398
|
+
const treeState = row.$v_tree
|
|
1399
|
+
return treeState && treeState.parent
|
|
1400
|
+
},
|
|
1401
|
+
|
|
1402
|
+
// 【扩展树形表格方法】获取父层所有节点
|
|
1403
|
+
getParentNodes (row) {
|
|
1404
|
+
const res = []
|
|
1405
|
+
let curRow = row
|
|
1406
|
+
while (curRow) {
|
|
1407
|
+
const treeState = curRow.$v_tree
|
|
1408
|
+
if (!treeState || !treeState.parent) break
|
|
1409
|
+
res.unshift(treeState.parent)
|
|
1410
|
+
curRow = treeState.parent
|
|
1411
|
+
}
|
|
1412
|
+
return res
|
|
1413
|
+
},
|
|
1414
|
+
|
|
1415
|
+
// 【扩展树形表格方法】重新加载节点
|
|
1416
|
+
// 删除原来子节点,并触发load函数重新加载
|
|
1417
|
+
async reloadNode (row) {
|
|
1418
|
+
const treeState = row.$v_tree
|
|
1419
|
+
if (!treeState) return
|
|
1420
|
+
const { children = 'children', hasChildren = 'hasChildren' } = this.treeProps
|
|
1421
|
+
row[children] = null
|
|
1422
|
+
row[hasChildren] = true
|
|
1423
|
+
treeState.loaded = false
|
|
1424
|
+
this.update()
|
|
1425
|
+
const vm = this.columnVms.find(vm => vm.isTree)
|
|
1426
|
+
vm && await vm.loadChildNodes(row, true)
|
|
1427
|
+
},
|
|
1428
|
+
|
|
1429
|
+
// 【扩展树形表格方法】收起所有树节点
|
|
1430
|
+
unexpandAllNodes () {
|
|
1431
|
+
const { children } = this.treeProps
|
|
1432
|
+
// 遍历树节点,展开或收起
|
|
1433
|
+
const traverse = (rows) => {
|
|
1434
|
+
rows.forEach(row => {
|
|
1435
|
+
this.toggleRowExpansion(row, false)
|
|
1436
|
+
if (Array.isArray(row[children])) traverse(row[children])
|
|
1437
|
+
})
|
|
1438
|
+
}
|
|
1439
|
+
traverse(this.listData)
|
|
1440
|
+
},
|
|
1441
|
+
|
|
1442
|
+
// 【扩展树形表格方法】展开所有树节点
|
|
1443
|
+
expandAllNodes () {
|
|
1444
|
+
// 遍历树节点,展开或收起
|
|
1445
|
+
const { children, hasChildren = 'hasChildren' } = this.treeProps
|
|
1446
|
+
const traverse = (rows) => {
|
|
1447
|
+
rows.forEach(row => {
|
|
1448
|
+
const treeState = row.$v_tree
|
|
1449
|
+
// 懒加载节点如果未加载则不展开
|
|
1450
|
+
if (row[hasChildren] && !treeState.loaded) return
|
|
1451
|
+
this.toggleRowExpansion(row, true)
|
|
1452
|
+
if (Array.isArray(row[children])) traverse(row[children])
|
|
1453
|
+
})
|
|
1454
|
+
}
|
|
1455
|
+
traverse(this.listData)
|
|
1456
|
+
},
|
|
1457
|
+
|
|
1458
|
+
// 表格销毁事件
|
|
1459
|
+
bindTableDestory () {
|
|
1460
|
+
const onTableDestory = () => {
|
|
1461
|
+
this.warn && console.warn('<el-table> 组件销毁时,建议将 <el-table-virtual-scroll> 组件一同销毁')
|
|
1462
|
+
this.destory()
|
|
1463
|
+
this.$nextTick(() => {
|
|
1464
|
+
this.initData()
|
|
1465
|
+
})
|
|
1466
|
+
}
|
|
1467
|
+
// 防止el-table绑定key时,重新渲染表格但没有重新初始化<virtual-scroll>组件
|
|
1468
|
+
this.elTable.$on('hook:beforeDestory', onTableDestory)
|
|
1469
|
+
this.unWatchs.push(() => {
|
|
1470
|
+
this.elTable.$off('hook:beforeDestory', onTableDestory)
|
|
1471
|
+
})
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
watch: {
|
|
1475
|
+
data (data, oldData) {
|
|
1476
|
+
this.listData = data
|
|
1477
|
+
|
|
1478
|
+
if (this.list && data !== oldData) {
|
|
1479
|
+
this.list = data
|
|
1480
|
+
}
|
|
1481
|
+
// 筛选数据后会调用update更新视图
|
|
1482
|
+
this.onFilterChange && this.onFilterChange()
|
|
1483
|
+
if (this.isReserveSelection()) {
|
|
1484
|
+
// 保留旧的选中值
|
|
1485
|
+
this.updateSelectionByRowKey(data, oldData)
|
|
1486
|
+
} else {
|
|
1487
|
+
// 对比新旧值,移除删除的选中值
|
|
1488
|
+
this.updateSelectionData(data, oldData)
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
virtualized: {
|
|
1492
|
+
immediate: true,
|
|
1493
|
+
handler () {
|
|
1494
|
+
// 筛选数据后会调用update更新视图
|
|
1495
|
+
this.onFilterChange && this.onFilterChange()
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
disabled () {
|
|
1499
|
+
this.doUpdate()
|
|
1500
|
+
},
|
|
1501
|
+
treeMap () {
|
|
1502
|
+
this.update()
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
created () {
|
|
1506
|
+
this.$nextTick(() => {
|
|
1507
|
+
this.initData()
|
|
1508
|
+
})
|
|
1509
|
+
},
|
|
1510
|
+
activated () {
|
|
1511
|
+
this.isDeactivated = false
|
|
1512
|
+
if(!this.elTable || (this.elTable && !this.elTable.fit)){
|
|
1513
|
+
this.restoreScroll()
|
|
1514
|
+
}
|
|
1515
|
+
// this.elTable?.fit === false && this.restoreScroll()
|
|
1516
|
+
},
|
|
1517
|
+
deactivated () {
|
|
1518
|
+
this.isDeactivated = true
|
|
1519
|
+
},
|
|
1520
|
+
beforeDestroy () {
|
|
1521
|
+
this.destory()
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
</script>
|
|
1525
|
+
|
|
1526
|
+
<style lang="scss">
|
|
1527
|
+
.el-table-virtual-scroll {
|
|
1528
|
+
&.has-custom-fixed-right {
|
|
1529
|
+
.el-table__cell.gutter {
|
|
1530
|
+
position: sticky;
|
|
1531
|
+
right: 0;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
</style>
|
|
1536
|
+
|
|
1537
|
+
<style lang='scss' scoped>
|
|
1538
|
+
.is-expanding {
|
|
1539
|
+
::v-deep .el-table__expand-icon {
|
|
1540
|
+
transition: none;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
.hide-append {
|
|
1544
|
+
::v-deep .el-table__append-wrapper {
|
|
1545
|
+
display: none;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
</style>
|