1.vue 结合原生tabe 渲染表格 实现 固定头部、固定右侧、单选按钮(避免el-table 数据量大卡顿)
// table.vue
<template>
<div id="oldTable" v-if="headerData.length && tableData.length">
<div class="table-scroll-mod" :style="{height: height+ 'px'}">
<div class="table-scroll-box" :style="{height: height+ 'px'}" ref="tableScrollBox">
<div class="fixed-head" ref="tableFixedHead">
<table>
<thead>
<tr>
<th v-if="checkBox" :width="checkBoxWidth">
<div class="te_cell">
<input type="checkbox" v-model="isAllCheck" @click="checkAll" name="table"
value="all"/>
</div>
</th>
<th :width="item.width || 100" v-for="(item, index) in headerCol" :key="index"
v-if="item.prop !== 'checkbox'">
<div class="te_cell">{{item.label}}</div>
</th>
</tr>
</thead>
</table>
</div>
<div class="fixed-right" :class="isMac ? '' : 'fixed-right_h'" ref="tableFixedRight"
:style="{width: (headerCol[rightIndex].width || 100) + 'px'}">
<table>
<thead>
<tr>
<td :width="headerCol[rightIndex].width">
<div class="te_cell">{{headerCol[rightIndex].label}}</div>
</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in tableData" :key="index">
<td :width="headerCol[rightIndex].width">
<div class="te_cell">
<slot name="fixedRight" :row="item"></slot>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="fixedTopRight" :style="{width: (headerCol[rightIndex].width || 100) + 'px'}">
<span class="te_cell">{{headerCol[rightIndex].label}}</span>
</div>
<table class="table-content__warrp">
<thead>
<tr>
<th :width="item.width" v-for="(item, index) in headerCol" :key="index">
<div class="te_cell">{{item.label}}</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in tableData" :key="index">
<td :width="checkBoxWidth">
<div class="te_cell" v-if="checkBox">
<input type="checkbox" name="table" v-model="checkBoxList[index]"
@click="checkItem(item, index)" :value="index"/>
</div>
</td>
<td :width="child.width || 100" v-for="(child, index_c) in headerCol" :key="index_c"
v-if="child.prop !== 'checkbox'"
:draggable="draggable"
@dragstart="onDragstart($event, item, child.prop)"
@dragend="onDragend($event)"
@dragover="onDragover($event)"
@drop="onDrop($event, item, child.prop)">
<div class="te_cell" v-if="child.slot">
<slot :name="child.slot" :row="item"></slot>
</div>
<div class="te_cell" v-if="!child.slot">{{item[child.prop]}}</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
const IS_MAC = function () {
return /macintosh|mac os x/i.test(navigator.userAgent);
}();
function throttle(delay, noTrailing, callback, debounceMode) {
var timeoutID;
var cancelled = false;
var lastExec = 0;
function clearExistingTimeout() {
if (timeoutID) {
clearTimeout(timeoutID);
}
}
function cancel() {
clearExistingTimeout();
cancelled = true;
}
if (typeof noTrailing !== 'boolean') {
debounceMode = callback;
callback = noTrailing;
noTrailing = undefined;
}
function wrapper() {
for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {
arguments_[_key] = arguments[_key];
}
var self = this;
var elapsed = Date.now() - lastExec;
if (cancelled) {
return;
}
function exec() {
lastExec = Date.now();
callback.apply(self, arguments_);
}
function clear() {
timeoutID = undefined;
}
if (debounceMode && !timeoutID) {
exec();
}
clearExistingTimeout();
if (debounceMode === undefined && elapsed > delay) {
exec();
} else if (noTrailing !== true) {
timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
}
}
wrapper.cancel = cancel;
return wrapper;
}
export default {
name: 'oldTable',
data() {
return {
isMac: IS_MAC,
checkBoxWidth: 40,
checkBoxList: [],
isAllCheck: false
}
},
props: {
draggable: {
type: Boolean,
default: true
},
headerData: {
type: Array,
default: () => []
},
tableData: {
type: Array,
default: () => []
},
checkBox: Boolean,
height: {
type: Number,
default: 500
}
},
computed: {
headerCol() {
if (this.checkBox) {
let _check = [
{
prop: 'checkbox',
label: '',
width: this.checkBoxWidth
}
]
return [..._check, ...this.headerData]
} else {
return this.headerData
}
},
rightIndex() {
if (Array.isArray(this.headerData) && this.headerData.length) {
return this.headerCol ? this.headerData.length : this.headerData.length - 1
} else {
return 0
}
}
},
watch: {
tableData: {
handler(val, oldVal) {
if (val !== oldVal) {
this.checkBoxList = Array(this.tableData.length).fill(false)
}
},
immediate: true,
deep: true
}
},
activated() {
},
created() {
},
methods: {
onDragstart(event, row, prop) {
this.$emit('drag-start', {event, row, prop});
},
onDragend(event) {
this.$emit('drag-drop-end', {event});
},
onDrop(event, row, prop) {
this.$emit('drag-drop', {event, row, prop});
},
onDragover(event) {
event.preventDefault();
},
checkAll() {
this.checkBoxList = this.checkBoxList.map((v) => !this.isAllCheck)
this.isAllCheck = !this.isAllCheck
this.$emit('checkList', this.isAllCheck ? this.tableData : [])
},
checkItem(row) {
if (this.isAllCheck) this.isAllCheck = false
this.$emit('checkList', [row])
},
getMultipleSelection() {
let _list = []
this.tableData.forEach((v, index) => {
if (this.checkBoxList[index]) {
_list.push(v)
}
})
return _list
},
bodyScroll() {
let scrollLeft = this.box.scrollLeft
let scrollTop = this.box.scrollTop
let fixedHead = this.$refs.tableFixedHead
fixedHead.scrollLeft = scrollLeft
if (scrollLeft > this.fixedHead.scrollLeft) {
this.box.scrollLeft = this.fixedHead.scrollLeft
}
let fixedLeft = this.$refs.tableFixedRight
fixedLeft.scrollTop = scrollTop
if (scrollTop > this.fixedLeft.scrollTop) {
this.box.scrollTop = this.fixedLeft.scrollTop;
}
},
headScroll() {
let scrollLeft = this.fixedHead.scrollLeft
let box = this.$refs.tableScrollBox
box.scrollLeft = scrollLeft
},
fixedLeftScroll() {
let scrollTop = this.fixedLeft.scrollTop
this.box.scrollTop = scrollTop
},
initTable() {
// 内容滚动区域监听联动头部区域
this.box = this.$refs.tableScrollBox
this.box && this.box.addEventListener('scroll', this.bodyScroll, false);
// 头部滚动区域监听联通内容区域
this.fixedHead = this.$refs.tableFixedHead;
this.fixedHead && this.fixedHead.addEventListener('scroll', this.headScroll, false);
// 固定左侧滚动监听联动内容
this.fixedLeft = this.$refs.tableFixedRight;
if (this.isMac) {
this.fixedLeft && this.fixedLeft.addEventListener('scroll', this.fixedLeftScroll, false)
} else {
this.fixedLeft && this.fixedLeft.addEventListener('scroll', throttle(300, this.fixedLeftScroll), false)
}
}
},
mounted() {
this.initTable()
},
components: {},
beforeDestroy() {
this.box.removeEventListener('scroll', this.bodyScroll, false)
this.fixedHead.removeEventListener('scroll', this.headScroll, false)
this.fixedLeft.removeEventListener('scroll', this.fixedLeftScroll, false)
},
destroyed() {
}
}
</script>
<style lang="scss" type="text/scss">
#oldTable {
.table-scroll-mod {
position: relative;
overflow: hidden;
border: 1px solid #ebeef5;
}
.table-scroll-box {
white-space: nowrap;
/*overflow: scroll;*/
overflow-x: scroll;
z-index: 2001;
.te_cell {
line-height: 23px;
padding: 0 5px;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
}
.table-content__warrp {
td, thead th {
border-color: #ddd;
padding: 6px 0;
font-size: 12px;
}
thead th {
padding: 8px 0;
}
td:not(:last-of-type) {
border-right: 1px solid #ddd;
box-sizing: border-box;
}
tr:not(:last-of-type) {
border-bottom: 1px solid #ddd;
box-sizing: border-box;
}
background-color: #fff;
}
.table-scroll-box > table {
overflow-x: scroll;
overflow-y: scroll;
}
.table-scroll-box > table tr {
display: -webkit-flex;
}
.table-scroll-box .fixed-head {
position: absolute;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
/*width: calc(100% - 8px);*/
overflow-x: scroll;
border-color: #ddd;
background: #f8f9fb;
th {
text-align: center;
color: #333633;
font-size: 14px;
font-weight: inherit;
padding: 8px 0;
}
th:not(:last-of-type) {
border-right: 1px solid #ddd;
box-sizing: border-box;
}
}
.table-scroll-box .fixed-head::-webkit-scrollbar {
width: 0px;
height: 0px;
}
.table-scroll-box .fixed-head::-webkit-scrollbar {
width: 0px;
height: 0px;
}
.table-scroll-box .fixed-head > table {
overflow-x: scroll;
}
.table-scroll-box .fixed-head > table tr {
display: -webkit-flex;
}
.fixed-right {
height: 100%;
overflow-y: scroll;
position: absolute;
top: 0;
right: -2px;
z-index: 999;
td {
text-align: center;
color: #333633;
font-size: 14px;
background-color: #fff;
font-weight: inherit;
padding: 5px 0;
}
thead td {
padding: 8px 0;
}
tbody td {
padding: 6px 0;
}
table {
margin: 0;
}
tr:not(:last-of-type) {
border-bottom: 1px solid #ddd;
box-sizing: border-box;
}
box-shadow: 0 0 10px rgba(0, 0, 0, .12);
}
.table-scroll-box .table-content__warrp::-webkit-scrollbar {
background-color: red;
}
.fixed-right_h {
height: calc(100% - 17px) !important;
}
.fixedTopRight {
height: 22px;
width: 240px;
background: #f8f9fb;
text-align: center;
position: absolute;
top: 0;
right: 0;
z-index: 1001;
line-height: 22px;
color: #333633;
font-size: 14px;
font-weight: inherit;
padding: 9.5px 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
2.引用
import oldTable from '../components/table'
export default {
title: 'Table',
};
const Template_dom = `
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
否
是
是
否
{{row.TeacherName2}}
{{row.TeacherName2}}
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
模式状态
{{ row.memo }}
`
let item = {
"lessonid": 172496219484,
"sid": 53,
"classCode": "模式状态",
"className": "模式状态",
"lessonNo": 3,
"isVip": 0,
"sectBegin": "2020-06-15T21:00:00",
"sectEnd": "2020-06-15T23:00:00",
"SectText": " 2020-06-15 21:00-23:00",
"minutesSpan": "120",
"hoursSpan": "2",
"teacherCode1": null,
"teacherName1": "",
"teacherCode2": null,
"teacherName2": "",
"roomCode": "RMLY01059",
"lessonTypeCode": 1,
"lessonTypeName": "模式状态",
"subjectCode": "",
"subjectName": null,
"courseCode": "CSLY01500024",
"courseName": "模式状态",
"operatorCode": null,
"operatorName": null,
"isVideolessonCode": 0,
"isVideolessonName": "模式状态",
"teachingContentTypeCode": 0,
"teachingContentTypeName": "模式状态",
"isUpLoadVideo": null,
"memoStatus": 0,
"memo": null,
"memoHistory": null,
"isAudit": 0,
"auditTime": null,
"auditTimeText": null,
"auditUser": null,
"auditName": null,
"sMemo": null,
"deptCode": "30",
"deptName": "模式状态",
"roomName": "模式状态",
"areaCode": "模式状态",
"areaName": "模式状态",
"currentCount": 2,
"useCard": 1,
"pushStudentStatus": 0,
"pushTeacher1Status": 0,
"pushTeacher2Status": 0,
"pushTeacher1UpLate": 0,
"pushTeacher1UpTime": null,
"pushTeacher2UpLate": 0,
"pushTeacher2UpTime": null,
"isHBFlag": 0,
"mergeClass": null,
"clearStatus": 0,
"isLock": false,
"gradeOuter": ""
}
export const toStorybook = () => ({
components: { oldTable },
template: Template_dom,
data() {
return {
colConfigsTable: [
{
prop: 'lessonNo',
label: '姓名',
width: 50
},
{
prop: 'classCode',
label: '随机编号',
width: 120
},
{
prop: 'className',
label: '随机名称',
width: 320
},
{
prop: 'SectText',
label: '时间Time',
width: 150
},
{
prop: 'hoursSpan',
label: '小时数北京',
width: 50
},
{
prop: 'courseName',
label: '课程名称',
width: 150
},
{
prop: 'gradeOuter',
label: '年级(中国)'
},
{
prop: 'roomName',
label: '教室',
width: 160
},
{
prop: 'teacherCode1',
label: '教师编号',
width: 140
},
{
prop: 'teacherName1',
label: '教师'
},
{
prop: 'isVideolessonName',
label: '授课方式标识',
width: 90
},
{
prop: 'teachingContentTypeName',
label: '内容类型',
width: 90
},
{
prop: 'pushStudentStatus',
label: '学员状态',
slot: 'pushStudentStatus'
},
{
prop: 'pushTeacher1Status',
label: '教师状态',
slot: 'pushTeacher1Status',
width: 100
},
{
prop: 'useCard',
label: '使用皮卡丘',
slot: 'useCard'
},
{
prop: 'operatorName',
label: '排课操作人'
},
{
prop: 'lessonTypeName',
label: '课节类型'
},
{
prop: 'areaName',
label: '教学区',
width: 150
},
{
prop: 'deptName',
label: '标准部门',
width: 90
},
{
prop: 'isVip',
label: '是否VIP',
slot: 'isVip'
},
{
prop: 'minutesSpan',
label: '分钟数'
},
{
prop: 'subjectName',
label: '科目'
},
{
prop: 'isUpLoadVideo',
label: '视频是否上传',
width: 120,
slot: 'isUpLoadVideo'
},
{
prop: 'currentCount',
label: '当前人数(占名额)',
width: 120
},
{
prop: 'teacherCode2',
label: '模式状态',
width: 140,
slot: 'teacherCode2'
},
{
prop: 'teacherName2',
label: '模式状态',
slot: 'teacherName2'
},
{
prop: 'pushTeacher2Status',
label: '模式状态',
slot: 'pushTeacher2Status',
width: 100
},
{
prop: 'isHBFlag',
label: '模式状态',
slot: 'isHBFlag'
},
{
prop: 'mergeClass',
label: '模式状态',
width: 100
},
{
prop: 'isAudit',
label: '状态',
slot: 'isAudit'
},
{
prop: 'auditUser',
label: '审核人ca',
width: 150
},
{
prop: 'auditTimeText',
label: '审核时间Time',
width: 150
},
{
prop: 'Operator',
label: '操作',
slot: 'Operator',
width: 200
}
],
tableData: Array(40).fill(item)
};
},
methods: {
}
});
toStorybook.story = {
name: 'table',
};