用vue写轮子的一些心得(九)——table表单组件

 

用vue写轮子的一些心得(九)——table表单组件_第1张图片

需求分析

  • 支持斑马纹,默认斑马纹样式;
  • 支持表格边框线,默认没有边框线;
  • 支持table内容padding间距配置(是否为紧凑型);
  • 支持给table设置高度;
  • 支持全选与全选取消,默认不展示;
  • 支持给任意选项排序;
  • 支持请求数据时,展示loading状态;
  • 支持可展开,当表格内容较多不能一次性完全展示时;
  • 支持在table最后一列传自定义内容,比如按钮;
  • 默认固定表头;

 

方法实现

一、table组件传参定义:

一共可传12个参数和1个事件:

  • striped:是否展示斑马纹样式,默认展示;
  • bordered:是否有表格边框线,默认无边框线;
  • compact:table内容间距配置,两种方案:4px和8px,默认8px;
  • height:table容器高度,默认无高度;
  • checkable:是否展示全选项,默认不展示;
  • orderBy:排序方式,比如升序、降序;
  • loading:是否为loading加载状态;
  • extendField:可展开内容数据名;
  • selectedItems:被选中的行,值为数组;
  • colums:表头内容配置,必填;
  • dataSource:表主体内容配置,必填;
  • @update:orderBy:点击排序按钮的事件,可向接口按指定排序方式请求数据;

    

table数据: 

data() {
    return {
        currentPage: 1,
        selected: [],
        columns: [
            {text: '姓名', field: 'name', width: 100},
            {text: '分数', field: 'score'},
        ],
        orderBy: {
            score: 'desc'
        },
        loading: false,
        dataSource: [
            {id:1, name:'A-Tione1', score: 99, description: '11111'},
            {id:2, name:'A-Tione2', score: 98, description: '22222'},
            {id:3, name:'A-Tione3', score: 97},
            {id:4, name:'A-Tione4', score: 96},
            {id:5, name:'A-Tione5', score: 90},
            {id:6, name:'A-Tione6', score: 105},
            {id:7, name:'A-Tione7', score: 80},
            {id:8, name:'A-Tione8', score: 150},
            {id:9, name:'A-Tione9', score: 60},
            {id:10, name:'A-Tione10', score: 107},
        ],
    }
},

二、table组件内部实现:

1、支持斑马纹,默认斑马纹样式

在table标签中定义striped类名,根据props传参来确定是否显示类名,具体的样式由css负责,js负责控制class的显影。

props: {
    striped: {
        type: Boolean,
        default: true
    },
    bordered: {
        type: Boolean,
        default: false
    },
    compact: {
        type: Boolean,
        default: false
    },

2、支持表格边框线,默认没有边框线

同上

3、支持table内容padding间距配置(是否为紧凑型)

同上

4、支持给table设置高度

首先在table外加一个包裹容器div,将高度设置在div容器上面,再给容器加上overflow:auto,可使容器里面的table滑动展示。

但这里要注意一个点,我们需要做固定表头,固定表头会使高度有问题(具体请看10、默认固定表头实现),所以我们需要用js计算将table的高度减掉一个表头的高度,这样table容器的总高度才会是设置的正确高度。

props: {
    height: {
      type: Number
    },
},
mounted() {
    let table2 = this.$refs.table.cloneNode(false)
    this.table2 = table2
    let tHead = this.$refs.table.children[0]
    let {height} = tHead.getBoundingClientRect()
    this.$refs.tableWrapper.style.height = this.height-height + 'px'
    table2.appendChild(tHead)
    this.$refs.wrapper.appendChild(table2)
},

5、支持全选与全选取消

在th标签中点击为全选或者全不选,在td标签中点击为单个选中或者不选中。

那么我们先看看th标签中的全选逻辑,onChangeAllItems方法,当用户点击全选checked时,我们判断checked是否为true,如果为true则将dataSource全部遍历,组件外接收并更新selectedItems。如果为checked为false,则为空数组。

inSelectedItems方法判断更新后的每一行数据checked为true还是为false。

用vue写轮子的一些心得(九)——table表单组件_第2张图片

另外这里还有一个半选的问题,如果数据没有满选,应该展示一个半选样式(如上图),mdn上说这里半选样式只能在js上面添加,因此我们要在watch上监听一下selectedItems,并适时的加入半选样式。

this.$refs.allChecked.indeterminate = false or true
watch: {
    selectedItems() {
        if (this.selectedItems.length === this.dataSource.length) {
            this.$refs.allChecked.indeterminate = false
        } else this.$refs.allChecked.indeterminate = this.selectedItems.length !== 0
    }
},
computed: {
    areAllItemsSelected() { //判断所有元素是否被选中
        const a = this.dataSource.map(item => item.id).sort()
        const b = this.selectedItems.map(item => item.id).sort()
        let equal = true
        if (!b.length) {
            return equal = false
        }
        if (a.length !== b.length) {
            return equal
        }
        for (let i = 0; i < a.length; i++) {
            if (a[i] !== b[i]) {
                equal = false
                break
            }
        }
        return equal
    },
},
methods: {
    inSelectedItems(item) {
        return this.selectedItems.filter(value => value.id === item.id).length > 0
    },
    onChangeItem(item, index, e) {
        let selected = e.target.checked
        let copy = JSON.parse(JSON.stringify(this.selectedItems))
        if (selected) {
            copy.push(item)
        } else {
            copy = copy.filter(i => i.id !== item.id)
        }
        this.$emit('update:selectedItems', copy)
    },
    onChangeAllItems(e) {
        let selected = e.target.checked
        this.$emit('update:selectedItems', selected ? this.dataSource : [])
    }
}

6、支持给任意选项排序

确定要排序的字段,score: 'desc' 。然后通过监听事件@update:orderBy="sort"监听触发排序事件,然后向后端请求排序数据。

这里要注意如果外部参数orderBy没有展示排序字段的,则再在table组件中对应列不展示排序样式也不可点击触发。

外部参数设置:




data() {
    return {
        columns: [
            {text: '姓名', field: 'name', width: 100},
            {text: '分数', field: 'score'},
        ],
        orderBy: {
            score: 'desc'
        },
        loading: false,
        dataSource: [
            {id:1, name:'A-Tione1', score: 99, description: '11111'},
            {id:2, name:'A-Tione2', score: 98, description: '22222'},
            {id:3, name:'A-Tione3', score: 97},
            {id:4, name:'A-Tione4', score: 96},
            {id:5, name:'A-Tione5', score: 90},
            {id:6, name:'A-Tione6', score: 105},
            {id:7, name:'A-Tione7', score: 80},
            {id:8, name:'A-Tione8', score: 150},
            {id:9, name:'A-Tione9', score: 60},
            {id:10, name:'A-Tione10', score: 107},
        ],
    }
},
methods: {
    sort() { //模拟排序,发送ajax请求,获取新数据
        this.loading = true
        setTimeout(()=> {
            this.dataSource.sort((a, b) => a.score - b.score)
            this.loading = false
        },1000)
    },

},

组件内部 :


    
{{column.text}}
methods: { changeOrderBy (key) { const copy = JSON.parse(JSON.stringify(this.orderBy)) let oldValue = copy[key] if (oldValue === 'asc') { copy[key] = 'desc' } else if (oldValue === 'desc') { copy[key] = true } else { copy[key] = 'asc' } this.$emit('update:orderBy', copy) }, }

7、支持请求数据时,展示loading状态

通常loading状态是用于处理请求接口时等待接口响应的样式展示。

内部实现无需js代码,只需在html中增加v-if判断,但loading为true时展示菊花,否则不展示即可。展示菊花为css样式,将其table组件全部遮盖展示loading动画即可。

用vue写轮子的一些心得(九)——table表单组件_第3张图片

外部数据传参:



sort() {
    this.loading = true
    setTimeout(()=> {
        this.dataSource.sort((a, b) => a.score - b.score)
        this.loading = false
    },1000)
},

内部实现:

8、支持可展开,当表格内容较多不能一次性完全展示时

在外部传入进来的数据结构中,extendField字段为展开内容字段,当展示时则可点击展开。

这里的展开是通过新开一个tr标签来实现的,要注意的是新的tr标签中的td是需要做合并操作的,这里需要动态计算colspan,这里用到了计算属性expendedCellColSpan方法来计算table中的一行有多少列,以此来计算合并的项数。

展开的内容确定则是通过一个数组来实现的,点击某一行找到这行的id,然后push到数组中,再进行hash匹配。否则splice将这行数据从数组中删除。

展开
computed: {
    expendedCellColSpan() {
        let result = 0
        if (this.checkable) {result += 1}
        if (this.numberVisible) {result += 1}
        if (this.extendField) {result += 1}
        if (this.$scopedSlots.default) {result += 1}
        return result
    }
},
methods: {
    inExpendedIds(id) {
        return this.expendedIds.indexOf(id) > -1
    },
    expendItem(id) {
        if (this.inExpendedIds(id)) {
            this.expendedIds.splice(this.expendedIds.indexOf(id), 1)
        } else {
            this.expendedIds.push(id)
        }
    },
}

 9、支持在table最后一列传自定义内容,比如按钮

在组件内部的solt 上定义一个属性,名字可以任意取(比如item)。然后我们在组件入参的位置加上一个slot-scope=”xx“,通过xx.item即可拿到solt上传递的值(例如一行数据的值)。从而实现在组件外部进行查看编辑等等逻辑。

组件传参:


        
        
    

组件内部: 

操作

 10、默认固定表头

思路:

因为table组件内部的td不能单独设置overflow: auto;,所以固定表头并不能很容易地实现,因此我们要另想办法。

怎么办呢?再创建一个table,将旧table的th装进新table中去,然后再通过css将新table放置在旧table上方即可。因此整个table的高度是由新table+旧table组成的,因此我们在设置容器高度时需要减去多出来的新table高度才正确。

这里我们在mounted钩子中就需要做些dom魔法:临时创建一个table容器, 再将旧table的children的第一个节点也就是th挪到新table中。注意这里虽然th移动了位置,但是th上面的node节点与监听事件仍然与旧table中的th保持一致,并不会丢失。简单来看仅仅是样式移动了位置,逻辑并没有移动。

同理,如果我们要做固定列,比如一行的内容太多,则需要固定编辑的列。我们也可以使用相同思路,重新创建一个新table,再将旧table中的编辑列移至新table中,然后再通过css调整到正确的位置即可。

组件内部实现:

mounted() {
    let table2 = this.$refs.table.cloneNode(false)
    this.table2 = table2
    let tHead = this.$refs.table.children[0]
    let {height} = tHead.getBoundingClientRect()
    this.$refs.tableWrapper.style.height = this.height-height + 'px'
    table2.appendChild(tHead)
    this.$refs.wrapper.appendChild(table2)
}

 

 

完整代码可参考我的GitHub:https://github.com/A-Tione/tione/blob/master/src/table/table.vue

你可能感兴趣的:(VUE,table组件实现,vue,table组件,table轮子,table组件轮子,vue,表格组件)