创建 Vite 项目模板选择 vue-ts
npm create vite@latest组件分成基础组件、表单组件、数据组件、通知组件、导航组件和其他组件几个类型,这些类型基本覆盖了组件库的适用场景,项目中的业务组件也是由这些类型组件拼接而来的
设计基础的布局组件,这个组件相对是比较简单的。共有 container、header、footer、aside、main 五个组件,这个组合可以很方便地实现常见的页面布局
在 src/components 目录下新建 container/Container.vue 布局组件没有交互逻辑,只需要通过 flex 布局就可以实现
在 element-plus 中所有的样式前缀都是 el 开头,每次都重复书写维护太困难,设计之初就需要涉及 Sass 的 Mixin 来提高书写 CSS 的代码效率
在 src/styles/mixin.scss 定义了 namespace 变量为 xy,使用 Mixin 注册一个可以重复使用的模块 b,可以通过传进来的 block 生成新的变量 $B,并且变量会渲染在 class 上,并且注册了 when 可以新增 class 选择器,实现多个 class 的样式
// bem
$namespace: 'xy';
$state-prefix: 'is-';
@mixin b($block) {
$B: $namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
// 添加 is- 前缀等状态类
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}<style lang="scss">
@import '../styles/mixin';
@include b(container) {
display: flex;
flex-direction: row;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
@include when(vertical) {
flex-direction: column;
}
}
</style>使用 b(container) 生成 .xy-container 样式类,在内部使用 when(vertical) 生成 .xy-container.is-vertical 样式类,去修改 flex 的布局方向
.xy-container {
display: flex;
flex-direction: row;
flex: 1;
flex-basis: auto;
box-sizing: border-box;
min-width: 0;
}
.xy-container.is-vertical {
flex-direction: column;
}container 组件如果内部没有 header 或者 footer 组件,就是横向布局,否则就是垂直布局。根据上面的 CSS 代码可以知道,只需要新增 is-vertical 这个 class,就可以实现垂直布局
因为开发组件库的时候要确保每个组件都有自己的名字,script setup 中没法返回组件的名字,需要一个单独的标签,使用 options 语法设置组件的 name 属性
<template>
<section class="xy-container" :class="{ 'is-vertical': isVertical }">
<slot />
</section>
</template>
<script lang="ts">
export default{
name:'XyContainer'
}
</script>
<script setup lang="ts">
import { useSlots, computed, type VNode, type Component } from 'vue'
interface Props {
direction?:string
}
const props = defineProps<Props>()
const slots = useSlots()
const isVertical = computed(() => {
if (slots && slots.default) {
return slots.default().some((vn:VNode) => {
const tag = (vn.type as Component).name
return tag === 'XyHeader' || tag === 'XyFooter'
})
} else {
return props.direction === 'vertical'
}
})
</script>这样 container 组件就实现,其他四个组件实现起来大同小异。以 header 组件举例,在 container 目录下新建 Header.vue。
template 中渲染 xy-header 样式类,通过 defineProps 定义传入的属性 height,并且通过 withDefaults 设置 height 的默认值为 60px,通过 b(header) 的方式实现样式,用到的变量都在 style/mixin 中注册,方便多个组件之间的变量共享
<template>
<header class="xy-header" :style="{ height: props.height }">
<slot />
</header>
</template>
<script lang="ts">
export default{
name:'XyHeader'
}
</script>
<script setup lang="ts">
import {withDefaults} from 'vue'
interface Props {
height?:string
}
const props = withDefaults(defineProps<Props>(),{
height:"60px"
})
</script>
<style lang="scss">
@import '../styles/mixin';
@include b(header) {
padding: $--header-padding;
box-sizing: border-box;
flex-shrink: 0;
}
</style>组件注册完毕之后,在 src/App.vue 中使用 import 语法导入后就可以直接使用了。但是这里有一个问题,组件库最后会有很多组件对外暴露,用户每次都 import 的话确实太辛苦了,所以还需要使用插件机制对外暴露安装的接口,在 container 目录下新建 index.ts
对外暴露了一个对象,对象的 install 方法中使用 app.component 注册这五个组件
import { App } from "vue"
import XyContainer from "./Container.vue"
import XyHeader from "./Header.vue"
import XyFooter from "./Footer.vue"
import XyAside from "./Aside.vue"
import XyMain from "./Main.vue"
export default {
install(app: App) {
app.component(XyContainer.name!, XyContainer)
app.component(XyHeader.name!, XyHeader)
app.component(XyFooter.name!, XyFooter)
app.component(XyAside.name!, XyAside)
app.component(XyMain.name!, XyMain)
}
}在 src/main.ts 文件中使用 app.use(ElContainer) 的方式注册全部布局组件,这样在项目内部就可以全局使用 container、header 等五个组件。实际的组件库开发过程中,每个组件都会提供一个 install 方法,可以很方便地根据项目的需求按需加载
import ElContainer from './Container.vue'
/* istanbul ignore next */
ElContainer.install = function (app) {
app.component(ElContainer.name, ElContainer)
}
export default ElContainermain.js 中安装组件
import { createApp } from 'vue'
import App from './App.vue'
import ElContainer from './components/container'
const app = createApp(App)
app.use(ElContainer).use(ElButton).mount('#app')在 src/App.vue 的代码中使用组件嵌套的方式就可以实现下面的页面布局
<template>
<xy-container>
<xy-header>Header</xy-header>
<xy-main>Main</xy-main>
<xy-footer>Footer</xy-footer>
</xy-container>
<hr />
<xy-container>
<xy-header>Header</xy-header>
<xy-container>
<xy-aside width="200px">Aside</xy-aside>
<xy-main>Main</xy-main>
</xy-container>
</xy-container>
<hr />
<xy-container>
<xy-aside width="200px">Aside</xy-aside>
<xy-container>
<xy-header>Header</xy-header>
<xy-main>Main</xy-main>
<xy-footer>Footer</xy-footer>
</xy-container>
</xy-container>
</template>
<script setup lang="ts"></script>
<style>
body {
width: 1000px;
margin: 10px auto;
}
</style>