使用 element 的 form 时候报错:
vue.runtime.esm.js:3065 Error: [ElementForm]unpected width
at VueComponent.getLabelWidthIndex (element-ui.common.js:23268:1)
at VueComponent.deregisterLabelWidth (element-ui.common.js:23281:1)
at VueComponent.updateLabelWidth (element-ui.common.js:23483:1)
at VueComponent.beforeDestroy (element-ui.common.js:23510:1)
原因:el-form-item 的 label 组件有个 beforeDestroy 生命同期,el-form 组件的 display: none 获取labelWidth 有问题导致的
解决办法:el-form 上加 v-if,用 el-dialog 的 :visible.sync="dialogVisible" 的话,也加 v-if="dialogVisible"
由于 el-form 标签 label-width 设为 "auto" ,而 el-form-item 会继承这个属性,在某种情况下页面销毁时获取这个 el-form-item 的 label 的 width 为 'auto',然后用 parseFloat 转换后为 NaN 就报错了。
这个方法是在 element-ui 共通方法里写的 node_modules\element-ui\lib\element-ui.common.js
或是
computedWidth 为 “auto" 或 "" 都会返回 NaN
网上说是因为代码中使用了 v-show 以及 el-form 标签中使用了label-width="auto",导致离开页面后产生报错
解决方法一:label-width 换成固定值
解决方法二: v-show 换成 v-if
但我的原因是因为使用了 keep-alive 用下面代码可以再现出来
src\router\index.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import Layout from '../views/management/Layout.vue';
Vue.use(Router);
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/management',
component: Layout,
children: [
{
path: '/management/audit',
component: () => import("@/views/management/audit/index.vue"),
name: "memberAudit",
},
{
path: '/management/detail',
component: () => import("@/views/management/detail/index.vue"),
name: "memberAudit",
}
]
}
]
});
export default router;
src\views\management\Layout.vue
<template>
<div class="main">
<el-scrollbar>
<div class="left">
<el-menu
default-active="$route.path"
background-color="#545c64"
text-color="#fff"
router
>
<el-menu-item :router="true" index="/management/audit">
<i class="el-icon-menu"></i>
<span slot="title">审核</span>
</el-menu-item>
<el-menu-item :router="true" index="/management/detail?label=menu">
<i class="el-icon-menu"></i>
<span slot="title">详情</span>
</el-menu-item>
<el-menu-item :router="true" index="/management/about">
<i class="el-icon-menu"></i>
<span slot="title">About</span>
</el-menu-item>
</el-menu>
</div>
<div class="right">
<keep-alive>
<router-view />
</keep-alive>
</div>
</el-scrollbar>
</div>
</template>
<style lang="scss">
.left {
float: left;
width: 200px;
}
.right {
float: left;
width: 80%;
}
</style>
src\views\management\audit\index.vue
<template>
<div class="hello1" style="margin-top: 100px">
audit
<span @click="detail">click</span>
</div>
</template>
<script>
export default {
methods: {
detail() {
this.$router.push("/management/detail?label=测试");
},
},
};
</script>
src\views\management\detail\index.vue
<template>
<div>
<el-form
:model="ruleForm"
status-icon
ref="ruleForm"
label-width="auto"
class="demo-ruleForm"
>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item :label="$route.query.label" prop="name">
<el-input v-model.number="ruleForm.name"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="resetForm()">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "Home",
components: {},
data() {
return {
ruleForm: {
age: "",
name: "",
},
};
},
methods: {
resetForm() {
this.$router.push("/management/audit");
},
},
};
</script>
点击详情
点击重置
再点击 about
报错了,这里注意报错的主要写法
1、src\views\management\Layout.vue
使用 keep-alive
2、src\router\index.js
这里我没设置 about 页面路由,可能这样会触发 VueComponent.beforeDestroy
3、src\views\management\detail\index.vue
el-form-item 必须两个以上,并且其中一个的 label 使用动态值 :label="$route.query.label"
我的这种情况即使设置 label-width 为固定值也不行,因为最后页面消失,获取 label-width 为空
parseFloat(label-width) 为 NaN 就报错了
去掉 keep-alive 就不会报错
去掉后就不会走 'update' 分支,因为页面切换时因为有个 label 是动态赋值的,所以即使页面销毁因为是 keep-alive 且 label 动态值变没了,导致 el-form 认为是 label 值 update ,走的这个方法,而这个时候页面找不到元素, label-width 就是空,所以报错了。
其实是因为 el-form-item 的 label 设为响应式的了,而且在页面销毁时响应式值还更新,可以换一种响应式使它在页面销毁时不更新值
<el-form-item :label="qLabel" prop="name">
created() {
this.qLabel = this.$route.query.label;
},
<template>
<div>
<el-form
:model="ruleForm"
status-icon
ref="ruleForm"
label-width="auto"
class="demo-ruleForm"
>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item :label="qLabel" prop="name">
<el-input v-model.number="ruleForm.name"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="resetForm()">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "Home",
components: {},
created() {
this.qLabel = this.$route.query.label;
},
data() {
return {
qLabel: "",
ruleForm: {
age: "",
name: "",
},
};
},
methods: {
resetForm() {
this.$router.push("/management/audit");
},
},
};
</script>
这么写不会报错,算是完美避开了这个 bug
但是公司的是另一个问题,是用 el-dialog 组件包裹 el-form 也出这个 bug了
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
:destroy-on-close="true"
>
<el-form
:model="ruleForm"
status-icon
ref="ruleForm"
label-width="auto"
class="demo-ruleForm"
>
<el-form-item :span="24" label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="resetForm()">重置</el-button>
</el-form-item>
</el-form>
</el-dialog>
resetForm() {
this.dialogVisible = false;
this.$router.push("/management/detail");
},
查看源码仔细分析发现,虽然报错在 element-ui.common.js 但其实报错是在源码里 label-wrap.vue
<script>
export default {
props: {
isAutoWidth: Boolean,
updateAll: Boolean
},
inject: ['elForm', 'elFormItem'],
render() {
const slots = this.$slots.default;
if (!slots) return null;
if (this.isAutoWidth) {
const autoLabelWidth = this.elForm.autoLabelWidth;
const style = {};
if (autoLabelWidth && autoLabelWidth !== 'auto') {
const marginLeft = parseInt(autoLabelWidth, 10) - this.computedWidth;
if (marginLeft) {
style.marginLeft = marginLeft + 'px';
}
}
return (<div class="el-form-item__label-wrap" style={style}>
{ slots }
</div>);
} else {
return slots[0];
}
},
methods: {
getLabelWidth() {
if (this.$el && this.$el.firstElementChild) {
const computedWidth = window.getComputedStyle(this.$el.firstElementChild).width;
return Math.ceil(parseFloat(computedWidth));
} else {
return 0;
}
},
updateLabelWidth(action = 'update') {
if (this.$slots.default && this.isAutoWidth && this.$el.firstElementChild) {
if (action === 'update') {
this.computedWidth = this.getLabelWidth();
} else if (action === 'remove') {
this.elForm.deregisterLabelWidth(this.computedWidth);
}
}
}
},
watch: {
computedWidth(val, oldVal) {
if (this.updateAll) {
this.elForm.registerLabelWidth(val, oldVal);
this.elFormItem.updateComputedLabelWidth(val);
}
}
},
data() {
return {
computedWidth: 0
};
},
mounted() {
this.updateLabelWidth('update');
},
updated() {
this.updateLabelWidth('update');
},
beforeDestroy() {
this.updateLabelWidth('remove');
}
};
</script>
因为 form-item 组件用了 label 组件,而 label 组件是用上面的 label-wrap 包裹的,所以 el-dialog 关闭并跳转到新页面时就调用了 label-wrap beforeDestroy生命周期函数,这个时候因为 el-dialog 是用的 v-show 所以 display 为 none,这个时候获取 label 的 width
if (this.$el && this.$el.firstElementChild) 这个判断条件是获取 label 标签因为用了 <keep-alive>,所以还是能找到的,但是window.getComputedStyle(this.$el.firstElementChild).width 获取的值就是 空或者 ’auto'
解决办法 v-show 改为 v-if 就是让 this.$el.firstElementChild 根本查询不到 label 标签,就不会走这个条件分支了,就不会报错了,对功能也没影响。
所以可以在 el-dialog 上加 v-if 和 visible 用一个变量即可,或是在 el-form 上加,或是 el-form-item上加 v-if 但是这种情况得所有 el-form-item 都加上
测试解决了