我們前端開發人員在使用表格的過程中,大概率碰到的都是表格頭部在表格的最上邊,然后呈一行展示,緊接着就是表格的每一行的每一個單元格來展示具體內容的場景,很少會遇到表格的頭部呈縱向一行展示,也就是說表格的頭部在左邊,具體的內容在右邊(也可以說是左右結構)這種使用場景,而且有時候的場景可能會是縱向表頭有兩列或多列。
遇到這樣的需求或者說使用場景,你也不能說人家產品提的需求不合理,畢竟使用場景不同。我不知道咱們同行的前端大佬是用啥牛逼格拉斯的技術來實現這樣的需求的,反正我以前基本都是直接上一個表格,然后各種tr、td的往上堆,如果需要展示的多了,最后會發現整個頁面上基本全是各種tr、td的標簽,Level Low就不說了,關鍵是看着鬧心、煩心外加惡心。
最近想着說再遇到這樣的需求,可不能再各種tr、td往上堆了,恰好近期的需求開發上也有這樣的使用場景,索性就動動腦筋自己封裝一個吧,以后用起來也方便,也能提高開發效率,界面看起來也能清爽不少。
一開始搞的時候,確實沒頭緒,很懵圈。上網搜了一把,想着說看看能不能找點靈感,或者說有好的例子能借鑒一下。結果一圈下來,發現就算是有人搞過這樣的封裝,也是感覺詞不達意,說好的封裝呢?有人基於elementUI就實現了一列縱向表頭的封裝,有人基於索引實現了兩列縱向表頭的封裝,但代碼中出現的各種判斷和數字令人費解,完全不知所雲,不利於擴展。
算了,還是自己想辦法吧。不有句話說的好嘛:“人的腦洞有多大,就能實現多牛逼的需求。”於是,對着設計稿上這樣的表格,我是左看右看,上看下看,后來突然想到我們經常使用的表單組件,大概率不就是左右結構嘛,基於這個我又想到曾經在封裝Vue Element的form表單組件的時候還使用過分段的思想,現在把這個分段的思想用在這里不正好嗎?
只要思想不滑坡,辦法總比困難多,這句老俗語總結的真是太TMD的完全正確了!
照例先上一張效果圖:
1、封裝的縱向表頭表格組件Table.vue
<template>
<table class="portait-table" border="1" cellspacing="0" cellpadding="0">
<tr v-for="(row, i) in columns" :key="i">
<template v-for="x in row">
<TD :config="x" :data="data" />
</template>
</tr>
</table>
</template>
<script>
import * as Components from '@/components/table/cell/components'
import { chunk } from '@/utils'
import { noop } from '@/common/constant'
export default {
props: {config: Object},
data() {
const {headers, data, rowSize = 2} = this.config || {}
return {
headers,
data,
rowSize,
};
},
computed: {
columns: ({headers, rowSize}) => chunk(headers, rowSize)
},
components: {
TD: {
functional: true,
props: {config: Object, data: Object},
render: (h, {props: {config, data}}) => {
let {type = 'Default', label, prop} = config, value = data[prop], isEmpty = value === '' || value === undefined || value === null, children = noop
if(label && isEmpty) children = h(Components.Default, {props: {value: '-'}})
else children = h(Components[type], {props: {value, data, ...config}})
return [h('td', label), h('td', [children])]
}
}
},
mounted(){
const {columns} = this, last = columns[columns.length - 1], lastTwo = columns[columns.length - 2]
if(lastTwo){
for(let i = 0; i < (lastTwo.length - last.length); i++){
this.headers.push({prop: '', label: ''})
}
}
},
}
</script>
<style lang="scss" scoped>
.portait-table{
border-collapse: collapse;
border: none;
width:100%;
td {
border: 1px solid #F3F3F3;
height: 40px;
padding-left: 10px;
color:#333;
&:nth-child(odd){
background: #EAEAEA;
color: #454545;
}
}
}
</style>
對於以上組件中的代碼,需要做一些以下說明。
2、分段工具的實現chunk
export const chunk = (arr, size) => {
if(!arr.length || size < 1) return [];
let list = [], index = 0, resIndex = 0, len = arr.length;
while (index < len) {
list[resIndex++] = arr.slice(index, index += size);
}
return list;
}
數組被分段后的效果,你可以自己打印出來看看就明白了。
3、封裝的組件中引入了一些其他的組件是干嘛使的呢?比如:
import * as Components from '@/components/table/cell/components'
引入這些組件,主要是為了對一些特殊的字段值進行特殊的處理,比如金額千分位、時間戳格式化、枚舉(映射)等,這些組件的具體代碼和介紹在我的這篇博文封裝Vue Element的table表格組件中有具體的描述,您可以擺駕過去上上眼。
4、代碼中的render函數,這里也不再贅述,我之前的博文中都有介紹,你也可以自己去看vue的API或查閱其他資料。
5、在封裝的組件的mounted生命周期里有這樣一段代碼:
const {columns} = this, last = columns[columns.length - 1], lastTwo = columns[columns.length - 2]
if(lastTwo){
for(let i = 0; i < (lastTwo.length - last.length); i++){
this.headers.push({prop: '', label: ''})
}
}
這段代碼是干嘛滴的呢?我們都知道,像這樣的縱向表頭左右結構展示的表格,不管你分了幾列表頭(只有一列表頭的除外)展示,都有可能在表格的最后一行出現不完整的情況,也就是說可能會在整個表格的右下角出現空缺的情況,如下圖:
這種類似表格殘缺不全的情況當然是不被允許的,mounted生命周期中的那段代碼就是用來補這個缺的。其原理就是拿分段后的最后一個數組長度與倒數第二個數組長度進行比較,如果兩者不相等,則最后一個數組長度比倒數第二個數組長度少了幾個,就在headers數組的最后push幾個屬性值是空的的對象,最后再利用計算屬性去重新分段(組件中的計算屬性會被執行兩次,第一次是初始分段,第二次是補缺后的再次分段),就達到了本文開頭展示的補缺后的效果圖。
其實,代碼中真正補缺的原理可能與我描述的實現不太一樣,比如代碼中並沒有拿分段后的最后一個數組長度與倒數第二個數組長度進行比較,而是用了一個for循環就搞定了,因為for循環也有自己的判斷嘛,但是原理真的差不離。
6、至於那個noop,它其實就是定義了一個能返回空對象的函數export const noop = () => {}
,它在封裝中的作用就是用來初始化函數。還有那個rowSize,是用來設置分成幾段的,默認是2段。
7、封裝時用到了computed計算屬性,這個屬性想必大家已經很熟了,這里再多說一句吧:計算屬性的key的值是一個函數,其參數是Vue的實例化this對象。為啥這里要強調這一點呢,是因為知道了這一點,我們就可以在其函數的參數中直接解構this了,比如:
computed: {
columns: ({headers, rowSize}) => chunk(headers, rowSize)
},
而我們平時常用的寫法是:
computed: {
columns(){
const {headers, rowSize} = this
return chunk(headers, rowSize)
}
},
兩廂對比,哪種寫法更優雅、性能更高呢?當然是前者了。
8、使用封裝后的表格Table組件
<template>
<Table :config="config" />
</template>
<script>
import Table from '@/components/Table'
export default {
components: {
Table
},
data(){
return {
config: {
headers: [
{prop: 'ruleName', label: '規則名稱'},
{prop: 'money', label: '執行金額', type: 'Currency'},
{prop: 'fileName', label: '附件名稱'},
{prop: 'ruleRiskLevel', label: '預警顏色'},
{prop: 'monitorResult', label: '監控結果', type: 'Enum', Enum: {name: 'monitor'}},
{prop: 'productCode', label: '產品系列'},
{prop: 'date', label: '執行時間', type: 'Date', format: 'yyyy-MM-dd'},
],
rowSize: 2,
data: {
ruleName: '股權質押',
money: 3256898,
fileName: '',
ruleRiskLevel: '紅色',
monitorResult: '00',
productCode: '事業部',
date: new Date().getTime(),
}
}
}
},
}
</script>
本文到此,基本就實現了縱向表頭的table表格封裝,想展示幾列表頭,就把rowSize設置成幾就可以了,非常方便。