二次封装这几个 element-ui 组件后,让代码更加优雅了
element-ui 因其组件丰富、可拓展性强、文档详细等优点成为 Vue 最火的第三方 UI 框架。element-ui 其本身就针对后台系统设计了很多实用的组件,基本上满足了平时的开发需求。
既然如此,那么我们为什么还要进行二次封装呢?
有以下两种场景
在日常的开发过程中,部分模块重复性比较强,这个时候就会产生大量重复的代码。这些模块的样式基本上是比较固定的,而且实现的功能也比较相近。如果每个地方都复制一份相似的代码,既不遵守代码的简洁之道,也不利于后期的维护修改
此外,在一些业务背景下,产品可能会要求设计新的交互。这个时候也可以基于 element-ui 进行二次开发,将其封装成一个新的组件方便多个地方使用
因为在日常开发过程中,项目主要以 Vue2 为主,并且现在很多公司仍在使用着 Vue2。故本文主要探讨 Vue2 + element-ui 的项目可以怎么封装一些比较通用化的组件
核心思想
- 主要以父组件传递数据给子组件来实现一些功能,子组件定义固定的展示样式,将具体要实现的业务逻辑抛出来给父组件处理
- 尽量保持 element-ui 组件原有的方法(可以使用 v-bind="$attrs" 和 v-on="$listeners"),如果确实要做更改也尽量让相似的方法方法名不变
组件
InputNumber
el-input-number 是一个很好用的组件,它只允许用户输入数字值。但是这个组件会有个默认值,给他赋予一个null 或""的时候会显示0
这对于有些业务来说并不是很友好,例如添加页面和编辑页面
并且它这个组件的值是居中显示的,和普通的input 框居左显示不同,这就导致了样式不太统一
改造:让 InputNumber 可以居左显示且没有默认值,用法保持和el-input-number组件相似
子组件 InputNumber.vue
<template>
<el-input-number id="InputNumber"
style="width: 100%"
v-model="insideValue"
v-bind="$attrs"
:controls="controls"
v-on="$listeners" />
</template>
<script>
export default {
// 让父组件 v-model 传参
model: {
prop: 'numberValue',
event: 'change',
},
props: {
numberValue: {
type: [Number, String],
default: undefined,
},
// 默认不显示控制按钮,这个可以根据实际情况做调整
controls: {
type: Boolean,
default: false,
},
},
data () {
return {
insideValue: undefined,
};
},
watch: {
numberValue (newVlalue) {
// 若传入一个数字就显示。为空则不显示
if (typeof newVlalue === 'number') {
this.insideValue = newVlalue;
} else this.insideValue = undefined;
},
},
};
</script>
<style lang="scss" scoped>
#InputNumber {
/deep/ .el-input__inner {
text-align: left;
}
}
</style>
父组件
<template>
<InputNumber v-model="value"
style="width: 200px" />
</template>
<script>
import InputNumber from './InputNumber';
export default {
components: {
InputNumber,
},
data () {
return {
value: null,
};
},
};
</script>
演示:

OptionPlus
select 组件用在有较多选项时,但是有些选项的长度难免比较长,就会把选项框整个给撑大,例如:

这种还是比较短的时候了,有时因为公司名称较长,或者其他业务要展示的字段过长时就不太友好。
改造:固定选项框的大小,让选项显示更加合理
子组件 OptionPlus.vue
<template>
<el-option :style="`width: ${width}px`"
v-bind="$attrs"
v-on="$listeners">
<slot />
</el-option>
</template>
<script>
export default {
props: {
width: {
type: Number,
},
},
};
</script>
<style lang="scss" scoped>
.el-select-dropdown__item {
min-height: 35px;
height: auto;
white-space: initial;
overflow: hidden;
text-overflow: initial;
line-height: 25px;
padding: 5px 20px;
}
</style>
父组件
<template>
<el-select v-model="value"
placeholder="请选择">
<OptionPlus v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
:width="200">
</OptionPlus>
</el-select>
</template>
<script>
import OptionPlus from './OptionPlus';
export default {
components: {
OptionPlus,
},
data () {
return {
value: null,
options: [{
value: '选项1',
label: '黄金糕',
}, {
value: '选项2',
label: '双皮奶特别好吃,以顺德的最出名,推荐尝试',
}, {
value: '选项3',
label: '蚵仔煎',
}, {
value: '选项4',
label: '龙须面',
}, {
value: '选项5',
label: '北京烤鸭',
}],
};
},
};
效果:

FormPlus
后台系统肯定会有查找功能,搜索条件大部分都是这三种,输入框、下拉框和日期选择。所以可以整合这三个常用的元素,将它们封装成一个易于使用的组件
这三个组件是用来过滤条件的,因此一般与查询和重置按钮在一起
子组件FormPlus.vue
<template>
<div id="FormPlus">
<el-form ref="ruleForm"
:rules="rules"
:inline="inline"
:model="ruleForm"
class="ruleForm"
:label-width="labelWidth"
:style="formStyle">
<template v-for="(item, index) in list">
<template v-if="!item.type || item.type === 'input'">
<el-form-item :key="index"
:label="item.label"
:prop="item.model"
:required="item.required">
<el-input v-model.trim="ruleForm[item.model]"
:clearable="item.clearable === undefined || item.clearable"
filterable
:placeholder="item.placeholder" />
</el-form-item>
</template>
<template v-if="item.type === 'select'">
<el-form-item :key="index"
:label="item.label"
:prop="item.model"
:required="item.required">
<el-select :style="`width: ${formItemContentWidth}`"
v-model.trim="ruleForm[item.model]"
:clearable="item.clearable === undefined || item.clearable"
filterable
:placeholder="item.placeholder || ''">
<!-- 使用上文提到的 OptionPlus 组件 -->
<OptionPlus v-for="(i, key) in item.options"
:key="i[item.optionsKey] || key"
:label="i[item.optionsLabel] || i.label"
:value="i[item.optionsValue] || i.value"
:width="formItemContentWidth" />
</el-select>
</el-form-item>
</template>
<template v-if="item.type === 'date-picker'">
<el-form-item :key="index"
:prop="item.model"
:label="item.label"
:required="item.required">
<el-date-picker v-model.trim="ruleForm[item.model]"
:clearable="item.clearable === undefined || item.clearable"
:type="item.pickerType"
:placeholder="item.placeholder"
:format="item.format"
:value-format="item.valueFormat"
:picker-options="item.pickerOptions" />
</el-form-item>
</template>
</template>
<slot />
</el-form>
<el-row>
<el-col class="btn-container">
<el-button class="el-icon-search"
type="primary"
@click="submitForm">查询</el-button>
<el-button class="el-icon-refresh"
@click="resetForm">重置</el-button>
</el-col>
</el-row>
</div>
</template>
<script>
import OptionPlus from './OptionPlus';
export default {
components: { OptionPlus },
props: {
list: {
type: Array,
default: () => [],
},
inline: {
type: Boolean,
default: true,
},
labelWidth: {
type: String,
default: '100px',
},
formItemWidth: {
type: String,
default: '400px',
},
formItemContentWidth: {
type: String,
default: '250px',
},
rules: {
type: Object,
default: () => { },
},
},
data () {
return {
ruleForm: {},
};
},
computed: {
formStyle () {
return {
'--formItemWidth': this.formItemWidth,
'--formItemContentWidth': this.formItemContentWidth,
};
},
},
watch: {
list: {
handler (list) {
this.handleList(list);
},
immediate: true,
deep: true,
},
},
methods: {
// 所填写数据
submitForm () {
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
const exportData = { ...this.ruleForm };
this.$emit('submitForm', exportData);
} else {