美文网首页Frontend前端
ant-design-vue 遇到的问题和常见场景的实现

ant-design-vue 遇到的问题和常见场景的实现

作者: 前端girl吖 | 来源:发表于2019-04-24 14:07 被阅读0次

    1、select/datepicker/asacader 组件菜单框显示时,滚动页面,菜单框不随父元素滚动,而是根据body页面滚动

    原因: ant默认append到body
    解决: 通过组件的属性getXxxContainer 修改渲染的父元素

      <template>
        <a-select
          v-decorator="['highestEdu']"
          class="highestEdu"
          placeholder="请选择最高学历"
          :getPopupContainer="() => doc.getElementsByClassName('highestEdu')[0]">
          <a-select-option
            v-for="edu in eductionOptions"
            :value="edu"
            :key="edu"> {{ edu }}</a-select-option>
        </a-select>
      </template>
      <script>
        export default {
          data () {
            return {
                doc: document
            }
         }
        }
        // 自定义封装级联组件遇到直接父组件绑定class无效或直接级联组件绑定class,会出现菜单项一点就消失的问题,需要包裹一层div绑定
    // provinceCity.js
      <template>
        <div :class="popupContainer">
          <a-cascader
            :options="provinceCity"
            :placeholder="placeholder"
            v-model="city"
            @change="handleCascader"
            :getPopupContainer="getPopupContainer"></a-cascader>
        </div>
      </template>
      <script>
        export default {
          props: {
            value: { type: Array },
            placeholder: {
              type: String
            },
            popupContainer: {
              type: String
            }
          },
          methods: {
            getPopupContainer () {
              return document.getElementsByClassName(this.popupContainer)[0]
            }  
          }
        </script>
     // form.vue
      <ProvinceCity
        v-decorator="['nativePlace']"
        placeholder="请选择籍贯"
        popupContainer="nativePlace"
        @emitCascader="handleNativePlaceChange"/>
    

    !!! 全局化配置

      <a-config-provider :getPopupContainer="getPopupContainer">
          <router-view style="min-width: 992px;"/>
       </a-config-provider>
      // script
      methods: {
        getPopupContainer (triggerNode) {
          // 触发节点 指某个页面的select,tooltip,menu等含弹框显示的组件,这些组件显示下拉框会触发该方法,不包括datapicker,可全局配置
          return triggerNode
        }
      }
    

    2、ant 内引入moment,日期均返回moment

    页面使用直接引入moment、不需要npm i

      import moment from 'moment'  
    

    moment组件值转化

      this.form.date.format('YYYY-MM-DD')
    

    3、form表单里upload选择xlsx文件(只选择一份,后选择的会覆盖前选择文件),最后提交上传(整个form表单form-data处理)

    // http.js
    postByForm (url, params) {
        return this.Axios.post(url, params, {
          headers: {
            'Content-type': 'multipart/form-data'
          }
        }).catch(e => {
          return Promise.reject(new ApiError(
            'network error', -1
          ))
        })
      }
    
    // apis.js 
    // 所有传的参数formData化
    export async function updateMultiMember ({accountID, upload}) {
      const formData = new FormData()
      upload.forEach((file) => {
        formData.append('upload[]', file)
      })
      formData.append('accountID', accountID)
      formData.append('appkey', Global.appkey)
      formData.append('channel', Global.channel)
      formData.append('chainTokenArray[]', [Global.chainToken])
      let res = await saveAgents(formData)
      return res
    }
    
    // upload.vue
    <template>
      <ContentWithFooter
        @emitCommit="handleUpload">
          <a-form
            class="multi-upload-form"
            :form="multiForm">
            <a-form-item
              v-bind="formItemLayout"
              label="选择文件">
              <a-upload
                accept='.xlsx'
                name="upload"
                :remove="handleRemove"
                :beforeUpload="beforeUpload"
                v-decorator="['upload',{
                  valuePropName: 'fileList',
                  getValueFromEvent: normFile,
                  rules: [{required: true, message: '请选择文件'}]
                }]">
                <a-button>上传文件</a-button>
              </a-upload>
            </a-form-item>
          </a-form>
      </ContentWithFooter>
    </template>
    <script>
    import { Utils } from '@/common'
    import ContentWithFooter from '@/components/content-with-footer'
    export default {
      name: 'member-multi',
      components: { ContentWithFooter },
      data () {
        return {
          formItemLayout: Utils.setFormLayout(2, 22),
          multiForm: this.$form.createForm(this),
          fileList: []
        }
      },
      methods: {
        handleRemove (file) {
          const index = this.fileList.indexOf(file)
          const newFileList = this.fileList.slice()
          newFileList.splice(index, 1)
          this.fileList = newFileList
        },
        beforeUpload (file) {
          this.fileList = [file]
          return false  // return false 实现手动上传
        },
        handleUpload () {
          this.multiForm.validateFields((err, values) => {
            if (!err) {
              // 这边出现一个就是,直接传values.upload,会出现类型错误,虽然打印出来的东西一样,但不再是File类型
              this.$emit('emitMultiFormSubmit', {upload: this.fileList})
            }
          })
        },
        normFile (e) {
          if (Array.isArray(e)) {
            return e
          }
          // return e && e.fileList 
          return e && [e.file]  // 如果单单一个upload组件,不在form表单里,则直接beforeUpload方法里this.fileList = [file]则会实现替换文件,只显示一条记录的动态效果,但form表单里需要在这个方法里设置
        }
      }
    }
    

    单纯的upload组件流程都是正常的:

    • 上传后返回的文件类型是正常的File类型
    • beforeUpload或者change事件里直接代码this.fileList = [file]都可以实现选择文件后 上传列表永远只显示最后选择的文件

    但是,放入form表单,不再是uplaod组件上:fileList="fileList",而是通过

      v-decorator="['upload',{  // form表单项属性
         valuePropName: 'fileList',  // 表示upload组件的fileList属性
         getValueFromEvent: normFile, // 表示upload组件的fileList属性的值
         rules: [{required: true, message: '请选择文件'}]
       }
    

    绑定获取值,这时会出现2个问题

    • 一般form表单处理,都是在this.multiForm.validateFields ((err, values) => {} 表单校验通过后,直接取values里对应的表单项值,但这边取upload,会出现类型由File变为Object,导致后端无法解析,下图可见(前者upload绑定值,后者form取form表单项绑定值,虽然浏览器控制台打印出来看上去一样,但是类型变了!!!一定要注意):
    image.png

    然后就是上传列表永远只显示最后选择的文件这个过渡效果,需要在form表单项的v-decorator里配置getValueFromEvent: normFile

      normFile (e) {
          if (Array.isArray(e)) {
            return e
          }
          // return e && e.fileList 
          return e && [e.file]  // 如果单单一个upload组件,不在form表单里,则直接beforeUpload方法里this.fileList = [file]则会实现替换文件,只显示一条记录的动态效果,但form表单里需要在这个方法里设置
        }
      }
    

    4、需要form colomu布局,但每项row

    image.png
     <a-form :form="basicForm">
        <a-form-item
          label="姓名"
          v-bind="formItemLayout">
          <a-input
            v-decorator="[
              'name',
              {
                rules: [{required: true, message: '请输入姓名'}]
              }]"
            placeholder="请输入姓名"/>
        </a-form-item>
     </a-form>
    <script>
      const FORMLAYOUTITEM = {
        labelCol: { span: 2 },
        wrapperCol: { span: 8 }
      }
    
      export default {
         data () {
            formItemLayout: FORMLAYOUTITEM 
         }
      }
    

    5、table列表分页实现

    <a-table
      :columns="columns"
      :dataSource="tableData"
      :loading="isTableLoading"
      :rowKey="record => record.id" 
      :pagination="pagination" // 分页配置
      @change="handleTableChange"/> // 分页、排序、筛选变化时触发
    
      <script>
        export default {
          data () {
              return {
                pagination: {
                  showSizeChanger: true, // 显示当前页显示几条
                  total: 0,
                  pageSize: 10,
                  current: 1
                },
            }
          }
        }
      methods: {
        handleTableChange (pagination) {
          this.pagination.current = pagination.current
          this.pagination.pageSize = pagination.pageSize
          this.getLists()
        }
      }
    
    

    6、form+table form搜索条件变化,table分页当前页参数重置

    image.png
    实现:通过ant form创键的时候设置onValuesChange这个optionmemberQueryForm: this.$form.createForm(this, {onValuesChange: this.onFormValuesChange}) form任一表单项的值发生变化时的回调

    7、menu 页面刷新,保留点击状态 + 路由跳转,菜单项选中项状态

    <template>
      <div>
        <a-menu
          theme="dark"
          mode="inline"
          @openChange="onOpenChange"
          width="auto"
          :openKeys="openKeys"
          :selectedKeys="selectedKey"
          :defaultSelectedKeys="selectedKey">
          <a-sub-menu
            v-if="item.child"
            v-for="item of sidebars"
            :key="item.key">
            <template slot="title">{{ item.name }}</template>
            <a-menu-item
              v-for="subItem of item.child"
              :key="subItem.key"
              @click="$router.push({name:subItem.key})">{{ subItem.name }}</a-menu-item>
          </a-sub-menu>
          <a-menu-item
            v-if="!item.child"
            v-for="item of sidebars"
            :key="item.key"
            @click="$router.push({name:item.key})">{{ item.name }}</a-menu-item>
        </a-menu>
      </div>
    </template>
    <script>
    import { SIDERBARS } from '@/const'
    export default {
      name: 'Sidebar',
      data () {
        return {
          sidebars: [],
          allSubmenuKeys: [],
          openKeys: [], // 展开父菜单项
          selectedKey: [] // 选中子菜单项
        }
      },
      created () {
        // 页面刷新 菜单项激活状态
        this.updateSidebars()
      },
      watch: {
        '$route.name': function () {
        // 路由跳轉 菜单项激活状态
          this.updateSidebars()
        }
      },
      methods: {
        onOpenChange (openKeys) {
          // 只展示当前父级菜单
          const lastOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
          if (this.allSubmenuKeys.indexOf(lastOpenKey) === -1) {
            this.openKeys = openKeys
          } else {
            this.openKeys = lastOpenKey ? [lastOpenKey] : []
          }
        },
        updateSidebars () {
          let that = this
          if (this.$route.path.indexOf('admin') !== -1) {
            this.sidebars = SIDERBARS.admin
          } else if (this.$route.path.indexOf('operation') !== -1) {
            this.sidebars = SIDERBARS.operation
          } else {
            this.sidebars = SIDERBARS.hr
          }
          _.forEach(this.sidebars, function (val, ind) {
            that.allSubmenuKeys.push(val.key)
          })
          if (this.$route.meta.parent) {
            this.openKeys = [this.$route.meta.parent]
          }
          if (this.$route.name) {
            this.$set(this, 'selectedKey', [this.$route.name])
          }
        }
      }
    }
    </script>
    

    8、form表单编辑状态初始化 通过配置mapPropsToFields

    let options = info.chainOrgName ? {
          mapPropsToFields: () => {
            return {
              chainOrgName: this.$form.createFormField({
                value: info.chainOrgName
              }),
              orgDomain: this.$form.createFormField({
                value: info.orgDomain
              }),
              description: this.$form.createFormField({
                value: info.description
              })
            }
          }
        } : {}
        this.nodeForm = this.$form.createForm(this, options)
      },
    

    相关文章

      网友评论

        本文标题:ant-design-vue 遇到的问题和常见场景的实现

        本文链接:https://www.haomeiwen.com/subject/ctdwbqtx.html