前言

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

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

效果图

  • 2019-04-25

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

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

  • 2019-04-28

    • 增加了对input type的控制

实现思路和功能

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

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

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

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

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

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

演示的代码用法


  1. <form-list @change="onFormListChange">
  2. <template #field="{options}">
  3. <a-form-item label="Upload" v-bind="options">
  4. <a-upload
  5. v-decorator="[
  6. 'upload',
  7. {
  8. valuePropName: 'fileList',
  9. getValueFromEvent: normFile
  10. }
  11. ]"
  12. name="logo"
  13. action="/upload.do"
  14. list-type="picture"
  15. >
  16. <a-button> <a-icon type="upload" /> Click to upload </a-button>
  17. </a-upload>
  18. </a-form-item>
  19. </template>
  20. </form-list>
  21. 复制代码

代码

  • FieldRender.vue

  1. <template>
  2. <a-form-item
  3. :label="fieldOptions.labelText"
  4. :label-col="fieldOptions.labelCol"
  5. :wrapper-col="fieldOptions.wrapperCol"
  6. >
  7. <a-input
  8. v-if="fieldOptions.fieldName && fieldOptions.type === 'text'"
  9. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  10. v-decorator="[
  11. fieldOptions.fieldName,
  12. {
  13. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',
  14. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  15. }
  16. ]"
  17. :placeholder="fieldOptions.placeholder"
  18. />
  19. <a-select
  20. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'select'"
  21. style="width: 100%"
  22. showSearch
  23. :options="fieldOptions.options"
  24. :filterOption="selectFilterOption"
  25. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  26. allowClear
  27. v-decorator="[
  28. fieldOptions.fieldName,
  29. {
  30. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : undefined,
  31. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  32. }
  33. ]"
  34. :placeholder="fieldOptions.placeholder"
  35. />
  36. <a-input-number
  37. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'number'"
  38. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  39. :min="fieldOptions.min ? fieldOptions.min : 1"
  40. style="width: 100%"
  41. v-decorator="[
  42. fieldOptions.fieldName,
  43. {
  44. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',
  45. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  46. }
  47. ]"
  48. :placeholder="fieldOptions.placeholder"
  49. />
  50. <a-radio-group
  51. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'radio' && Array.isArray(fieldOptions.options)"
  52. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  53. buttonStyle="solid"
  54. v-decorator="[
  55. fieldOptions.fieldName,
  56. {
  57. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : '',
  58. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  59. }
  60. ]"
  61. >
  62. <template v-for="(item, index) in fieldOptions.options">
  63. <a-radio-button :key="index" :value="item.value">{{ item.label }} </a-radio-button>
  64. </template>
  65. </a-radio-group>
  66. <a-date-picker
  67. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'datetime'"
  68. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  69. :placeholder="fieldOptions.placeholder"
  70. v-decorator="[
  71. fieldOptions.fieldName,
  72. {
  73. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,
  74. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  75. }
  76. ]"
  77. />
  78. <a-range-picker
  79. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'datetimeRange'"
  80. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  81. v-decorator="[
  82. fieldOptions.fieldName,
  83. {
  84. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,
  85. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  86. }
  87. ]"
  88. :placeholder="fieldOptions.placeholder"
  89. />
  90. <a-cascader
  91. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'cascader'"
  92. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  93. :options="fieldOptions.options"
  94. :showSearch="{ cascaderFilter }"
  95. v-decorator="[
  96. fieldOptions.fieldName,
  97. { initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : [] }
  98. ]"
  99. :placeholder="fieldOptions.placeholder"
  100. />
  101. <a-time-picker
  102. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'timepicker'"
  103. v-decorator="[
  104. fieldOptions.fieldName,
  105. {
  106. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,
  107. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  108. }
  109. ]"
  110. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  111. />
  112. <a-textarea
  113. :placeholder="fieldOptions.placeholder"
  114. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'textarea'"
  115. v-decorator="[
  116. fieldOptions.fieldName,
  117. {
  118. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : null,
  119. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  120. }
  121. ]"
  122. :autosize="{ minRows: 6, maxRows: 24 }"
  123. />
  124. <a-select
  125. mode="multiple"
  126. :size="fieldOptions.size ? fieldOptions.size : 'default'"
  127. optionFilterProp="children"
  128. v-else-if="fieldOptions.fieldName && fieldOptions.type === 'multiple'"
  129. :placeholder="fieldOptions.placeholder"
  130. style="width: 100%"
  131. :options="fieldOptions.options"
  132. v-decorator="[
  133. fieldOptions.fieldName,
  134. {
  135. initialValue: fieldOptions.defaultValue ? fieldOptions.defaultValue : [],
  136. rules: Array.isArray(fieldOptions.rules) && fieldOptions.rules.length > 0 ? fieldOptions.rules : []
  137. }
  138. ]"
  139. />
  140. </a-form-item>
  141. </template>
  142. <script>
  143. export default {
  144. props: {
  145. fieldOptions: {
  146. // 待渲染的对象
  147. type: Object,
  148. default: function() {
  149. return {};
  150. }
  151. }
  152. },
  153. methods: {
  154. selectFilterOption(input, option) {
  155. // 下拉框过滤函数
  156. return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0;
  157. },
  158. cascaderFilter(inputValue, path) {
  159. // 级联过滤函数
  160. return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
  161. }
  162. }
  163. };
  164. </script>
  165. 复制代码
  • FormList.vue

  1. <template>
  2. <div class="form-list-wrapper">
  3. <a-form :layout="formLayout" :form="form">
  4. <template v-for="(item, index) in renderDataSource">
  5. <template v-if="item.type && item.fieldName">
  6. <field-render :fieldOptions="item" :key="item.fieldName" />
  7. </template>
  8. </template>
  9. <slot name="field" :options="GlobalOptions" />
  10. <a-form-item :wrapper-col="buttonItemLayout.wrapperCol">
  11. <a-tooltip placement="bottom">
  12. <template slot="title">
  13. <span>提交表单</span>
  14. </template>
  15. <a-button type="primary" :size="size" @click="handleSubmit">提交</a-button>
  16. </a-tooltip>
  17. <a-tooltip placement="bottom">
  18. <template slot="title">
  19. <span>清空所有控件的值</span>
  20. </template>
  21. <a-button :size="size" style="margin-left: 8px" @click="resetSearchForm">重置</a-button>
  22. </a-tooltip>
  23. </a-form-item>
  24. </a-form>
  25. </div>
  26. </template>
  27. <script>
  28. import FieldRender from './FieldRender';
  29. export default {
  30. name: 'FormList',
  31. components: {
  32. FieldRender
  33. },
  34. props: {
  35. formLayout: {
  36. // 表单布局
  37. type: String, // 'horizontal'|'vertical'|'inline'
  38. default: 'horizontal'
  39. },
  40. datetimeTotimeStamp: {
  41. // 是否把时间控件的返回值全部转为时间戳
  42. type: Boolean,
  43. default: false
  44. },
  45. size: {
  46. // 全局控件大小
  47. type: String,
  48. default: 'default'
  49. },
  50. responsive: {
  51. // 表单项的响应布局
  52. type: Object,
  53. default: function() {
  54. return {
  55. labelCol: { span: 5 },
  56. wrapperCol: { span: 16 }
  57. };
  58. }
  59. },
  60. dataSource: {
  61. type: Array,
  62. default: function() {
  63. return [
  64. {
  65. type: 'text', // 控件类型
  66. labelText: '控件名称', // 控件显示的文本
  67. fieldName: 'formField1',
  68. placeholder: '文本输入区域', // 默认控件的空值文本
  69. rules: [
  70. {
  71. required: true,
  72. message: '必填'
  73. }
  74. ]
  75. },
  76. {
  77. labelText: '数字输入框',
  78. type: 'number',
  79. fieldName: 'formField2',
  80. placeholder: '这只是一个数字的文本输入框'
  81. },
  82. {
  83. labelText: '单选框',
  84. type: 'radio',
  85. fieldName: 'formField3',
  86. defaultValue: '0',
  87. options: [
  88. {
  89. label: '选项1',
  90. value: '0'
  91. },
  92. {
  93. label: '选项2',
  94. value: '1'
  95. }
  96. ]
  97. },
  98. {
  99. labelText: '日期选择',
  100. type: 'datetime',
  101. fieldName: 'formField4',
  102. placeholder: '选择日期'
  103. },
  104. {
  105. labelText: '日期范围',
  106. type: 'datetimeRange',
  107. fieldName: 'formField5',
  108. placeholder: ['开始日期', '选择日期']
  109. },
  110. {
  111. labelText: '时刻选择',
  112. type: 'timepicker',
  113. fieldName: 'formField8',
  114. placeholder: '请选择时刻(时间)'
  115. },
  116. {
  117. labelText: '文本区域',
  118. type: 'textarea',
  119. fieldName: 'formField9',
  120. placeholder: '请输入文本了内容'
  121. },
  122. {
  123. type: 'multiple',
  124. labelText: '角色',
  125. fieldName: 'role',
  126. defaultValue: [],
  127. rules: [
  128. {
  129. required: true,
  130. message: '必须选择一种角色'
  131. }
  132. ],
  133. options: [
  134. {
  135. label: '系统管理员',
  136. value: '0'
  137. },
  138. {
  139. label: '风控管理员',
  140. value: '1'
  141. },
  142. {
  143. label: '催收管理员',
  144. value: '2'
  145. },
  146. {
  147. label: '催收员',
  148. value: '3'
  149. },
  150. {
  151. label: '审核员',
  152. value: '4'
  153. },
  154. {
  155. label: '财务',
  156. value: '5'
  157. }
  158. ]
  159. },
  160. {
  161. labelText: '下拉框',
  162. type: 'select',
  163. fieldName: 'formField7',
  164. placeholder: '下拉选择你要的',
  165. options: [
  166. {
  167. label: 'text1',
  168. value: '0'
  169. },
  170. {
  171. label: 'text2',
  172. value: '1'
  173. }
  174. ]
  175. },
  176. {
  177. labelText: '联动',
  178. type: 'cascader',
  179. fieldName: 'formField6',
  180. placeholder: '级联选择',
  181. options: [
  182. {
  183. value: 'zhejiang',
  184. label: 'Zhejiang',
  185. children: [
  186. {
  187. value: 'hangzhou',
  188. label: 'Hangzhou',
  189. children: [
  190. {
  191. value: 'xihu',
  192. label: 'West Lake'
  193. },
  194. {
  195. value: 'xiasha',
  196. label: 'Xia Sha',
  197. disabled: true
  198. }
  199. ]
  200. }
  201. ]
  202. },
  203. {
  204. value: 'jiangsu',
  205. label: 'Jiangsu',
  206. children: [
  207. {
  208. value: 'nanjing',
  209. label: 'Nanjing',
  210. children: [
  211. {
  212. value: 'zhonghuamen',
  213. label: 'Zhong Hua men'
  214. }
  215. ]
  216. }
  217. ]
  218. }
  219. ]
  220. }
  221. ];
  222. }
  223. }
  224. },
  225. beforeCreate() {
  226. this.form = this.$form.createForm(this);
  227. },
  228. computed: {
  229. GlobalOptions() {
  230. // 全局配置
  231. return {
  232. size: this.size,
  233. ...this.formItemLayout
  234. };
  235. },
  236. renderDataSource() {
  237. // 重组传入的数据,合并全局配置,子项的配置优先全局
  238. return this.dataSource.map(item => ({ ...this.GlobalOptions, ...item }));
  239. },
  240. formItemLayout() {
  241. // 更改布局项目的尺寸
  242. const { formLayout } = this;
  243. if (formLayout === 'horizontal') {
  244. return this.responsive;
  245. } else {
  246. return {};
  247. }
  248. },
  249. buttonItemLayout() {
  250. // 提交按钮布局
  251. const { formLayout } = this;
  252. return formLayout === 'horizontal'
  253. ? {
  254. wrapperCol: { span: 14, offset: 4 }
  255. }
  256. : {};
  257. }
  258. },
  259. methods: {
  260. handleParams(obj) {
  261. // 判断必须为obj
  262. if (!(Object.prototype.toString.call(obj) === '[object Object]')) {
  263. return {};
  264. }
  265. let tempObj = {};
  266. for (let [key, value] of Object.entries(obj)) {
  267. if (Array.isArray(value) && value.length <= 0) continue;
  268. if (Object.prototype.toString.call(value) === '[object Function]') continue;
  269. if (this.datetimeTotimeStamp) {
  270. // 若是为true,则转为时间戳
  271. if (Object.prototype.toString.call(value) === '[object Object]' && value._isAMomentObject) {
  272. // 判断moment
  273. value = value.valueOf();
  274. }
  275. if (Array.isArray(value) && value[0]._isAMomentObject && value[1]._isAMomentObject) {
  276. // 判断moment
  277. value = value.map(item => item.valueOf());
  278. }
  279. }
  280. // 若是为字符串则清除两边空格
  281. if (value && typeof value === 'string') {
  282. value = value.trim();
  283. }
  284. tempObj[key] = value;
  285. }
  286. return tempObj;
  287. },
  288. handleSubmit(e) {
  289. // 触发表单提交,也就是搜索按钮
  290. e.preventDefault();
  291. this.form.validateFields((err, values) => {
  292. if (!err) {
  293. console.log('处理前的表单数据', values);
  294. const queryParams = this.handleParams(values);
  295. this.$emit('change', queryParams);
  296. }
  297. });
  298. },
  299. resetSearchForm() {
  300. // 重置整个查询表单
  301. this.form.resetFields();
  302. this.$emit('change', null);
  303. }
  304. }
  305. };
  306. </script>
  307. <style lang="scss">
  308. .form-list-wrapper {
  309. .ant-form-inline {
  310. .ant-form-item {
  311. display: flex;
  312. margin-bottom: 12px;
  313. margin-right: 0;
  314. .ant-form-item-control-wrapper {
  315. flex: 1;
  316. display: inline-block;
  317. vertical-align: middle;
  318. }
  319. > .ant-form-item-label {
  320. line-height: 32px;
  321. padding-right: 8px;
  322. width: auto;
  323. }
  324. .ant-form-item-control {
  325. height: 32px;
  326. line-height: 32px;
  327. display: flex;
  328. justify-content: flex-start;
  329. align-items: center;
  330. .ant-form-item-children {
  331. min-width: 160px;
  332. }
  333. }
  334. }
  335. }
  336. .table-page-search-submitButtons {
  337. display: block;
  338. margin-bottom: 24px;
  339. white-space: nowrap;
  340. }
  341. }
  342. </style>
  343. 复制代码

问题

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

问题:

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

解决方案:

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

总结

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

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

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

转载于:https://juejin.im/post/5cb5d89af265da03a85ab689

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

  1. 基于Ant Design Vue封装一个表单控件

    开源代码 https://github.com/naturefwvue/nf-vue3-ant 有缺点本来是写在最后的,但是博文写的似乎有点太长了,估计大家没时间往下看,于是就把有缺点写在前面了,不喜 ...

  2. 文档驱动 —— 表单组件(五):基于Ant Design Vue 的表单控件的demo,再也不需要写代码了。

    源码 https://github.com/naturefwvue/nf-vue3-ant 特点 只需要更改meta,既可以切换表单 可以统一修改样式,统一升级,以最小的代价,应对UI的升级.切换,应 ...

  3. Ant Design Vue select下拉列表设置默认值

    在项目中需要为Ant Design Vue 的 select 组件设置一个默认值,如下图所示的状态下拉选择框,默认选择全部 代码如下: <a-select v-model="query ...

  4. Ant Design Vue Pro 项目实战-项目初始化(一)

    写在前面 时间真快,转眼又是新的一年.随着前后端技术的不断更新迭代,尤其是前端,在目前前后端分离开发模式这样的一个大环境下,交互性.兼容性等传统的开发模式已经显得有些吃力.之前一直用的是react,随 ...

  5. 使用ant design vue的日历组件,实现一个简单交易日与非交易日的切换

    使用ant design vue的日历组件,实现一个简单交易日与非交易日的切换 需求: 日历区分交易日.非交易日 可以切换面板查看整年交易日信息 可以在手动调整交易日.非交易日 演示实例 序--使用软 ...

  6. 使用npm安装 Ant Design Vue 时报错—ant-design-vue@latest(sha1-qsf / gCIFcRYxyGmOKgx7TmHf1z4 =)seems to be corrupted.

    安装 Ant Design Vue 时报错: npm install ant-design-vue --save ant-design-vue @ latest(sha1-qsf / gCIFcRYx ...

  7. Vue3学习(二)之集成Ant Design Vue

    一.集成Ant Design Vue npm install ant-design-vue@2.0.0-rc.3 --save 兼容性 Ant Design Vue 2.x 支持所有的现代浏览器. 如 ...

  8. 从后端到前端之Vue(六)表单组件

    表单组件 做项目的时候会遇到一个比较头疼的问题,一个大表单里面有好多控件,一个一个做设置太麻烦,更头疼的是,需求还总在变化,一会多选.一会单选.一会下拉的,变来变去的烦死宝宝了. 那么怎么解决这个问题 ...

  9. 封装Vue Element的form表单组件

    前两天封装了一个基于vue和Element的table表格组件,阅读的人还是很多的,看来大家都是很认同组件化.高复用这种开发模式的,毕竟开发效率高,代码优雅,逼格高嘛.虽然这两天我的心情很糟糕,就像& ...

随机推荐

  1. Html 慕课园编程练习10-1

    23:10:25 2019-08-14 自己写的这个好丑.... 题目:利用之前我们学过的JavaScript知识,实现选项卡切换的效果. 效果图: (另外 这个动图是怎么插入的 用url就行 复制就 ...

  2. python学习要点(二)

    我的博客:https://www.luozhiyun.com/archives/269 '==' VS 'is' '=='操作符比较对象之间的值是否相等. 'is'操作符比较的是对象的身份标识是否相等 ...

  3. Golang入门(2):一天学完GO的基本语法

    摘要 在配置好环境之后,要研究的就是这个语言的语法了.在这篇文章中,作者希望可以简单的介绍一下Golang的各种语法,并与C和Java作一些简单的对比以加深记忆.因为这篇文章只是入门Golang的第二 ...

  4. JavaScript中||和&&的运算

    一般来讲 && 运算和 | | 运算得到的结果都是 true 和 false ,但是 js 中的有点不太一样.当进行 a&&b 和 a| |b (如 1&&am ...

  5. 世界疫情app柱形图显示

    访问云服务器的mysql实现数据的获取.最后通过柱形图的形式将数据显示在页面上: 遇到的主要困难时对于云服务器的mysql连接本地的navicat之间事情,最后通过网上的各种解决办法完成了相关的内容. ...

  6. Python爬虫利器 cURL你用过吗?

    hello,小伙伴们,今天给大家分享的开源项目是一个python爬虫利器,感兴趣的小伙伴看完这篇文章不妨去尝试一下,这个开源项目就是curlconverter,不知道小伙伴们分析完整个网站后去code ...

  7. Python设计模式(2)-策略模式

    # 策略模式和简单工厂模式相比,少了使用switch case 做判断,然后去实例化相应的 # 对象,比简单工厂模式更灵活. 它们代码的区别就在于此处使用了抽象类代替工厂类 # coding=utf- ...

  8. Django-rest-framework 是个什么鬼?

    作者:HelloGitHub-追梦人物 我们首先来回顾一下传统的基于模板引擎的 django 开发工作流: 绑定 URL 和视图函数.当用户访问某个 URL 时,调用绑定的视图函数进行处理. 编写视图 ...

  9. java异常处理:finally中不要return

    java异常处理:finally中不要return 复制代码 public class Ex1 { public static void main(String[] args) { System.ou ...

  10. 视频图文教学 - 用最快的速度把 DotNet Core Blazor 程序安装到 树莓派中 并且用网页控制 GPIO 闪灯

    前言 dotnet core 在3.0时代已经发展得很好. 尤其是在跨平台方面更已经是达到了很实用的阶段. 作为 dotnet 程序员, 应该对 Linux 有充分的了解, 也可以在业余时间玩玩硬件, ...