Vue相信玩前端的小伙伴一定不会陌生。使用Vue最大的好处就是组件化,通过组件的复用可以大大减小我们日常开发的工作量。最近在工作中遇到这样的情况,于是便趁着周末正好将自己写的组件整理一下,做成更加通用的组件。
先介绍一下背景。这段时间因为工作上的需要,要做后台管理系统,这类系统相信很多小伙伴也很熟悉。主要以表单的填写以及表格的展示、编辑为主。前端这一部分采用Vue全家桶,UI组件采用的是iview。UI组件大大提高了开发效率以及降低了开发的复杂度,但是组件也不是万能的,在面对实际的业务时,还是需要靠我们自己来制作组件。而这一次要制作的组件就是可以根据传入的数据不同,自动生成不同样式的表单以及表格了。(当然,利用的组件还是基于iview的)
制作思路
组件的本质就是拼装。通过拼装一个个原子组件,形成一个大组件最后来满足我们的实际业务需求。而在我这一次的需求中,原子组件已经有iview提供了。我要做的就是怎样来制作出想要的表单以及表格了。Vue提倡的思想就是数据驱动,所以在与其说是拼装组件,更不如说是设计一个合理的数据结构。
首先是表单,通过v-for对数据进行循环,然后判断数据对应的表单组件类型就可以了。因此我们需要的数据结构就需要组件的类型、绑定的值、值对应的变量名(类似username:'用户'),而其他的属性就可以根据需要再做追加。
再是表格,同样也是通过v-for对数据进行循环展示。在编辑时也需要切换到对应的组件。因此与表单的数据结构设计相同,我们需要组件的类型、绑定的值、值对应的变量名(类似username:'用户')。
而其中的难点,无非就是在于行数和列数的计算上了。怎么灵活地变换行数,还是需要利用到v-for循环中的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>
<!-- 级联菜单 -->
<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>
<!-- 级联菜单 -->
<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
当我们传入如下数据格式的数据后,就会生成这样的表单。
mockForm: [
{ title: '输入框', name: 'text', type: "text", placeholder: '请输入输入框内容', value: '' },
{
title: '地区级联菜单', name: 'cascader', type: "cascader", placeholder: '请选择地区', data: [{
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黄浦区' },
{ value: 'huangpu', label: '普陀区' },
{ value: 'huangpu', label: '静安区' },
{ value: 'huangpu', label: '闸北区' }
]
}, {
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黄浦区' },
{ value: 'huangpu', label: '普陀区' },
{ value: 'huangpu', label: '静安区' },
{ value: 'huangpu', label: '闸北区' }
]
}],
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
当我们传入如下数据格式的数据后,就会生成这样的表格。点击编辑后会转为编辑模式。
mockTable: {
tableHeader: '表格标题',
dataList: [
{ title: '输入框', name: 'input', value: '点击编辑后为输入框', edit_type: 'text' },
{
title: '级联菜单', name: 'cascader', value: ['shanghai', 'huangpu'], edit_type: 'cascader', data: [{
value: 'shanghai', label: '上海', children: [
{ value: 'huangpu', label: '黄浦区' },
{ value: 'huangpu', label: '普陀区' },
{ value: 'huangpu', label: '静安区' },
{ value: 'huangpu', label: '闸北区' }
]
}, {
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' }
]
}
表格组件 视图模式
表格组件 编辑模式
最后,现在这两个组件只是目前在我自己的项目中会比较常用。灵活性以及适用性还不够广泛(比如缺乏校验,表单的格式等),同时由于依赖iview,一些iview上组件的bug也同样会有。如果各位小伙伴有好的建议,也欢迎来交流。
网友评论