Vue相信玩前端的小伙伴一定不會陌生。使用Vue最大的好處就是組件化,通過組件的復用可以大大減小我們?nèi)粘i_發(fā)的工作量。最近在工作中遇到這樣的情況,于是便趁著周末正好將自己寫的組件整理一下,做成更加通用的組件。
先介紹一下背景。這段時間因為工作上的需要,要做后臺管理系統(tǒng),這類系統(tǒng)相信很多小伙伴也很熟悉。主要以表單的填寫以及表格的展示、編輯為主。前端這一部分采用Vue全家桶,UI組件采用的是iview。UI組件大大提高了開發(fā)效率以及降低了開發(fā)的復雜度,但是組件也不是萬能的,在面對實際的業(yè)務時,還是需要靠我們自己來制作組件。而這一次要制作的組件就是可以根據(jù)傳入的數(shù)據(jù)不同,自動生成不同樣式的表單以及表格了。(當然,利用的組件還是基于iview的)
制作思路
組件的本質(zhì)就是拼裝。通過拼裝一個個原子組件,形成一個大組件最后來滿足我們的實際業(yè)務需求。而在我這一次的需求中,原子組件已經(jīng)有iview提供了。我要做的就是怎樣來制作出想要的表單以及表格了。Vue提倡的思想就是數(shù)據(jù)驅(qū)動,所以在與其說是拼裝組件,更不如說是設計一個合理的數(shù)據(jù)結構。
首先是表單,通過v-for對數(shù)據(jù)進行循環(huán),然后判斷數(shù)據(jù)對應的表單組件類型就可以了。因此我們需要的數(shù)據(jù)結構就需要組件的類型、綁定的值、值對應的變量名(類似username:'用戶'),而其他的屬性就可以根據(jù)需要再做追加。
再是表格,同樣也是通過v-for對數(shù)據(jù)進行循環(huán)展示。在編輯時也需要切換到對應的組件。因此與表單的數(shù)據(jù)結構設計相同,我們需要組件的類型、綁定的值、值對應的變量名(類似username:'用戶')。
而其中的難點,無非就是在于行數(shù)和列數(shù)的計算上了。怎么靈活地變換行數(shù),還是需要利用到v-for循環(huán)中的index來進行幫忙。相信這些,只要看過一眼代碼就馬上可以理解。
// 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>
<!-- 級聯(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>
<!-- 日期時間選擇器 -->
<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>
<!-- 日期時間圍選擇器 -->
<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>
<!-- 時間范圍選擇器 -->
<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>
<!-- 日期時間選擇器 -->
<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>
<!-- 日期時間圍選擇器 -->
<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>
<!-- 時間范圍選擇器 -->
<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>
<!-- 級聯(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>
項目地址:my-components 項目主頁上也有相關的說明。這里就不做贅述了。
說完了思路那么還是一如既往,先來看看效果直接感受一下吧。
表單組件 commonForm
當我們傳入如下數(shù)據(jù)格式的數(shù)據(jù)后,就會生成這樣的表單。
mockForm: [
{ title: '輸入框', name: 'text', type: "text", placeholder: '請輸入輸入框內(nèi)容', value: '' },
{
title: '地區(qū)級聯(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: '下拉框選項', name: 'select', type: "selection", selection: [{ title: '選項一', value: '選項一' }, { title: '選項二', value: '選項二' }], placeholder: '請選擇選項', value: '' },
{ title: '日期時間', name: 'datetime', type: "datetime", placeholder: '請輸入日期', value: '' },
{ title: '日期時間范圍', name: 'datetimerange', type: "datetime-range", placeholder: '請輸入日期', value: '' },
{ title: '日期范圍', name: 'daterange', type: "date-range", placeholder: '請輸入日期', value: '' },
{ title: '時間范圍', name: 'timerange', type: "time-range", placeholder: '請輸入時間范圍', value: [] },
{ title: '輸入框', name: 'textarea', type: "textarea", placeholder: '請輸入客戶的簡介', value: '' },
],

表格組件 commonTable
當我們傳入如下數(shù)據(jù)格式的數(shù)據(jù)后,就會生成這樣的表格。點擊編輯后會轉(zhuǎn)為編輯模式。
mockTable: {
tableHeader: '表格標題',
dataList: [
{ title: '輸入框', name: 'input', value: '點擊編輯后為輸入框', edit_type: 'text' },
{
title: '級聯(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: '日期時間', name: 'datetime', value: '2017-01-01 01:30', edit_type: 'datetime' },
{ title: '日期時間范圍', 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: '時間范圍', name: 'timerange', value: '2017-01-01 01:30', edit_type: 'time-range' },
{ title: '塊級輸入框', name: 'textarea', value: '這是塊級輸入框', edit_type: 'textarea' }
]
}


最后,現(xiàn)在這兩個組件只是目前在我自己的項目中會比較常用。靈活性以及適用性還不夠廣泛(比如缺乏校驗,表單的格式等),同時由于依賴iview,一些iview上組件的bug也同樣會有。如果各位小伙伴有好的建議,也歡迎來交流。