需求分析
按钮组件的核心目标是为用户提供直观、高效的交互方式,因此需要具备以下关键特性:
- 插槽支持:通过允许用户自定义按钮的内容,无论是文本、图标还是其他复杂的 HTML 结构,都能轻松嵌入按钮内部,极大地增强了组件的灵活性和复用性,使其能够适应各种不同的场景和设计要求。
- 事件传递:支持按钮点击事件是实现交互功能的基础。组件应能够准确地捕获用户的点击操作,并将相关的事件信息传递给父组件,以便进行进一步的业务逻辑处理,如提交表单、触发弹窗、执行特定的函数等。
实现细节
定义 Props
为赋予按钮组件高度的可定制性,首先需精确定义其属性(Props),以便外部使用者依据具体需求灵活配置按钮的功能与样式。
// 定义按钮的尺寸类型,包括大、小两种尺寸,方便后续样式设置和组件使用时的类型检查
type ButtonSize = "large" | "small";
// 定义按钮的类型,如主要按钮、危险按钮等,每种类型对应不同的样式风格
type ButtonType = "primary" | "danger";
// 接口定义按钮组件接受的属性
interface ButtonProps {
// 是否禁用按钮,默认为 false,当设置为 true 时,按钮将不可点击且显示为禁用状态
disabled?: boolean;
// 自定义类名,用于外部样式覆盖或添加额外的样式类,增强样式的扩展性
className?: string;
// 按钮尺寸,可选值为之前定义的 ButtonSize 类型,控制按钮的大小样式
size?: ButtonSize;
// 按钮类型,可选值为之前定义的 ButtonType 类型,决定按钮的颜色和样式主题
btnType?: ButtonType;
}
定义 Emits
除了接收外部传入的属性,按钮组件还需要向外发送事件,以便与父组件进行交互。在本组件中,我们主要关注点击事件的处理和传递。
// 定义按钮组件向外发射的事件接口
interface ButtonEmits {
// 当按钮被点击时,发射 'click' 事件,并携带鼠标事件对象作为参数,以便父组件获取更多点击相关的信息
(event: "click", payload: MouseEvent): void;
}
编写 Template
模板部分是按钮组件的核心结构,它负责将 Props 和插槽内容结合起来,构建出最终呈现给用户的按钮元素。
<template>
<button
ref="button"
:class="classes"
:disabled="props.disabled"
v-bind="$attrs"
@click="handleClick"
>
<!-- 插槽用于插入用户自定义的内容,如文本、图标等 -->
<slot></slot>
</button>
</template>
组件样式
为了确保按钮组件在视觉上具有吸引力且符合现代设计风格,我们使用 SCSS 来精心定义按钮的样式。通过变量和混合宏的使用,实现了样式的可维护性和可扩展性,能够轻松地调整按钮的各种样式属性,如颜色、大小、边框等,以适应不同的设计系统和主题要求。
// 引入 sass:color 模块,用于颜色的调整和操作
@use 'sass:color';
// 引入自定义的样式变量文件,包含颜色、字体、边框等通用变量,方便统一管理和修改样式
@use "../../styles/variables" as *;
// 按钮的基本字体权重,默认为正常字体权重
$btn-font-weight: $font-weight-normal!default;
// 按钮在垂直方向上的内边距,默认为 0.375rem
$btn-padding-y: 0.375rem!default;
// 按钮在水平方向上的内边距,默认为 0.75rem
$btn-padding-x: 0.75rem!default;
// 按钮的字体家族,默认为基础字体家族
$btn-font-family: $font-family-base!default;
// 按钮的字体大小,默认为基础字体大小
$btn-font-size: $font-size-base!default;
// 按钮的行高,默认为基础行高
$btn-line-height: $line-height-base!default;
// 不同大小按钮的垂直和水平内边距以及字体大小设置
$btn-padding-y-sm: 0.25rem!default;
$btn-padding-x-sm: 0.5rem!default;
$btn-font-size-sm: $font-size-sm!default;
$btn-padding-y-lg: 0.5rem!default;
$btn-padding-x-lg: 1rem!default;
$btn-font-size-lg: $font-size-lg!default;
// 按钮的边框宽度,默认为基础边框宽度
$btn-border-width: $border-width!default;
// 按钮的 box-shadow 属性,用于添加阴影效果,增强按钮的立体感
$btn-box-shadow: inset 0 1px 0 rgba($white, 0.15), 0 1px 1px rgba($black, 0.075)!default;
// 按钮在禁用状态下的透明度,默认为 0.65,使其呈现出不可用的视觉效果
$btn-disabled-opacity: 0.65!default;
// 按钮的边框圆角半径,默认为基础边框圆角半径
$btn-border-radius: $border-radius!default;
// 大尺寸按钮的边框圆角半径,默认为大尺寸边框圆角半径
$btn-border-radius-lg: $border-radius-lg!default;
// 小尺寸按钮的边框圆角半径,默认为小尺寸边框圆角半径
$btn-border-radius-sm: $border-radius-sm!default;
// 按钮的过渡效果,包括颜色、背景色、边框颜色和 box-shadow 的过渡,使按钮在状态变化时具有平滑的动画效果
$btn-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out!default;
// 定义混合宏用于设置按钮的大小,接收垂直内边距、水平内边距、字体大小和边框圆角半径作为参数
@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {
padding: $padding-y $padding-x;
font-size: $font-size;
border-radius: $border-radius;
}
// 定义混合宏用于设置按钮的样式,包括背景色、边框颜色、文字颜色以及鼠标悬停时的样式变化
@mixin button-style(
$background,
$border,
$color,
$hover-background: color.adjust($background, $lightness:7.5%),
$hover-border: color.adjust($border, $lightness:10%),
$hover-color: $color
) {
background: $background;
border-color: $border;
color: $color;
&:hover {
background: $hover-background;
border-color: $hover-border;
color: $hover-color;
}
&:focus,
&.focus {
background: $hover-background;
border-color: $hover-border;
color: $hover-color;
}
&:disabled,
&.disabled {
background: $background;
border-color: $border;
color: $color;
}
}
// 基础按钮类,设置了按钮的通用样式,如字体、颜色、内边距、边框、阴影、过渡效果等
.ld-button {
position: relative;
display: inline-block;
font-weight: $btn-font-weight;
line-height: $btn-line-height;
color: $body-color;
white-space: nowrap;
text-align: center;
vertical-align: middle;
background-image: none;
border: $btn-border-width solid transparent;
@include button-size(
$btn-padding-y,
$btn-padding-x,
$btn-font-size,
$btn-border-radius
);
@include button-style(
$white,
$gray-400,
$body-color,
$white,
$primary,
$primary
);
box-shadow: $btn-box-shadow;
cursor: pointer;
transition: $btn-transition;
&.disabled,
&[disabled] {
cursor: not-allowed;
opacity: $btn-disabled-opacity;
box-sizing: unset;
> * {
pointer-events: none; // 确保按钮内部的元素在按钮禁用时也不接受鼠标事件
}
}
& +.ld-button {
margin-left: 0.5rem; // 设置按钮之间的间距,增强布局的合理性
}
}
// 大尺寸按钮类,通过混合宏应用大尺寸按钮的特定样式
.ld-button-large {
@include button-size(
$btn-padding-y-lg,
$btn-padding-x-lg,
$btn-font-size-lg,
$btn-border-radius-lg
);
}
// 小尺寸按钮类,通过混合宏应用小尺寸按钮的特定样式
.ld-button-small {
@include button-size(
$btn-padding-y-sm,
$btn-padding-x-sm,
$btn-font-size-sm,
$btn-border-radius-sm
);
}
// 主要按钮类,通过混合宏应用主要按钮的特定样式,如颜色、背景色等
.ld-button-primary {
@include button-style($primary, $primary, $white);
}
// 危险按钮类,通过混合宏应用危险按钮的特定样式,通常用于表示删除、取消等具有潜在风险的操作
.ld-button-danger {
@include button-style($danger, $danger, $white);
}
业务场景组件
在业务场景中,按钮常用于查询操作。若不加以处理,很容易导致频繁触发按钮。针对这种情况,我们可以采取以下措施:
- 节流防抖:采用节流防抖操作来控制点击事件,避免其过于频繁地触发。
- 前端接口 API 封装:对相同接口请求进行处理,取消之前尚未返回的相同接口请求,仅返回最后一次请求的数据。
- 控制按钮可点击状态:通过改变按钮的
disabled属性来控制按钮的可点击状态。
对于第 3 点,如果在项目中不提取公共处理逻辑,那么将会有许多变量用于控制按钮状态。为此,我们可以基于基础的按钮组件来封装一个加载组件。当按钮被点击后,将其设置为disabled状态,使其不可操作,待按钮事件结束(请求结束后),再恢复按钮的可操作状态。
<script lang="ts" setup>
import { ref, useAttrs } from "vue";
import LdButton from "./Button.vue";
defineOptions({
name: "LoadingButton",
inheritAttrs: false,
});
const isLoading = ref(false);
const attrs = useAttrs();
const handleClick = async () => {
isLoading.value = true;
try {
await Promise.resolve().then(attrs?.onClick as () => Promise<void>);
} finally {
isLoading.value = false;
}
};
</script>
<template>
<ld-button :disabled="isLoading" @click="handleClick">
// 自定义加载icon
<i v-if="isLoading" class="loading"></i>
<slot></slot>
</ld-button>
</template>
加载按钮的使用
<script lang="ts" setup>
const handleClick = () => {
console.log("操作开始");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, 2000);
}).then(res => {
console.log("操作完成", res);
});
};
</script>
<loading-button @click="handleClick">确定</loading-button>
感谢阅读,敬请斧正!