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 库帮忙疾速实现性能

在笔者写过的一篇文章《在CSDN上ctrl + c时自带的版权小尾巴以及“复制代码“,能够怎么实现》,这篇文章中有提到怎么手写复制性能

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

子组件 CopyIcon.vue

<template>    <i :class="`${icon} icon-cursor`"       title="点击复制"       @click="handleCopy($event, text)" /></template><script>// 引入 clipboard.jsimport 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 原生的组件

因为笔者程度无限,对组件都是进行比较简单的封装,并且有些中央设计可能不是很正当,还请多多指教~