Icon组件
我们的第一个组件是Icon,因为它最简单,在有些组件中还会用到,也能激起大家学习的兴趣
废话不多说了,直接开干
首先,创建好我们icon组件的目录结构
我们的icon是yike设计的并且已经上传到了iconfont上,yk-design-icon
那既然咱们说到iconfont了,那大家可能也知道了icon的组件开发方式了
我们直接采用symbol用法,也就是我们把两套图标(面性和线性的)的js直接引入进来
放到我们的assets下。并且新建一个font文件夹,专门用来存放图标
这两个js文件大家直接去仓库里面拿就行啦~(yike-design-dev/packages/yike-design-ui/src/assets/font at monorepo-dev · ecaps1038/yike-design-dev (github.com))
那么我们icon组件的代码就显而易见啦~
icon.vue
<template>
<svg class="icon" aria-hidden="true">
<use :xlink:href="'#' + name"></use>
</svg>
</template>
<script setup lang="ts">
import '../../../assets/font/line/iconfont.js'
import '../../../assets/font/surface/iconfont.js'
import '../style'
import { IconProps } from './icon'
defineOptions({
name: 'YkIcon',
})
withDefaults(defineProps<IconProps>(), {
name: '',
})
</script>
aria-hidden="true":这是一个辅助技术属性,用于指示屏幕阅读器忽略此图标,因为它只是用作视觉装饰而不包含任何有意义的文本内容。如果没说明白的话,看看这个:指路
这段代码其实关键就在
:xlink:href="'#' + name":这是动态绑定属性,name 是一个变量,通过将其与'#'拼接,可以指定使用哪个SVG片段作为复用模板。
通俗点说,我只要传个name,那么就知道我要哪个图标了
icon.ts
export type IconProps = {
name?: string;
};
既然我就需要传一个name,那么我IconProps就设置一个name这一个type就好了
这也是我最开始讲解icon组件的原因,因为它最简单,而且能走遍整个组件的开发流程
然后呢,我们需要给icon设置一个初始样式
index.less
.icon {
overflow: hidden;
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
}
然后我们style文件夹下新建index.ts用于样式的导出
import './index.less'
index.ts
组件写完之后我们需要在index.ts中进行导出
import Icon from './src/icon.vue';
import { withInstall } from '../../utils/index';
export const YkIcon = withInstall(Icon);
export default YkIcon;
export * from './src/icon';
最后呢,我们在yk-design的根目录的index.ts进行导出即可
怕大家迷糊,我指出一下
import { YkTest } from './components/test/index';
import YkIcon from './components/icon';
import type { Component, App } from 'vue';
import './styles/index.less';
const components: {
[propName: string]: Component;
} = {
YkTest,
YkIcon
};
export {
YkTest,
YkIcon
};
// 全局注册
export default {
install: (app: App) => {
for (const c in components) {
app.component(c, components[c]);
}
},
};
使用Icon组件
我们直接回到demo工程下,使用我们的YkIcon组件
我们看效果
OK,显示没有问题,那么我们的icon组件就开发完成啦
Button组件
我们这一章来开发一个组件库中较为基础的组件:Button
首先,把我们组件文件结构搭建出来
我们先思考button.vue中应该怎么去写
- 首先:我们要有一个最基础的
button - 其次:我们按钮会有加载状态,那我们可以用
svg去实现 - 再有:按钮的
class应该是动态的,那么我们是不是应该使用computed去实现
那我们现在基于这几点思考,我们得出了一个基本的结构
<template>
<button :class="ykButtonClass" :disabled="disabled || loading">
<svg v-if="loading" viewBox="25 25 50 50">
<circle r="20" cy="50" cx="50"></circle>
</svg>
<slot name="icon"></slot>
<slot></slot>
</button>
</template>
其中:disabled="disabled || loading" 绑定了一个动态属性 disabled,如果disabled或者 loading 为真,则按钮会被禁用。
<svg> 通过 v-if,当 loading 为真时,会渲染一个圆形进度条的 SVG 动画。
<slot name="icon"></slot>是具名插槽,用于插入按钮的图标。<slot></slot>是默认插槽,用于插入按钮的文本或其他内容
通过这样书写,我们可以在需要的地方插入按钮的图标和内容,并根据需要设置按钮的样式、禁用状态和加载状态。
好的,那我们现在应该思考button.ts怎么写了,我们props都需要传什么值
- 按钮类型
type - 按钮尺寸
size - 按钮形状
shape - 图标按钮
icon - 按钮状态
status - 禁用状态
disabled - 加载中按钮
loading - 长按钮
long
好的,这是我们预想的几种情况,那么我们是不是可以书写button.ts了
button.ts
import { Shape, Size, Status, Type } from '../../../utils/constant';
export type ButtonProps = {
type?: Type;
status?: Status;
size?: Size;
shape?: Shape;
long?: boolean;
loading?: boolean;
disabled?: boolean;
};
诶,这个Shape, Size, Status, Type,我在最开始组件库准备工作中是不是有提到,我们将一些常用的类型,状态封装在了constant.ts中,这回是不是就用上啦!
好的,那我们props的类型定义好了,我们可以继续书写我们的组件啦
button.vue
我们现在引入buttonProps,并设置默认值
import { ButtonProps } from './button'
const props = withDefaults(defineProps<ButtonProps>(), {
type: 'primary',
size: 'l',
shape: 'default',
long: false,
loading: false,
disabled: false,
})
好的,现在我们是不是就差通过computed来动态渲染类名了
const ykButtonClass = computed(() => {
return {
'yk-button': true,
'yk-button--loading': props.loading,
'yk-button--long': props.long,
'yk-button--disabled': props.disabled || props.loading,
[`yk-button--${props.status}`]: props.status,
[`yk-button--${props.type}`]: props.type,
[`yk-button--${props.size}`]: props.size,
[`yk-button--${props.shape}`]: props.shape,
}
})
在计算属性的回调函数中,返回一个对象,该对象描述了按钮可能具有的各种 CSS 类。
通过使用这个计算属性,你可以根据传入的属性值来动态设置按钮的样式类。这样,你就可以很方便地根据不同的属性配置来自定义按钮的外观和行为。
我相信,这不仅会教你怎么开发组件库,也会让你真正了解computed的作用,不要面试一问computed和watch区别就死记硬背啦~
好的,我们组件代码完成了,现在是这样的
<template>
<button :class="ykButtonClass" :disabled="disabled || loading">
<svg v-if="loading" viewBox="25 25 50 50">
<circle r="20" cy="50" cx="50"></circle>
</svg>
<slot name="icon"></slot>
<slot></slot>
</button>
</template>
<script lang="ts">
export default {
name: 'YKButton',
}
</script>
<script setup lang="ts">
import { computed } from 'vue'
import { ButtonProps } from './button'
import '../style'
const props = withDefaults(defineProps<ButtonProps>(), {
type: 'primary',
size: 'l',
shape: 'default',
long: false,
loading: false,
disabled: false,
})
const ykButtonClass = computed(() => {
return {
'yk-button': true,
'yk-button--loading': props.loading,
'yk-button--long': props.long,
'yk-button--disabled': props.disabled || props.loading,
[`yk-button--${props.status}`]: props.status,
[`yk-button--${props.type}`]: props.type,
[`yk-button--${props.size}`]: props.size,
[`yk-button--${props.shape}`]: props.shape,
}
})
</script>
然后我们需要书写样式啦
style
我们在style下定义个variables.less
我们在这里面设置不同类型按钮的颜色和样式
@import '../../../styles/index.less';
@btn-primary-color-text: #fff;
@btn-primary-color-text_primary: #fff;
@btn-primary-color-text_success: #fff;
@btn-primary-color-text_warning: #fff;
@btn-primary-color-text_danger: #fff;
@btn-primary-color-bg: @pcolor;
@btn-primary-color-bg_primary: @pcolor;
@btn-primary-color-bg_success: @scolor;
@btn-primary-color-bg_warning: @wcolor;
@btn-primary-color-bg_danger: @ecolor;
@btn-primary-color-border: @pcolor;
@btn-primary-color-hover: @pcolor-6;
@btn-primary-color-border_primary: @pcolor;
@btn-primary-color-border_success: @scolor;
@btn-primary-color-border_warning: @wcolor;
@btn-primary-color-border_danger: @ecolor;
@btn-primary-color-hover_primary: @pcolor-6;
@btn-primary-color-hover_success: @scolor-6;
@btn-primary-color-hover_warning: @wcolor-6;
@btn-primary-color-hover_danger: @ecolor-6;
@btn-secondary-color-text: @font-color-l;
@btn-secondary-color-text_primary: @pcolor;
@btn-secondary-color-text_success: @scolor;
@btn-secondary-color-text_warning: @wcolor;
@btn-secondary-color-text_danger: @ecolor;
@btn-secondary-color-bg: @gray-1;
@btn-secondary-color-hover: @gray-2;
@btn-secondary-color-bg_primary: @pcolor-1;
@btn-secondary-color-bg_success: @scolor-1;
@btn-secondary-color-bg_warning: @wcolor-1;
@btn-secondary-color-bg_danger: @ecolor-1;
@btn-secondary-color-hover_primary: @pcolor-2;
@btn-secondary-color-hover_success: @scolor-2;
@btn-secondary-color-hover_warning: @wcolor-2;
@btn-secondary-color-hover_danger: @ecolor-2;
@btn-secondary-color-border: transparent;
@btn-secondary-color-border_primary: transparent;
@btn-secondary-color-border_success: transparent;
@btn-secondary-color-border_warning: transparent;
@btn-secondary-color-border_danger: transparent;
@btn-outline-color-text: @font-color-l;
@btn-outline-color-text_primary: @pcolor;
@btn-outline-color-text_success: @scolor;
@btn-outline-color-text_warning: @wcolor;
@btn-outline-color-text_danger: @ecolor;
@btn-outline-color-bg: transparent;
@btn-outline-color-hover: @gray-1;
@btn-outline-color-bg_primary: transparent;
@btn-outline-color-bg_success: transparent;
@btn-outline-color-bg_warning: transparent;
@btn-outline-color-bg_danger: transparent;
@btn-outline-color-border: @gray-3;
@btn-outline-color-border_primary: @pcolor-3;
@btn-outline-color-border_success: @scolor-3;
@btn-outline-color-border_warning: @wcolor-3;
@btn-outline-color-border_danger: @ecolor-3;
@btn-outline-color-hover_primary: @pcolor-1;
@btn-outline-color-hover_success: @scolor-1;
@btn-outline-color-hover_warning: @wcolor-1;
@btn-outline-color-hover_danger: @ecolor-1;
这里面都定义了什么呢,我们拿primary举例即可
- 主要按钮的文本颜色
- 主要按钮的背景颜色
- 主要按钮的边框颜色和鼠标悬停状态下的背景颜色
当然,其中的一些变量是在根目录下的styles下定义的
这在准备工作的那节文章也说过了~
secondary和outline同理
index.less
@import './variables.less';
/* stylelint-disable */
.yk-button--loading {
opacity: 0.7 !important;
svg {
margin-right: 4px;
width: 16px;
transform-origin: center;
animation: rotate4 2s linear infinite;
}
circle {
fill: none;
stroke: hsl(227, 16%, 89%);
stroke-width: 3;
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
stroke-linecap: round;
animation: dash4 1.5s ease-in-out infinite;
}
@keyframes rotate4 {
100% {
transform: rotate(360deg);
}
}
@keyframes dash4 {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dashoffset: -125px;
}
}
}
.yk-button {
display: inline-flex;
justify-content: center;
align-items: center;
padding: 6px 16px;
white-space: nowrap;
// 默认按钮状态
color: @btn-primary-color-text;
background-color: @btn-primary-color-bg;
outline: none;
transition: all @animatb ease-in-out;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
cursor: pointer;
user-select: none;
color: @btn-primary-color-text;
circle {
stroke: @btn-primary-color-text;
}
.btn-type(@type) {
.normal() {
color:~'@{btn-@{type}-color-text}';
circle {
stroke:~'@{btn-@{type}-color-text}';
}
background-color:~'@{btn-@{type}-color-bg}';
border-color:~'@{btn-@{type}-color-border}';
&:not(:disabled):hover {
background-color:~'@{btn-@{type}-color-hover}';
}
&:not(:disabled):active {
background-color:~'@{btn-@{type}-color-bg}';
}
}
&--@{type} {
.normal();
}
}
.btn-status(@type: primary, @status: primary) {
.normal() {
color:~'@{btn-@{type}-color-text_@{status}}';
circle {
stroke:~'@{btn-@{type}-color-text_@{status}}';
}
background-color:~'@{btn-@{type}-color-bg_@{status}}';
border-color:~'@{btn-@{type}-color-border_@{status}}';
&:not(:disabled):hover {
background-color:~'@{btn-@{type}-color-hover_@{status}}';
}
&:not(:disabled):active {
background-color:~'@{btn-@{type}-color-bg_@{status}}';
}
}
&.yk-button--@{type}.yk-button--@{status} {
.normal();
}
}
.btn-type(primary);
.btn-type(secondary);
.btn-type(outline);
.btn-status(primary);
.btn-status(primary, success);
.btn-status(primary, warning);
.btn-status(primary, danger);
.btn-status(secondary);
.btn-status(secondary, success);
.btn-status(secondary, warning);
.btn-status(secondary, danger);
.btn-status(outline);
.btn-status(outline, success);
.btn-status(outline, warning);
.btn-status(outline, danger);
// 尺寸 size
&--s {
padding: 0px @space-s;
min-width: 24px;
height: 24px;
font-size: @size-ss;
border-radius: @radius-s;
}
&--m {
padding: 0px @space-l;
min-width: 32px;
height: 32px;
border-radius: @radius-s;
}
&--l {
padding: 0px @space-l;
min-width: 36px;
height: 36px;
border-radius: @radius-m;
}
&--xl {
padding: 0px @space-xl;
min-width: 48px;
height: 48px;
font-size: @size-m;
border-radius: @radius-m;
}
&--long {
display: block;
width: 100%;
}
// 圆角样式
&--round {
border-radius: @radius-r;
}
&--circle {
padding: 0;
border-radius: @radius-r;
}
&--square {
padding: 0;
}
// 禁用
&--disabled {
.disabled();
}
}
index.ts
import './index.less'
我们在这里面定义了示按钮加载状态的动画效果,SVG 图标的旋转动画和圆圈的动画效果,按钮的基本样式,默认状态下的按钮样式,特定类型和状态下的按钮样式等
这里原谅我没法一句一句地讲每一行less是什么意思,因为讲less并不是咱们的主要目的
好的,最后咱们按照之前的方式,进行注册和导出
然后我们可以在demo工程中去试验一下啦!
在demo工程中使用yk-button
OK,没有问题,我们的button组件也就完成啦~