前言
众所周知,使用组件库进行开发无疑能帮我们节省开发精力,提高开发效率。目前开源的组件库也有很多,那你知道它们怎么被打造出来的吗?接下来我们试着打造一下ElementUI的一些简单组件吧!
准备
-
搭建环境:
yarn create vite myelementui --template vue-ts
-
初始化:
yarn
下载依赖 -
创建公共样式:在styles文件夹下用
scss
写入公共的样式
一、打造Container 布局容器
container容器
思路:
- 创建一个总体容器,里面可以放其他标签(用slot插槽)
- 给默认类名,也就给了该容器默认样式
- 设置该容器内的内容默认排列方向,也能接收给此组件传的排列方向的值
- 在component目录下创建
container
文件夹,并在此文件夹下创建对应组件文件
- 编写
Container.vue
文件
xml
<template> //section 标签h5自带
<section class="el-container" :class="{'is-vertical':isVertical}">//动态绑定类名 设置排列方向ops>(),{ vue自带的有默认值
<slot></slot> //放插槽 就可以在里面写文字了
</section>
</template>
<script lang="ts">
export default { //定义组件的名字 抛出给别人用的
name:'ElContainer'
}
</script>
<script setup lang="ts">
import {computed,useSlots,VNode,Component} from 'vue'
const slots=useSlots() //可以获取插槽slot内用到的标签
//用泛型限定传入的值类型
interface Props{
direction?:string //此属性可存在可不存在
}
const props=defineProps<Props>() //接收父组件传的值
//根据isVertical来设置布局方向
const isVertical=computed(()=>{//父组件传入的属性
if(slots&&slots.default){
return slots.default().some((vn:VNode)=>{
const tag=(vn.type as Component).name //断言成组件类型
return tag==='ElHeader'||tag==='ElFooter' //设置只有里面的组件名为ElHeader 和ElFooter 的布局方向是纵轴方向
})
}else if(props.direction==='vertical'){ //如果在用到组件时直接在组件上写明方向为vertical
return true
}else{
return false
}
})
</script>
<style lang="scss" scoped>
@import '../../styles/mixin.scss';
@include b(container) {
display: flex;
flex-direction: row; //默认弹性主轴方向为横轴
flex:1;
flex-basis: auto;
box-sizing: content-box;
@include when(vertical) { //有is-vertical的类名 则设置弹性主轴方向为纵轴
flex-direction: column;
}
}
</style>
- 编写
index.ts
文件(将打造的组件注册为全局组件)
这里为了方便一次性写完需要注册所有组件
javascript
import {App} from 'vue'
import ElContainer from './Container.vue'
import ElHeader from './Header.vue'
import ElMain from './Main.vue'
import ElAside from './Aside.vue'
import ElFooter from './Footer.vue'
export default {
install(app:App){
app.component(ElContainer.name,ElContainer) //注册全局组件
app.component(ElHeader.name,ElHeader)
app.component(ElMain.name,ElMain)
app.component(ElAside.name,ElAside)
app.component(ElFooter.name,ElFooter)
}
}
- 让
container
下的文件生效,在main.ts
下引入该文件(让组件生效,能被使用)
python
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElContainer from './components/container
const app=createApp(App)
app.use(ElContainer)
app.mount('#app')
- 这样
container
组件打造好了,可以直接在App.vue
里用了
这里一次性写完用打造出的组件写了两个不同的布局
xml
<template>
<div>
<el-container>
<el-header height="100px">header</el-header>
<el-container>
<el-aside>aside</el-aside>
<el-main>main</el-main>
</el-container>
<el-footer height="100px">footer</el-footer>
</el-container>
<br>
//这是第2个布局,只是位置换了一下
<el-container>
<el-header height="100px">header</el-header>
<el-container>
<el-aside>aside</el-aside>
<el-container>
<el-main>main</el-main>
<el-footer height="100px">footer</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
<style lang="scss">
body{
width: 1000px;
margin: 10px auto;
}
.el-header,.el-footer{
background: #b3c0d1;
text-align: center;
line-height: 100px;
}
.el-aside{
background: #d3dce6;
}
.el-main{
background: #e9eef3;
text-align: center;
line-height: 160px;
}
body > .el-container{
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
</style>
Header 头部
思路:
- 利用html自带标签header创建头部容器,同时给类名
- 该容器可以设置高度
- 标签内可以放其他内容(slot)
- 编写
Header.vue
文件
xml
<template>
<header class="el-header" :style="{ height }"> //为了可以设置头部高度
<slot></slot> //为了可以在里面放内容
</header>
</template>
<script lang="ts">
export default {
name:'ElHeader' //定义组件名字
}
</script>
<script setup lang="ts">
interface Props{ //泛型 设置父组件传的高度的类型
height?:string
}
withDefaults(defineProps<Props>(),{ vue自带的有默认值的withDefaults:不仅可以接收父组件传的值,还能设置默认值
height:'60px'
})
</script>
<style lang="scss" scoped>
@import '../../styles/mixin.scss';
@include b(header){
padding: $--header-padding;
box-sizing: border-box;
flex-shrink: 0;
}
</style>
index.ts
下添加 声明为全局组件(已写在了开头)- 就可以直接去
App.vue
中使用了(已写在了开头)
Main 主体部分
思路:
- 利用html自带的main标签,同时给类名
- 在标签内放插槽(slot)
- 编写
Main.vue
的内容
xml
<template>
<main class="el-main">
<slot />
</main>
</template>
<script lang="ts">
export default {
name:'ElMain' //组件名
}
</script>
<style lang="scss" scoped>
@import '../../styles/mixin.scss';
@include b(main){ //组件样式
display: block;
flex:1;
flex-basis: auto;
overflow: auto;
box-sizing: border-box;
padding: $--main-padding;
}
</style>
index.ts
下添加 声明为全局组件(已写在了开头)- 就可以直接去
App.vue
中使用了(已写在了开头)
Aside 侧边栏组件
思路:
- 利用html自带的aside标签,同时给类名
- 可以设置该aside容器的宽度
- 在该标签内可以放内容(slot)
- 编写
Aside.vue
文件
xml
<template>
<aside class="el-aside" :style="{width}">
<slot></slot>
</aside>
</template>
<script lang="ts">
export default {
name:'ElAside'
}
</script>
<script setup lang="ts">
interface Props{
width:string
}
withDefaults(defineProps<Props>(),{
width:'200px'
})
</script>
<style lang="scss" scoped>
@import '../../styles/mixin.scss';
@include b(aside){
overflow: auto;
box-sizing: border-box;
flex-shrink: 0;
}
</style>
index.ts
下添加 声明为全局组件(已写在了开头)- 就可以直接去
App.vue
中使用了(已写在了开头)
Footer 尾部
思路:
- 利用html自带的footer标签创建容器,同时给类名
- 可以设置该footer容器的高度
- 在该标签内可以放内容(slot)
- 编写
Footer.vue
文件
xml
<template>
<footer class="el-footer" :style="{ height} ">
<slot />
</footer>
</template>
<script lang="ts">
export default {
name:'ElFooter'
}
</script>
<script setup lang="ts">
interface Props{
height?:string
}
withDefaults(defineProps<Props>(),{
height:"60px"
})
</script>
<style lang="scss" scoped>
@import '../../styles/mixin.scss';
@include b(footer){
padding: $--footer-padding;
box-sizing: border-box;
flex-shrink: 0;
}
</style>
index.ts
下添加 声明为全局组件(已写在了开头)- 就可以直接去
App.vue
中使用了(已写在了开头)
最终效果:
二、打造Button按钮组件
思路:
- 利用html自带的
button
标签打造,同时给默认类名button
上可以设置大小,样式(在别的地方用到此组件时传值给这个button
)- 标签内可以放文字(slot插槽)
- 准备好公用样式(同样在
mixin.css
下写,由于样式有点多,这里就放一部分用到的,可以自行添加)
css
//color
$--color-white: #ffffff !default;
$--color-text-regular: #606266 !default;
$--color-text-placeholder: #c0c4cc !default;
$--border-color-base: #dcdfe6 !default;
$--button-default-border-color: $--border-color-base !default;
/* Color
-------------------------- */
/// color|1|Brand Color|0
$--color-primary: #409eff !default;
/// color|1|Background Color|4
$--color-white: #ffffff !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;
/// color|1|Functional Color|1
$--color-success: #67c23a !default;
/// color|1|Functional Color|1
$--color-warning: #e6a23c !default;
/// color|1|Functional Color|1
$--color-danger: #f56c6c !default;
/// color|1|Functional Color|1
$--color-info: #909399 !default;
//button
$--button-font-size: $--font-size-base !default;
$--button-font-weight: $--font-weight-primary !default;
$--button-primary-font-color: $--color-white !default;
$--button-default-background-color: $--color-white !default;
$--button-default-font-color: $--color-text-regular !default;
$--button-padding-vertical: 12px !default;
$--button-padding-horizontal: 20px !default;
$--button-border-radius: $--border-radius-base !default;
$--button-medium-font-size: $--font-size-base !default;
/// borderRadius||Border|2
$--button-medium-border-radius: $--border-radius-base !default;
/// padding||Spacing|3
$--button-medium-padding-vertical: 10px !default;
/// padding||Spacing|3
$--button-medium-padding-horizontal: 20px !default;
/// fontSize||Font|1
$--button-small-font-size: 12px !default;
$--button-small-border-radius: #{$--border-radius-base - 1} !default;
/// padding||Spacing|3
$--button-small-padding-vertical: 9px !default;
/// padding||Spacing|3
$--button-small-padding-horizontal: 15px !default;
@mixin button-size(
$padding-vertical,
$padding-horizontal,
$font-size,
$border-radius
) {
padding: $padding-vertical $padding-horizontal;
font-size: $font-size;
border-radius: $border-radius;
&.is-round {
padding: $padding-vertical $padding-horizontal;
}
}
@mixin button-variant($color, $background-color, $border-color) {
color: $color;
background-color: $background-color;
border-color: $border-color;
&:hover,
&:focus {
background: mix(
$--color-white,
$background-color,
20%
);
border-color: mix(
$--color-white,
$border-color,
20%
);
color: $color;
}
&:active {
background: mix(
$--color-black,
$background-color,
$--button-active-shade-percent
);
border-color: mix(
$--color-black,
$border-color,
$--button-active-shade-percent
);
color: $color;
outline: none;
}
&.is-active {
background: mix(
$--color-black,
$background-color,
$--button-active-shade-percent
);
border-color: mix(
$--color-black,
$border-color,
$--button-active-shade-percent
);
color: $color;
}
}
- 编写
Button.vue
文件
less
<template>
<button class="el-button" :class="[size?`el-button--${size}`:'',type?`el-button--${type}`:''] ">
<slot></slot>
</button>
</template>
<script lang="ts">
export default {
name:'ElButton' //按钮名
}
</script>
<script setup lang="ts">
interface Props{
size?:''|'small'| 'medium' |'large' //只能传这几种值的一种
type?:''|'primary'|'success'|'danger'
}
withDefaults(defineProps<Props>(),{ //接收父组件传的值
size:'',
type:''
})
</script>
<style lang="scss" scoped>
@import "../../styles/mixin.scss";
@include b(button){
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: $--button-default-background-color;
color: $--button-default-font-color;
-webkit-appearance: none;
text-align: center;
border: $--border-base;
border-color: $--button-default-border-color;
box-sizing: border-box;
outline: none;
margin: 0;
font-weight: $--button-font-weight;
& + & {
margin-left: 10px;
}
@include button-size(
$--button-padding-vertical,
$--button-padding-horizontal,
$--button-font-size,
$--button-border-radius
);
&:hover,
&:focus {
color: $--color-primary;
border-color: mix($--color-white,$--color-primary,70%);
background-color: mix($--color-white,$--color-primary,90%);
}
@include m(medium) {
@include button-size(
$--button-medium-padding-vertical,
$--button-medium-padding-horizontal,
$--button-medium-font-size,
$--button-medium-border-radius
);
}
@include m(small) {
@include button-size(
$--button-small-padding-vertical,
$--button-small-padding-horizontal,
$--button-small-font-size,
$--button-small-border-radius
);
}
@include m(large) {
@include button-size(
$--button-large-padding-vertical,
$--button-large-padding-horizontal,
$--button-large-font-size,
$--button-large-border-radius
);
}
@include m(primary) {
@include button-variant(
$--button-primary-font-color,
$--button-primary-background-color,
$--button-primary-border-color
);
}
@include m(success) {
@include button-variant(
$--button-success-font-color,
$--button-success-background-color,
$--button-success-border-color
);
}
@include m(danger) {
@include button-variant(
$--button-danger-font-color,
$--button-danger-background-color,
$--button-danger-border-color
);
}
}
</style>
index.ts
下添加 声明为全局组件
javascript
import ElButton from './Button.vue'
import {App} from 'vue'
export default {
install(app:App){
app.component(ElButton.name,ElButton)
}
}
- 在
main.ts
文件下使该组件生效
javascript
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElContainer from './components/container'
import ElButton from './components/button'
const app=createApp(App)
app.use(ElContainer)
app.use(ElButton)
app.mount('#app')
- 就可以直接去
App.vue
中使用了
xml
<template>
<div>
<el-button size="small" type="primary">按钮</el-button>
<el-button >提交</el-button>
</div>
</template>
最终效果:
到此为止,打造的两个简单的组件就完成啦~
结束语
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正 。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!