vue封装表格组件,左边固定列右边滑动,可排序可操作
2021/11/8 23:13:17
本文主要是介绍vue封装表格组件,左边固定列右边滑动,可排序可操作,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
如标题,效果如下图:
我做的是移动端,表格太长,所以做左边固定列右边滑动,上图右边显示动态箭头是让用户知道表格可以滑动。
表格的思路:
- 写两个table
- 一个是整体的table,外面的div保持宽度固定,内部的table宽度大于外部才能滚动;
- 一个是左边固定列,这个固定
position:absolute
在左边,就是盖在大table上面;
下面table组件
// table.vue <template> <div> <div class="product-list"> <!-- 可滑动的全表格 --> <div class="table-scroll-container" ref="tableScrollBox"> <table class="table-scroll" border="0" cellpadding="0" cellspacing="0"> <tbody class="tbody"> <tr class="title-tr"> <th class="ta-c" v-for="(item,index) in headData" :key="index" :class="{ 'first-col': index == 0 }"> {{ item.name }} </th> </tr> <template> <tr class="tbody-content" v-for="(item, index) in bodyData" :key="index"> <td class="info-box" v-for="(colItem, colIndex) in headData" :class="[{ 'first-col clearfix': colIndex == 0 },colItem.class]"> {{ item[colItem.prop] }} </td> </tr> </template> </tbody> </table> </div> <!-- 覆盖的左侧表格 --> <div class="table-left-container" v-if="isTableLeft"> <table class="table-left" :class="{ 'norem-table-fixed-left': isLeft }" border="0" cellpadding="0" cellspacing="0"> <tbody class="tbody"> <tr class="title-tr"> <th class="first-col">{{ headData[0].name }}</th> </tr> <template> <tr class="tbody-content" v-for="(item, index) in bodyData" :key="index"> <td class="info-box" v-if="headData[0].prop">{{ item[headData[0].prop] }}</td> </tr> </template> </tbody> </table> </div> </div> <!-- 动态箭头 bodyData没有数据就不显示 --> <div class="arrow" v-if="bodyData.length != 0 && !arrow_left"></div> </div> </template> <script> export default { props: { // 表头内容 headData: { type: Array, default: () => [] }, // 表格内容 bodyData: { type: Array, default: () => [] }, // 是否要固定左边列 isTableLeft: { type: Boolean, default: () => true }, // 是否要动态箭头 arrow_left: { type: Boolean, default: () => false } }, data() { return { isLeft: false, }; }, mounted() { //监听表格滚动,表格滚动到左边0时候去掉左边固定列的阴影,滚动到大于0时候才显示左边固定列的列 this.$refs.tableScrollBox.addEventListener( "scroll", () => { this.$refs.tableScrollBox.scrollLeft if(this.$refs.tableScrollBox.scrollLeft > 0){ this.isLeft = true; }else{ this.isLeft = false; } }, true ); }, }; </script> // 动态箭头 <style scoped lang="less"> .arrow { display: block; width: 10px; height: 10px; position: fixed; top: 50%; right: 0; margin-left: -11px; border: 3px solid transparent; border-top: 3px solid @primary_color; border-left: 3px solid @primary_color; z-index: 99; opacity: .8; transform: rotate(313deg); animation: arrow 1.5s infinite ease-in-out; } @keyframes arrow { 0% { opacity:0.2; transform:translate(0, 0px) rotate(313deg) } 50% { opacity:1; transform:translate(-5px, 0) rotate(313deg) } 100% { opacity:0.2; transform:translate(-10px, 0) rotate(313deg) } } </style>
下面需要表格组件的vue文件
// userList.vue <template> <div> <tableLocked :headData="tableHead" :bodyData="tableBody" :isTableLeft='true'> </tableLocked> </div> </template> <script> import tableLocked from './tableLocked.vue' export default { data() { return { // table表头 tableHead: [ { name: '账号', prop: 'user_name' }, { name: '内容1', prop: 'user1' }, { name: '内容2', prop: 'user2' }, { name: '内容3', prop: 'user3', }, { name: '内容4', prop: 'user4', }, { name: '内容5', prop: 'user5' }, { name: '操作', prop: '' } ], // table内容 tableBody: [], } }, components: { tableLocked } } </script>
排序的思路:
- 给需要排序的表头多两个字段,一个显示,一个传值(Asc/Desc)
- 排序事件把Asc/Desc和该表头的值给父组件传过去
userList.vue:需要排序的表头添加两个字段
table.vue组件:添加排序的元素
<span class="sort" v-if="item.isSort" @click="onSort(item)"> <i class="up" :class="{ active: item.SortType == 'Asc' }"></i> <i class="down" :class="{ active: item.SortType == 'Desc' }"></i> </span>
methods: { // 排序 onSort(item){ for(var i = 0; i < this.headData.length; i++){ if(this.headData[i].prop != item.prop){ this.headData[i].SortType = '' } } // 避免重复请求 if(item.SortType == ''){ item.SortType = 'Asc' }else if(item.SortType == 'Asc'){ item.SortType = 'Desc' }else{ item.SortType = '' } // 把该参数给父组件过去,并触发父组件排序事件 this.$emit('goSort', item) } },
userList.vue:排序事件中获取该值并请求/逻辑
methods: { // 排序 sortClick (item){ console.log(item) // 逻辑 // ... }, },
操作的思路:
- 操作的prop字段为空,可以判断这个字段是否空,是空的话就是操作
- 这个字段空的时候使用< slot >来填充操作下面的元素
- 在table.vue触发父组件对应的事件函数
table.vue:添加下面红圈
<template> <tr class="tbody-content" v-for="(item, index) in bodyData" :key="index"> <td class="info-box" v-for="(colItem, colIndex) in headData" v-if="colItem.prop" :class="[{ 'first-col clearfix': colIndex == 0 },colItem.class]"> {{ item[colItem.prop] }} </td> <td class="operation" :key="colIndex" v-else @click.stop.capture="doThis($event, item, index)"> <slot></slot> </td> </tr> </template>
触发父组件对应的操作事件函数,并把参数传过去
methods: { doThis(e, item, index) { // e.target.dataset.func是父组件操作下面元素的属性data-func this.$emit(e.target.dataset.func, item) }, },
userList.vue:添加两个元素和两个事件,data-func这个是来方便子组件table.vue触发对应的事件函数
<tableLocked :headData="tableHead" :bodyData="tableBody" :isTableLeft='true' @goSort="sortClick" @goModify="modifyClick" @goDelete="deleteClick"> <span data-func="goModify">修改</span> <span data-func="goDelete">删除</span> </tableLocked>
methods: { modifyClick (item) { console.log("修改:" + item) }, deleteClick (item) { console.log("删除:" + item) }, },
完成代码:
table.vue
<template> <div> <div class="product-list"> <!-- 可滑动的全表格 --> <div class="table-scroll-container" ref="tableScrollBox"> <table class="table-scroll" border="0" cellpadding="0" cellspacing="0"> <tbody class="tbody"> <tr class="title-tr"> <th class="ta-c" v-for="(item,index) in headData" :key="index" :class="{ 'first-col': index == 0 }"> {{ item.name }} <span class="sort" v-if="item.isSort" @click="onSort(item)"> <i class="up" :class="{ active: item.SortType == 'Asc' }"></i> <i class="down" :class="{ active: item.SortType == 'Desc' }"></i> </span> </th> </tr> <template> <tr class="tbody-content" v-for="(item, index) in bodyData" :key="index"> <td class="info-box" v-for="(colItem, colIndex) in headData" v-if="colItem.prop" :class="[{ 'first-col clearfix': colIndex == 0 },colItem.class]"> {{ item[colItem.prop] }} </td> <td class="operation" :key="colIndex" v-else @click.stop.capture="doThis($event, item)"> <slot></slot> </td> </tr> </template> </tbody> </table> </div> <!-- 覆盖的左侧表格 --> <div class="table-left-container" v-if="isTableLeft"> <table class="table-left" border="0" cellpadding="0" cellspacing="0" :class="{ 'norem-table-fixed-left': isLeft }"> <tbody class="tbody"> <tr class="title-tr"> <th class="first-col">{{ headData[0].name }}</th> </tr> <template> <tr class="tbody-content" v-for="(item, index) in bodyData" :key="index"> <td class="info-box" v-if="headData[0].prop">{{ item[headData[0].prop] }}</td> </tr> </template> </tbody> </table> </div> </div> <!-- 动态箭头 --> <div class="arrow" v-if="bodyData.length != 0 && !arrow_left"></div> </div> </template> <script> export default { props: { // 表头内容 headData: { type: Array, default: () => [] }, // 表格内容 bodyData: { type: Array, default: () => [] }, // 是否要固定左边列 isTableLeft: { type: Boolean, default: () => true }, tdSlot: { type: Boolean, default: () => false }, // 是否要动态箭头 arrow_left: { type: Boolean, default: () => false } }, data() { return { isLeft: false, }; }, methods: { doThis(e, item) { // e.target.dataset.func是父组件操作下面元素的属性data-func this.$emit(e.target.dataset.func, item) }, // 排序 onSort(item){ for(var i = 0; i < this.headData.length; i++){ if(this.headData[i].prop != item.prop){ this.headData[i].SortType = '' } } // 避免重复请求 if(item.SortType == ''){ item.SortType = 'Asc' }else if(item.SortType == 'Asc'){ item.SortType = 'Desc' }else{ item.SortType = '' } // 把该数据给父组件过去,并触发父组件排序事件 this.$emit('goSort', item) } }, mounted() { this.$refs.tableScrollBox.addEventListener( "scroll", () => { this.$refs.tableScrollBox.scrollLeft if(this.$refs.tableScrollBox.scrollLeft > 0){ this.isLeft = true; }else{ this.isLeft = false; } }, true ); }, }; </script>
@primary_color: #617DEF; .product-list{ position: relative; margin-top: 16px; overflow: hidden; margin-bottom: 20px; .table-left{ position: absolute; top: 0; left: 0; z-index: 2; background: #fff; border-collapse: collapse; text-align: left; .tbody-content { &:nth-child(2n+3) td{ background-color: #F8F8F8; } td { height: 40px; text-align: center; box-sizing: border-box; } } } .table-scroll-container { overflow-x: scroll; overflow-y: hidden; width: 100%; } .table-scroll{ width: 472px; border-collapse: collapse; text-align: left; background-color: #fff; .tbody-content:nth-child(2n+3){ background-color: #F8F8F8; } } .title-tr{ height:17px; font-size:12px; font-weight:400; color: #666; line-height:17px; border-bottom: 1px #E9E9E9 solid; .first-col { height: 32px; border-bottom: 0; } } .class-tr { background-color: #fff; width:48px; height:24px; font-size:12px; line-height:20px; box-sizing: border-box; } .first-col { height: 40px; box-sizing: border-box; .first-col-cont { width: 100px; } } .info-box { height: 40px; text-align: center; box-sizing: border-box; } div::-webkit-scrollbar { width: 0; height: 0; } th,td{ word-break: keep-all; white-space:nowrap; padding: 0 14px; text-align: center; } span{ // color: @primary_color; padding: 0 8px; font-size: 12px; } th{ /deep/ i{ color: #a6a6a6; vertical-align: text-top; } .sort{ padding: 0; position: relative; i{ color: #a6a6a6; } .up{ position: absolute; top: -2px; transform:rotate(-90deg); -ms-transform:rotate(-90deg); /* IE 9 */ -moz-transform:rotate(-90deg); /* Firefox */ -webkit-transform:rotate(-90deg); /* Safari 和 Chrome */ -o-transform:rotate(-90deg); &.active{ color: @primary_color; } } .down{ position: absolute; bottom: -2px; transform:rotate(90deg); -ms-transform:rotate(90deg); /* IE 9 */ -moz-transform:rotate(90deg); /* Firefox */ -webkit-transform:rotate(90deg); /* Safari 和 Chrome */ -o-transform:rotate(90deg); &.active{ color: @primary_color; } } } } .operation { /deep/ span{ color: @primary_color; padding: 0 8px; font-size: 12px; } } .ta-c { text-align: center; } .norem-table-fixed-left{ box-shadow: 1px -3px 6px #00000029; } .add{ font-size: 16px; vertical-align: text-top; color: @primary_color; } .arrow { display: block; width: 10px; height: 10px; position: fixed; top: 50%; right: 0; margin-left: -11px; border: 3px solid transparent; border-top: 3px solid @primary_color; border-left: 3px solid @primary_color; z-index: 99; opacity: .8; transform: rotate(313deg); animation: arrow 1.5s infinite ease-in-out; } @keyframes arrow { 0% { opacity:0.2; transform:translate(0, 0px) rotate(313deg) } 50% { opacity:1; transform:translate(-5px, 0) rotate(313deg) } 100% { opacity:0.2; transform:translate(-10px, 0) rotate(313deg) } } }
userList.vue
<template> <div> <tableLocked :headData="tableHead" :bodyData="tableBody" :isTableLeft='true' @goSort="sortClick" @goModify="modifyClick" @goDelete="deleteClick"> <span data-func="goModify">修改</span> <span data-func="goDelete">删除</span> </tableLocked> </div> </template> <script> import tableLocked from './tableLocked.vue' export default { data() { return { // table表头 tableHead: [ { name: '账号', prop: 'user_name' }, { name: '内容1', prop: 'user1' }, { name: '内容2', prop: 'user2' }, { name: '内容3', prop: 'user3', isSort: true, SortType: "" }, { name: '内容4', prop: 'user4', isSort: true, SortType: "" }, { name: '内容5', prop: 'user5' }, { name: '操作', prop: '' }, ], // table内容 tableBody: [], } }, methods: { modifyClick (item) { console.log("修改:" + item) }, deleteClick (item) { console.log("删除:" + item) }, // 排序 sortClick (item){ console.log(item) // 请求时候把这个data数据传过去 // ... }, }, components: { tableLocked } } </script>
这篇关于vue封装表格组件,左边固定列右边滑动,可排序可操作的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27Vue2面试真题详解与实战教程
- 2024-12-27Vue3面试真题详解与实战攻略
- 2024-12-27JS大厂面试真题解析与实战指南
- 2024-12-27JS 大厂面试真题详解与实战指南
- 2024-12-27React 大厂面试真题详解及应对策略
- 2024-12-27Vue2 大厂面试真题详解及实战演练
- 2024-12-27Vue3 大厂面试真题详解及实战指南
- 2024-12-27Vue3大厂面试真题详解与实战攻略
- 2024-12-26React入门教程:从零开始搭建你的第一个React应用
- 2024-12-25Vue2入门教程:轻松掌握前端开发基础