vue实现表格组件,带分页

 最近因为项目需要,利用vue开发了一套利于扩展的表格组件,可选择分页展示,带有排序功能支持,支持自定义操作按钮与class以及自定义render渲染。效果如下:

点击在线体验

使用简单:

html:

vue实例中传入基本的列信息:

 完整代码:

<html>
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="http://code.jquery.com/jquery-1.4.1.min.js"></script>
        <style>
            table { width: 100%; margin-bottom: 24px; border-collapse: collapse; border-spacing: 0; empty-cells: show; border: 1px solid #e9e9e9;}
            table th { font: bold 14px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;background: #CAE8EA; color: #5c6b77; font-weight: 600; white-space: nowrap; border-top: 1px solid #C1DAD7;}
            table td, table th { padding: 8px 16px; text-align: left;border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7;}
            table th a { display: inline-block; margin: 0 4px; cursor: pointer;}
            table th a.on {color: #3399ff;}
            table th a:hover {color: #3399ff;}
            .row{display: flex;-ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -12px; margin-left: -12px;}
            .row .col-4{ -webkit-box-flex: 0; -ms-flex: 0 0 33.33333%; flex: 0 0 33.33333%; max-width: 20%;    position: relative; width: 100%; padding-right: 12px; padding-left: 12px;}
            .pagination{list-style: none;display: -webkit-box;display: -ms-flexbox;border-radius: .25rem;}
            .col-4{-webkit-box-flex: 0;flex: 0 0 33.33333%;max-width: 20%;}
            .pagination-sm{margin: 0;}
            button{outline: none;}
            .pagination-sm .page-link{padding: .25rem .5rem;font-size: .74375rem;line-height: 1.5;margin: 0;}
            .pagination-sm .active .page-link{color: red;}
            .btn-confirm {position: absolute; top: 50%; left: 70%; transform: translate(-50%, -50%); width: 80px; height: 30px; line-height: 20px; text-align: center; color: #fff;text-transform: uppercase; text-decoration: none; font-family: sans-serif; box-sizing: border-box; background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4); background-size: 400%; border-radius: 60px; z-index: 1; }
            .btn-confirm:hover { animation: animate 8s linear infinite; } 
            @keyframes animate { 0% { background-position: 0%; } 100% { background-position: 400%; } } 
            .btn-confirm::before { content: ''; position: absolute; top: -5px; left: -5px; right: -5px; bottom: -5px; z-index: -1; background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4); background-size: 400%; border-radius: 40px; opacity: 0; transition: 0.5s; }
            .btn-confirm:hover::before { filter: blur(20px); opacity: 1; animation: animate 8s linear infinite; } 
        </style>
    </head>
    <body>
        <div id="app">
            <tt-table :datas="datas" :columns="columns" pageSize=2 :rows="rows"></tt-table>
        </div>
        <script>
/**分页组件嵌入到表格中*/
Vue.component('page-split', {
    props: {
        totalRows: {
            required: true,
            type: Number
        },
        pageSize: {
            type: Number,
            default: 8
        },
        pageNumAmount: {
            type: Number,
            default: 3
        },
        pageNum: {
            type: Number,
            default: 1
        }
    },
    model: {
        prop: 'pageNum',
        event: 'switch-page'
    },
    data: function data() {
        return {
            totalPage: 0,
            inputPage: '',
            showPageList: []
        };
    },
    created: function() {
        this.calShowPageNums();
    },
    watch: {
        totalRows: function totalRows() {
            this.calShowPageNums();
        }
    },
    methods: {
        invokeAjaxMethod: function(){
            this.$emit('switch-page', this.pageNum);
            this.calShowPageNums();
        },
        toSpecialPage: function toSpecialPage() {
            if (!/^\d{1,10}$/.test(this.inputPage)) {
                this.inputPage = '';
                return false;
            }
            if (this.inputPage > this.totalPage || this.inputPage < 1) {
                this.inputPage = '';
                return false;
            }
            this.pageNum = this.inputPage;
            this.invokeAjaxMethod();
        },
        prePage: function prePage() {
            if (this.pageNum < 2) {
                return false;
            }
            this.pageNum--;
            this.invokeAjaxMethod();
        },
        goPage: function goPage(pageNo) {
            this.pageNum = pageNo;
            this.invokeAjaxMethod();
        },
        nextPage: function nextPage() {
            if (this.pageNum == this.totalPage) {
                return false;
            }
            this.pageNum++;
            this.invokeAjaxMethod();
        },
        //计算应该展示出的页号
        calShowPageNums: function calShowPageNums() {
            this.totalPage = this.getNumMultiple(this.totalRows, this.pageSize);
            var showPageNum = 1,
                showListIndex = 0;
            if (this.totalPage > this.pageNumAmount) {
                showPageNum = this.pageNum - (this.pageNumAmount >> 1); //显示的第一个页号
                if (this.totalPage - showPageNum < this.pageNumAmount) {
                    //末尾几页页号数不够的时候
                    showPageNum = this.totalPage - this.pageNumAmount + 1;
                }
                showPageNum = showPageNum < 1 ? 1 : showPageNum;
            }
            this.showPageList = []; //先清空原来的数据
            for (var currentNum = 0; currentNum < this.pageNumAmount; ++currentNum, showPageNum++) {
                if (showPageNum > this.totalPage) {
                    break;
                }
                this.showPageList[showListIndex++] = showPageNum;
            }
            if (this.pageNum > this.totalPage && this.pageNum > 1){//删除最后一页的最后一条数据后,pageNum需要更改过来
                if (this.totalPage > 0) {
                    this.pageNum = this.totalPage;
                    this.invokeAjaxMethod();
                } else {//删除了所有数据
                    this.pageNum = 0;
                }
            }
        },
        getNumMultiple(rawNum, baseNum, greedy = true) {
            var muti = parseInt(rawNum / baseNum);
            if (greedy && rawNum % baseNum > 0) {
                muti++;
            }
            return muti;
        }
    },
    template:
    '<div class="row">' +
        '<div class="col-4">' +
            '<div class="dataTables_info">共{{totalRows}}条记录</div>' +
        '</div>' +
        '<div class="col-4" style="white-space: nowrap;">' +
            '<nav aria-label="..." class="float-right">' +
                '<ul class="pagination pagination-sm">' +
                    '<li class="page-item">' +
                        '<button :class="pageNum <= 1 ? \'btn-disabled\': \'\'" class="page-link" @click="prePage()">上一页</button>' +
                    '</li>' +
                    '<li v-for="pageItemNo in showPageList" class="page-item" :class="pageItemNo == pageNum ? \'active\': \'\'">' +
                        '<a class="page-link" @click="goPage(pageItemNo)" href="javascript:void(0)">{{pageItemNo}}<span class="sr-only"></span></a>' +
                    '</li>' +
                    '<li class="page-item">' +
                        '<button :class="pageNum >= totalPage ? \'btn-disabled\' : \'\'" class="page-link" @click="nextPage()">下一页</button>' +
                    '</li>' +
                    '<span style="line-height: 27px;">共{{totalPage}}页</span>' +
                '</ul>' +
            '</nav>' +
        '</div>' +
        '<div class="col-4">' +
            '<div style="display: inline-flex;">' +
                '<label style="line-height: 27px;">到第</label>' +
                '<input v-model="inputPage" class="form-control form-control-sm" type="text" style="width: 80px;" maxlength="10"/>' +
                '<label style="line-height: 27px;">页</label>' +
                '<button style="height: 30px;line-height: 15px;" @click="toSpecialPage" class="btn btn-confirm">确定</button>' +
            '</div>' +
        '</div>' +
    '</div>'
});
            //单元格组件
 Vue.component('table-column', {
    props: {
        label: {//td展示的内容
            default: ""
        },
        view: {//是否展示
            type: Boolean,
            default: true
        },
        render: {
            type: Function
        },
        item: {
            type: Object,
            required: true
        }
    },
    data(){
        return {
            itemStyle: ''
        }
    },
    created() {
        if (!this.view && "undefined" != typeof this.view) {
            this.itemStyle += "display: none";
        }
    },
    render(h) {
        const that = this;
        if (that.render){
            return that.render(h, that.item);
        }
        return h("td", {
            style: that.itemStyle,
            class: 'table-td',
            domProps: {
                innerHTML:that.label
            }
        });
    }
});

 //操作按钮组件
 Vue.component('table-button', {
    props: {
        item: {//该条内容
            type: Object,
            required: true
        }
    },
    data(){
        return {
            cls: this.item.cls,
            label: this.item.label
        }
    },
    created(){
        if (!this.cls) {
            this.cls = "btn btn-outline-info btn-sm"
        }
    },
    methods: {
        clickEvt(){
            this.$emit("handel-click", this.item);
        }
    },
    template: `<button v-text="label" :class="cls" @click="clickEvt"></button>`
});

 //表格组件
 Vue.component("tt-table", {
    props: {
        pageSize: {
            type: Number,
            default: 2
        },
        rows: {//总数量
            default: 0
        },
        pageNo: {//当前页
          type: Number,
          default: 1
        },
        paging: {
            default: true
        },
        datas: {
            type: Array,
            required: true
        },
        columns: {
            type: Array,
            required: true
        }
    },
     data(){
        return {
            needPaging: this.paging,
            showList: []//当前页展示的内容
        }
     },
    created(){
        if (this.needPaging == "false"){
            this.needPaging = false;//转换成boolean
        } else {
            if (this.needPaging && this.needPaging != "true"){
                this.needPaging = true;
            }
        }
        this.handlePaging(1);
    },
    render(h){
        const that = this;
        var cols = new Array();
        var heads = new Array();
        //组装column
        that.columns.forEach((item, index) => {
            var colWidth = item.width;
            if (!colWidth) {
                colWidth = "15%";
            }
            let columnStyle = {
                width: colWidth
            };
            if (!item.view && typeof item.view != "undefined"){
                columnStyle.display = "none";
            }
            cols.push(h("col", {
                style: columnStyle
            }));
            if (item.sort){
                heads.push(h("th", {
                    style: columnStyle
                }, [
                    h("label", item.label),
                    h("span", {
                        on: {
                            click: function(){
                                that.handleOrder(item.code, true);
                            }
                        }
                    }, "↑"),
                    h("span", {
                        on: {
                            click: function(){
                                that.handleOrder(item.code, false);
                            }
                     }
                }, "↓")]));
            } else {
                heads.push(h("th", {
                    style: columnStyle
                }, item.label));
            }
        });
        var tbodys = new Array();
        that.showList.forEach((item, index) =>{
            var tds = new Array();
            that.columns.forEach((columnItem, columnIndex) =>{
                if (columnItem.code == 'opt') {
                    //组装操作按钮
                    let optBtns = [];
                    columnItem.datas.forEach((btnItem) => {
                        optBtns.push(h("table-button", {
                            props: {
                                item: btnItem
                            },
                            on: {
                                'handel-click': function(obj){
                                    btnItem.clickHander(obj);
                                }
                            }
                        }));
                    });
                    tds.push(h("td",{
                        props:{
                            label: ""
                        }
                    }, optBtns));
                } else {
                    tds.push(h("table-column", {
                        props: {
                            label: item[ columnItem.code ],
                            item: item,
                            render: columnItem.render,
                            view: columnItem.view//是否展示
                        }
                    }));
                }
            });
            tbodys.push(h("tr", tds));
        });
        if (that.needPaging) {
            return h("div", [
                h("div", {
                    class: "table-responsive",
                    style: "margin-top: 10px;"
                }, [
                    h("table", {
                        class: "table mb-0 table-striped"
                    }, [
                        h("colgroup", cols),
                        h("thead", [
                            h('tr', heads)
                        ]),
                        h("tbody", tbodys)
                    ])
                ]),
                h("page-split", {
                    props: {
                        totalRows: window.parseInt( that.rows ),
                        pageNum: that.pageNo,
                        pageSize: that.pageSize
                    },
                    domProps: {
                        pageNum: that.pageNo,
                    },
                    on: {
                        'switch-page': function(thisPage){
                            that.pageNo = thisPage;
                            that.handlePaging(thisPage);
                        }
                    }
                })
            ]);
        } else {
            return h("div", {
                class: "table-responsive",
                style: "margin-top: 10px;"
            }, [
                h("table", {
                    class: "table mb-0 table-striped"
                }, [
                    h("colgroup", cols),
                    h("thead", [
                        h('tr', heads)
                    ]),
                    h("tbody", tbodys)
                ])
            ]);
        }
    },
    methods: {
        /**
         * 处理排序
        */
        handleOrder(sortedCode, isAsc){
            //如果是数字或者时间则按照大小排序,字符串则按照长短进行排序
            //找出一个非空的列
            var firstSortVal = '';
            for (let key in this.showList) {
                if (this.showList[key][sortedCode]) {
                    firstSortVal = this.showList[key][sortedCode];
                    break;
                }
            }
            if (!firstSortVal) {
                return false;
            }
            //判断类型
            var columnType = 0;//0-2分别标识string,number,date类型
            if (typeof window.parseFloat(firstSortVal) == "number") {
                columnType = 1;
            } else {
                let tempDate = new Date(firstSortVal);
                if (tempDate instanceof Date) {
                    columnType = 2;
                }
            }
            var sortedFunction = '';
            switch (columnType){
                case 0:
                    if (isAsc) {
                        sortedFunction = function(a, b){
                            return a[sortedCode].length < b[sortedCode].length ? -1 : 1;
                        }
                    } else {
                        sortedFunction = function(a, b){
                            return a[sortedCode].length > b[sortedCode].length ? -1 : 1;
                        }
                    }
                    break;
                case 1:
                    if (isAsc) {
                        sortedFunction = function(a, b){
                            return window.parseFloat(a[sortedCode]) < window.parseFloat(b[sortedCode]) ? -1 : 1;
                        }
                    } else {
                        sortedFunction = function(a, b){
                            return window.parseFloat(a[sortedCode]) > window.parseFloat(b[sortedCode]) ? -1 : 1;
                        }
                    }
                    break;
                case 2:
                    if (isAsc) {
                        sortedFunction = function(a, b){
                            return new Date( a[sortedCode] ) < new Date( b[sortedCode] ) ? -1 : 1;
                        }
                    } else {
                        sortedFunction = function(a, b){
                            return new Date( a[sortedCode] ) > new Date( b[sortedCode] ) ? -1 : 1;
                        }
                    }
                    break;
            }
            this.showList.sort(sortedFunction);
        },
        /**处理分页*/
        handlePaging(pageNo){
            var showList = this.datas.slice((pageNo -1) * this.pageSize, pageNo * this.pageSize);
            this.showList = [].concat(showList);
        }
    }
});

//挂载实例:
new Vue({
            el: "#app",
            data: {
                label: "hello world from vue inst",
                rows: 0,
                columns: [
                    {
                        code: "id",
                        label: "ID",
                        sort: true
                    },{
                        code: "name",
                        label: "姓名",
                        render: function(h, item){
                            const that = this;
                            return h({
                                template: "<td>" + that.label + "</td>"
                            });
                        }
                    },{
                        code: "age",
                        label: "年龄"
                    },
                    {
                        code: "opt",
                        label: "操作",
                        datas: [
                            {
                                label: "查看",
                                cls: 'class',
                                clickHander(item){
                                    window.alert("click view:" + JSON.stringify(item));
                                }
                            },{
                                label: "编辑",
                                cls: 'class',
                                clickHander(item){
                                    window.alert("click edit:" + JSON.stringify(item));
                                }
                            }
                        ]
                    }
                ],
                datas: [
                    {
                        id: "1",
                        name: "name1",
                        age: 21
                    },{
                        id: "2",
                        name: "name2",
                        age: 22
                    },
                    {
                        id: "3",
                        name: "name3",
                        age: 33
                    },{
                        id: "4",
                        name: "name4",
                        age: 44
                    },
                    {
                        id: "5",
                        name: "name5",
                        age: 55
                    },{
                        id: "6",
                        name: "name6",
                        age: 66
                    }
                ]
            },
            created(){
                this.rows = this.datas.length;
            }
        });
        </script>
    </body>
</html>

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值