ant-design-vue 遇到的问题和常见场景的实现
2019-04-24 本文已影响0人
前端girl吖
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表单项绑定值,虽然浏览器控制台打印出来看上去一样,但是类型变了!!!一定要注意):
然后就是上传列表永远只显示最后选择的文件这个过渡效果,需要在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这个option
memberQueryForm: 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)
},