Element-plus组件二次封装项目实现过程
本文目录:
- 1.项目准备
- 2.伸缩菜单组件
- 3.图标选择器组件
- 4.表单组件
- 5.弹窗表单组件
- 6.表格组件
- 7.日历组件
- 8.打包构建与发布npm
项目持续更新中,github:https://github.com/chenhui-syz/rc-element-plus-components.git
1.项目准备
1.1.新建vite项目与依赖安装
https://vitejs.cn/guide/#scaffolding-your-first-vite-project
升级一下npm版本到最新版本
npm install -g npm
因为我们打算搞的是ts版本的vue,所以将vue改为vue-ts
npm init vite@latest my-vue-app -- --template vue-ts
npm i安装项目依赖,然后安装vue-router和element-plus和scss
npm i -S vue-router@next element-plus
新建router文件夹和其下的index.ts,新建views文件夹新建一个home.vue,重构下app.vue代码,让项目启动下可以正常访问‘/’路由。
<template>
<router-view></router-view>
</template>
<script lang="ts" setup></script>
<style lang="scss">
@import './styles/ui.scss';
@import './styles/base.scss';
</style>
main.ts中
引入element-plus
npm i -D sass sass-loader
1.2.全局注册图标
先安装npm install @element-plus/icons,然后main.ts全局引用和注册
组件默认是驼峰命名法,写一个工具函数去进行转换
全局注册了之后就可以直接在页面通过<el-icon-edit></el-icon-edit>这种形式去使用icon图标了
element-plus的字体图标的本质是svg标签,给全局svg设置默认的宽高
svg {
width: 1em;
height: 1em;
}
2.伸缩菜单组件
示例图.png2.1.伸缩菜单布局
用到容器组件container和菜单组件menu,让容器组件成为根路由的页面
让元素实现宽度自适应变化的小技巧 <el-aside>组件的width属性设置为auto
另外要添加上官网给的样式,固定非收缩状态下的宽度
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
}
2.2.defineEmits快速改变props传递的值
vue3的语法糖defineEmits一定要注意改变父组件传递过来的值,传递方式要是v-model进行传递,这样才能快速的动态改变
3.图标选择器组件
示例图.png3.1.弹窗显示控制快速修改
子组件首先拷贝父组件传递过来的visible,拷贝一份的意义就是我们不希望子组件直接去使用父组件传递过来的数据,并直接产生修改
let dialogVisible = ref<boolean>(props.visible);
父组件传递:
v-model:visible="visible"
子组件先接收:
let props = defineProps<{ visible: boolean }>();
定义改变这个变量的emits:
let emits = defineEmits(["update:visible"]);
子组件实现在自己组件内部就快速实现父组件传递过来的变量:
emits("update:visible", !props.visible);
3.2.监听visible变化
第一个watch:父组件传递过来的visible改变的时候,弹窗的控制dialogVisible也要对应的进行改变
第二个watch:弹窗的显示控制dialogVisible其实是已经改变的了,需要通过emits把父组件对应的visible给改变掉
watch(
() => props.visible,
(val) => {
dialogVisible.value = val;
}
);
watch(
() => dialogVisible.value,
(val) => {
emits("update:visible", val);
}
);
3.3.显示图标与自定义复制文本到粘贴板的hooks
显示所有利用vue3新提供的动态组件component,is定义标签,可以快速生成对应的标签
<component :is="`el-icon-${toLine(item)}`"></component>
当用户点击图标的时候,将点击的图标名字复制到粘贴板,自定义一个useCope的hooks
import { ElMessage } from "element-plus";
// 复制文本
export const useCopy = (text: string) => {
// 创建输入框
let input = document.createElement("input");
// 给输入框赋值
input.value = text;
// 追加到body里面去
document.body.appendChild(input);
// 选择输入框的操作
input.select();
// 执行复制的操作
document.execCommand("Copy");
// 删除掉input元素
document.body.removeChild(input);
// 提示用户
ElMessage.success("复制成功");
};
4.表单组件
示例图.png页面调用封装好的表单组件,传递options参数即可快速生成表单。
4.1.带有children组件的处理
将不带有children和有children组件分开进行渲染,比如单选框组、下拉选框、复选框组,循环children,利用动态组件component生成表单
4.2.上传组件
像action,multiple这类组件属性,通过v-bind快速注入到el-upload组件中,另外像组件的所有方法也应该都统一暴漏出去
<el-upload
v-if="item.type === 'upload'"
v-bind="item.uploadAttrs"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
:on-error="onError"
:on-progress="onProgress"
:on-change="onChange"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
:http-request="httpRequest"
:on-exceed="onExceed"
>
// 把上传组件的时间全部发出去给父组件处理
let emits = defineEmits([
"on-preview",
"on-remove",
"on-success",
"on-error",
"on-progress",
"on-change",
"before-upload",
"before-remove",
"on-exceed",
]);
// 以上传资料成功的onSuccess为例
let onSuccess = (response: File, file: File, fileList: FileList) => {
// 上传图片成功,给表单上传项赋值
let uploadItem = props.options!.find((items) => items.type === "upload");
// 将返回数据统一先都怼进model对应的上传数据中
model.value[uploadItem.prop!] = { response, file, fileList };
emits("on-success", { response, file, fileList });
};
上传组件自带两个slot,如uploadArea,页面在调用表单组建的时候自定义传入slot内容
<slot name="uploadArea"></slot>
// 页面调用表单组件
<rc-form>
<template #uploadArea>
<el-button size="default" type="primary">Click to upload</el-button>
</template>
</rc-form>
4.3. 富文本组件
富文本组件借助适配vue的wangEditor,在初始化表单的时候,需要手动初始表单内容,并设置onchange事件,实现富文本组件内容的双向绑定
// 初始化富文本
if (item.type === "editor") {
const editor = new E("#editor");
// 加个感叹号,表示该属性一定是符合条件的
editor.config.placeholder = item.placeholder!;
editor.create();
// 初始化富文本的内容
editor.txt.html(item.value);
// 动态获取富文本的输入内容
editor.config.onchange = (newValue: string) => {
// console.log(newValue);
model.value[item.prop!] = newValue;
};
// 记录editor,这样在别的地方就可以使用
edit.value = editor;
}
在重置表单的时候也需要手动重置富文本表单
// 获取富文本的配置项
if (props.options && props.options.length) {
let editorItem = props.options.find((item) => item.type === "editor")!;
edit.value.txt.html(editorItem.value);
}
4.4.提交与重置表单
定义命名slot,利用作用域插槽将表单实例form,表单值model 发送给父组件的插槽进行使用
<slot name="action" :form="form" :model="model"></slot>
页面调用组件进行提交时,直接调用表单原生校验就行了,但是重置事件需要麻烦点,因为富文本表单需要手动清空内容
<!-- 这里拿到作用域插槽传递过来的表单实例form -->
<!-- 这里拿到作用域插槽传递过来的表单的值model -->
<!-- 默认为scope -->
<template #action="scope">
<el-button type="primary" @click="submitForm(scope)">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</template>
这里需要注意的是vue2可以通过ref拿到组件实例,并且调用组件上的属性和方法,但是vue3要使用defineExpose分发出去才能使用,defineExposevue3对组件的优化和保护,如果不通过defineExpose进行暴露,则通过ref拿到的组件实例是空。
// 重置表单的方法
// 为了能够自动一键清空自定义的上传组件和富文本组件的值
let resetFields = () => {
// 重置element-plus表单
form.value!.resetFields();
// 重置富文本编辑器的内容
// 获取富文本的配置项
if (props.options && props.options.length) {
let editorItem = props.options.find((item) => item.type === "editor")!;
edit.value.txt.html(editorItem.value);
}
};
// 表单验证,包装一下,用于分发出去
let validate = () => {
return form.value!.validate;
};
defineExpose({
resetFields,
validate,
});
5.弹窗表单组件
示例图.png弹窗表单组件是弹窗组件和表单组件的结合,因为在日常开发使用频率高,所以进行一下单独的封装。
像上传组件的slot以及弹窗底部操作栏,在弹窗表单组件都是通过template包裹slot组件,实现组件的二次传递
<template #footer>
<!-- 把表单组件form利用作用域插槽传递给底部操作栏 -->
<slot name="footer" :form="form"></slot>
</template>
在form表单组件中,封装并暴漏了一个getFormDate方法,用于外部获取表单数据,model就是当前表单的数据,注意一定不要把这个数据分发出去,而是分发一个获得表单数据的方法,直接分发model,只会拿到表单的初始值,而不是最新的
let getFormDate = ()=>{
return model.value
}
defineExpose({
getFormDate
});
6.表格组件
示例图.png在封装表格的时候,原生el-table上写v-bind="$attrs"
可以默认为表格注入element-plus表格原有提供的所有属性(没在props进行定义的),比如stripe属性,默认为false,代表没有没有斑马纹,如果在页面调用组件添加上这个属性<rc-table :stripe="false"></rc-table>
,那么el-table就会显示有斑马纹。
6.1.自定义操作栏
自定义操作栏利用命名插槽的方式,页面在调用组件的时候进行单独定义
<el-table-column
v-else
:label="item.label"
:prop="item.prop"
:align="item.align"
:width="item.width"
>
<!-- 首先利用表格自带的插槽拿到当前列的数据scope -->
<template #default="scope">
<slot :name="item.slot" :scope="scope"></slot>
</template>
</el-table-column>
// 页面自定义内容
<template #name="{ scope }">
这里就可以使用scope的内容了,如{{ scope.row.name }}<
</template>
操作栏也大致是这个思路,但是操作栏的那一列数据在初始化表格的时候就给单独挑出来
let actionOptions = computed(() => props.options!.find((item) => item.action));
7.日历组件
示例图.pngelement-plus提供的日历插件,功能太单一,这里我们选用支持vue3和ts的环境中使用,功能更完善的日历插件fullCalendar
npm i -S @fullcalendar/core @fullcalendar/interaction @fullcalendar/daygrid
7.1.新增组件的流程
如calendar
1.components文件夹中新增calendar文件夹,src+index.vue,index.ts
2.views文件夹下新增calendar文件夹,index.vue
3.router下新增对应的路由
4.components文件夹下的index.ts文件中将组件进行汇总引用
vite使用fullCalendar会有这个报错
vdom.js:3 Uncaught Error: Please import the top-level fullcalendar lib before attempting to import a plugin.
解决办法:
import '@fullcalendar/core/vdom'
引入vdom文件到对应的页面文件中
这个组件的封装和element-plus没什么关系,算是对element-plus的一种补充。
8.打包构建与发布npm
根目录下新建command文件夹,并新建build.js文件,作为打包的配置文件,在项目package.json中配置lib脚本,打包的时候就是运行这个build文件。
build构建思路:
1.定义打包入口文件夹packages和出口文件夹lib
packages文件为打包入口文件夹,将components文件夹下的组件代码手动复制进这个文件夹中,另外hooks,style,utils三个文件夹都复制进packages文件夹中,packages文件夹中的代码关于hooks和utils文件的引用路径记得改一下,否则打包之后会报错,lib文件夹不需要手动建,一会打包的时候会自动生成
2.设置vite基础配置和rollup配置,并写入全量打包构建方法buildAll和单组件打包构建方法buildSingle
3.给每个组件单独生成package.json,记得输出定义好的文件时,需要调用到node的fs模块读取文件夹,在js环境中使用node模块,需要借助fs-extra插件npm i -D fs-extra
4.循环构建,打包成库
5.配置好脚本后因为项目是用的ts,所以还要在packages文件夹新增个ts的类型声明vue.d.ts
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
在lib文件夹下也要新增index.d.ts,因为组件支持按需引入,所以index.d.ts文件写好后需要复制到lib中每个组件文件夹里面一份,index.d.ts文件内容是统一复制下面这个内容就行
import { App } from "vue";
declare const _dafult: {
install(app: App): void;
};
export default _dafult;
6.lib上新建的package.json文件,是进行发布的配置文件,写好配置文件之后,接下来就是lib文件上继续发布操作npm publish,注意npm源一定要改成官方源,包的name也不能重复,否则会导致发布失败。
如何更新已经发布的组件库
1.packages文件夹中的入口文件index.ts进行引入图标组件和工具函数
2.把lib文件夹的package.json+index.d.ts复制出来到npm_backup文件夹,因为这个文件是我们为了发布npm而手写的配置文件,不是打包自动生成的
3.把那两个文件复制出来之后,就可以npm run lib重新打包了,打包后再把两个文件复制进去,打包后生产的utils可以手动再删掉
4.把index.d.ts再手动复制到每个组件的文件夹中
5.手动修改package.json的版本号,然后cd到lib文件夹中执行npm publish更新组件库代码