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

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)
  },
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271