zoukankan      html  css  js  c++  java
  • Vue 2.x折腾记

    前言

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

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

    效果图

    • 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-upload
                 v-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-input
        v-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-select
        v-else-if="fieldOptions.fieldName && fieldOptions.type === 'select'"
        style=" 100%"
        showSearch
        :options="fieldOptions.options"
        :filterOption="selectFilterOption"
        :size="fieldOptions.size ? fieldOptions.size : 'default'"
        allowClear
        v-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-number
        v-else-if="fieldOptions.fieldName && fieldOptions.type === 'number'"
        :size="fieldOptions.size ? fieldOptions.size : 'default'"
        :min="fieldOptions.min ? fieldOptions.min : 1"
        style=" 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-group
        v-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-picker
        v-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-picker
        v-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-cascader
        v-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-picker
        v-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-select
        mode="multiple"
        :size="fieldOptions.size ? fieldOptions.size : 'default'"
        optionFilterProp="children"
        v-else-if="fieldOptions.fieldName && fieldOptions.type === 'multiple'"
        :placeholder="fieldOptions.placeholder"
        style=" 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) {
         // 判断必须为obj
         if (!(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) {
               // 判断moment
               value = value.valueOf();
             }
             if (Array.isArray(value) && value[0]._isAMomentObject && value[1]._isAMomentObject) {
               // 判断moment
               value = 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;
            auto;
         }
         .ant-form-item-control {
           height: 32px;
           line-height: 32px;
           display: flex;
           justify-content: flex-start;
           align-items: center;
           .ant-form-item-children {
             min- 160px;
           }
         }
       }
     }
    
     .table-page-search-submitButtons {
       display: block;
       margin-bottom: 24px;
       white-space: nowrap;
     }
    }
    </style>
    
    
    复制代码

    问题

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

    问题:

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

    解决方案:

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

    总结

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

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

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

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

  • 相关阅读:
    thinkphp5 tp5 命名空间 报错 Namespace declaration statement has to be the very first statement in the script
    开启 php 错误 提示 php-fpm 重启 nginx 500错误 解决办法 wdlinux lnmp 一键包 php脚本无法解析执行
    js 设置 cookie 定时 弹出层 提示层 下次访问 不再显示 弹窗 getCookie setCookie setTimeout
    php 二维数组 转字符串 implode 方便 mysql in 查询
    nginx 重启 ps -ef|grep nginx kill -HUP 主进程号
    jquery bootstrap help-block input 表单 提示 帮助 信息
    jquery 倒计时 60秒 短信 验证码 js ajax 获取
    jQuery如何获取同一个类标签的所有的值 遍历
    linux下C语言文件操作相关函数
    gcc,gdb用法
  • 原文地址:https://www.cnblogs.com/twodog/p/12134911.html
Copyright © 2011-2022 走看看