前言

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

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

效果图

  • 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. 匿名函数应用-多线程测试代码
  2. sql exists用法_新同事不讲武德,这SQL语句写得忒野了
  3. 使用netstat检测及监测网络连接
  4. enumerate()函数用法
  5. JVM运行时数据区概览
  6. 压缩感知(I) A Compressed Sense of Compressive Sensing (I)
  7. Android:JAVA使用HDF5存储
  8. 【BZOJ - 4337】BJOI2015 树的同构(树哈希)
  9. 记录使用IDEA部署Tomcat时提示错误:the selected directory is not a TomEE home
  10. 最安全的浏览器?黑客大赛微软Edge被破解5次夺下“冠军”
  11. 再获信通院权威认证,优等生华为云GaussDB数据库凭什么?
  12. 三套致富战略-教你成为百万富翁
  13. decorator 装饰
  14. Activity 生命周期及其栈管理方式
  15. C#安装本地nupkg包
  16. 计算机图书馆管理系统论文模板,基于Java的图书馆管理系统计算机科学与技术毕业设计(论文)...
  17. 剪贴板 Clipbrd 直接用法
  18. RDLC报表金额数字转大写
  19. epub转换mobi
  20. 2016-HitCon-Pwn-house_of_orange学习(附赠FSOP基础知识)

热门文章

  1. python中val的意思_python中val是什么
  2. NB-IOT电信云北向开发,电信天翼物联网平台对接应用服务
  3. socket()函数介绍
  4. Java 源码剖析(13)--MyBatis 使用了哪些设计模式?
  5. L2范数-欧几里得范数
  6. 面对外挂、诈骗、工作室等一系列游戏黑产,腾讯会怎么做?
  7. Excel如何统计同一单元格内姓名个数
  8. MMC / eMMC / SD
  9. 相册列表 鼠标悬停显示照片介绍
  10. 怎样去除图片水印?教你一个一键去除水印的方法