Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)
前言
使用Vue3+Vite+TS基于Element plus 二次封装业务组件,注册挂载为全局使用,代码简洁了许多。封装好我们的业务组件今后开发中遇到时,就可以直接使用啦!
一、初始化vite项目
前提,你已经安装好了相关node环境依赖,查看vite的start连接
一般的
- 你要先这样
npm create vite@latest
- 再这样,
npm install
npm run dev
- 最后,就可以看到这样
- vite 默认端口是3000 ,如果要改为熟悉的vue的默认端口8080,可以这样。在
vite.config.ts
这个配置文件中添加server选项
export default defineConfig({
plugins: [vue()],
// 添加的部分
server: {
port: 8080
}
})
二、安装Element plus 依赖
- 首先要这样,我们进入element plus的官方文档 执行如下安装命令
npm install element-plus --save
- 我们再这样在项目中引入,直接全局引入,打包后体积不会相差多少,而且全局引入代码整洁好看。 如果我们都是新手不会操作,那就和我一样看官方文档, 在main.ts 文件中引入,像这样
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus' // (1)
import 'element-plus/dist/index.css' // (2)
const app = createApp(App)
app.use(ElementPlus) //(3)
app.mount('#app')
是不是全局引入简单,三句代码就可以使用Element plus UI 了,来这里复制几个按钮看看样式吧
没有问题引入🆗
配置一下vite.config.ts
文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 8080, // 自定义端口号,一般3000以后
open: true, // 是否自动浏览器打开
}
})
三、调整目录结构,添加布局组件
- 先创建
router
,styles
,utils
,views
,功能如图
- 安装路由、sass、lodash 、等npm包和库,引入依赖包
npm i vue-router
npm i sass sass-loader -D
npm i lodash
element plus 的图标
由于 element plus 的图标要单独为组件形式使用,官方文档说要单独安装
npm install @element-plus/icons-vue
然后再main.ts中引入,遍历循环全局注册图标,牺牲一点点性能,
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as Icons from '@element-plus/icons-vue' // (1)引入
const app = createApp(App)
// 遍历注册全局icon
for(let icon in Icons) {
app.component(icon, (Icons as any)[icon]) // (2) 全局注册
}
app.use(router).use(ElementPlus)
app.mount('#app')
- 创建组件目录
在components 组件目录下创建base
目录来存放基本组件,创建layout
来存放布局组件,这里先简单 创建一个navHeader
头部组件,和sideBar
侧边栏组件,然后index.vue
为布局入口文件。
index.vue的代码, 使用el-container容器布局组件
<template>
<el-container>
<el-aside width="auto">
<side-bar :collapse="isCollapse"></side-bar>
</el-aside>
<el-container>
<el-header>
<nav-header v-model:collapse="isCollapse"></nav-header>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import SideBar from "./sideBar.vue";
import NavHeader from './navHeader.vue';
const isCollapse = ref(false)
</script>
<style lang="scss" scoped>
.el-header {
background-color: pink;
padding: 0;
border-bottom: 1px solid #eee;
opacity: 0.8;
}
</style>
sideBar.vue的代码,现在还没有使用动态路由,关注router
属性, 使用的el-menu菜单组件
<template>
<div class="sideBar">
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="1"
text-color="#fff"
:collapse="collapse"
router
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="/">
<el-icon><location /></el-icon>
<span>首 页</span>
</el-menu-item>
<el-menu-item index="/about">
<el-icon><document /></el-icon>
<span>关 于</span>
</el-menu-item>
<el-menu-item index="/setting">
<el-icon><setting /></el-icon>
<span>设 置</span>
</el-menu-item>
</el-menu>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
let props = defineProps<{
collapse: boolean
}>()
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath);
};
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath);
};
</script>
<style lang="scss" scoped>
.sideBar {
height: 100%;
background-color: #545c64;
width: 100%
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>
navHeader的代码, 头部还只用了展开,收起的图标,
<template>
<div class="header">
<span @click="toggle">
<div v-if="collapse"><el-icon><expand /></el-icon></div>
<div v-else><el-icon><fold /></el-icon></div>
</span>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue'
let props = defineProps<{
collapse: boolean
}>()
let emits = defineEmits(['update:collapse'])
let toggle = () => {
emits('update:collapse', !props.collapse)
}
</script>
<style lang="scss" scoped>
.header{
height: 60px;
display: flex;
align-items: center;
padding: 0 20px;
}
</style>
Vue3 知识点- setup
相信你了解过setup函数的写法,类似下面这样,每定义一个变量和方法都需要同setup()函数return出去,才能在上面的template模板中访问到.
<template>
<div>{{hhh}}</div>
</template>
<script>
export default {
setup(){
const hhh = ref('张三')
const sayHi = () => {
console.log('Hi);
}
return {
hhh,
sayHi
}
}
}
</script>
现在直接在<script>
标签中使用setup 关键字,这是vue3.2
出现的语法糖
<script setup></script>
像这样,就简洁多了。定义完直接可以在template模板视图中使用变量和方法。
<template>
<div>{{hhh}}</div>
</template>
<script setup>
const hhh = ref('张三')
const sayHi = () => {
console.log('Hi);
}
</script>
Vue3 知识点-组件传值
组件中涉及的传值关系主要是父子组件传值,定义和简单使用如下
父-->子 使用props
子-->父 使用emits
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue'
let props = defineProps<{
collapse: boolean
}>()
let emits = defineEmits(['update:collapse'])
// 子组件传值给父组件,通常需要触发的方法函数,像下面这个
let toggle = () => {
emits('update:collapse', !props.collapse)
}
</script>
四、创建视图组件和路由
我们先创建一个可以伸缩自如的侧边栏和简单的路由,这里还没有设置动态路由,
在views 目录下创建三个视图文件,如图,
像Home.vue 页面,先简单显示home
<template>
<div>home</div>
</template>
<script lang="ts">
import { defineEmits } from 'vue'
</script>
<style lang="scss" scoped> </style>
有了视图和布局组件可以添加路由了,在router目录下,创建index.ts文件,代码可以像这样
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";
import Layout from "../components/layout/index.vue";
const routes: RouteRecordRaw[] = [
{
path: "/",
component: Layout,
children: [
{
path: "/",
component: Home,
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
{
path: '/setting',
name: 'Setting',
component: () => import('../views/Setting.vue')
},
],
},
];
const router = createRouter({
routes,
history: createWebHistory(),
});
export default router;
如果你是一步一步操作了,看看能不能伸缩自如吧。准备工作已经可以了,下面开始组件的封装,其实,布局的时候已经有简单的封装了一下侧边栏和头部组件,下面我们封装一下业务组件。
五、业务组件封装
1、图标选择器组件
前面我们 在components 目录下创建了base 目录,专门来放我们基础业务组件,base下有一个index.ts文件是来统一导出和注册的。组下的目录结果比较统一,以chooseIcon组件为例,它下面有src 目录和index.ts文件
,src目录下有index.vue文件
index.vue
知识点提炼
引入了@element-plus/icons-vue
图标和element-plus
的消息提示组件ElMessage
import * as Icons from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus'
1、template
上两个知识点,slot
插槽和<component :is = item></component>
动态组件渲染。
2、script
里,三个知识点,组件传值props
、emits
, 事件变量变化的监听watch
,文本点击复制的方法useCopy
。
3、style
主要使用flex
布局
select()选中文本的方法 execCommand()方法的示例
useCopy
方法的思路,创建一个input元素,给它的value
属性赋值为获取到的text
,然后把节点添加到body下面,选择元素的文本,复制到剪贴板,然后从body中移除这个元素。
<template>
<div>
<el-button @click="handleClick" type="primary">
<slot></slot>
</el-button>
</div>
<div>
<el-dialog :title="title" v-model="dialogVisible">
<div class="container">
<div class="item"
v-for="(item, index) in Object.keys(Icons)"
:key="index"
@click="clickItem(item)"
>
<div class="text">
<component :is=item></component>
</div>
<div class="icon">{{ item }}</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { watch, ref, defineProps, defineEmits } from 'vue'
import * as Icons from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus'
let props = defineProps<{
title: string, // 弹框标题
visible: boolean, // 控制弹出框的显示与隐藏
}>()
let emits = defineEmits(['update:visible'])
let dialogVisible = ref<boolean>(props.visible)
let handleClick = () => {
emits('update:visible', !props.visible)
}
// (1)获取文本的方法
const useCopy = (text: string) => {
let input = document.createElement('input')
input.value = text
document.body.appendChild(input)
input.select() // 选中文本
document.execCommand('Copy')
document.body.removeChild(input)
ElMessage.success('复制成功')
}
// (2)点击复制文本
let clickItem = (item: string) => {
let text = `<${item}/>`
useCopy(text)
dialogVisible.value = false
}
watch(() => props.visible, (val:boolean) => {
dialogVisible.value = val
})
watch(() => dialogVisible.value, (val: boolean) => {
emits('update:visible', val)
})
</script>
<style lang="scss" scoped>
.container{
display: flex;
flex-wrap: wrap;
align-items: center;
.item{
width: 25%;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
cursor: pointer;
margin-bottom: 20px;
.text {
font-size: 14px;
svg {
height: 2em;
width: 2em;
}
}
.icon {
flex: 1;
}
}
}
</style>
挂载为全局组件
在chooseIcon 目录下的index.ts
文件中添加如下代码,这样让这个组件可以通过use的形式使用
import { App } from 'vue'
import chooseIcon from './src/index.vue';
export default {
install(app: App) {
app.component('w-choose-icon',chooseIcon)
}
}
然后我们在base 目录下的index.ts
文件里统一导出
import {App} from 'vue'
import chooseIcon from './chooseIcon'
// 后面添加的组件就可以逐个添加进入这个数组里了
const components =[
chooseIcon,
]
export default {
install(app: App) {
components.map(item => {
app.use(item)
})
}
}
好了,现在可以去外面的main.ts 文件进行全局注册了
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as Icons from '@element-plus/icons-vue'
import WComponents from './components/base'; // (1) 这里导入
const app = createApp(App)
// 遍历注册全局icon
for(let icon in Icons) {
app.component(icon, (Icons as any)[icon])
}
app.use(router).use(ElementPlus).use(WComponents) // (2)然后在app实例中使用use()链式挂载注册。
app.mount('#app')
效果截图
经过组件的全局注册挂载,,我们可以直接在视图组件中使用了,像这样直接使用
<div>
home
<w-choose-icon title="图标按钮" v-model:visible="isVisible">选择图标 </w-choose-icon>
粘贴到这里试一下<el-input v-model='test'></el-input>
</div>
目前为止总体上是这样
2、区域选择器组件
万事开头难,有了上面的准备工作,现在我们可以继续封装其他组件了,现在可以直接关注组件本身的内容了,然后导出挂载就行了。
前置条件
这个组件我们需要有省市区的数据,所以首先我们要先获取到这个data,众所周知github
是资源比较多的地方,所以这样
知识提炼
选择框有情况状态和禁选状态。
- 首先引入
json
省市区数据,三个选择框, - 定义三个
v-model
双向绑定的变量, - 定义三个
List
数据列表 - 设置三个
wacth
监听函数 - 在最后一个选择框选中后,使用
emits('change',{a,b,c})
将数据传回给父组件
<template>
<div>
<el-select clearable placeholder="请选择省份" v-model="province">
<el-option
v-for="item in provincesList"
:key="item.code"
:value="item.code"
:label="item.name"
></el-option>
</el-select>
<el-select
clearable
placeholder="请选择城市"
v-model="city"
:disabled="!province"
>
<el-option
v-for="item in citiesList"
:key="item.code"
:value="item.code"
:label="item.name"
></el-option>
</el-select>
<el-select clearable placeholder="请选择区域" v-model="area" :disabled='!province || !city'>
<el-option
v-for="item in areasList"
:key="item.code"
:value='item.code'
:label='item.name'
></el-option>
</el-select>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, defineEmits } from "vue";
import data from "../lib/pac-code.json";
export interface DataItem {
code: string;
name: string;
children?: DataItem[];
}
export interface ChangeData {
code: string;
name: string;
}
let province = ref<string>("");
let city = ref<string>("");
let area = ref<string>("");
let provincesList = ref(data);
let citiesList = ref<DataItem[]>([]);
let areasList = ref<DataItem[]>([]);
let emits = defineEmits(['change'])
watch(() => province.value, (val: string) => {
if (val) {
citiesList.value = provincesList.value.find((item: any) => item.code === province.value)!.children!;
}
city.value = "";
area.value = "";
});
watch(() => city.value, (val: string) => {
if(val) {
areasList.value = citiesList.value.find((item: any) => item.code === city.value)!.children!;
}
area.value = ''
});
watch(() => area.value, (val: string) => {
if(val) {
let provinceData: ChangeData = {
code: province.value,
name: province.value && data.find((item:any) => item.code === province.value)!.name
}
let cityData: ChangeData = {
code: city.value,
name: city.value && citiesList.value.find((item: any) => item.code === city.value)!.name
}
let areaData: ChangeData = {
code: val,
name: val && areasList.value.find((item: any) => item.code === val)!.name
}
emits('change', {
province: provinceData,
city: cityData,
area: areaData
})
}
})
</script>
a!.b!的用法解释
!
用在变量前表示取反- 用在赋值的内容后时,使null和undefined类型可以赋值给其他类型并通过编译,表示该变量值可空 使用TypeScript 也要定义接口,
效果动图
具体见效果图
总结
我们使用Vue3+Vite+TS基于Element plus 二次封装业务组件包含了部分vue3的知识,后面还会继续,学习其他组件的封装,如果想一起学习的话就,点个赞哈哈哈。 如果像直接使用,可以到下面地址下载
项目地址
可以点个Star
,后面回持续更新其他业务组件
转载自:https://juejin.cn/post/7090917855524782088