前言

写了个类似上篇搜索的封装,但是要考虑的东西更多。

具体业务比展示的代码要复杂,篇幅太长就不引入了。

效果图

  • 2019-04-25

  • 添加了下拉多选的渲染,并搜索默认过滤文本而非值

  • 简化了渲染的子组件的代码

  • 2019-04-28

    • 增加了对input type的控制

实现思路和功能

基础的功能直接配置上来渲染,而上传组件就不大合适了;

所以选择了slot来实现,如何保证传入的form-item的布局一致,则是拿slot-scope

我这边选型用的是vue 2.6 +的版本,所以直接用的是最新的写法

而且作为表单组件,校验这些肯定需要考虑,所以数据的构造改造了下,

对于校验规则这些走的是antd form用的那套,所以在传递的时候把对应的属性拍平了,

到里面再进行数据结构调整,目前部分控件样式依旧需要自己修正!!!

演示的代码用法


<form-list @change="onFormListChange"><template #field="{options}"><a-form-item label="Upload" v-bind="options"><a-uploadv-decorator="['upload',{valuePropName: 'fileList',getValueFromEvent: normFile}]"name="logo"action="/upload.do"list-type="picture"><a-button> <a-icon type="upload" /> Click to upload </a-button></a-upload></a-form-item></template></form-list>

代码

  • FieldRender.vue

<template>
<a-form-item:label="fieldOptions.labelText":label-col="fieldOptions.labelCol":wrapper-col="fieldOptions.wrapperCol"
><a-inputv-if="fieldOptions.fieldName && fieldOptions.type === 'text'":size="fieldOptions.size ? fieldOptions.size : 'default'"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":placeholder="fieldOptions.placeholder"/><a-selectv-else-if="fieldOptions.fieldName && fieldOptions.type === 'select'"style="width: 100%"showSearch:options="fieldOptions.options":filterOption="selectFilterOption":size="fieldOptions.size ? fieldOptions.size : 'default'"allowClearv-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : undefined,rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":placeholder="fieldOptions.placeholder"/><a-input-numberv-else-if="fieldOptions.fieldName && fieldOptions.type === 'number'":size="fieldOptions.size ? fieldOptions.size : 'default'":min="fieldOptions.min ? fieldOptions.min : 1"style="width: 100%"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":placeholder="fieldOptions.placeholder"/><a-radio-groupv-else-if="fieldOptions.fieldName && fieldOptions.type === 'radio' && Array.isArray(fieldOptions.options)":size="fieldOptions.size ? fieldOptions.size : 'default'"buttonStyle="solid"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]"><template v-for="(item, index) in fieldOptions.options"><a-radio-button :key="index" :value="item.value">{{ item.label }} </a-radio-button></template></a-radio-group><a-date-pickerv-else-if="fieldOptions.fieldName && fieldOptions.type === 'datetime'":size="fieldOptions.size ? fieldOptions.size : 'default'":placeholder="fieldOptions.placeholder"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]"/><a-range-pickerv-else-if="fieldOptions.fieldName && fieldOptions.type === 'datetimeRange'":size="fieldOptions.size ? fieldOptions.size : 'default'"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":placeholder="fieldOptions.placeholder"/><a-cascaderv-else-if="fieldOptions.fieldName && fieldOptions.type === 'cascader'":size="fieldOptions.size ? fieldOptions.size : 'default'":options="fieldOptions.options":showSearch="{ cascaderFilter }"v-decorator="[fieldOptions.fieldName,{ initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : [] }]":placeholder="fieldOptions.placeholder"/><a-time-pickerv-else-if="fieldOptions.fieldName && fieldOptions.type === 'timepicker'"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":size="fieldOptions.size ? fieldOptions.size : 'default'"/><a-textarea:placeholder="fieldOptions.placeholder"v-else-if="fieldOptions.fieldName && fieldOptions.type === 'textarea'"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]":autosize="{ minRows: 6, maxRows: 24 }"/><a-selectmode="multiple":size="fieldOptions.size ? fieldOptions.size : 'default'"optionFilterProp="children"v-else-if="fieldOptions.fieldName && fieldOptions.type === 'multiple'":placeholder="fieldOptions.placeholder"style="width: 100%":options="fieldOptions.options"v-decorator="[fieldOptions.fieldName,{initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : [],rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []}]"/>
</a-form-item>
</template><script>
export default {props: {fieldOptions: {// 待渲染的对象type: Object,default: function() {return {};}}
},
methods: {selectFilterOption(input, option) {// 下拉框过滤函数return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0;},cascaderFilter(inputValue, path) {// 级联过滤函数return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);}
}
};
</script>
  • FormList.vue

<template><div class="form-list-wrapper"><a-form :layout="formLayout" :form="form"><template v-for="(item, index) in renderDataSource"><template v-if="item.type && item.fieldName"><field-render :fieldOptions="item" :key="item.fieldName" /></template></template><slot name="field" :options="GlobalOptions" /><a-form-item :wrapper-col="buttonItemLayout.wrapperCol"><a-tooltip placement="bottom"><template slot="title"><span>提交表单</span></template><a-button type="primary" :size="size" @click="handleSubmit">提交</a-button></a-tooltip><a-tooltip placement="bottom"><template slot="title"><span>清空所有控件的值</span></template><a-button :size="size" style="margin-left: 8px" @click="resetSearchForm">重置</a-button></a-tooltip></a-form-item></a-form></div>
</template><script>
import FieldRender from './FieldRender';
export default {name: 'FormList',components: {FieldRender},props: {formLayout: {// 表单布局type: String, // 'horizontal'|'vertical'|'inline'default: 'horizontal'},datetimeTotimeStamp: {// 是否把时间控件的返回值全部转为时间戳type: Boolean,default: false},size: {// 全局控件大小type: String,default: 'default'},responsive: {// 表单项的响应布局type: Object,default: function() {return {labelCol: { span: 5 },wrapperCol: { span: 16 }};}},dataSource: {type: Array,default: function() {return [{type: 'text', // 控件类型labelText: '控件名称', // 控件显示的文本fieldName: 'formField1',placeholder: '文本输入区域', // 默认控件的空值文本rules: [{required: true,message: '必填'}]},{labelText: '数字输入框',type: 'number',fieldName: 'formField2',placeholder: '这只是一个数字的文本输入框'},{labelText: '单选框',type: 'radio',fieldName: 'formField3',defaultValue: '0',options: [{label: '选项1',value: '0'},{label: '选项2',value: '1'}]},{labelText: '日期选择',type: 'datetime',fieldName: 'formField4',placeholder: '选择日期'},{labelText: '日期范围',type: 'datetimeRange',fieldName: 'formField5',placeholder: ['开始日期', '选择日期']},{labelText: '时刻选择',type: 'timepicker',fieldName: 'formField8',placeholder: '请选择时刻(时间)'},{labelText: '文本区域',type: 'textarea',fieldName: 'formField9',placeholder: '请输入文本了内容'},{type: 'multiple',labelText: '角色',fieldName: 'role',defaultValue: [],rules: [{required: true,message: '必须选择一种角色'}],options: [{label: '系统管理员',value: '0'},{label: '风控管理员',value: '1'},{label: '催收管理员',value: '2'},{label: '催收员',value: '3'},{label: '审核员',value: '4'},{label: '财务',value: '5'}]},{labelText: '下拉框',type: 'select',fieldName: 'formField7',placeholder: '下拉选择你要的',options: [{label: 'text1',value: '0'},{label: 'text2',value: '1'}]},{labelText: '联动',type: 'cascader',fieldName: 'formField6',placeholder: '级联选择',options: [{value: 'zhejiang',label: 'Zhejiang',children: [{value: 'hangzhou',label: 'Hangzhou',children: [{value: 'xihu',label: 'West Lake'},{value: 'xiasha',label: 'Xia Sha',disabled: true}]}]},{value: 'jiangsu',label: 'Jiangsu',children: [{value: 'nanjing',label: 'Nanjing',children: [{value: 'zhonghuamen',label: 'Zhong Hua men'}]}]}]}];}}},beforeCreate() {this.form = this.$form.createForm(this);},computed: {GlobalOptions() {// 全局配置return {size: this.size,...this.formItemLayout};},renderDataSource() {// 重组传入的数据,合并全局配置,子项的配置优先全局return this.dataSource.map(item => ({ ...this.GlobalOptions, ...item }));},formItemLayout() {// 更改布局项目的尺寸const { formLayout } = this;if (formLayout === 'horizontal') {return this.responsive;} else {return {};}},buttonItemLayout() {// 提交按钮布局const { formLayout } = this;return formLayout === 'horizontal'? {wrapperCol: { span: 14, offset: 4 }}: {};}},methods: {handleParams(obj) {// 判断必须为objif (!(Object.prototype.toString.call(obj) === '[object Object]')) {return {};}let tempObj = {};for (let [key, value] of Object.entries(obj)) {if (Array.isArray(value) && value.length <= 0) continue;if (Object.prototype.toString.call(value) === '[object Function]') continue;if (this.datetimeTotimeStamp) {// 若是为true,则转为时间戳if (Object.prototype.toString.call(value) === '[object Object]' && value._isAMomentObject) {// 判断momentvalue = value.valueOf();}if (Array.isArray(value) && value[0]._isAMomentObject && value[1]._isAMomentObject) {// 判断momentvalue = value.map(item => item.valueOf());}}// 若是为字符串则清除两边空格if (value && typeof value === 'string') {value = value.trim();}tempObj[key] = value;}return tempObj;},handleSubmit(e) {// 触发表单提交,也就是搜索按钮e.preventDefault();this.form.validateFields((err, values) => {if (!err) {console.log('处理前的表单数据', values);const queryParams = this.handleParams(values);this.$emit('change', queryParams);}});},resetSearchForm() {// 重置整个查询表单this.form.resetFields();this.$emit('change', null);}}
};
</script><style lang="scss">
.form-list-wrapper {.ant-form-inline {.ant-form-item {display: flex;margin-bottom: 12px;margin-right: 0;.ant-form-item-control-wrapper {flex: 1;display: inline-block;vertical-align: middle;}> .ant-form-item-label {line-height: 32px;padding-right: 8px;width: auto;}.ant-form-item-control {height: 32px;line-height: 32px;display: flex;justify-content: flex-start;align-items: center;.ant-form-item-children {min-width: 160px;}}}}.table-page-search-submitButtons {display: block;margin-bottom: 24px;white-space: nowrap;}
}
</style>

问题

暴露的方法和搜索组件一样,@change回来表单数据;

问题:

操作父的props会造成死循环(在有slot的情况下,因slot-scope拿的是父props经过computed后的值)。

解决方案:

已经改用其他实现姿势,抽离成独立组件,再联动数据。

总结

antd vue版本目前的功能复现上,还是有所欠缺,可能vuereact实现的机子不一致导致;

不管怎么说,不考虑极端情况下,目前这个库用起来感觉还好;

至少是可用状态,后续若有修正,会继续更新文章,谢谢阅读

Vue 2.x折腾记 - (17) 基于Ant Design Vue 封装一个配置式的表单组件相关推荐

  1. Vue 2.x折腾记 - (16) 基于Ant Design Vue 封装一个配置式的表单搜索组件

    前言 这次的后台管理系统项目选型用了Vue来作为主技术栈: 因为前段时间用过React来写过项目(用了antd),感觉棒棒的. 所以这次就排除了Element UI,而采用了Ant Design Vu ...

  2. vue将每个路由打包成html,Ant Design Vue pro 动态路由的实现和打包

    Ant Design Vue pro 动态路由的实现和打包 Ant Design Vue pro 动态路由的实现和打包 配置路由权限 在config文件夹下router.config.js中配置路由权 ...

  3. 基于Ant Design vue框架登录demo

    我们直接进入正题吧~~~ 先来看下效果图 那么前端代码呢~~~ 不着急,这就双手奉上哈~~ <a-col :span="12"><div class=" ...

  4. 基于Ant Design vue框架之三 删除功能细分

    我们还是老规矩,先上效果图吧~~ 需要看整个页面的小盆友可以点下面这个路径哈~~ 页面路径:总页面展示 继续上干货吧~翠花,上代码~~ <a-button type="danger&q ...

  5. 基于Ant Design of Vue实现时长组件 duration

    最近遇到一个需求,需要一个输入时长的组件,在经过一番寻找后没有合适的,最终自己动手写一个(实现了v-model双向绑定),记录一下,也给小伙伴们提供一个方便. 本示例基于ant design of v ...

  6. 基于 Vue3.0 和 Ant Design Vue ,高颜值管理后台UI框架vue-vben-admin运行

    简介 Vue Vben Admin 是一个免费开源的中后台模版.使用了最新的vue3,vite2,TypeScript等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考. Github地址 ...

  7. ant design vue input change_ElementUI 不维护了?供我们选择的 Vue 组件库还有很多!

    1 ElementUI 近况 根据我最近的观察,得知一些关于 ElementUI 维护人员都退去的消息,这意味着什么? 这意味着后期 ElementUI 将无人维护,就算 Vue3.0 正式版出来 E ...

  8. 【Ant Design Vue】之Grid栅格和Space间距

    文章目录 Grid 栅格 Space 间距 Grid 栅格 Ant Design Vue 将整个设计建议区域按照 24 等分的原则进行划分,划分之后的信息区块我们称之为『盒子』.建议横向排列的盒子数量 ...

  9. ant design vue table 高度自适应_很受欢迎的vue前端UI框架

    最近在逛各大网站,论坛,SegmentFault等编程问答社区,发现Vue.js异常火爆,重复性的提问和内容也很多,小编自己也趁着这个大前端的热潮,着手学习了一段时间的Vue.js,目前用它正在做自己 ...

最新文章

  1. Java 中 Comparable 和 Comparator 比较
  2. 预处理_不锈钢锻件预处理的必要性
  3. 时间序列数据库——索引用ES、聚合分析时加载数据用什么?docvalues的列存储貌似更优优势一些...
  4. Delphi常见的运行期Access Violation错误分析
  5. python通讯录管理程序的用户可行性_通讯录管理系统项目可行性分析
  6. jQuery 效果 - 动画
  7. PAT_B_1055_Java(25分)
  8. powerCat进行常规tcp端口转发
  9. 单链表的应用(电话本)
  10. 钉钉老版本下载3.31_钉钉3.3.1老版本官方下载-钉钉3.3.1旧版本PC版官方版-东坡下载...
  11. VMWare NSX安全生产和DMZ用例的详细设计指南
  12. SQL纯手写创建数据库到表内内容
  13. 【论文解读】ICLR2021 知识建模与信息抽取
  14. cpc专利电子申请客户端安装教程以及常见错误
  15. Android 使用Vector XML文件创建矢量图片资源
  16. PHP navicat数据搭建,navicat怎么建表
  17. 高精度信号链电路精密模拟器件双轨供电方案
  18. Android 9.0 http无法访问网络问题
  19. 12个开源的后台端管理系统
  20. 课后自主练习(递归)1059. Fj haozi medium《编程思维与实践》个人学习笔记

热门文章

  1. 基于python Moviepy的视频字幕识别和合成!
  2. Ireport 导出pdf 特殊字体设置
  3. 【微信】微信小程序前后端数据请求示例
  4. 光纤收发器结构介绍和故障解决
  5. Android Studio与Bmob关联
  6. Github上利用win10使用TensorFlow(GPU)上如何去训练一个目标多分类检测的例子
  7. 4 anbox 树莓派_Anbox让你在Linux上“原生运行”Android应用
  8. 数学基础:积分中值定理
  9. FreeCAD新手入门
  10. 面试题,移动端APP测试常见bug记录