小蚁智能摄像机
217.1MB · 2025-11-09
在后台管理系统或数据监控场景中,经常需要实现表格无缝滚动展示数据,同时希望隐藏滚动条保持界面整洁。本文将基于 Element UI 实现一个 无滚动条、无缝循环、hover 暂停、状态高亮 的高性能滚动表格,全程流畅无卡顿,适配多浏览器。
scrollTop 控制滚动,关闭平滑滚动避免停顿| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tableData | Array | [] | 表格数据源(必传) |
columns | Array | [] | 列配置(必传,支持 statusConfig 状态样式) |
rowHeight | Number | 36 | 行高(单位:px) |
scrollSpeed | Number | 20 | 滚动速度(毫秒 / 像素),值越小越快 |
scrollPauseOnHover | Boolean | true | 鼠标悬浮是否暂停滚动 |
tableHeight | Number | 300 | 表格高度(父组件配置) |
<template>
<div class="tableView">
<el-table
:data="combinedData"
ref="scrollTable"
style="width: 100%"
height="100%"
@cell-mouse-enter="handleMouseEnter"
@cell-mouse-leave="handleMouseLeave"
:cell-style="handleCellStyle"
:show-header="true"
>
<el-table-column
v-for="(column, index) in columns"
v-bind="column"
:key="index + (column.prop || index)"
:min-width="column.minWidth || '100px'"
>
<template slot-scope="scope">
<span v-if="column.statusConfig" :class="getColumnStatusClass(column, scope.row)">
{{ scope.row[column.prop] }}
</span>
<span v-else>
{{ scope.row[column.prop] }}
</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'SeamlessScrollTable',
props: {
tableData: {
type: Array,
required: true,
default: () => [],
},
columns: {
type: Array,
required: true,
default: () => [],
},
rowHeight: {
type: Number,
default: 36,
},
scrollSpeed: {
type: Number,
default: 20, // 滚动速度(毫秒/像素),20-40ms
},
scrollPauseOnHover: {
type: Boolean,
default: true,
},
},
data() {
return {
autoPlay: true,
timer: null,
offset: 0,
combinedData: [], // 拼接后的数据,用于实现无缝滚动
}
},
computed: {
// 计算表格可滚动的总高度(仅当数据足够多时才滚动)
scrollableHeight() {
return this.tableData.length * this.rowHeight
},
// 表格容器可视高度
viewportHeight() {
return this.$refs.scrollTable?.$el.clientHeight || 0
},
},
watch: {
tableData: {
handler(newVal) {
// 数据变化时,重新拼接数据
this.combinedData = [...newVal, ...newVal]
this.offset = 0
this.restartScroll()
},
immediate: true,
deep: true,
},
autoPlay(newVal) {
newVal ? this.startScroll() : this.pauseScroll()
},
},
mounted() {
this.$nextTick(() => {
// 只有当数据总高度 > 可视高度时,才启动滚动
if (this.scrollableHeight > this.viewportHeight) {
this.startScroll()
}
})
},
beforeDestroy() {
this.pauseScroll()
},
methods: {
handleMouseEnter() {
this.scrollPauseOnHover && (this.autoPlay = false)
},
handleMouseLeave() {
this.scrollPauseOnHover && (this.autoPlay = true)
},
startScroll() {
this.pauseScroll()
const tableBody = this.$refs.scrollTable?.bodyWrapper
if (!tableBody || this.tableData.length === 0) return
this.timer = setInterval(() => {
if (!this.autoPlay) return
this.offset += 1
tableBody.scrollTop = this.offset
// 关键:当滚动到原数据末尾时,瞬间重置滚动位置到开头
if (this.offset >= this.scrollableHeight) {
this.offset = 0
tableBody.scrollTop = 0
}
}, this.scrollSpeed)
},
pauseScroll() {
this.timer && clearInterval(this.timer)
this.timer = null
},
restartScroll() {
this.pauseScroll()
if (this.scrollableHeight > this.viewportHeight) {
this.startScroll()
}
},
getColumnStatusClass(column, row) {
const statusKey = column.statusField || column.prop
const statusValue = row[statusKey]
return typeof column.statusConfig === 'function'
? column.statusConfig(statusValue, row)
: column.statusConfig[statusValue] || ''
},
handleCellStyle() {
return {
padding: '4px 0',
height: `${this.rowHeight}px`,
lineHeight: `${this.rowHeight}px`,
}
},
},
}
</script>
<style scoped lang="scss">
.tableView {
width: 100%;
height: 100%;
overflow: hidden;
::v-deep .el-table {
background-color: transparent;
color: #303133;
border-collapse: separate;
border-spacing: 0;
&::before {
display: none;
}
th.el-table__cell.is-leaf {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
background: transparent !important;
font-weight: 500;
color: rgba(0, 0, 0, 0.6);
padding: 8px 0;
}
tr.el-table__row {
background-color: transparent;
transition: background-color 0.2s ease;
&:hover td {
background-color: rgba(0, 0, 0, 0.02) !important;
}
}
.el-table__cell {
border: none;
padding: 4px 0;
.cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 8px;
}
}
.el-table__body-wrapper {
height: 100%;
scroll-behavior: auto;
&::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
}
::v-deep .status-warning {
color: #e6a23c;
font-weight: 500;
}
::v-deep .status-danger {
color: #f56c6c;
font-weight: 500;
}
::v-deep .status-success {
color: #67c23a;
font-weight: 500;
}
::v-deep .status-info {
color: #409eff;
font-weight: 500;
}
}
</style>
<template>
<div class="table-container">
<h2 class="table-title">设备状态监控表格</h2>
<div class="table-wrapper" :style="{ height: tableHeight + 'px' }">
<!-- 配置滚动参数 -->
<seamless-scroll-table
:table-data="tableData"
:columns="columns"
:row-height="36"
:scroll-speed="30"
/>
</div>
</div>
</template>
<script>
import SeamlessScrollTable from './SeamlessScrollTable.vue'
export default {
name: 'DeviceStatusTable',
components: { SeamlessScrollTable },
data() {
return {
tableHeight: 300, // 表格高度可配置
// 表格数据
tableData: [
{ id: '1001', name: '设备A', type: '温度', state: '待检查' },
{ id: '1002', name: '设备B', type: '压力', state: '已超期' },
{ id: '1003', name: '设备C', type: '湿度', state: '已完成' },
{ id: '1004', name: '设备D', type: '电压', state: '超期完成' },
{ id: '1005', name: '设备E', type: '电流', state: '待检查' },
{ id: '1006', name: '设备F', type: '电阻', state: '已超期' },
{ id: '1007', name: '设备G', type: '功率', state: '已完成' },
],
// 列配置
columns: [
{ prop: 'id', label: '编号', minWidth: '140px' },
{ prop: 'name', label: '名称', width: '100px' },
{ prop: 'type', label: '设备类型', width: '120px' },
{
prop: 'state',
label: '状态',
width: '100px',
statusField: 'state',
// 状态样式配置(支持对象/函数)
statusConfig: {
待检查: 'status-warning',
已超期: 'status-danger',
已完成: 'status-success',
超期完成: 'status-info',
},
},
],
}
},
methods: {
getStatusClass(state) {
const statusMap = {
待检查: 'status-warning',
已超期: 'status-danger',
已完成: 'status-success',
超期完成: 'status-info',
}
return statusMap[state] || ''
},
},
}
</script>
<style scoped lang="scss">
.table-container {
width: 100%;
max-width: 500px;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
}
.table-title {
color: #303133;
margin-bottom: 16px;
font-size: 18px;
font-weight: 500;
text-align: center;
position: relative;
}
.table-wrapper {
background-color: #ffffff;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
box-sizing: border-box;
}
</style>