vue封装表格组件,左边固定列右边滑动,可排序可操作

 如标题,效果如下图:

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:需要排序的表头添加两个字段

vue封装表格组件,左边固定列右边滑动,可排序可操作

table.vue组件:添加排序的元素

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:排序事件中获取该值并请求/逻辑

vue封装表格组件,左边固定列右边滑动,可排序可操作

methods: {
    // 排序
    sortClick (item){
        console.log(item)
        // 逻辑
        // ...
    },
},

操作的思路:

  • 操作的prop字段为空,可以判断这个字段是否空,是空的话就是操作
  • 这个字段空的时候使用< slot >来填充操作下面的元素
  • 在table.vue触发父组件对应的事件函数

table.vue:添加下面红圈

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>
上一篇:11.9


下一篇:opacity和rgba的区别以及prop的用法