Vue相信玩前端的小伙伴一定不會(huì)陌生。使用Vue最大的好處就是組件化啼肩,通過組件的復(fù)用可以大大減小我們?nèi)粘i_發(fā)的工作量妇斤。最近在工作中遇到這樣的情況骆撇,于是便趁著周末正好將自己寫的組件整理一下来吩,做成更加通用的組件哆键。
先介紹一下背景囊卜。這段時(shí)間因?yàn)楣ぷ魃系男枰M龊笈_(tái)管理系統(tǒng)塞赂,這類系統(tǒng)相信很多小伙伴也很熟悉泪勒。主要以表單的填寫以及表格的展示、編輯為主宴猾。前端這一部分采用Vue全家桶圆存,UI組件采用的是iview。UI組件大大提高了開發(fā)效率以及降低了開發(fā)的復(fù)雜度仇哆,但是組件也不是萬能的沦辙,在面對實(shí)際的業(yè)務(wù)時(shí),還是需要靠我們自己來制作組件讹剔。而這一次要制作的組件就是可以根據(jù)傳入的數(shù)據(jù)不同油讯,自動(dòng)生成不同樣式的表單以及表格了。(當(dāng)然延欠,利用的組件還是基于iview的)
制作思路
組件的本質(zhì)就是拼裝陌兑。通過拼裝一個(gè)個(gè)原子組件,形成一個(gè)大組件最后來滿足我們的實(shí)際業(yè)務(wù)需求由捎。而在我這一次的需求中兔综,原子組件已經(jīng)有iview提供了。我要做的就是怎樣來制作出想要的表單以及表格了。Vue提倡的思想就是數(shù)據(jù)驅(qū)動(dòng)软驰,所以在與其說是拼裝組件涧窒,更不如說是設(shè)計(jì)一個(gè)合理的數(shù)據(jù)結(jié)構(gòu)。
首先是表單碌宴,通過v-for對數(shù)據(jù)進(jìn)行循環(huán)杀狡,然后判斷數(shù)據(jù)對應(yīng)的表單組件類型就可以了。因此我們需要的數(shù)據(jù)結(jié)構(gòu)就需要組件的類型贰镣、綁定的值呜象、值對應(yīng)的變量名(類似username:'用戶'),而其他的屬性就可以根據(jù)需要再做追加碑隆。
再是表格恭陡,同樣也是通過v-for對數(shù)據(jù)進(jìn)行循環(huán)展示。在編輯時(shí)也需要切換到對應(yīng)的組件上煤。因此與表單的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)相同休玩,我們需要組件的類型、綁定的值劫狠、值對應(yīng)的變量名(類似username:'用戶')拴疤。
而其中的難點(diǎn),無非就是在于行數(shù)和列數(shù)的計(jì)算上了独泞。怎么靈活地變換行數(shù)呐矾,還是需要利用到v-for循環(huán)中的index來進(jìn)行幫忙。相信這些懦砂,只要看過一眼代碼就馬上可以理解蜒犯。
// commonForm 代碼
<template>
<div class="wrapper">
<Form ref="commonForm" :label-width="100">
<Row v-for="(rowNum, rowIndex) in _formRow" :key="rowIndex" :gutter="10">
<Col v-for="(colNum, colIndex) in _colNum" :span="_formSpan" :key="colIndex">
<FormItem v-if="(rowNum - 1) * _colNum + colIndex < _formLength" :label="_formLayout[(rowNum - 1) * _colNum + colIndex].title">
<!-- 輸入框 input -->
<Input v-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'text'" type="text" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Input>
<!-- 輸入框 textarea -->
<Input v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'textarea'" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" type="textarea" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Input>
<!-- 級(jí)聯(lián)菜單 -->
<Cascader v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'cascader'" :data="_formLayout[(rowNum - 1) * _colNum + colIndex].data" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Cascader>
<!-- 下拉菜單 -->
<Select v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'selection'" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder">
<Option v-for="(opt, optIndex) in _formLayout[(rowNum - 1) * _colNum + colIndex].selection" :key="optIndex" :value="opt.value">{{opt.title}}</Option>
</Select>
<!-- 日期時(shí)間選擇器 -->
<DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='datetime'" type="datetime" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
<!-- 日期時(shí)間圍選擇器 -->
<DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='datetime-range'" type="datetimerange" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
<!-- 日期范圍選擇器 -->
<DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='date-range'" type="daterange" placement="bottom-end" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
<!-- 時(shí)間范圍選擇器 -->
<TimePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='time-range'" format="HH’mm’ss" type="timerange" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></TimePicker>
</FormItem>
</Col>
</Row>
<Row>
<slot name="btn-area" :form-content="_formLayout"></slot>
</Row>
</Form>
</div>
</template>
// commonTable 代碼
<template>
<div class="table-wrapper">
<table class="content-table">
<thead class="table-header" v-if="_detialData.tableHeader">
<td :colspan="_colNum">
<h3>{{_detialData.tableHeader}}</h3>
</td>
</thead>
<tr class="table-row" v-for="(rownum, rowIndex) in _rowNum" :key="rowIndex">
<td :class="['table-cell', {'table-bg-gray': (colnum % 2 !== 0)}]" v-for="(colnum, colIndex) in _colNum " :key="colIndex" v-if="">
<h4 v-if="(colnum % 2 !== 0)">
{{_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].title}}
</h4>
<div v-else>
<p v-if="viewMode" class="content-text">
{{_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value}}
</p>
<div v-else>
<!-- 輸入框 -->
<Input v-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'text'" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value"></Input>
<!-- textarea -->
<Input v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'textarea'" type="textarea" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value"></Input>
<!-- 日期時(shí)間選擇器 -->
<Date-picker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'datetime'" type="datetime" format="yyyy-MM-dd HH:mm" placement="top" placeholder="請選擇日期..." :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></Date-picker>
<!-- 日期時(shí)間圍選擇器 -->
<DatePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='datetime-range'" type="datetimerange" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></DatePicker>
<!-- 日期范圍選擇器 -->
<DatePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='date-range'" type="daterange" placement="bottom-end" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></DatePicker>
<!-- 時(shí)間范圍選擇器 -->
<TimePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='time-range'" format="HH’mm’ss" type="timerange" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></TimePicker>
<!-- 級(jí)聯(lián)菜單 -->
<Cascader v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'cascader'" :data="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].data" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" :placeholder="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].placeholder"></Cascader>
<!-- 下拉菜單 -->
<Select v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'selection'" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" :placeholder="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].placeholder">
<Option v-for="(opt, optIndex) in _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].selection" :key="optIndex" :value="opt.value">{{opt.title}}</Option>
</Select>
</div>
</div>
</td>
</tr>
</table>
<slot name="btn-area" :detials="_detialData" :view-mode="_viewMode"></slot>
</div>
</template>
項(xiàng)目地址:my-components 項(xiàng)目主頁上也有相關(guān)的說明。這里就不做贅述了荞膘。
說完了思路那么還是一如既往罚随,先來看看效果直接感受一下吧。
表單組件 commonForm
當(dāng)我們傳入如下數(shù)據(jù)格式的數(shù)據(jù)后羽资,就會(huì)生成這樣的表單淘菩。
mockForm: [
{ title: '輸入框', name: 'text', type: "text", placeholder: '請輸入輸入框內(nèi)容', value: '' },
{
title: '地區(qū)級(jí)聯(lián)菜單', name: 'cascader', type: "cascader", placeholder: '請選擇地區(qū)', data: [{
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黃浦區(qū)' },
{ value: 'huangpu', label: '普陀區(qū)' },
{ value: 'huangpu', label: '靜安區(qū)' },
{ value: 'huangpu', label: '閘北區(qū)' }
]
}, {
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黃浦區(qū)' },
{ value: 'huangpu', label: '普陀區(qū)' },
{ value: 'huangpu', label: '靜安區(qū)' },
{ value: 'huangpu', label: '閘北區(qū)' }
]
}],
value: []
},
{ title: '下拉框選項(xiàng)', name: 'select', type: "selection", selection: [{ title: '選項(xiàng)一', value: '選項(xiàng)一' }, { title: '選項(xiàng)二', value: '選項(xiàng)二' }], placeholder: '請選擇選項(xiàng)', value: '' },
{ title: '日期時(shí)間', name: 'datetime', type: "datetime", placeholder: '請輸入日期', value: '' },
{ title: '日期時(shí)間范圍', name: 'datetimerange', type: "datetime-range", placeholder: '請輸入日期', value: '' },
{ title: '日期范圍', name: 'daterange', type: "date-range", placeholder: '請輸入日期', value: '' },
{ title: '時(shí)間范圍', name: 'timerange', type: "time-range", placeholder: '請輸入時(shí)間范圍', value: [] },
{ title: '輸入框', name: 'textarea', type: "textarea", placeholder: '請輸入客戶的簡介', value: '' },
],
表格組件 commonTable
當(dāng)我們傳入如下數(shù)據(jù)格式的數(shù)據(jù)后,就會(huì)生成這樣的表格屠升。點(diǎn)擊編輯后會(huì)轉(zhuǎn)為編輯模式瞄勾。
mockTable: {
tableHeader: '表格標(biāo)題',
dataList: [
{ title: '輸入框', name: 'input', value: '點(diǎn)擊編輯后為輸入框', edit_type: 'text' },
{
title: '級(jí)聯(lián)菜單', name: 'cascader', value: ['shanghai', 'huangpu'], edit_type: 'cascader', data: [{
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黃浦區(qū)' },
{ value: 'huangpu', label: '普陀區(qū)' },
{ value: 'huangpu', label: '靜安區(qū)' },
{ value: 'huangpu', label: '閘北區(qū)' }
]
}, {
value: 'beijing', label: '北京', children: [
{ value: 'huangpu', label: '天安門' },
{ value: 'huangpu', label: '故宮' },
{ value: 'huangpu', label: '頤和園' },
{ value: 'huangpu', label: '紫禁城' }
]
}]
},
{ title: '下拉菜單', name: 'select', value: '是', edit_type: 'selection', selection: [{ title: '是', value: '是' }, { title: '否', value: '否' }] },
{ title: '日期時(shí)間', name: 'datetime', value: '2017-01-01 01:30', edit_type: 'datetime' },
{ title: '日期時(shí)間范圍', name: 'datetimerange', value: '2017-01-01 01:30', edit_type: 'datetime-range' },
{ title: '日期范圍', name: 'daterange', value: '2017-01-01 01:30', edit_type: 'date-range' },
{ title: '時(shí)間范圍', name: 'timerange', value: '2017-01-01 01:30', edit_type: 'time-range' },
{ title: '塊級(jí)輸入框', name: 'textarea', value: '這是塊級(jí)輸入框', edit_type: 'textarea' }
]
}
最后,現(xiàn)在這兩個(gè)組件只是目前在我自己的項(xiàng)目中會(huì)比較常用弥激。靈活性以及適用性還不夠廣泛(比如缺乏校驗(yàn)进陡,表單的格式等),同時(shí)由于依賴iview微服,一些iview上組件的bug也同樣會(huì)有趾疚。如果各位小伙伴有好的建議,也歡迎來交流。