造輪子-table組件的實(shí)現(xiàn)

我們需要實(shí)現(xiàn)的功能
  1. 展示數(shù)據(jù)(帶邊框拟淮,緊湊型/松散型网杆,斑馬條紋)
  2. 選中數(shù)據(jù)(單選/全選)
  3. 展示排序
  4. 固定表頭/列
  5. 可展開
組件的基本結(jié)構(gòu)
  • table.vue
<template>
    <div>
        <table>
            <thead>
            <tr>
                <th>#</th>
                <th v-for="column in columns">
                    {{column.text}}
                </th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(item,index) in dataSource">
                <td>{{index}}</td>
                <template v-for="column in columns">
                    <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                    <td>{{item[column.field]}}</td>
                </template>
            </tr>
            </tbody>
        </table>
    </div>
</template>

<script>
    export default {
        name: "LiFaTable",
        props: {
            columns: {
                type: Array,
                required: true
            },
            dataSource: {
                type: Array,
                required: true
            }
        }
    }
</script>
  • demo.vue
<lf-table :columns="columns" :data-source="dataSource"></lf-table>
data() {
            return {         
                columns: [
                    //表頭每一列顯示的文本和字段
                    {text: '姓名', field: 'name'},
                    {text: '分?jǐn)?shù)', field: 'score'}
                ],
                dataSource: [
                    {id: 1, name: '發(fā)發(fā)', score: 100},
                    {id: 2, name: '琳琳', score: 99}
                ]
            }
        },
實(shí)現(xiàn)checkbox全選和單選

思路: 通過單向數(shù)據(jù)流删豺,外界好傳入一個selectedItem,table接受這個selectedItem骡尽,默認(rèn)為空數(shù)組
單選:對表單內(nèi)容的checkbox添加change事件onChangeItem把每一列行的index积仗,item還有原生event都傳進(jìn)去,然后當(dāng)改變checkbox狀態(tài)的時候蚂子,先對selectedItem數(shù)組進(jìn)行深拷貝沃测,然后如果選中狀態(tài)就把item push到深拷貝后的數(shù)組,如果沒選中就從數(shù)組中找到當(dāng)前item食茎,刪除蒂破,最后觸發(fā)一個updae:selectedItem事件,把拷貝后的數(shù)組傳進(jìn)去 多選:給表頭添加change事件onChangeItemAll傳入一個原生event别渔,如果是選中狀態(tài)就把傳入的所有數(shù)據(jù)的數(shù)組dataSource作為參數(shù)通過emit傳給updae:selectedItem事件附迷,沒選中就把空數(shù)組傳進(jìn)去惧互,然后在表單內(nèi)容里的checkbox標(biāo)簽中每一個都加一個checked變量,通過判斷selectedItem數(shù)組中是否包含當(dāng)前對應(yīng)的列來更改選中狀態(tài) 半選:通過給全選的checkbox加一個ref="a"喇伯,然后監(jiān)聽selectedItem喊儡,只要selectedItem.length大于0就設(shè)置this.refs.a.indeterminate = true,其他情況都等于false

  • demo.vue
<div style="margin: 28px">
            <lf-table :columns="columns" :data-source="dataSource"
                      bordered :selected-item.sync="selectedItem"
            ></lf-table>
        </div>
  • table.vue
<template>
    <div class="lifa-table-wrapper">
        <table class="lifa-table" :class="{bordered,compact,striped}">
            <thead>
            <tr>
                <th>
                    <input type="checkbox" @change="onChangeItemAll($event)" ref="a">
                </th>
                <th v-if="numberVisible">#</th>
                <th v-for="column in columns">
                    {{column.text}}
                </th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(item,index) in dataSource">
                <th>
                    <input type="checkbox" @change="onChangeItem(item, index, $event)"
                        :checked="onChecked(item)"
                    >
                </th>
                <td v-if="numberVisible">{{index+1}}</td>
                <template v-for="column in columns">
                    <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                    <td>{{item[column.field]}}</td>
                </template>
            </tr>
            </tbody>
        </table>
    </div>
</template>

<script>
    export default {
        name: "LiFaTable",
        props: {
            columns: {
                type: Array,
                required: true
            },
            dataSource: {
                type: Array,
                required: true
            },
            selectedItem: {
                type: Array,
                default: ()=>[]
            },
            striped: {
                type: Boolean,
                default: true
            },
            //是否顯示索引
            numberVisible: {
                type: Boolean,
                default: false
            },
            //是否帶邊框
            bordered: {
                type: Boolean,
                default: false
            },
            //是否是緊湊型
            compact: {
                type: Boolean,
                default: false
            }
        },
        methods: {
            onChangeItem(item, index, e){
                let copy = JSON.parse(JSON.stringify(this.selectedItem))
                if(e.target.checked){
                    copy.push(item)
                }else{
                    copy.splice(copy.indexOf(item),1)
                }
                this.$emit('update:selectedItem',copy)
            },
            onChangeItemAll(e){
                if(e.target.checked){
                    this.$emit('update:selectedItem',this.dataSource)
                }else{
                    this.$emit('update:selectedItem',[])
                }
            },
            onChecked(item){
                return this.selectedItem.filter(n=>n.id === item.id).length > 0 ? true : false
            }
        },
        watch: {
            selectedItem(){
                if(this.selectedItem.length === this.dataSource.length){
                    this.$refs.a.indeterminate = false
                    this.$refs.a.checked = true
                }else if(this.selectedItem.length === 0){
                    this.$refs.a.indeterminate = false
                }else{
                    this.$refs.a.indeterminate = true
                }
            }
        }
    }
</script>

<style scoped lang="scss">
    @import 'var';
    .lifa-table{
        border-collapse: collapse;
        border-spacing: 0;
        border-bottom: 1px solid $gray;
        width: 100%;
        &.bordered{
            border: 1px solid $gray;
            td,th{
                border: 1px solid $gray;
            }
        }
        &.compact{
            td,th{
                padding: 4px;
            }
        }
        &.striped{
            tbody{
                > tr{
                    &:nth-child(odd){
                        background: white;
                    }
                    &:nth-child(even){
                        background: #fafafa;
                    }
                }
            }
        }
        th,td{
            border-bottom: 1px solid $gray;
            text-align: left;
            padding: 8px;
        }
        th{
            color: #909399;
        }
        td{
            color: #606266;
        }

    }
</style>

解決取消選中異常的bug
當(dāng)我們?nèi)x的時候稻据,比如我點(diǎn)擊第二個取消選中可最后一個也跟著取消選中了艾猜,原因就是因?yàn)樯羁截惖脑颍驗(yàn)樯羁截惡髷?shù)組里的對象和原先的不是同一個索引捻悯,所以不能通過indexOf查找匆赃,而要通過取消選中的時候,當(dāng)前取消選中項(xiàng)的id今缚,找到深拷貝后數(shù)組里的id不等于當(dāng)前選中項(xiàng)的id的元素算柳,然后傳給父組件

    let copy = JSON.parse(JSON.stringify(this.selectedItem))
                if(e.target.checked){
                    copy.push(item)
                }else{
                    //取消選中狀態(tài):點(diǎn)擊當(dāng)前的checkbox保留數(shù)組中id不等于當(dāng)前id的項(xiàng)
                    copy= copy.filter(i=>i.id !== item.id)
                }
                this.$emit('update:selectedItem',copy)
            },
watch: {
    selectedItem(){
        if(this.selectedItem.length === this.dataSource.length){
            this.$refs.a.indeterminate = false  
        }else if(this.selectedItem.length === 0){
            this.$refs.a.indeterminate = false 
        }else{
            this.$refs.a.indeterminate = true
        }
    }
}

實(shí)現(xiàn)選中所有單選的checkbox后,全選的checkbox也跟著選中
錯誤寫法:
直接判斷selectedItem.length和dataSource.length的長度是否相等姓言,這樣雖然可以實(shí)現(xiàn)我們要的功能瞬项,但是是錯誤的寫法

<input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
computed: {
  areAllItemChecked(){
  return this.selectedItem.length === this.dataSource
}
}

正確寫法:
我們需要判斷這兩個數(shù)組里的所有項(xiàng)的id是否都相等來判斷是否全選。
那么現(xiàn)在的問題是我們?nèi)绾闻袛鄡蓚€數(shù)組是一樣的何荚。比如this.dataSource=[{id:1},{id:2}]this.selectedItem = [{id:2},{id:1}]我們該如何判斷兩個數(shù)組是一樣的哪囱淋?
我們無法通過遍歷判斷第一個dataSource的第一個是否等于selectedItem的第一個來判斷是否相等,因?yàn)樯厦娴捻樞虿灰粯邮奁彩窍嗟鹊摹?br> 所以我們需要

  1. 先對這兩個數(shù)組里的id進(jìn)行排序
this.dataSource.sort((a,b)=>a.id - b.id)

但是sort會改變原來的數(shù)組绎橘,所以我們需要先用map生成一個新的數(shù)組,然后在排序

areAllItemChecked(){
  const a = this.dataSource.map(n=>n.id).sort()
  const b = this.selectedItem.map(n=>n.id).sort()
  if(a.length === b.length){
      for(let i = 0;i<a.length;i++){
          if(a[i] !== b[i]){
              return false
          }else{
              return true
          }
      }
  }
}
表格排序的實(shí)現(xiàn)

補(bǔ)充知識:在vue里唠倦,你不可能根據(jù)一個屬性檢查另一個屬性的合法性,因?yàn)槟阍趘alidate中拿不到實(shí)例(this)

export default{
  props: {
    name: 'lifa'
  },
  orderBy: {
    type: Object,
    default: ()=>({}),
    validator(object){
      console.log(this)//undefined
    }
  }
  
}

定義排序規(guī)則:
通過外界傳入一個ordreBy對象來定義

orderBy: {//true:開啟排序涮较,但是不確定asc desc
    name: true,
    score: 'desc'
},

如果想要某個列按升序排列就在當(dāng)前字段后加一個'asc'稠鼻,降序就加'score',默認(rèn)開啟排序狂票,但是不是升序也不是降序就用true候齿,然后在組件中接受這個orderBy

<th v-for="column in columns" :key="column.field">
    <div class="lifa-table-header">
        {{column.text}}
        <!--如果對應(yīng)的key在orderBy這個對象里,就顯示-->
        <span class="lifa-table-sorter" v-if="column.field in orderBy" @click="changeOrderBy(column.field)">
            <lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
            <lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
        </span>
    </div>
</th>
props: {
  //通過什么排序
            orderBy: {
                type: Object,
                default: ()=>({})
            }
},
methods: {
  changeOrderBy(key){
const copy = JSON.parse(JSON.stringify(this.orderBy))
if(copy[key] === 'asc'){
    copy[key] = 'desc'
}else if(copy[key] === 'desc'){
    copy[key] = true
}else{
    copy[key] = 'asc'
}
this.$emit('update:orderBy',copy)
}
}

上面的代碼可以實(shí)現(xiàn)狀態(tài)的切換闺属,當(dāng)點(diǎn)擊的時候如果是升序就會變成降序慌盯,如果是降序就會變成默認(rèn)狀態(tài),否則就會變成升序掂器,然后需要在外界.sync一下

<lf-table :order-by.sync="orderBy"></lf-table>

然后我們只需要監(jiān)聽這個update:orderBy事件亚皂,當(dāng)事件觸發(fā)的時候執(zhí)行一個方法,來修改我們dataSource里的數(shù)據(jù)

<lf-table :order-by.sync="orderBy"
                      @update:orderBy="changeOrder"
            ></lf-table>
methods: {
            changeOrder(data){
                this.$nextTick(()=>{
                    let type
                    let arr = this.dataSource.map(item=>{
                        type = typeof item[this.key]
                        return item[this.key]
                    })
                    if( data[this.key]=== 'asc'){
                        if(type === 'number'){
                            arr.sort((a,b)=>a-b)
                        }else{
                            arr.sort((a, b) => b.localeCompare(a, 'zh-Hans-CN', {sensitivity: 'accent'}))
                        }
                    }else if(data[this.key] === 'desc'){
                        if(type === 'number'){
                            arr.sort((a,b)=>b-a)
                        }else{
                            arr.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN', {sensitivity: 'accent'}))
                        }
                    }
                    arr.map((item,index)=>{
                        this.dataSource[index][this.key]=item
                    })

                })
            },
        },
        watch: {
        //監(jiān)聽orderBy的變化国瓮,如果新值里的屬性值不等于舊值的屬性值說明當(dāng)前屬性變了灭必,拿到這個key狞谱,比如我一開始是{name: '發(fā)發(fā)',score:100}現(xiàn)在是{name: '發(fā)發(fā)',score:90},那么我們就可以拿到score這個key
          orderBy(val,oldVal){
              for(let key in this.orderBy){
                  if(val[key] !== oldVal[key]){
                      this.key = key
                  }
              }
          }
        },
實(shí)現(xiàn)表頭固定

思路:

  1. 對table拷貝一份禁漓,然后把拷貝后的table中的tbody刪除跟衅,只留一個thead固定在頂部(這里因?yàn)閠head必須在table中,所以我們不能像div一樣把它單獨(dú)拿出來)
  2. 復(fù)制后的table刪除tbody后播歼,寬度會和之前的不一致伶跷,這時候你需要獲取拷貝前的table里每一個th的寬度,然后設(shè)置給拷貝后的th
  • table.vue
<template>
    <div class="lifa-table-wrapper" ref="warpper">
        <div :style="{height,overflow:'auto'}">
            <table class="lifa-table" :class="{bordered,compact,striped}" ref="table">
                <thead>
                <tr>
                    <th>
                        <input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
                    </th>
                    <th v-if="numberVisible">#</th>
                    <th v-for="column in columns" :key="column.field">
                        <div class="lifa-table-header">
                            {{column.text}}
                            <!--如果對應(yīng)的key在orderBy這個對象里秘狞,就顯示-->
                            <span class="lifa-table-sorter" v-if="column.field in orderBy"
                                  @click="changeOrderBy(column.field)">
                            <lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
                            <lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
                        </span>
                        </div>
                    </th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item,index) in dataSource" :key="item.id">
                    <th>
                        <input type="checkbox" @change="onChangeItem(item, index, $event)"
                               :checked="onChecked(item)" class="checkbox"
                        >
                    </th>
                    <td v-if="numberVisible">{{index+1}}</td>
                    <template v-for="column in columns">
                        <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                        <td :key="column.field">{{item[column.field]}}</td>
                    </template>
                </tr>
                </tbody>
            </table>
        </div>
        <div class="lifa-table-loading" v-if="loading">
            <lf-icon name="loading"></lf-icon>
        </div>
    </div>
</template>
props: {
  height: {
    type: [Number, String],
  }
},
mounted(){
  let oldTable = this.$refs.table
let newTable = oldTable.cloneNode(true)
this.newTable = newTable
this.updateHeadersWidth()
//窗口改變時重新獲取一下各個th的寬度叭莫,重新賦值
window.addEventListener('resize', this.onWindowResize)
newTable.classList.add('lifa-table-copy')
this.$refs.warpper.appendChild(newTable)
},
beforeDestroy() {
            window.removeEventListener('resize', this.onWindowResize)
            this.newTable.remove()
        },
methods: {
  onWindowResize() {
                this.updateHeadersWidth()
            },
            updateHeadersWidth() {
                let tableHeader = Array.from(this.$refs.table.children).filter(node => node.nodeName.toLocaleLowerCase() === 'thead')[0]
                let tableHeader2
                Array.from(this.newTable.children).map((node) => {
                    if (node.nodeName.toLocaleLowerCase() === 'tbody') {
                        node.remove()
                    } else {
                        //也就是tableHeader=<thead>
                        tableHeader2 = node
                    }
                })
                Array.from(tableHeader.children[0].children).map((node, index) => {
                    let {width} = node.getBoundingClientRect()
                    console.log(width);
                    tableHeader2.children[0].children[index].style.width = `${width}px`
                })
            },
}

上面的代碼雖然實(shí)現(xiàn)了頂部表頭固定,但是因?yàn)槟闶菑?fù)制了一個谒撼,所以表頭的點(diǎn)擊事件并不會起作用食寡。
解決方法:
我們在拷貝一個新的table的時候不全部拷貝只拷貝table不拷貝table里面的子元素,然后我們把原有的thead添加到拷貝后的table中廓潜,這樣就還是原來的thead抵皱,而且可以單獨(dú)拿出來,然后我們通過讓用戶給每一列傳一個寬度辩蛋,通過這個寬度來設(shè)置原來的table里的寬度呻畸,并且拷貝后的table因?yàn)闀采w表格內(nèi)容的第一行,所以還要通過margin-top移下來thead的高度的距離

data(){
  return {
    columns: [
                    //表頭每一列顯示的文本和字段
                    {text: '姓名', field: 'name', width: 100},
                    {text: '分?jǐn)?shù)', field: 'score',width: 100},
                    {text: '年齡', field: 'age'}
                ],
                orderBy: {//true:開啟排序悼院,但是不確定asc desc
                    name: true,
                    score: 'desc'
                },
                dataSource: [
                    {id: 1, name: '發(fā)發(fā)', score: 100, age:18},
                    {id: 2, name: '琳琳', score: 99, age: 16},
                    {id: 3, name: '西西', score: 99, age: 20},
                    {id: 4, name: '泳兒', score: 99, age: 21},
                    {id: 5, name: '美美', score: 99, age: 22},
                    {id: 6, name: '阿宇', score: 99, age: 26},
                    {id: 7, name: '發(fā)發(fā)', score: 100, age:18},
                    {id: 8, name: '琳琳', score: 99, age: 16},
                    {id: 9, name: '西西', score: 99, age: 20},
                    {id: 10, name: '泳兒', score: 99, age: 21},
                    {id: 11, name: '美美', score: 99, age:18},
                    {id: 12, name: '阿宇', score: 99, age: 16}
                ],
  }
}
  • table.vue
<template>
    <div class="lifa-table-wrapper" ref="wrapper">
        <div :style="{height: `${height}px`,overflow:'auto'}" ref="tableContent">
            <table class="lifa-table" :class="{bordered,compact,striped}" ref="table">
                <thead>
                <tr>
                    <th :style="{width: '50px'}">
                        <input type="checkbox" @change="onChangeItemAll($event)" ref="a" :checked="areAllItemChecked">
                    </th>
                    <th v-if="numberVisible" :style="{width: '50px'}">#</th>
                    <th v-for="column in columns" :key="column.field" :style="{width: `${column.width}px`}">
                        <div class="lifa-table-header">
                            {{column.text}}
                            <!--如果對應(yīng)的key在orderBy這個對象里伤为,就顯示-->
                            <span class="lifa-table-sorter" v-if="column.field in orderBy"
                                  @click="changeOrderBy(column.field)">
                            <lf-icon name="asc" :class="{active:orderBy[column.field] === 'asc'}"></lf-icon>
                            <lf-icon name="desc" :class="{active: orderBy[column.field] === 'desc'}"></lf-icon>
                        </span>
                        </div>
                    </th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item,index) in dataSource" :key="item.id">
                    <th :style="{width: '50px'}">
                        <input type="checkbox" @change="onChangeItem(item, index, $event)"
                               :checked="onChecked(item)" class="checkbox"
                        >
                    </th>
                    <td v-if="numberVisible" :style="{width: '50px'}">{{index+1}}</td>
                    <template v-for="column in columns">
                        <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                        <td :key="column.field" :style="{width: `${column.width}px`}">{{item[column.field]}}</td>
                    </template>
                </tr>
                </tbody>
            </table>
        </div>
        <div class="lifa-table-loading" v-if="loading">
            <lf-icon name="loading"></lf-icon>
        </div>
    </div>
</template>
mounted() {
    let oldTable = this.$refs.table
    let newTable = oldTable.cloneNode()
    let {height} = oldTable.children[0].getBoundingClientRect()
    newTable.appendChild(oldTable.children[0])
    this.$refs.tableContent.style.marginTop = height + 'px'
    this.$refs.wrapper.style.height = this.height - height + 'px'
    newTable.classList.add('lifa-table-copy')
    this.$refs.wrapper.appendChild(newTable)
},
實(shí)現(xiàn)展開行功能

實(shí)現(xiàn)思路:通過用戶在dataSource里傳入description指定展開要顯示的字段,然后通過一個expend-field傳入我們的description字段据途,也就是expend-field="description"绞愚。因?yàn)槲覀円诿恳粋€tr下面再加一個tr,但是你不能再單獨(dú)v-for那就不能同步了颖医,所以要想同時遍歷多個外層就要用template位衩,然后給每一列前面加一個展開的按鈕,點(diǎn)擊這個按鈕的時候把當(dāng)前的id傳給一個數(shù)組熔萧,然后通過判斷數(shù)組里是否有這個id糖驴,沒有就加到數(shù)組里,有就刪除佛致,之后再給展開的這一欄通過判斷數(shù)組里是否有當(dāng)前id來讓它是否展示

  • table.vue
<template v-for="(item,index) in dataSource">
                    <tr :key="item.id">
                        <td :style="{width: '50px'}" @click="expendItem(item.id)"
                            class="lifa-table-center" :class="{expend: expendVisible(item.id)}"
                        >
                            <lf-icon name="right"></lf-icon>
                        </td>
                        <td :style="{width: '50px'}" class="lifa-table-center">
                            <input type="checkbox" @change="onChangeItem(item, index, $event)"
                                   :checked="onChecked(item)" class="checkbox"
                            >
                        </td>
                        <td v-if="numberVisible" :style="{width: '50px'}">{{index+1}}</td>
                        <template v-for="column in columns">
                            <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                            <td :key="column.field" :style="{width: `${column.width}px`}">{{item[column.field]}}</td>
                        </template>
                    </tr>
                    <tr v-if="expendVisible(item.id)">
                        <td :key="`${item.id}-1`" :colspan="columns.length+2">
                            {{item[expendField] || '空'}}
                        </td>
                    </tr>
                </template>
methods: {
  expendItem(id){
                if(this.expendIds.indexOf(id) >= 0){
                    this.expendIds.splice(this.expendIds.indexOf(id),1)
                }else{
                    this.expendIds.push(id)
                }
            },
            expendVisible(id){
                return this.expendIds.indexOf(id) >= 0
            },
}
<lf-table :columns="columns" :data-source="dataSource"
                      bordered :selected-item.sync="selectedItem" :order-by.sync="orderBy"
                      @update:orderBy="changeOrder" :loading="loading" :height="400"
                      expend-field="description"
            ></lf-table>
dataSource: [
                    {id: 1, name: '發(fā)發(fā)', score: 100, age:18, description: '你最帥'},
                    {id: 2, name: '琳琳', score: 99, age: 16, description: '為啥不做我媳婦'},
                    {id: 3, name: '西西', score: 99, age: 20, description: '好累啊'},
                    {id: 4, name: '泳兒', score: 99, age: 21},
                    {id: 5, name: '美美', score: 99, age: 22},
                    {id: 6, name: '阿宇', score: 99, age: 26},
                    {id: 7, name: '發(fā)發(fā)', score: 100, age:18},
                    {id: 8, name: '琳琳', score: 99, age: 16},
                    {id: 9, name: '西西', score: 99, age: 20},
                    {id: 10, name: '泳兒', score: 99, age: 21},
                    {id: 11, name: '美美', score: 99, age:18},
                    {id: 12, name: '阿宇', score: 99, age: 16}
                ],

上面的展開行里的列數(shù)是寫死的贮缕,而我們有可能沒有展開按鈕或者沒有選擇框,那么這個列數(shù)就不對了俺榆,所以我們需要通過計(jì)算屬性來計(jì)算

<tr v-if="expendVisible(item.id)">
                        <td :key="`${item.id}-1`" :colspan="columns.length+ expendedCellColSpan">
                            {{item[expendField] || '空'}}
                        </td>
                    </tr>
computed: {
  expendedCellColSpan(){
    let result = 0
    if(this.checkable){
        result += 1
    }
    if(this.expendField){
        result += 1
    }
    return result
}
},
props: {
  //是否顯示選擇框
            checkable: {
                type: Boolean,
                default: false
            }
}

table里面可以有按鈕

思路通過用戶傳入一個template感昼,然后在table中通過slot來把你template里的按鈕放到指定位置

<lf-table :columns="columns" :data-source="dataSource"
                      bordered :selected-item.sync="selectedItem" :order-by.sync="orderBy"
                      @update:orderBy="changeOrder" :loading="loading" :height="400"
                      expend-field="description" checkable
            >
                <template slot-scope="xxx">
                    <button @click="edit(xxx.item.id)">編輯</button>
                    <button @click="view(xxx.item.id)">查看</button>
                </template>
            </lf-table>
  • table.vue
<td>
    <slot :item="item"></slot>
</td>
methods: {
  edit(id){
                alert(`正在編輯第${id}個`)
            },
            view(id){
                alert(`正在查看第${id}個`)
            }
}
升級table組件,支持自定義

問題:我們table里只能傳入data而不能傳入a標(biāo)簽肋演,我們現(xiàn)在想給每一列添加一個a標(biāo)簽抑诸,使用vue就沒法實(shí)現(xiàn)
思路:使用jsx

  1. 在vue中使用JSX
    (1).lang="jsx"
    (2).render返回一個標(biāo)簽
    (3).使用css直接還是用class
<script lang="jsx">
    export default {
        data() {
            return {
                n: 13,
                items: [2, 3, 4, 5, 6]
            }
        },
        name: 'demo2',
        render(h) {
            return (
                <div class="xxx">
                    {this.n > 10 ? <span>大</span> : <span>小</span>}
                    <ul>
                        {this.items.map(i =>
                            <li>{i}</li>
                        )}
                    </ul>
                </div>
            )
        }
    }
</script>
<style scoped lang="scss">
  .xxx {
    color: red;
  }
</style>

問題:我們必須得將之前的代碼都改成jsx烂琴,而且使用的人也必須使用jsx格式,限制性太多

  1. 使用slot插槽自定義
    相關(guān)補(bǔ)充:我們可以在組件里寫任何組件標(biāo)簽蜕乡,這些組件標(biāo)簽會默認(rèn)轉(zhuǎn)為插槽奸绷,獲取他們的方式有兩種,一:在mounted中通過this.slots层玲;二直接在父組件中添加slot標(biāo)簽号醉,然后就可以通過this.children拿到他們

將原來的columns數(shù)組去掉,使用組件table-columns來代替

  • table-columns.vue
<template>
  <div></div>
</template>

<script>
    export default {
        name: "table-column.vue",
        props: {
            text: {
                type: String,
                required: true
            },
            field: {
                type: String,
                required: true
            },
            width: {
                type: Number
            }
        }
    }
</script>
  • demo.vue
<lf-table :data-source="dataSource"
                  bordered :selected-item.sync="selectedItem" :height="400"
                  expend-field="description" checkable>
<!--            <template slot-scope="xxx">-->
<!--                <lf-button @click="edit(xxx.item.id)">編輯</lf-button>-->
<!--                <lf-button @click="view(xxx.item.id)">查看</lf-button>-->
<!--            </template>-->
          <lf-table-column text="姓名" field="name" :width="100">
            <template slot-scope="scope">
              <a href="#">{{scope.value}}</a>
            </template>
          </lf-table-column>
          <lf-table-column text="分?jǐn)?shù)" field="score"></lf-table-column>
        </lf-table>
import LfTable from './table'
    import LfTableColumn from './table-column'
    export default {
        name: "demo",
        components: {
            LfTable,
            LfTableColumn
        },
        data() {
            return {
                dataSource: [
                    {id: 1, name: '發(fā)發(fā)', score: 100, age:18, description: '你最帥辛块,將來一定會成為一個了不起的演員'},
                    {id: 2, name: '琳琳', score: 99, age: 16, description: '為啥不做我媳婦'},
                    {id: 3, name: '西西', score: 99, age: 20, description: '好累啊'},
                    {id: 4, name: '泳兒', score: 99, age: 21},
                    {id: 5, name: '美美', score: 99, age: 22},
                    {id: 6, name: '阿宇', score: 99, age: 26},
                    {id: 7, name: '泳兒', score: 99, age: 21},
                    {id: 8, name: '美美', score: 99, age: 22},
                    {id: 9, name: '阿宇', score: 99, age: 26}
                ],
                selectedItem: [],
            }
        },
  • table.vue
<template v-for="column in columns">
      <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
      <td :key="column.field" :style="{width: `${column.width}px`}">
    // 在原來遍歷td內(nèi)容的地方直接加判斷
     {{column.render ? column.render({value: item[column.field]}) : item[column.field]}}
      </td>
</template>
data() {
+ columns: []
},
mounted() {
  // this.$slots遍歷拿到每一個table-columns組件
            this.columns = this.$slots.default.map(node => {
                // node.componentOptions.propsData拿到組件中外界傳進(jìn)來的props的值
                let { text, field, width } = node.componentOptions.propsData
                // 如果組件里面使用了插槽那么就可以拿到插槽里對應(yīng)的render函數(shù)也就是
                // <template slot-scope="scope">
                //     <a href="#">{{scope.value}}</a>
                // </template>
                let render = node.data.scopedSlots && node.data.scopedSlots.default
                return {
                    text, field, width, render
                }
            })
    // 這里之所以要render里面?zhèn)饕粋€對象key是value畔派,是因?yàn)槲覀兦懊鎸懙膕cope.value,
// scope就是我們的形參也就是下面的{value:'立發(fā)'}
      let result = this.columns[0].render({value: '立發(fā)'})
       console.log(result)
}

問題: 上面的代碼如果直接在原來的td渲染的時候修改會報錯

單獨(dú)將this.columns[0].render({value: '立發(fā)'})打印我們發(fā)現(xiàn),他是一個Vnode

問題:那么我們該如何將vnode展示在template里哪
方法:
(1).聲明一個vnodes組件如下

components: {
            LfIcon,
            vnodes: {
                function: true,
                render: (h, ctx) => ctx.props.vnodes
            }
        },

(2).對vnodes組件傳入一個vnodes润绵,綁定的數(shù)據(jù)就是你要傳入的value

<template v-for="column in columns">
                            <!--顯示dataSource中對應(yīng)表頭字段里的內(nèi)容-->
                            <td :key="column.field" :style="{width: `${column.width}px`}">
                              <template v-if="column.render">
                                <vnodes :vnodes="column.render({value: item[column.field]})"></vnodes>
                              </template>
                              <template v-else>
                                {{item[column.field]}}
                              </template>
                            </td>
                        </template>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末线椰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子尘盼,更是在濱河造成了極大的恐慌憨愉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卿捎,死亡現(xiàn)場離奇詭異配紫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)午阵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門躺孝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人底桂,你說我怎么就攤上這事植袍。” “怎么了籽懦?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵奋单,是天一觀的道長。 經(jīng)常有香客問我猫十,道長,這世上最難降的妖魔是什么呆盖? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任拖云,我火速辦了婚禮,結(jié)果婚禮上应又,老公的妹妹穿的比我還像新娘宙项。我一直安慰自己,他們只是感情好株扛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布尤筐。 她就那樣靜靜地躺著汇荐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盆繁。 梳的紋絲不亂的頭發(fā)上掀淘,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音油昂,去河邊找鬼革娄。 笑死,一個胖子當(dāng)著我的面吹牛冕碟,可吹牛的內(nèi)容都是我干的拦惋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼安寺,長吁一口氣:“原來是場噩夢啊……” “哼厕妖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挑庶,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤言秸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挠羔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體井仰,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年破加,在試婚紗的時候發(fā)現(xiàn)自己被綠了俱恶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡范舀,死狀恐怖合是,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锭环,我是刑警寧澤聪全,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站辅辩,受9級特大地震影響难礼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玫锋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一蛾茉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撩鹿,春花似錦谦炬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽础爬。三九已至,卻和暖如春吼鳞,著一層夾襖步出監(jiān)牢的瞬間看蚜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工赖条, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留失乾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓纬乍,卻偏偏與公主長得像碱茁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仿贬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 專業(yè)考題類型管理運(yùn)行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項(xiàng)A選項(xiàng)B選項(xiàng)C選項(xiàng)D選項(xiàng)E選項(xiàng)F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚閱讀 8,977評論 0 13
  • 在后臺管理系統(tǒng)纽竣、數(shù)據(jù)類產(chǎn)品等的設(shè)計(jì)中,表格作為一種常見的信息組織整理手段茧泪,甚至是web頁面的基礎(chǔ)設(shè)施之一蜓氨,其重要性...
    停停走走UP閱讀 5,600評論 3 46
  • (注1:如果有問題歡迎留言探討,一起學(xué)習(xí)队伟!轉(zhuǎn)載請注明出處穴吹,喜歡可以點(diǎn)個贊哦!)(注2:更多內(nèi)容請查看我的目錄嗜侮。) ...
    love丁酥酥閱讀 4,182評論 2 5
  • 最近做了幾個后臺管理系統(tǒng)港令,表格在其中占據(jù)著不可或缺的地位。在此對于表格的設(shè)計(jì)做一個整理锈颗,如有考慮不周之處顷霹,歡迎留言...
    數(shù)學(xué)老師胡坤鵬閱讀 1,201評論 0 28
  • 作為頁面布局的重要組成部分,表格的身影隨處可見击吱。了解與熟知To B業(yè)務(wù)平臺軟件設(shè)計(jì)的工作人員都應(yīng)該知道淋淀,表格在平臺...
    弘毅道閱讀 19,885評論 3 59