标签 element-plus 下的文章

如果你平时老是需要编写各种列表,例如商品列表、图片列表,不妨自己写一个通用的列表组件,只需要传入几个参数,就能帮我们自动分列、自动调整间距。下面就是一个实现起来非常简单,但真的很好用的自定义列表组件。

定义

直接上完整的代码:

<script setup>
const props = defineProps({
    data: {
        type: Array,
        default: []
    },
    columnCount: {
        type: Number,
        default: 2
    },
    gap: {
        type: Number,
        default: 10
    }
});

const emit = defineEmits(['select']);
</script>

<template>
    <div class="list" :style="{ gap: `${gap}px` }">
        <template v-for="i in data">
            <div class="item" :style="{ 'flex-basis': `calc((100% - ${columnCount - 1} * ${gap}px) / ${columnCount})` }" @click="emit('select', i)">
                <slot :row="i"></slot>
            </div>
        </template>
    </div>
</template>

<style lang="scss" scoped>
.list {
    display: flex;
    flex-wrap: wrap;

    >.item {
        flex-shrink: 0;
        flex-grow: 0;
        cursor: pointer;
    }
}
</style>

在 script 部分,这个组件定义了 3 个属性,data是需要传入的数组形式的数据,就像 el-table 的 data 属性。columnCount是列数,可以是任意整数列,默认值为 2,这也是最常用的列数。gap是每一项与其他项的边距。如果你需要实现其他特定,可以非常容易地自己增加其他参数。

模板部分,列表使用了 flex 布局,根据参数自动计算每一列的宽度。还提供了一个默认插槽,用来自定义每一项里面的内容。

使用

使用起来非常简单,假设我们将上面的代码保存到一个叫 CommonMultiColumnList.vue 的文件中,首先引入这个组件:

import CommonMultiColumnList from 'CommonMultiColumnList.vue';

然后在模板中使用:

<CommonMultiColumnList :data="someData" :column-count="1" :gap="15">
    <template #default="{row}">
        <!-- 这里是自定义的每一项的结构 -->
    </template>
</CommonMultiColumnList>

现在只需要编写每一项的结构和样式,列表就会展示出来了。
如果你希望更加方便,也可以在组件中写好每一项的样式,而不是提供插槽,并多建立几个每一项样式不同的组件。


End

本文以封装一个旋转的 Loading 图标为例,讲解如何自定义 Message 动态(这里的“动态”的意思是"会动的")图标。

Element-Plus Message 提供了一个icon属性,用来自定义图标。icon属性接受Component或者String类型的参数。这个时候,就会想到可以引入 Element-Plus 自带的图标组件:

...
import { Loading } from '@element-plus/icons-vue'

ElMessage({
    icon:  Loading,
    ...
});
...

可惜上面这个图标不会动。

Element-Plus 提供了is-loadingCSS Class,只要添加在el-icon组件上,就能够这个组件旋转起来。
那么我们自己封装旋转的el-icon成一个自定义组件,就能够让 Message 的图标动起来了:

//新建组件loading.vue
<script setup>
import { Loading } from '@element-plus/icons-vue';
</script>

<template>
    <el-icon class="is-loading"><Loading /></el-icon>
</template>

使用上面的组件:

//javascript
import MyLoading from 'loading.vue';

ElMessage({
    icon:  MyLoading,
    ...
});

这样就实现了目标,不足就是需要建立一个组件文件。

如果不希望为了实现旋转效果而封装一个自定义组件,还有其他办法可以实现目标。
查看 Message 的源代码(位置在你的项目\node_modules\element-plus\es\utils\vue\icon.mjs),可以发现,Message 的icon属性可以接受 3 种类型:

const iconPropType = definePropType([
  String,
  Object,
  Function
]);

String 接受一个不能有特殊字符的字符串,经过 Vue 的 CreateNode 函数处理,会在页面中生成一个以这个字符串命名的标签,例如:

ElMessage({
    icon:  'MyTag',
    ...
});

会在图标应该出现的位置生成一个自定义 HTML 标签:

<MyTag></MyTag>

但暂时没有找到为它添加属性的方法。如果有明白的同学,欢迎通过站内联系方式告诉我,感谢!

最后,还可以传递 Function 类型。Element Message 文档中提到的icon属性可以是Component,也就是组件。组件可以是一个符合 Vue 组件格式的 JavaScript 对象,也可以是一个函数。前面的文章(戳这里)讲到了函数式组件,这种组件是一个函数。

所以,可以直接创建一个函数,该函数使用渲染函数h返回一个组件,最终代码如下:

import { Loading } from '@element-plus/icons-vue';

export const loading = ElMessage({
    ...
    icon: () => {
        return h(
            'div',
            //给任意 HTML 元素增加下面的两个 Class,都会应用旋转效果
            //这两个 Class 是 Element-Plus 提供的
            //这里这样用仅仅为了方便,也可以自己写动画
            { class: 'el-icon is-loading' },
            [h(Loading)]
        );
    },
    //不自动关闭,通过 Message 实例的 close 方法手动关闭
    duration: 0,
    ...
});

这样,一个会显示加载动画的自定义 Message 就实现了。下面是完整的代码,还顺便封装成了一个更方便的工具:

//新建 msg.js
import { h } from 'vue';
import { ElMessage } from "element-plus";
import { Loading } from "@element-plus/icons-vue";

//可以在全局样式中修改 Message 的样式
const customClass = 'el-message-custom';

const duration = 4000;
function msg(message, type = 'message') {
    if (type === 'loading') {
        return ElMessage({
            icon: () => {
                return h(
                    'div',
                    { class: 'el-icon is-loading' },
                    [h(Loading)]
                );
            },
            message,
            customClass,
            duration: 0,
        });
    }

    ElMessage({ message, type, customClass, duration });
}

export default {
    message: message => {
        msg(message);
    },
    success: message => {
        msg(message, 'success');
    },
    warning: message => {
        msg(message, 'warning');
    },
    error: message => {
        msg(message, 'error');
    },
    loading: message => {
        return msg(message, 'loading');
    }
};

使用:

import msg from 'msg.js'

const loading = msg.loading('提交中,请稍候');
setTimeout(()=>{
    loading.close();
    msg.success('提交成功');
}, 3000)

End

在 Element plus 中,除了对整个表单进行验证,还可以对单独的表单项进行验证。
查看 Form 文档,在 FormItem Exposes 节,可以看到 FormItem 提供了validate方法。该方法的使用方式与 Formrefvalidate方法基本相同,不同的就是需要获取 FormItemref

//javascript
const mobileRef = ref(null);

//template
...
<el-form-item prop="mobile" ref="mobileRef">...</el-form-item> 
...

接着,就能够调用该 ref 的 validate方法了:

//javascript
const submit = async () => {
    const result = await mobileRef.value.validate();
}

validate方法返回一个Promise,如果验证通过,上面代码中的result将会等于true。如果验证不通过,相应的表单将会显示在el-form上设置的rules中提供的报错信息。

End

UPDATE 2023-05-04

某些情况下,css 属性inset 不被支持,造成本文介绍的方法失效。此时只要用 lefttop 加上宽高替换 inset 就好了。下面的代码已经修改。


Element Plus 的 Table 组件文档写道

只要在 el-table 元素中定义了 height 属性,即可实现固定表头的表格,而不需要额外的代码。

可是有几个场景能知道 Table 的固定高度?所以文档中的方法不太可行。

尝试使用 :deep() 深度选择器改变 el-table 的样式,使它形成 overflow-y: auto 的区域,就会触发一个非常奇怪的行为:el-table 的宽度会一直慢慢变大!因为看起来是脚本控制的,也就没有精力深究。

最后还是在 CSDN 上找到的办法,使用绝对定位。我的实践,下面的代码是最方便的:

HTML:

<div class="box">
    <div class="menu">菜单之类的顶部区域,不需要可以去掉</div>
    <div class="table-box">
        <el-table class="el-table"></el-table>
    </div>
</div>

CSS:

.box {
    display: flex;
    flex-direction: column;
}

.menu {
    flex: none;
    height: 100px;
}

.table-box {
    flex: auto;
    overflow: hidden;
    position: relative;
}

.el-table {
    position: absolute;
    - inset: 0;
    + left: 0;
    + top: 0;
    + width: 100%;
    + height: 100%;
}

现在,el-table 就会在固定表头的同时,使用内置的 el-scrollbar 来滚动表格内容了。

End

element-plus 的图标其实是一个个的组件:

JavaScript:

import { IconName, IconName2 } from '@element-plus/icons-vue';

Template:

<el-icon>
    <IconName/>
</el-icon>

使用 :is 切换组件:

<component :is="bool ? IconName : IconName2"></component>

然而 :is 的值必须是引入的组件,不能是字符串,否则就会渲染成这个样子:

<iconname></iconname>

如果菜单结构单独放在一个文件中,比如 menu.js

export default [
    {
        name: 'Menu 1',
        icon: 'IconName'
    }
]

这里的 icon的值就不能是字符串,需要是一个组件对象的引用。这就需要先在 menu.js 中引入图标组件:

import { IconName } from '@element-plus/icons-vue';

然后将组件传给 icon

export default [
    {
        name: 'Menu 1',
        icon: IconName
    }
]

这样,就能够在组件中正确显示图标了。当然,这时候组件中就不需要再引入图标了。


End