主题方案和基础组件:如何设计组件库的主题方案?

注:此文章为杨文坚老师的课程笔记,非作者原创

什么是"可定制化主题"呢?

如果你在电商企业中进行业务功能的前端页面开发,原有使用的组件库是蓝色风格的样式,但是想在节假日里快速转变成红色风格的组件样式,再比如,如果你开发的页面是亮色系的效果,哪天产品经理需要前端快速实现暗色系的黑夜效果,提升用户夜间的使用体验,那么,你会怎么做前端页面的改造呢?

这些场景,都要处理前端页面整体颜色以及视觉风格的变化,这类"变化"在前端开发中一般定义为"主题"的控制,也就是"可定制化主题"。

组件库的主题方案设计需要做什么准备?

既然是方案设计,首先要做的是方案的规范准备,这里主题的方案设计需要准备以下两种规范:

  • 颜色的设计规范
  • CSS 的开发规范

前面我们提到,页面主题的变化主要是整体颜色视觉风格的变化。而且,使用组件库开发,业务功能页面控制主题是通过组件库的内置主题系统来控制的。所以,我们主题方案设计的第一步就是需要设计好组件库的颜色规范

这里要明确一点,颜色规范设计通常不是前端开发者的职责工作,而是设计师的工作。但是前端作为设计稿和实现代码之间的"桥梁",需要做好设计稿的沟通和讨论。不过,设计师一般对颜色规范设计的流程比较严格,也有很多讲究。所以,作为前端开发者,我们有必要简单了解这些颜色规范设计的过程。

第一步是颜色种类的选择。一般设计师会选择几种大类型的颜色,例如红色、蓝色和绿色等。然后根据业务需要挑选这几类型中的一个基准色号。

第二步,基于上一步选择好的基准色号,进行颜色梯度的处理,例如颜色亮度和饱和度从浅到深的梯度处理。举个例子,灰色的颜色梯度处理如下图片所示:

最后一步,也就是第三步,根据不同颜色的颜色梯度,进行语义化使用处理。我们拿上述灰色每个梯度的颜色来"语义化处理",将"灰色 1 号"作为页面背景颜色,"灰色 10 号"作为页面的字体颜色。效果如下述所示:

有很多开源组件库都提供了现成的颜色设计规范。例如Ant Design 官方团队的颜色规范Element Plus 官方团队的颜色规范都可以直接参考。

前端开发中对组件库的主题开发和控制主要基于 CSS 来处理的,所以在开发之前,我们要制定 CSS 的开发规范。

首先,我们要选择 CSS 的预处理器语言来开发,主要利用 CSS 预处理器语言的"可编程的逻辑语法"来编写 CSS。目前主流的 CSS 的预处理器语言有 Less 和 Sass,两者语法比较类似。

  • Less,对 CSS 原始语法增加了少许方便的扩展,例如函数、运算等语法,学习更容易
  • 预处理器 Sass,语法更加丰富和全面。

两种预处理器语言都有很多开源组件库都在使用,例如 Ant Design 使用了 Less,Element Plus 选择了 Sass。这里我们主要选择 Less 来开发组件库的 CSS 代码,主要是考虑到 Less 简单易用,能满足绝大部分的开发需要。

我们可以用 Less 里的变量语法来管理所有的颜色梯度,如下述代码所示:

less 复制代码
@gray-1: #f5f5f5;
@gray-2: #f0f0f0;
@gray-3: #d9d9d9;
@gray-4: #bfbfbf;
@gray-5: #8c8c8c;
@gray-6: #595959;
@gray-7: #434343;
@gray-8: #262626;
@gray-9: #1f1f1f;
@gray-10: #141414;

不过,虽然我们已经有了 Less 作为预处理器语法来管理 CSS 代码,例如主题颜色梯度都用了 Less 变量语法来管理,但接下来我们还要管理具体组件的主题样式上面,而且还要语义化控制到具体某个组件的某个维度的颜色

什么是语义化颜色呢?一个按钮的背景颜色是"蓝色 1 号",语义化颜色就是将"蓝色 1 号"语义化给了按钮背景颜色。但是实际中,组件语义化的内容维度是有很多层次的。

一个按钮的颜色,有背景颜色、字体颜色和边框颜色这一整套颜色体系,在按钮默认状态、点击状态和禁用状态时,背景、字体和边框颜色又需要独立的一套新的颜色体系。这个时候,就有三套颜色体系。如果再叠加一个按钮类型,例如是"实心颜色"的按钮和"空心颜色"的按钮,就演变成 3x2 的 6 种颜色体系。

你想,就这一个按钮组件,都有 6 种颜色体系,那如何做好 CSS 的语义化代码管理和维护呢?如果再来一个"白天"和"黑夜"模式的主题切换颜色功能,那要如何管理呢?

这个时候就需要用到 CSS Variable 来管理语义化的组件颜色了。

什么是 CSS Variable 呢?就是 CSS 自定义属性,也可以称作 CSS 变量或者级联变量,主要是 CSS 代码在浏览器中,定义一个 CSS 属性(或者称为"变量")后,这个 CSS 属性就可以在页面中全局其它 CSS 代码中使用,甚至是覆盖重写。例如下面代码所示:

less 复制代码
@prefix-name: my-vue;

@white: #ffffff;
@black: #222222;
// ...
@gray-4: #bfbfbf;
// ...

:root {
  // 页面背景颜色
  --@{prefix-name}-page-bg-color: @white;
  // 页面字体颜色
  --@{prefix-name}-page-text-color: @black;
  // 页面通用边框颜色
  --@{prefix-name}-page-border-color: @gray-4;
}

.@{prefix-name}-box {
  background: ~'var(--@{prefix-name}-page-bg-color)';
  color: ~'var(--@{prefix-name}-page-text-color)';
}

这里的代码是用 Less 来管理颜色梯度,再用 CSS Variable 来使用颜色梯度的 Less 变量,同时语义化来管理不同维度的语义化颜色

我们现在有了颜色设计规范和 CSS 开发规范,最后要面临的问题就是"如何实现组件库的主题方案"了,我下面就用一个最简单的案例来实现一个主题方案,示范一下。

如何实现组件库主题方案?

在讲解组件库的主题方案前,我们先看看效果:

在这张动图中,我实现了一个组件 Box,设置了组件语义化的背景色和字体色,切换主题的时候,就用 className 的优先级来控制语义化的 CSS Variable,也就是用新主题的颜色样式覆盖掉原有的默认主题的颜色样式,达到动态切换主题。

具体代码如下述所示。Box 组件的 Less 代码:

less 复制代码
@prefix-name: my-vue;

@white: #ffffff;
@black: #222222;
// ...
@gray-4: #bfbfbf;
// ...

// 默认明亮主题颜色
:root {
  // 页面背景颜色
  --@{prefix-name}-page-bg-color: @white;
  // 页面字体颜色
  --@{prefix-name}-page-text-color: @black;
}

:root {
  // 暗黑主题颜色
  &.@{prefix-name}-theme-dark {
    // 页面背景颜色
    --@{prefix-name}-page-bg-color: @black;
    // 页面字体颜色
    --@{prefix-name}-page-text-color: @white;
  }
}

.@{prefix-name}-box {
  background: ~'var(--@{prefix-name}-page-bg-color)';
  color: ~'var(--@{prefix-name}-page-text-color)';
}

Box 组件的 Vue 代码:

xml 复制代码
<template>
  <div :class="{ [baseClassName]: true }">
    <slot v-if="$slots.default"></slot>
  </div>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
const baseClassName = `${prefixName}-box`;
</script>

Box 组件的使用代码:

xml 复制代码
<template>
  <Box class="example">
    <div :style="{ padding: 10, fontSize: 24 }">这是一个主题演示案例</div>
    <button @click="onClick">点击换主题色</button>
  </Box>
</template>

<script setup lang="ts">
import { Box } from '../src';
import { prefixName } from '../src/theme/index';

const onClick = () => {
  const darkThemeName = `${prefixName}-theme-dark`;
  const html = document.querySelector('html');
  if (html?.classList.contains(darkThemeName)) {
    html?.classList.remove(darkThemeName);
  } else {
    html?.classList.add(darkThemeName);
  }
};
</script>

<style>
html,
body {
  height: 100%;
  width: 100%;
}
.example {
  height: 100%;
  padding: 100px;
  box-sizing: border-box;
  text-align: center;
}
</style>

通过上述"明亮主题"和"暗黑主题"的切换代码和演示案例,你会发现,组件库的主题方案设计核心就是颜色规范 + CSS Variable 控制。是不是很简单?

其实,理论上是可以这么简单理解的,但是实际在组件库的每个组件开发过程中,我们要做好组件内部的主题方案的实现,还是有点"复杂度"的。

这个"复杂度"体现在不同组件的使用场景不同,不同组件的主题方案都要"因地制宜"来实现。具体情况,我给你演示一个基础的组件实现,你就知道了。接下来我就给你演示一下,如何开发一个常见的多状态的按钮组件。

如何开发一个多状态的按钮组件?

在开发组件前,还是先演示一下最终的效果:

上面动图中,演示的是常见组件库里实现的按钮组件,具备多种组件的状态维度。

这里维度分成:

  • 按钮类型
  • 按钮变种(种类)
  • 按钮禁用状态。

其中按钮类型有五类:

  • Default
  • Primary
  • Success
  • Warn
  • Danger

按钮变种有两类:

  • Contented
  • Outlined

按钮禁用状态有两类。

  • Enabled
  • Disabled

总的来讲,上述按钮有这三种维度状态,也就是类型、变种和是否禁用。那么,如何实现这个三种维度的叠加管理呢?分解成这三个步骤:

  • 第一步,基础按钮组件样式的开发;
  • 第二步,实现按钮不同维度组合的样式;
  • 第三步,组件的使用状态叠加。

第一步,基础按钮组件的样式开发,也就是实现一个按钮的"底座",后续可以基于这个底座做各类维度叠加的样式开发,具体代码如下述所示。

less 复制代码
.@{prefix-name}-button {
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  height: 32px;
  padding: 4px 15px;
  font-size: 14px;
  border-radius: 2px;
  box-sizing: border-box;
  border-width: 1px;
}

Vue 代码:

xml 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
const baseClassName = `${prefixName}-button`;
</script>

第二步,实现按钮不同维度组合的样式,也就是我们要根据不同状态维度的组合来实现样式的叠加。

我先将按钮的不同维度的样式的 className 做个统一的管理,其中按钮类型和按钮变种统一管理,形成 5 x 3 的 15 个基础按钮样式。

具体 CSS Variable 语义化,如下面所示:

less 复制代码
// variable.less
:root {

  // 按钮 default-contained: 默认状态
  --@{prefix-name}-btn-default-contained-color: @gray-1;
  --@{prefix-name}-btn-default-contained-border-color: @gray-6;
  --@{prefix-name}-btn-default-contained-bg-color: @gray-6;

  // 按钮 primary-contained: 默认状态
  --@{prefix-name}-btn-primary-contained-color: @blue-1;
  --@{prefix-name}-btn-primary-contained-border-color: @blue-6;
  --@{prefix-name}-btn-primary-contained-bg-color: @blue-6;

  // 按钮 success-contained: 默认状态
  --@{prefix-name}-btn-success-contained-color: @green-1;
  --@{prefix-name}-btn-success-contained-border-color: @green-6;
  --@{prefix-name}-btn-success-contained-bg-color: @green-6;

  // 按钮 warning-contained: 默认状态
  --@{prefix-name}-btn-warning-contained-color: @gold-1;
  --@{prefix-name}-btn-warning-contained-border-color: @gold-6;
  --@{prefix-name}-btn-warning-contained-bg-color: @gold-6;

  // 按钮 danger-contained: 默认状态
  --@{prefix-name}-btn-danger-contained-color: @red-1;
  --@{prefix-name}-btn-danger-contained-border-color: @red-6;
  --@{prefix-name}-btn-danger-contained-bg-color: @red-6;

  // 按钮 default-outlined: 默认状态
  --@{prefix-name}-btn-default-outlined-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-border-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-bg-color: @gray-1;

  // 按钮 primary-outlined: 默认状态
  --@{prefix-name}-btn-primary-outlined-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-border-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-bg-color: @blue-1;

  // 按钮 success-outlined: 默认状态
  --@{prefix-name}-btn-success-outlined-color: @green-6;
  --@{prefix-name}-btn-success-outlined-border-color: @green-6;
  --@{prefix-name}-btn-success-outlined-bg-color: @green-1;

  // 按钮 warning-outlined: 默认状态
  --@{prefix-name}-btn-warning-outlined-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-border-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-bg-color: @gold-1;

  // 按钮 danger-outlined: 默认状态
  --@{prefix-name}-btn-danger-outlined-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-border-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-bg-color: @red-1;
}

具体 className 组合实现如下述所示:

less 复制代码
@import '../../theme/variable.less';

.@{prefix-name}-button {
  // ....

  // contented
  &.@{prefix-name}-button-default-contained {
    background: ~'var(--@{prefix-name}-btn-default-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-default-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-default-contained-border-color)';
  }
  &.@{prefix-name}-button-primary-contained {
    background: ~'var(--@{prefix-name}-btn-primary-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-primary-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-primary-contained-border-color)';
  }
  &.@{prefix-name}-button-success-contained {
    background: ~'var(--@{prefix-name}-btn-success-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-success-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-success-contained-border-color)';
  }
  &.@{prefix-name}-button-warning-contained {
    background: ~'var(--@{prefix-name}-btn-warning-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-warning-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-warning-contained-border-color)';
  }
  &.@{prefix-name}-button-danger-contained {
    background: ~'var(--@{prefix-name}-btn-danger-contained-bg-color)';
    color: ~'var(--@{prefix-name}-btn-danger-contained-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-danger-contained-border-color)';
  }
  // outlined
  &.@{prefix-name}-button-default-outlined {
    background: ~'var(--@{prefix-name}-btn-default-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-default-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-default-outlined-border-color)';
  }
  &.@{prefix-name}-button-primary-outlined {
    background: ~'var(--@{prefix-name}-btn-primary-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-primary-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-primary-outlined-border-color)';
  }
  &.@{prefix-name}-button-success-outlined {
    background: ~'var(--@{prefix-name}-btn-success-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-success-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-success-outlined-border-color)';
  }
  &.@{prefix-name}-button-warning-outlined {
    background: ~'var(--@{prefix-name}-btn-warning-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-warning-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-warning-outlined-border-color)';
  }
  &.@{prefix-name}-button-danger-outlined {
    background: ~'var(--@{prefix-name}-btn-danger-outlined-bg-color)';
    color: ~'var(--@{prefix-name}-btn-danger-outlined-color)';
    border: 1px solid ~'var(--@{prefix-name}-btn-danger-outlined-border-color)';
  }
}

Vue 代码实现如下:

vue 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
      [btnClassName]: true
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
import type { ButtonType, ButtonVariant } from './types';
const props = withDefaults(
  defineProps<{
    type?: ButtonType;
    variant?: ButtonVariant;
  }>(),
  {
    type: 'default',
    variant: 'contained',
    disabled: false
  }
);

const baseClassName = `${prefixName}-button`;
const btnClassName = `${baseClassName}-${props.type}-${props.variant}`;
</script>

实现后的效果如下:

接下来实现按钮禁用的样式,主要也是通过添加 className,利用其添加在后面,优先级更高来覆盖样式,具体实现代码如下:

css 复制代码
@import '../../theme/variable.less';

.@{prefix-name}-button {

  // ...
  &.@{prefix-name}-button-disabled {
    opacity: 0.5;
    cursor: not-allowed;
    &:hover {
      opacity: 0.5;
    }
  }
}

Vue 代码改造后如下:

xml 复制代码
<template>
  <button
    :class="{
      [baseClassName]: true,
      [btnClassName]: true,
      [disabledClassName]: props.disabled
    }"
  >
    <slot v-if="$slots.default"></slot>
  </button>
</template>

<script setup lang="ts">
import { prefixName } from '../theme/index';
import type { ButtonType, ButtonVariant } from './types';
const props = withDefaults(
  defineProps<{
    type?: ButtonType;
    variant?: ButtonVariant;
    disabled?: boolean;
  }>(),
  {
    type: 'default',
    variant: 'contained',
    disabled: false
  }
);

const baseClassName = `${prefixName}-button`;
const btnClassName = `${baseClassName}-${props.type}-${props.variant}`;
const disabledClassName = `${baseClassName}-disabled`;
</script>

实现上述代码后,使用该按钮组件枚举所有按钮状态叠加效果如下图所示:

到了这一步,我们就已经实现了一个多状态的 Vue.js 3.x 按钮组件,可以通过传入不同的 Props 来控制显示不同的状态样式和状态叠加的样式。

讲到这,你是不是已经觉得"复杂度"有所提升了?其实这个按钮组件的复杂度基本就到此为止了,也就是说这个按钮的"基础底座"已经实现了。

我们接下来要做的就是基于已有的"底座",也就是已经实现好的按钮语义化的 className 和 CSS Variable,来配置主题控制和样式主题的切换效果。

如何对多状态的按钮组件进行主题控制?

通过上述的规范设计阶段,我们应该知道,主题核心就是颜色梯度的控制,我们在处理按钮组件的不同颜色的时候,只是选择某个颜色的某个梯度号。也就是说,当我们想按钮主题风格时候,只需要控制"颜色"和"梯度"就行了,再覆盖对应的 CSS Variable,就能实现主题快速切换,不需要关注其他。

我这里具体拆解成两步:

  • 第一步,对按钮不同状态维度组合选择对应色板的颜色梯度;
  • 第二步,将选好的颜色用新 className 来覆盖原来的 CSS Variable。

刚刚实现按钮组件是默认的"明亮"模式的主题,我现在针对按钮组件按钮的所有维度状态,快速实现一个"暗黑"主题的颜色效果。这里可以直接切换颜色"梯度",直接从取梯度号的镜像号数,例如"蓝色 2 号"的就换成"蓝色 8 号"来替换。

然后按照第二步的操作,全部替换到对应的 CSS Variable,根据 className 优先级操作,来覆盖主题样式,具体代码如下述所示:

明亮主题 Less 代码:

scss 复制代码
@import "./variable.less";

:root {
  // 按钮 default-contained: 默认状态
  --@{prefix-name}-btn-default-contained-color: @gray-1;
  --@{prefix-name}-btn-default-contained-border-color: @gray-6;
  --@{prefix-name}-btn-default-contained-bg-color: @gray-6;
  // 按钮 default-contained: Hover状态
  --@{prefix-name}-btn-default-contained-color-hover: @gray-2;
  --@{prefix-name}-btn-default-contained-border-color-hover: @gray-8;
  --@{prefix-name}-btn-default-contained-bg-color-hover: @gray-8;

  // 按钮 primary-contained: 默认状态
  --@{prefix-name}-btn-primary-contained-color: @blue-1;
  --@{prefix-name}-btn-primary-contained-border-color: @blue-6;
  --@{prefix-name}-btn-primary-contained-bg-color: @blue-6;
  // 按钮 primary-contained: Hover状态
  --@{prefix-name}-btn-primary-contained-color-hover: @blue-2;
  --@{prefix-name}-btn-primary-contained-border-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-contained-bg-color-hover: @blue-8;

  // 按钮 success-contained: 默认状态
  --@{prefix-name}-btn-success-contained-color: @green-1;
  --@{prefix-name}-btn-success-contained-border-color: @green-6;
  --@{prefix-name}-btn-success-contained-bg-color: @green-6;
  // 按钮 success-contained: Hover状态
  --@{prefix-name}-btn-success-contained-color-hover: @green-2;
  --@{prefix-name}-btn-success-contained-border-color-hover: @green-8;
  --@{prefix-name}-btn-success-contained-bg-color-hover: @green-8;

  // 按钮 warning-contained: 默认状态
  --@{prefix-name}-btn-warning-contained-color: @gold-1;
  --@{prefix-name}-btn-warning-contained-border-color: @gold-6;
  --@{prefix-name}-btn-warning-contained-bg-color: @gold-6;
  // 按钮 warning-contained: Hover状态
  --@{prefix-name}-btn-warning-contained-color-hover: @gold-2;
  --@{prefix-name}-btn-warning-contained-border-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-contained-bg-color-hover: @gold-8;

  // 按钮 danger-contained: 默认状态
  --@{prefix-name}-btn-danger-contained-color: @red-1;
  --@{prefix-name}-btn-danger-contained-border-color: @red-6;
  --@{prefix-name}-btn-danger-contained-bg-color: @red-6;
  // 按钮 danger-contained: Hover状态
  --@{prefix-name}-btn-danger-contained-color-hover: @red-2;
  --@{prefix-name}-btn-danger-contained-border-color-hover: @red-8;
  --@{prefix-name}-btn-danger-contained-bg-color-hover: @red-8;

  // 按钮 default-outlined: 默认状态
  --@{prefix-name}-btn-default-outlined-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-border-color: @gray-6;
  --@{prefix-name}-btn-default-outlined-bg-color: @gray-1;
  // 按钮 default-outlined: Hover状态
  --@{prefix-name}-btn-default-outlined-color-hover: @gray-8;
  --@{prefix-name}-btn-default-outlined-border-color-hover: @gray-8;
  --@{prefix-name}-btn-default-outlined-bg-color-hover: @gray-2;

  // 按钮 primary-outlined: 默认状态
  --@{prefix-name}-btn-primary-outlined-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-border-color: @blue-6;
  --@{prefix-name}-btn-primary-outlined-bg-color: @blue-1;
  // 按钮 primary-outlined: Hover状态
  --@{prefix-name}-btn-primary-outlined-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-outlined-border-color-hover: @blue-8;
  --@{prefix-name}-btn-primary-outlined-bg-color-hover: @blue-2;

  // 按钮 success-outlined: 默认状态
  --@{prefix-name}-btn-success-outlined-color: @green-6;
  --@{prefix-name}-btn-success-outlined-border-color: @green-6;
  --@{prefix-name}-btn-success-outlined-bg-color: @green-1;
  // 按钮 success-outlined: Hover状态
  --@{prefix-name}-btn-success-outlined-color-hover: @green-8;
  --@{prefix-name}-btn-success-outlined-border-color-hover: @green-8;
  --@{prefix-name}-btn-success-outlined-bg-color-hover: @green-2;

  // 按钮 warning-outlined: 默认状态
  --@{prefix-name}-btn-warning-outlined-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-border-color: @gold-6;
  --@{prefix-name}-btn-warning-outlined-bg-color: @gold-1;
  // 按钮 warning-outlined: Hover状态
  --@{prefix-name}-btn-warning-outlined-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-outlined-border-color-hover: @gold-8;
  --@{prefix-name}-btn-warning-outlined-bg-color-hover: @gold-2;

  // 按钮 danger-outlined: 默认状态
  --@{prefix-name}-btn-danger-outlined-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-border-color: @red-6;
  --@{prefix-name}-btn-danger-outlined-bg-color: @red-1;
  // 按钮 danger-outlined: Hover状态
  --@{prefix-name}-btn-danger-outlined-color-hover: @red-8;
  --@{prefix-name}-btn-danger-outlined-border-color-hover: @red-8;
  --@{prefix-name}-btn-danger-outlined-bg-color-hover: @red-2
}

暗黑主题 Less 代码:

scss 复制代码
@import "../variable.less";

:root {
  &.@{prefix-name}-theme-dark {
    // 按钮 default-contained: 默认状态
    --@{prefix-name}-btn-default-contained-color: @gray-9;
    --@{prefix-name}-btn-default-contained-border-color: @gray-4;
    --@{prefix-name}-btn-default-contained-bg-color: @gray-4;
    // 按钮 default-contained: Hover状态
    --@{prefix-name}-btn-default-contained-color-hover: @gray-8;
    --@{prefix-name}-btn-default-contained-border-color-hover: @gray-2;
    --@{prefix-name}-btn-default-contained-bg-color-hover: @gray-2;

    // 按钮 primary-contained: 默认状态
    --@{prefix-name}-btn-primary-contained-color: @blue-9;
    --@{prefix-name}-btn-primary-contained-border-color: @blue-4;
    --@{prefix-name}-btn-primary-contained-bg-color: @blue-4;
    // 按钮 primary-contained: Hover状态
    --@{prefix-name}-btn-primary-contained-color-hover: @blue-8;
    --@{prefix-name}-btn-primary-contained-border-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-contained-bg-color-hover: @blue-2;

    // 按钮 success-contained: 默认状态
    --@{prefix-name}-btn-success-contained-color: @green-9;
    --@{prefix-name}-btn-success-contained-border-color: @green-4;
    --@{prefix-name}-btn-success-contained-bg-color: @green-4;
    // 按钮 success-contained: Hover状态
    --@{prefix-name}-btn-success-contained-color-hover: @green-8;
    --@{prefix-name}-btn-success-contained-border-color-hover: @green-2;
    --@{prefix-name}-btn-success-contained-bg-color-hover: @green-2;

    // 按钮 warning-contained: 默认状态
    --@{prefix-name}-btn-warning-contained-color: @gold-9;
    --@{prefix-name}-btn-warning-contained-border-color: @gold-4;
    --@{prefix-name}-btn-warning-contained-bg-color: @gold-4;
    // 按钮 warning-contained: Hover状态
    --@{prefix-name}-btn-warning-contained-color-hover: @gold-8;
    --@{prefix-name}-btn-warning-contained-border-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-contained-bg-color-hover: @gold-2;

    // 按钮 danger-contained: 默认状态
    --@{prefix-name}-btn-danger-contained-color: @red-9;
    --@{prefix-name}-btn-danger-contained-border-color: @red-4;
    --@{prefix-name}-btn-danger-contained-bg-color: @red-4;
    // 按钮 danger-contained: Hover状态
    --@{prefix-name}-btn-danger-contained-color-hover: @red-8;
    --@{prefix-name}-btn-danger-contained-border-color-hover: @red-2;
    --@{prefix-name}-btn-danger-contained-bg-color-hover: @red-2;

    // 按钮 default-outlined: 默认状态
    --@{prefix-name}-btn-default-outlined-color: @gray-4;
    --@{prefix-name}-btn-default-outlined-border-color: @gray-4;
    --@{prefix-name}-btn-default-outlined-bg-color: @gray-9;
    // 按钮 default-outlined: Hover状态
    --@{prefix-name}-btn-default-outlined-color-hover: @gray-2;
    --@{prefix-name}-btn-default-outlined-border-color-hover: @gray-2;
    --@{prefix-name}-btn-default-outlined-bg-color-hover: @gray-8;

    // 按钮 primary-outlined: 默认状态
    --@{prefix-name}-btn-primary-outlined-color: @blue-4;
    --@{prefix-name}-btn-primary-outlined-border-color: @blue-4;
    --@{prefix-name}-btn-primary-outlined-bg-color: @blue-9;
    // 按钮 primary-outlined: Hover状态
    --@{prefix-name}-btn-primary-outlined-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-outlined-border-color-hover: @blue-2;
    --@{prefix-name}-btn-primary-outlined-bg-color-hover: @blue-8;

    // 按钮 success-outlined: 默认状态
    --@{prefix-name}-btn-success-outlined-color: @green-4;
    --@{prefix-name}-btn-success-outlined-border-color: @green-4;
    --@{prefix-name}-btn-success-outlined-bg-color: @green-9;
    // 按钮 success-outlined: Hover状态
    --@{prefix-name}-btn-success-outlined-color-hover: @green-2;
    --@{prefix-name}-btn-success-outlined-border-color-hover: @green-2;
    --@{prefix-name}-btn-success-outlined-bg-color-hover: @green-8;

    // 按钮 warning-outlined: 默认状态
    --@{prefix-name}-btn-warning-outlined-color: @gold-4;
    --@{prefix-name}-btn-warning-outlined-border-color: @gold-4;
    --@{prefix-name}-btn-warning-outlined-bg-color: @gold-9;
    // 按钮 warning-outlined: Hover状态
    --@{prefix-name}-btn-warning-outlined-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-outlined-border-color-hover: @gold-2;
    --@{prefix-name}-btn-warning-outlined-bg-color-hover: @gold-8;

    // 按钮 danger-outlined: 默认状态
    --@{prefix-name}-btn-danger-outlined-color: @red-4;
    --@{prefix-name}-btn-danger-outlined-border-color: @red-4;
    --@{prefix-name}-btn-danger-outlined-bg-color: @red-9;
    // 按钮 danger-outlined: Hover状态
    --@{prefix-name}-btn-danger-outlined-color-hover: @red-2;
    --@{prefix-name}-btn-danger-outlined-border-color-hover: @red-2;
    --@{prefix-name}-btn-danger-outlined-bg-color-hover: @red-8;

  }
}

最终效果下述所示:

好了,至此,我们就基于主题方案实现了 Vue.js 3.x 组件库的一个基础组件------按钮组件的功能和主题效果。

完整代码在这里

相关推荐
小镇程序员1 小时前
vue2 src自定义事件
前端·javascript·vue.js
炒毛豆1 小时前
vue3+echarts+ant design vue实现进度环形图
javascript·vue.js·echarts
别拿曾经看以后~4 小时前
原生Android调用uniapp项目中的方法
android·vue.js·uni-app
川石教育4 小时前
Vue前端开发子组件向父组件传参
前端·vue.js·前端开发·vue前端开发·vue组件传参
Embrace9244 小时前
为什么 Vue2会出现数据更新视图不更新 Vue3不会出现
javascript·vue.js·ecmascript
qq_415628174 小时前
bpmn.js显示流程图
javascript·vue.js·流程图
GISer_Jing5 小时前
Vue前端进阶面试题目(二)
前端·vue.js·面试
乐闻x5 小时前
Pinia 实战教程:构建高效的 Vue 3 状态管理系统
前端·javascript·vue.js
程序员学姐5 小时前
基于SpringBoot+Vue的高校社团管理系统
java·开发语言·vue.js·spring boot·后端·mysql·spring
橘子味小白菜5 小时前
el-table的树形结构后端返回的id没有唯一键怎么办
前端·vue.js