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 {
return false;
}
});
},
// 默认清空所填写数据
resetForm () {
this.$refs.ruleForm.resetFields();
this.handleList(this.list);
this.$emit('resetForm');
},
handleList (list) {
for (let i = 0; i < list.length; i++) {
const formitem = list[i];
const { model } = formitem;
this.$set(this.ruleForm, model, '');
}
},
},
};
</script> <style lang="scss" scoped>
#FormPlus {
.ruleForm {
width: 100%;
::v-deep.el-form-item {
width: var(--formItemWidth);
}
::v-deep.el-form-item__content {
width: var(--formItemContentWidth);
}
::v-deep.el-form-item__content .el-date-editor,
.el-input {
width: var(--formItemContentWidth);
}
}
.btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
}
</style>

父组件

<template>
<FormPlus :list="formList"
@submitForm="searchPage"
@resetForm="resetForm" />
</template> <script>
import FormPlus from './FormPlus';
export default {
components: {
FormPlus,
},
data () {
return {
formList: [
{ label: '编号', model: 'applyNumber', placeholder: '请输入编号' },
{ label: '名称', model: 'name', placeholder: '请输入名称' },
{ type: 'date-picker', label: '开始时间', model: 'startTime', valueFormat: 'yyyy-MM-dd HH:mm:ss', placeholder: '请选择开始时间' },
{ type: 'select', label: '状态', model: 'status', placeholder: '请选择状态', options: [] },
],
};
},
methods: {
// 可以取到子组件传递过来的数据
searchPage (ruleForm) {
console.log(ruleForm, 'ruleForm');
},
resetForm () { },
},
};
</script>

演示:

接口获取到的数据可以用this.formList[index] = res.data;来将数据塞进 el-select 的选项数组中

这个组件其实是有一定局限性的,如果确实有特别的需求还是要用 el-form 表单来写

DrawerPlus

抽屉组件可以提供更深一级的操作,往往内容会比较多比较长。因此可以封装一个组件,让操作按钮固定在 drawer 底部,以实现较好的交互

子组件 DrawerPlus.vue

<template>
<div id="drawerPlus">
<el-drawer v-bind="$attrs"
v-on="$listeners">
<el-scrollbar class="scrollbar">
<slot />
<div class="seat"></div>
<div class="footer">
<slot name="footer" />
</div>
</el-scrollbar>
</el-drawer>
</div>
</template> <style lang="scss" scoped>
$height: 100px;
#drawerPlus {
.scrollbar {
height: 100%;
position: relative;
.seat {
height: $height;
}
.footer {
z-index: 9;
box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.08);
width: 100%;
position: absolute;
bottom: 0px;
height: $height;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

父组件

<template>
<DrawerPlus title="编辑"
:visible.sync="drawerVisible"
direction="rtl"
size="45%">
<template slot="footer">
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary"
@click="drawerVisible = false">确定</el-button>
</template>
</DrawerPlus>
</template> <script>
import DrawerPlus from './DrawerPlus';
export default {
components: {
DrawerPlus,
},
data () {
return {
drawerVisible: false,
};
},
};
</script>

效果:

使用 el-scrollbar 组件来实现更优雅的滚动效果,底部固定并增加一些阴影增加美观

CopyIcon

在日常开发中,有时可能想实现一键复制,我们可以选择手写复制方法,也可以选择引入 clipboard.js 库帮助快速实现功能

在笔者写过的一篇文章《在网站copy时自带的版权小尾巴以及“复制代码“,可以怎么实现 》,这篇文章中有提到怎么手写复制功能

当然,严格意义上来说,这个组件主要实现不是依赖 element-ui 的,但也有用到其中的一些组件,所以也写在这里

子组件 CopyIcon.vue

<template>
<i :class="`${icon} icon-cursor`"
title="点击复制"
@click="handleCopy($event, text)" />
</template> <script>
// 引入 clipboard.js
import Clipboard from 'clipboard';
export default {
props: {
// 接收复制的内容
text: {
type: [String, Number],
default: null,
},
// 默认是复制 icon,可自定义 icon
icon: {
type: [String],
default: 'el-icon-copy-document',
},
// 自定义成功提示
message: {
type: [String, Number],
default: null,
},
},
methods: {
handleCopy (e, _text, message) {
const clipboard = new Clipboard(e.target, { text: () => _text });
const messageText = message || `复制成功:${_text}`;
clipboard.on('success', () => {
this.$message({ type: 'success', message: messageText });
clipboard.off('error');
clipboard.off('success');
clipboard.destroy();
});
clipboard.on('error', () => {
this.$message({ type: 'warning', message: '复制失败,请手动复制' });
clipboard.off('error');
clipboard.off('success');
clipboard.destroy();
});
clipboard.onClick(e);
},
},
};
</script> <style lang="scss" scoped>
.icon-cursor {
cursor: pointer;
}
</style>

父组件

<template>
<div>
<span>{{ value }}</span>
<CopyIcon :text="value" />
</div>
</template> <script>
import CopyIcon from './CopyIcon';
export default {
components: {
CopyIcon,
},
data () {
return {
value: '这里来测试一下-初见雨夜',
};
},
};
</script>

演示:

二次封装虽说方便了后续的开发,但是当封装的组件不能满足需求时,可以考虑迭代或者用回 element-ui 原生的组件

因为笔者水平有限,对组件都是进行比较简单的封装,并且有些地方设计可能不是很合理,还请多多指教~

二次封装这几个 element-ui 组件后,大大减少了我 CRUD 的时间的更多相关文章

  1. element ui组件的开始时间-结束时间验证

    <el-date-picker v-model="seach.before" type="date" placeholder="开始时间&quo ...

  2. (Element UI 组件 Table)去除单元格底部的横线

    Element UI 组件 Table 有一个属性 border,添加它可以增加纵向边框,但是无法控制横线边框,因此即使是最简单的 el-table,也会包含一个底部横线. 这个底部横线其实是一个 b ...

  3. 封装一个优雅的element ui表格组件

    现在做后台系统用vue + elementUI 的越来越多,那element ui的 el-table 组件肯定也离不开.虽然element ui的table组件很好.但是表格和分页是分离的.每次写表 ...

  4. vue-cli按需引入Element UI组件

    一.环境 使用vue-cli搭建的环境 二.安装 babel-plugin-component npm install babel-plugin-component -D 三.修改.babelrc文件 ...

  5. Element UI组件说明

    -<el-card>-查询及展示列表页面-[v-show]属性控制显示隐藏-<el-card class="box-card" >-多标签页面-<el ...

  6. vue问题五:element ui组件的开始时间-结束时间验证

    <el-date-picker v-model="seach.before" type="date" placeholder="开始时间&quo ...

  7. Vue + Element UI 实现权限管理系统(工具模块封装)

    封装 axios 模块 封装背景 使用axios发起一个请求是比较简单的事情,但是axios没有进行封装复用,项目越来越大,会引起越来越多的代码冗余,让代码变得越来越难维护.所以我们在这里先对 axi ...

  8. Vue + Element UI 实现权限管理系统 前端篇(三):工具模块封装

    封装 axios 模块 封装背景 使用axios发起一个请求是比较简单的事情,但是axios没有进行封装复用,项目越来越大,会引起越来越多的代码冗余,让代码变得越来越难维护.所以我们在这里先对 axi ...

  9. element-UI el-table二次封装

    Part.1 为什么要二次封装? 这是 Element 网站的 table 示例: <template> <el-table :data="tableData" ...

随机推荐

  1. 学习廖雪峰的Git教程2--远程仓库

    今天跳过之前版本管理,先来学习远程仓库内容: 1.创建ssh(这是为没有ssh key准备的,如果有就可以进行下一步: 敲入 $ ssh-keygen -t rsa -C "youremai ...

  2. Oracle Yum源

    仓库地址(非Yum源仓库):https://public-yum.oracle.com/repo/OracleLinux/OL7/latest/x86_64/index.html 如何连接到 Orac ...

  3. BIO、NIO、AIO 有什么区别?

    BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低.NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 ...

  4. spring cloud 和dubbo区别?

    1.服务调用方式 dubbo是RPC springcloud Rest Api2.注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper3.服务网 ...

  5. Docker 的目的是什么?

    Docker 提供了一个可用于托管任何应用程序的容器环境.在此,软件应用程序和 支持它的依赖项紧密打包在一起. 因此,这个打包的产品被称为 Container,因为它是由 Docker 完成的,所以它 ...

  6. 学习k8s(四)

    1.K8S核心组件 1.Master节点: etcd: 分布式键值对数据库,保存集群状态 api-server: 接受并响应用户的请求 controller: 控制器管理,控制容器的副本数,故障检测 ...

  7. Volcano:在离线作业混部管理平台,实现智能资源管理和作业调度

    摘要:本文结合华为CCE团队在混合部署方面的研究和实战,介绍了混合部署的背景.概念.混部技术的设计方案和实际落地情况,以及对未来的计划和展望. 现代互联网数据中心的规模随着应用服务需求的快速增长而不断 ...

  8. js技术之分割split()

    案例:把所有单词以空格为分割并将首字母转为大写 <!DOCTYPE html><html lang="en"><head> <meta c ...

  9. leetcode_两数之和

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同 ...

  10. python中PIL库的使用

    API参考 打开dos窗口,安装库: pip install pillow 很明显,图片有点大,咱们缩略一下: from PIL import Image im = Image.open(" ...