Vue2+el-table实现表格行上下滚动,表格单元格内容溢出左右滚动 TextScroll、TableWrapper
TextScroll 文本左右滚动容器组件
<template>
<div ref="wrapper" class="scroll-wrapper" @mouseenter="mouseenter" @mouseleave="mouseleave">
<div ref="text" class="text" :style="{transform: `translateX(${translateX}px)`}">
<slot>{{ text }}</slot>
</div>
</div>
</template>
<script>
export default {
name: 'TextScroll',
props: {
text: [String, Number],
step: {
type: [String, Number],
default: 0.2
}
},
data() {
return {
translateX: 0,
trip: 0,
realStep: 1,
frameId: null
}
},
mounted() {
const rect = this.$refs.text.getBoundingClientRect()
const wrapperRect = this.$refs.wrapper.getBoundingClientRect()
this.trip = rect.right - wrapperRect.width - rect.left
this.mouseleave()
this.$emit('hook:beforeDestroy', () => {
this.mouseenter()
})
},
methods: {
mouseenter() {
window.cancelAnimationFrame(this.frameId)
},
mouseleave() {
if (this.trip > 8) {
this.frameId = window.requestAnimationFrame(this.animate)
}
},
animate() {
if (Math.abs(this.translateX) >= this.trip) {
this.realStep = this.step
}
if (this.translateX >= 0) {
this.realStep = this.step * -1
}
this.translateX += this.realStep
this.frameId = window.requestAnimationFrame(this.animate)
}
}
}
</script>
<style lang="scss" scoped>
.scroll-wrapper {
overflow: hidden;
.text {
padding: 0 4px;
display: inline-block;
white-space: nowrap;
}
}
</style>
TableWrapper 表格上下滚动容器组件
- columns:表格列配置属性
Eg. [{ label: '表头', prop: 'name', formatter: (content,row)=>content, style: { width: '160px' } }] - data:表格配置数据
Eg.[{ name: '父类', child: [{name: '子类0'}] }] - full:表格在容器内平铺展示
- tdspace:表格边框是否分隔
- expand:是否开启子行
- step:行上下滚动补偿
<template>
<div :class="['table-wrapper',{full:full}]">
<table>
<thead>
<tr>
<th v-for="(column,index) in columns" :key="index" :class="`th-${index}`"
:style="Object.assign({},column.style||{},column.headStyle||{})">
{{ column.label }}</th>
</tr>
</thead>
</table>
<el-scrollbar ref="scroll">
<table v-if="data.length" :class="['table-body',{'td-space':tdspace}]"
:style="{transform: `translateY(${translateY}px)`}" @mouseenter="mouseenter"
@mouseleave="mouseleave">
<tbody>
<template v-for="(item,key) in data">
<tr :key="key" :class="[`tr-${key}`, `border-${item.key}`]"
:style="{cursor: expand?'pointer':'auto'}" @click="doExpand(item.key)">
<template v-for="(column,index) in columns">
<td v-if="column.type === 'index'" :key="`${key}-${index}`" :class="`td-${index}`"
:style="column.style||{}">
{{ key+1 }}
</td>
<td v-else :key="`${key}-${index}`" :class="`td-${index}`"
:style="column.style||{}">
<TextScroll
:text="column.formatter?column.formatter(item[column.prop],item):item[column.prop]" />
</td>
</template>
</tr>
<template v-if="item.child && expandKey[item.key]">
<tr v-for="(v,i) in item.child" :key="`${item.key}-${i}`"
:class="[`tr-${key+i+1}`,`border-${item.key}`]" class="child">
<template v-for="(column,index) in columns">
<td v-if="column.type === 'index'" :key="`${key+i+1}-${index}`"
:class="`td-${index}`" :style="column.style||{}">
{{ i+1 }}
</td>
<td v-else :key="`${key+i+1}-${index}`" :class="`td-${index}`"
:style="column.style||{}">
<TextScroll
:text="column.formatter?column.formatter(v[column.prop],v):v[column.prop]" />
</td>
</template>
</tr>
</template>
</template>
</tbody>
</table>
<div v-else class="empty">暂无数据</div>
</el-scrollbar>
</div>
</template>
<script>
import TextScroll from './TextScroll.vue'
export default {
name: 'TablerWrapper',
components: {
TextScroll
},
props: {
columns: Array,
data: Array,
full: Boolean,
tdspace: {
type: Boolean,
default: false
},
expand: Boolean,
step: {
type: [String, Number],
default: 0.3
}
},
data() {
return {
translateY: 0,
staticTranslateY: 0,
trip: 0,
realStep: 1,
frameId: null,
expandKey: {}
}
},
watch: {
data(n) {
if (n.length) {
this.expandKey = this.data.reduce((s, v) => {
s[v.key] = true
return s
}, {})
this.$nextTick(() => {
this.initScroll()
this.mouseleave()
})
}
}
},
methods: {
initScroll() {
window.cancelAnimationFrame(this.frameId)
const rect = this.$refs.scroll.$el.querySelector('.table-body').getBoundingClientRect()
const wrapperRect = this.$refs.scroll.$el.querySelector('.el-scrollbar__view').getBoundingClientRect()
this.trip = rect.bottom - wrapperRect.height - rect.top
},
mouseenter() {
window.cancelAnimationFrame(this.frameId)
const translateY = this.translateY
this.translateY = 0
this.$nextTick(() => {
this.$refs.scroll.$el.querySelector('.el-scrollbar__wrap').scrollTop = Math.abs(translateY)
})
},
mouseleave() {
const translateY = this.$refs.scroll.$el.querySelector('.el-scrollbar__wrap').scrollTop
this.$refs.scroll.$el.querySelector('.el-scrollbar__wrap').scrollTop = 0
this.translateY = translateY * -1
if (this.trip > 8) {
this.frameId = window.requestAnimationFrame(this.animate)
}
},
animate() {
if (Math.abs(this.translateY) >= this.trip) {
this.realStep = this.step
}
if (this.translateY >= 0) {
this.realStep = this.step * -1
}
this.translateY += this.realStep
this.frameId = window.requestAnimationFrame(this.animate)
},
doExpand(key) {
this.expandKey = { ...this.expandKey, [key]: !this.expandKey[key] }
this.$nextTick(() => {
this.initScroll()
})
}
}
}
</script>
<style lang="scss" scoped>
.table-wrapper {
color: #fff;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
table {
table-layout: fixed;
border-collapse: separate;
border-spacing: 0 3px;
width: 100%;
thead th {
font-size: 14px;
text-align: center;
vertical-align: bottom;
padding: 10px 5px;
background: rgba(69, 167, 248, 0.2980392156862745);
}
td {
color: #fff;
font-size: 18px;
padding: 6px 6px;
text-align: center;
line-height: 16px;
vertical-align: middle;
background: rgba(69, 167, 248, 0.09803921568627451);
}
.child {
td {
background: rgba(60, 165, 250, 0.298);
}
}
td,
th {
&:first-child {
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
}
}
}
.table-body.td-space {
border-collapse: separate;
border-spacing: 3px 3px;
}
.el-scrollbar {
position: relative;
flex: 1;
overflow: hidden;
}
}
.full {
::v-deep .el-scrollbar__view {
height: 100%;
}
.table-body {
min-height: 100%;
td {
vertical-align: middle;
}
}
}
.empty {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-size: 12px;
}
</style>