前言
最近产品大大找我一顿吐槽,说我们的后台管理系统表格数据超出屏幕时,都得滑到最下面才能去拉横向滚动条,体验非常不好。
个人觉得键盘左右键也挺好的,要不再忍忍?
行,听产品的
实现思路
由于项目中涉及的页面过多,一个个页面修改肯定不现实。
可以利用vue的属性透传,在不改变原Table组件功能的前提下,包装原组件,新增自适应高度方法,列表页需要的就替换成新组件,其它页则不受影响。
属性透传涉及,属性,方法以及作用域插槽。
ref属性并不支持透传,为了方便原组件事件调用,可以在新组件中,对原组件方法进行代理访问.
实现效果图
具体实现
新建AutoHeightTable组件,进行属性透传
通过Vue提供的h函数方法,进行属性,方法和作用域插槽的透传。
$attrs
为属性,$listeners
为方法,$scopedSlots
保存作用域插槽。
js
import { h } from 'vue'
render() {
return h('Table', {
ref: 'table',
props: {
...this.$attrs,
// 传入计算后的最大高度
height: this.maxHeight
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
}
通过MutationObserver监听dom变化
通过MutationObserver
监听表格dom的变化,dom变化后,计算当前页面的剩余空间,并将值赋予height属性。
用户缩小屏幕尺寸时,表格顶部的搜索框会换行,这个时候就需要重新计算表格高度。
由于产品要求默认充满屏幕,滚动条始终在底部,所以使用的height属性,个人觉得maxHeight会更加友好。
由于要保持页面源码的最小改动,默认底部距离bottom
给了固定页脚高度80
,如需对页脚高度进行计算,可通过传入类名或元素dom的方式进行计算。
js
mounted() {
this.initObServer()
},
beforeDestroy() {
this.disconnectObserver()
},
methods: {
initObServer() {
if (!this.$refs.table) {
return
}
// 通过MutationObserver监听dom变化
this.observer = new MutationObserver(() => {
this.getMaxHeight()
})
this.observer.observe(this.$refs.table.$el, {
childList: true
})
// 监听尺寸变化
window.addEventListener('resize', this.getMaxHeight)
},
disconnectObserver() {
// 关闭监听
if (this.observer) {
this.observer.disconnect()
this.observer = null
}
window.removeEventListener('resize', this.getMaxHeight)
},
// 计算高度
getMaxHeight() {
if (!this.$refs.table) {
return
}
const { top } = this.$refs.table.$el.getBoundingClientRect()
// 屏幕剩余空间 = 屏幕高度 - 表格顶部 - 表格底部
this.maxHeight = window.innerHeight - top - this.bottom
}
}
原Table组件方法代理
包装原组件经常会遇到一个问题
父组件通过ref直接调用组件内部的方法,而vue中并没有如react进行ref透传的方法。
父组件调用方法则必须通过refs.selection.refs.table.getSelection()。
html
<AutoHeightTable
border
:loading="loading"
:columns="columnsData"
:data="tableData"
ref="selection"></AutoHeightTable>
js
// 获取表格选中项
this.$refs.selection.$refs.table.getSelection()
为了最小化改动源代码,通过属性代理
的方式,来减少ref链的调用。
js
// 代理属性
const PROXY_PROPERTIES = [
'getSelection'
]
beforeCreate() {
// ref无法透传,对相关属性进行代理
PROXY_PROPERTIES.forEach(key => {
Object.defineProperty(this, key, {
get() {
return this.$refs.table[key]
}
})
})
}
此时源码中的调用便会正常获取到
js
// 获取表格选中项
this.$refs.selection.getSelection()
完整代码
js
import { h } from 'vue'
// 代理属性
const PROXY_PROPERTIES = [
'getSelection'
]
export default {
name: 'auto-table',
props: {
// 底部距离
bottom: {
type: Number,
default: 80
}
},
data() {
return {
maxHeight: 0
}
},
beforeCreate() {
// ref无法透传,对相关属性进行代理
PROXY_PROPERTIES.forEach(key => {
Object.defineProperty(this, key, {
get() {
return this.$refs.table[key]
}
})
})
},
mounted() {
this.initObServer()
},
beforeDestroy() {
this.disconnectObserver()
},
methods: {
initObServer() {
if (!this.$refs.table) {
return
}
this.observer = new MutationObserver(() => {
this.getMaxHeight()
})
this.observer.observe(this.$refs.table.$el, {
childList: true
})
window.addEventListener('resize', this.getMaxHeight)
},
disconnectObserver() {
if (this.observer) {
this.observer.disconnect()
this.observer = null
}
window.removeEventListener('resize', this.getMaxHeight)
},
getMaxHeight() {
if (!this.$refs.table) {
return
}
const { top } = this.$refs.table.$el.getBoundingClientRect()
this.maxHeight = window.innerHeight - top - this.bottom
}
},
render() {
return h('Table', {
ref: 'table',
props: {
...this.$attrs,
height: this.maxHeight
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
})
}
}