likes
comments
collection
share

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

作者站长头像
站长
· 阅读数 16

前言

使用Vue3+Vite+TS基于Element plus 二次封装业务组件,注册挂载为全局使用,代码简洁了许多。封装好我们的业务组件今后开发中遇到时,就可以直接使用啦!

一、初始化vite项目

前提,你已经安装好了相关node环境依赖,查看vite的start连接

一般的

  1. 你要先这样
npm create vite@latest
  1. 再这样,
 npm install
 npm run dev
  1. 最后,就可以看到这样

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

  1. vite 默认端口是3000 ,如果要改为熟悉的vue的默认端口8080,可以这样。在 vite.config.ts 这个配置文件中添加server选项
export default defineConfig({
  plugins: [vue()],
  // 添加的部分
  server: {
    port: 8080
  }
})

二、安装Element plus 依赖

  1. 首先要这样,我们进入element plus的官方文档 执行如下安装命令
npm install element-plus --save
  1. 我们再这样在项目中引入,直接全局引入,打包后体积不会相差多少,而且全局引入代码整洁好看。 如果我们都是新手不会操作,那就和我一样看官方文档, 在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 了,来这里复制几个按钮看看样式吧

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点) 没有问题引入🆗

配置一下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, // 是否自动浏览器打开
  }
})

三、调整目录结构,添加布局组件

  1. 先创建 router, styles, utils, views,功能如图

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

  1. 安装路由、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')
  1. 创建组件目录

在components 组件目录下创建base目录来存放基本组件,创建layout来存放布局组件,这里先简单 创建一个navHeader头部组件,和sideBar侧边栏组件,然后index.vue为布局入口文件。

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

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>

四、创建视图组件和路由

我们先创建一个可以伸缩自如的侧边栏和简单的路由,这里还没有设置动态路由,

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

在views 目录下创建三个视图文件,如图,

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

像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文件

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

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里,三个知识点,组件传值propsemits, 事件变量变化的监听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>

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

目前为止总体上是这样

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点) 一个小巧轻便的gif 图片生成工具 使用方法

2、区域选择器组件

万事开头难,有了上面的准备工作,现在我们可以继续封装其他组件了,现在可以直接关注组件本身的内容了,然后导出挂载就行了。

前置条件

这个组件我们需要有省市区的数据,所以首先我们要先获取到这个data,众所周知github是资源比较多的地方,所以这样

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)

省市区的数据获取地址在这

Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点) 我们下载个三级联动的数据json格式带编码的那个下载地址

知识提炼

选择框有情况状态和禁选状态。

  1. 首先引入json省市区数据,三个选择框,
  2. 定义三个v-model双向绑定的变量,
  3. 定义三个List数据列表
  4. 设置三个wacth监听函数
  5. 在最后一个选择框选中后,使用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知识点)

总结

我们使用Vue3+Vite+TS基于Element plus 二次封装业务组件包含了部分vue3的知识,后面还会继续,学习其他组件的封装,如果想一起学习的话就,点个赞哈哈哈。 如果像直接使用,可以到下面地址下载

项目地址

可以点个Star,后面回持续更新其他业务组件