AntDesignVue 主题样式应用逻辑和修改方式

  • 项目代码结构
  • [Seeds 和 Map Token](#Seeds 和 Map Token "#seeds-%E5%92%8C-map-token")
  • [Alias Token](#Alias Token "#alias-token")
  • 切换主题
  • 内置的组件样式
  • [UI 需要关注的点](#UI 需要关注的点 "#ui-%E9%9C%80%E8%A6%81%E5%85%B3%E6%B3%A8%E7%9A%84%E7%82%B9")
    • [UI 应关注 Seed 和其他 Token 的派生逻辑](#UI 应关注 Seed 和其他 Token 的派生逻辑 "#ui-%E5%BA%94%E5%85%B3%E6%B3%A8-seed-%E5%92%8C%E5%85%B6%E4%BB%96-token-%E7%9A%84%E6%B4%BE%E7%94%9F%E9%80%BB%E8%BE%91")
  • 例子
    • 启动项目
    • [给 Tag 组件增加 size 尺寸选项](#给 Tag 组件增加 size 尺寸选项 "#%E7%BB%99-tag-%E7%BB%84%E4%BB%B6%E5%A2%9E%E5%8A%A0-size-%E5%B0%BA%E5%AF%B8%E9%80%89%E9%A1%B9")

近期,公司在使用 AntDesignVue 的过程中希望能够更高程度的自定义主题样式,并扩展部分组件的功能。于是,打算拉取其源代码,了解一下 AntDesignVue 的组件主题是如何运作的。下面介绍一下 AntDesignVue 的总体代码结构、Token 派生的逻辑、如何切换主题以及如何修改组件的样式。

在此之前我在官网中看到了这句话:

在 Design Token 中我们提供了一套更加贴合设计的三层结构,将 Design Token 拆解为 Seed Token、Map Token 和 Alias Token 三部分。这三组 Token 并不是简单的分组,而是一个三层的派生关系,由 Seed Token 派生 Map Token,再由 Map Token 派生 Alias Token。在大部分情况下,使用 Seed Token 就可以满足定制主题的需要。但如果您需要更高程度的主题定制,您需要了解 antd 中 Design Token 的生命周期。

Seed Token 是一切设计的基础

项目代码结构

对于组件开发者来说,需要关注如下部分:

组件相关的

这几个目录里存放所有组件的列表和通用的样式及全局配置信息

  • components 组件列表
    • ...
    • button
      • demo 组件示例
        • basic.vue
        • ....vue
        • index.vue
      • style 组件样式
        • ....ts
        • index.ts
      • button.tsx 子组件
      • button-group.tsx 子组件
      • buttonTypes.ts 组件级别的类型
      • index.ts 组件入口
      • index.zh-CN.md 这里是 api 文档
  • config-provider 通用的全局配置
  • style 通用样式

主题相关的

这里存放了主题相关的配置和 Seed 以及 Token 的派生逻辑等

  • theme
    • interface
      • maps Map Token 的类型
      • alias.ts Alias Token 的类型
      • seeds.ts Seeds Token 的类型
    • themes 主题配置
      • default 默认主题
        • index.ts 调用了 shared 里面的多个 Seeds 生成 Token 的方法
      • shared 多个生成 Token 的方法
      • seed.ts 定义众多 seed

Seeds 和 Map Token

Seeds 是 UI 设计师提供的基础样式值,核心的几个 seed 有 color、font、size、height,分别对应颜色、字体、尺寸大小、组件高度。其他 Token 皆由此派生而来。

Color

主题预先定义了几种状态,success, warning, error, info, primary,在 seed 阶段这几个状态默认的颜色在这里定义:

js 复制代码
// components/theme/themes/seed.ts
const seedToken: SeedToken = {
  // preset color palettes
  ...defaultPresetColors,
  // Color
  colorPrimary: "#1677ff",
  colorSuccess: "#52c41a",
  colorWarning: "#faad14",
  colorError: "#ff4d4f",
  colorInfo: "#1677ff",
  colorTextBase: "",
  // ...
};

基础的几个色彩会经过计算得到 11 个色值,这些色值从浅到深,对应不同的组件状态。

比如 primaryColors 就是:

js 复制代码
{
    "1": "#e6f4ff",
    "2": "#bae0ff",
    "3": "#91caff",
    "4": "#69b1ff",
    "5": "#4096ff",
    "6": "#1677ff", // 这个是配置的默认色,上下几个是经过计算得到的浅色和深色
    "7": "#0958d9",
    "8": "#4096ff",
    "9": "#1677ff",
    "10": "#0958d9"
}

经过 genColorMapToken 方法的派生,primary color 的 11 个从浅到深的色值分别会被派生到不同的 Map Token 并应用在不同的状态中,如背景色、悬浮状态背景色、边框色、悬浮状态边框色、默认悬浮状态色、默认色、激活状态色、悬浮状态文本色、文本色、激活状态文本色等。

ini 复制代码
colorPrimaryBg: primaryColors[1],
colorPrimaryBgHover: primaryColors[2],

colorPrimaryBorder: primaryColors[3],
colorPrimaryBorderHover: primaryColors[4],

colorPrimaryHover: primaryColors[5],
colorPrimary: primaryColors[6],
colorPrimaryActive: primaryColors[7],

colorPrimaryTextHover: primaryColors[8],
colorPrimaryText: primaryColors[9],
colorPrimaryTextActive: primaryColors[10],

除此之外还有中性色,同样的这种中性色也生成了一系列的使用场景

json 复制代码
{
  "colorBgBase": "#fff",

  "colorTextBase": "#000",
  "colorText": "rgba(0, 0, 0, 0.88)",
  "colorTextSecondary": "rgba(0, 0, 0, 0.65)",
  "colorTextTertiary": "rgba(0, 0, 0, 0.45)",
  "colorTextQuaternary": "rgba(0, 0, 0, 0.25)",

  "colorFill": "rgba(0, 0, 0, 0.15)",
  "colorFillSecondary": "rgba(0, 0, 0, 0.06)",
  "colorFillTertiary": "rgba(0, 0, 0, 0.04)",
  "colorFillQuaternary": "rgba(0, 0, 0, 0.02)",

  "colorBgLayout": "#f5f5f5",
  "colorBgContainer": "#ffffff",
  "colorBgElevated": "#ffffff",
  "colorBgSpotlight": "rgba(0, 0, 0, 0.85)",

  "colorBorder": "#d9d9d9",
  "colorBorderSecondary": "#f0f0f0"
}

如果我们需要自定义几种新的状态,如果这几种状态没有那么常用的话,可以放在 defaultPresetColors 的 seed 里,在业务逻辑开发的过程中,使用这些自定义的颜色即可。默认已经有了很多种颜色了。我觉得应该够用了。

Font

文字的多个不同尺寸也是从基础大小派生而来的,在 seed 阶段我们定义了默认的 fontSize 为 14。经过计算得到 10 个逐渐变大的字体大小和行高。因浏览器最小支持 12px 的字体大小,所以我认为默认的 14 是最合理的。计算逻辑可以修改但是没有必要。

js 复制代码
[
  {
    size: 12,
    lineHeight: 1.6666666666666667,
  },
  {
    size: 14,
    lineHeight: 1.5714285714285714,
  },
  {
    size: 16,
    lineHeight: 1.5,
  },
  // ...
];

经过计算我们可以得到多个 Map Token,fontSizes 数组中第一个元素必然是最小的文字大小,token 被命名为 fontSizeSM、此外还有 LG, XL 等。

对应的标题字体大小还有 fontSizeHeading 开头的 token 以及行高和标题的行高:

ini 复制代码
fontSizeSM: fontSizes[0],
fontSize: fontSizes[1],
fontSizeLG: fontSizes[2],
fontSizeXL: fontSizes[3],

fontSizeHeading1: fontSizes[6],
fontSizeHeading2: fontSizes[5],
fontSizeHeading3: fontSizes[4],
fontSizeHeading4: fontSizes[3],
fontSizeHeading5: fontSizes[2],

lineHeight: lineHeights[1],
lineHeightLG: lineHeights[2],
lineHeightSM: lineHeights[0],

lineHeightHeading1: lineHeights[6],
lineHeightHeading2: lineHeights[5],
lineHeightHeading3: lineHeights[4],
lineHeightHeading4: lineHeights[3],
lineHeightHeading5: lineHeights[2],

Size

这个 size 指的是 sizeUnit 和 sizeStep 一个是基础的大小单位,另外一个是增加或减小大小的步长。

可以控制这两个 seed 和计算方式去计算多种尺寸大小,鉴于公司的应用一般要显示的内容较多,并且偏向于使用尽可能小的间隙,较为紧凑。所以可以适当调整这个算法。如普通大小样式以及紧凑样式的算法对比如下:

js 复制代码
// 普通默认样式
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
  const { sizeUnit, sizeStep } = token;
  return {
    sizeXXL: sizeUnit * (sizeStep + 8), // 48
    sizeXL: sizeUnit * (sizeStep + 4), // 32
    sizeLG: sizeUnit * (sizeStep + 2), // 24
    sizeMD: sizeUnit * (sizeStep + 1), // 20
    sizeMS: sizeUnit * sizeStep, // 16
    size: sizeUnit * sizeStep, // 16
    sizeSM: sizeUnit * (sizeStep - 1), // 12
    sizeXS: sizeUnit * (sizeStep - 2), // 8
    sizeXXS: sizeUnit * (sizeStep - 3), // 4
  };
}

// 紧凑样式
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
  const { sizeUnit, sizeStep } = token;
  const compactSizeStep = sizeStep - 2; // 在 base 大小的基础上再减少一些
  return {
    sizeXXL: sizeUnit * (compactSizeStep + 10),
    sizeXL: sizeUnit * (compactSizeStep + 6),
    sizeLG: sizeUnit * (compactSizeStep + 2),
    sizeMD: sizeUnit * (compactSizeStep + 2),
    sizeMS: sizeUnit * (compactSizeStep + 1),
    size: sizeUnit * compactSizeStep,
    sizeSM: sizeUnit * compactSizeStep,
    sizeXS: sizeUnit * (compactSizeStep - 1),
    sizeXXS: sizeUnit * (compactSizeStep - 1),
  };
}

生成的 MapToken 在后面还会继续生成多种如 paddingXXS、marginXS 等等的 Alias Token。

Height

组件高度,常用与高度等相关的属性

鉴于公司平常对紧凑模式的偏好,组件内部的样式仍然需要做出修改。在这里可以新增多几个尺寸,如 XXS 等。

js 复制代码
const genControlHeight = (token: SeedToken): HeightMapToken => {
  const { controlHeight } = token;
  return {
    controlHeightSM: controlHeight * 0.75,
    controlHeightXS: controlHeight * 0.5,
    controlHeightLG: controlHeight * 1.25,
  };
};

除了上述之外,主题还通过 genCommonMapToken 来生成 motion, line, radius 的 token。这里不再赘述。

Alias Token

此阶段是将 Map Token 转换为 Alias Token 的过程。比如输入框的外边框颜色与主色调的背景色一致,那么就没有必要再创建一个外边框的 token 了,只需要派生一个 AliasToken 值与主色调的背景色一致即可。

比如 padding 和 margin 相关的多个 Alias Token 就是由 size 派生而成:

makefile 复制代码
paddingXXS: mergedToken.sizeXXS,
paddingXS: mergedToken.sizeXS,
paddingSM: mergedToken.sizeSM,
padding: mergedToken.size,
paddingMD: mergedToken.sizeMD,
paddingLG: mergedToken.sizeLG,
paddingXL: mergedToken.sizeXL,

paddingContentHorizontalLG: mergedToken.sizeLG,
paddingContentVerticalLG: mergedToken.sizeMS,
paddingContentHorizontal: mergedToken.sizeMS,
paddingContentVertical: mergedToken.sizeSM,
paddingContentHorizontalSM: mergedToken.size,
paddingContentVerticalSM: mergedToken.sizeXS,

marginXXS: mergedToken.sizeXXS,
marginXS: mergedToken.sizeXS,
marginSM: mergedToken.sizeSM,
margin: mergedToken.size,
marginMD: mergedToken.sizeMD,
marginLG: mergedToken.sizeLG,
marginXL: mergedToken.sizeXL,
marginXXL: mergedToken.sizeXXL,

另外,内部定义了多个 screen width 宽度的尺寸大小:

js 复制代码
const screenXS = 480;
const screenSM = 576;
const screenMD = 768;
const screenLG = 992;
const screenXL = 1200;
const screenXXL = 1600;
const screenXXXL = 2000;

并派生出 screenMDMin, screenMDMax 等等方便使用媒体查询的 Alias Token,一般这里也不需要改动。

切换主题

根据官方示例,我们只需要切换 token 的值即可,那么实际上,切换主题就是调用不同的主题处理 seed 的算法,就是上面提到的 Map Algorithm,即 seed 生成 map token 阶段。

示例代码如下,切换 theme 的事件:

js 复制代码
const changeTheme = (t: ThemeName) => {
  theme.value = t;
  localStorage.setItem("theme", t);
};

绑定的 theme 响应式变量:

js 复制代码
const theme = ref<ThemeName>((localStorage.getItem('theme') as ThemeName) || 'light');

获取主题 Map Token 算法:

js 复制代码
export type ThemeName = "" | "light" | "dark" | "compact";
const getAlgorithm = (themes: ThemeName[] = []) =>
  themes
    .filter((theme) => !!theme)
    .map((theme) => {
      if (theme === "dark") {
        return antdTheme.darkAlgorithm;
      }
      if (theme === "compact") {
        return antdTheme.compactAlgorithm;
      }
      return antdTheme.defaultAlgorithm;
    });

计算后的主题配置:

js 复制代码
const themeConfig = computed(() => {
  return {
    algorithm: getAlgorithm([...new Set([theme.value, compactTheme.value])]),
  };
});

最后通过 a-config-provider 组件应用配置:

html 复制代码
<a-config-provider :locale="locale" :theme="themeConfig">
  <SiteToken>
    <router-view />
  </SiteToken>
</a-config-provider>

内置的组件样式

在不修改 token 的前提下,修改组件内的样式,有两种途径:

  1. 给组件创建一个新的 prop 或 attr 如果是某个值或存在,那么给组件添加 class 后缀(以此方法为例);
  2. 根据 prop 或 attr 做一些逻辑判断,并创建内部的 inline style;

如 tag 组件:

如果 tag 组件设置了 bordered,那么 tag 需要加上 border 样式:

下面是 tag 的 props:

ts 复制代码
export const tagProps = () => ({
  prefixCls: String,
  // ...
  bordered: { type: Boolean, default: true }, // bordered prop
});

const Tag = defineComponent({
  compatConfig: { MODE: 3 },
  name: "ATag",
  inheritAttrs: false,
  props: tagProps(), // props
  //   ...
});

在组件里面的 setup,创建 class,最终应用到组件 render 方法上:

ts 复制代码
const tagClassName = computed(() =>
  classNames(prefixCls.value, hashId.value, {
    // ...
    [`${prefixCls.value}-borderless`]: !props.bordered,
  })
);

const tagNode = (
  <span class={[tagClassName.value, attrs.class]}>{/* ... */}</span>
);

具体的样式在 style 文件夹下的 index.ts 文件中:

genBaseStyle 里,可以看到这个 class 需要使用到的属性和样式值。borderColor 在 borderless 的 class 中被设置成了透明。

ts 复制代码
const genBaseStyle = (token: TagToken): CSSObject => {
  // ...
  return {
    // ...
    [`${componentCls}-borderless`]: {
      borderColor: "transparent",
      background: token.tagBorderlessBg,
    },
  };
};

最后该方法会在 genComponentStyleHook 中被执行:

jsx 复制代码
// ============================== Export ==============================
export default genComponentStyleHook("Tag", (token) => {
  // ...
  return [
    genBaseStyle(tagToken),
    // ...
  ];
});

如果调整了 API 注意一定要修改文档,位置在组件文件夹下面的 index.zh-CN.md

UI 需要关注的点

UI 应关注 Seed 和其他 Token 的派生逻辑

既然 AntDesignVue 不能够满足公司对主题定制的要求,那么就需要 UI 来重新对需要改动的地方进行设计。在此之前需要充分理解 Seed 和 Map Token 以及 Alias Token 的派生规则。

代码里面有这样一段注释可以体现这一点:

arduino 复制代码
/**
 * Seed (designer) > Derivative (designer) > Alias (developer).
 *
 * Merge seed & derivative & override token and generate alias token for developer.
 */

比如在 seeds interface 文件内有所有 seed tooken 的含义定义,这是一切样式的基础。

ts 复制代码
// components/theme/interface/seeds.ts
export interface SeedToken extends PresetColorType {
  //  ----------   Color   ---------- //
  /**
   * @nameZH 品牌主色
   * @nameEN Brand Color
   * @desc 品牌色是体现产品特性和传播理念最直观的视觉元素之一。在你完成品牌主色的选取之后,我们会自动帮你生成一套完整的色板,并赋予它们有效的设计语义
   * @descEN Brand color is one of the most direct visual elements to reflect the characteristics and communication of the product. After you have selected the brand color, we will automatically generate a complete color palette and assign it effective design semantics.
   */
  colorPrimary: string;
  /**
   * @nameZH 成功色
   * @nameEN Success Color
   * @desc 用于表示操作成功的 Token 序列,如 Result、Progress 等组件会使用该组梯度变量。
   * @descEN Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.
   */
  colorSuccess: string;
  /**
   * @nameZH 警戒色
   * @nameEN Warning Color
   * @desc 用于表示操作警告的 Token 序列,如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该组梯度变量。
   * @descEN Used to represent the warning map token, such as Notification, Alert, etc. Alert or Control component(like Input) will use these map tokens.
   */
  colorWarning: string;
  /**
   *
   */
}

然后是 Map Token:

ts 复制代码
export interface MapToken
  extends SeedToken,
    ColorPalettes,
    ColorMapToken,
    SizeMapToken,
    HeightMapToken,
    StyleMapToken,
    FontMapToken,
    CommonMapToken {}

比如其中的文字相关的 token:

ts 复制代码
export interface FontMapToken {
  // Font Size
  fontSizeSM: number;
  fontSize: number;
  fontSizeLG: number;
  fontSizeXL: number;
  /**
   * @nameZH 一级标题字号
   * @desc H1 标签所使用的字号
   * @default 38
   */
  fontSizeHeading1: number;
  /**
   * @nameZH 二级标题字号
   * @desc h2 标签所使用的字号
   * @default 30
   */
  fontSizeHeading2: number;
  /**
   * @nameZH 三级标题字号
   * @desc h3 标签使用的字号
   * @default 24
   */
  fontSizeHeading3: number;
  /**
   * @nameZH 四级标题字号
   * @desc h4 标签使用的字号
   * @default 20
   */
  fontSizeHeading4: number;
  /**
   * @nameZH 五级标题字号
   * @desc h5 标签使用的字号
   * @default 16
   */
  fontSizeHeading5: number;
  // LineHeight
  lineHeight: number;
  lineHeightLG: number;
  // ...
}

再是 Alias Token 包括常见的内外边距等

ts 复制代码
export interface AliasToken extends MapToken {
  // Padding
  paddingXXS: number;
  paddingXS: number;
  paddingSM: number;
  padding: number;
  paddingMD: number;
  paddingLG: number;
  paddingXL: number;

  // Padding Content
  paddingContentHorizontalLG: number;
  paddingContentHorizontal: number;
  paddingContentHorizontalSM: number;
  paddingContentVerticalLG: number;
  paddingContentVertical: number;
  paddingContentVerticalSM: number;

  // Margin
  marginXXS: number;
  marginXS: number;
  marginSM: number;
  margin: number;
  marginMD: number;
  marginLG: number;
  marginXL: number;
  marginXXL: number;

修改某个组件的颜色、背景颜色、文字大小、内外边距等公共的变量,一定要成体系并说明 Token 的计算逻辑。不建议在修改某个组件中特别定义一种特殊的、没有派生规则的颜色、尺寸和其他值。

例子

下面是一个修改 Tag 组件的例子,我为其增加了一个 size 属性,可以调整 Tag 组件的尺寸大小。

启动项目

  1. npm i
  2. npm run dev

"dev": "npm run routes && vite serve site",

run dev 会先运行 npm run routes 生成所有组件 Demo 的路由,然后再启动 site 项目

site/src/router 中的 demoRoutes.js 如下:

js 复制代码
export default [
  {
    path: "affix:lang(-cn)?",
    meta: {
      category: "Components",
      type: "其他",
      title: "Affix",
      cover:
        "https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original",
      coverDark:
        "https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original",
      subtitle: "固钉",
    },
    component: () => import("../../../components/affix/demo/index.vue"),
  },
  {
    path: "alert:lang(-cn)?",
    meta: {
      category: "Components",
      type: "反馈",
      t,
      // ...
    },
  },
];

访问需要改动的 Demo 路径即可查看效果,我本地启动的服务路径地址为:http://localhost:5173/components/tag-cn

给 Tag 组件增加 size 尺寸选项

Tag 与 Button 有很多相似之处,如果不知道如何下手可以参考 Button 组件是如何进行 size 样式处理的。

首先需要给组件增加 prop,这里的 SizeType 参考 Button 是从 config-provider 引入的:

config-provider 还提供 DirectionType 等常用的类型

ts 复制代码
export const tagProps = () => ({
  // ...
  size: {
    type: String as PropType<SizeType>,
  },
});

然后在组件 setup 中获取到 size 的值:

既然用到了 provider 那么参考 Button 我们还需要使用 config injector 获取到这个 prop

ts 复制代码
const Tag = defineComponent({
  compatConfig: { MODE: 3 },
  name: 'ATag',
  inheritAttrs: false,
  props: tagProps(),
  // emits: ['update:visible', 'close'],
  slots: Object as CustomSlotsType<{
    closeIcon: any;
    icon: any;
    default: any;
  }>,
  setup(props, { slots, emit, attrs }) {
    const { prefixCls, direction, size } = useConfigInject('tag', props); // 使用 useConfigInject
// ...

接下来需要添加 class 用来处理样式:

参考 Button 组件,我们定义了一个 size class name 的 map

ts 复制代码
const tagClassName = computed(() => {
  const sizeClassNameMap = { large: "lg", small: "sm", middle: undefined }; // map
  const sizeFullname = size.value;
  const sizeCls = sizeFullname ? sizeClassNameMap[sizeFullname] || "" : ""; // size

  return classNames(prefixCls.value, hashId.value, {
    [`${prefixCls.value}-${props.color}`]: isInternalColor.value,
    [`${prefixCls.value}-has-color`]: props.color && !isInternalColor.value,
    [`${prefixCls.value}-hidden`]: !visible.value,
    [`${prefixCls.value}-rtl`]: direction.value === "rtl",
    [`${prefixCls.value}-borderless`]: !props.bordered,
    [`${prefixCls.value}-${sizeCls}`]: sizeCls, // size
  });
});

覆盖样式:

每一个组件里面都有 genComponentStyleHook 这个函数返回所有的样式,如果我们需要修改 size,那么增加一个两个样式计算函数,一个是 small 一个是 large:

ts 复制代码
// ============================== Export ==============================
export default genComponentStyleHook("Tag", (token) => {
  const { fontSize, lineHeight, lineWidth, fontSizeIcon } = token;
  // ...
  const tagHeight = Math.round(fontSize * lineHeight);

  const tagFontSize = token.fontSizeSM;
  const tagLineHeight = tagHeight - lineWidth * 2;
  const tagDefaultBg = token.colorFillAlter;
  const tagDefaultColor = token.colorText;

  const tagToken = mergeToken<TagToken>(token, {
    tagFontSize,
    tagLineHeight,
    tagDefaultBg,
    tagDefaultColor,
    tagIconSize: fontSizeIcon - 2 * lineWidth, // Tag icon is much more smaller
    tagPaddingHorizontal: 8, // Fixed padding.
    tagBorderlessBg: token.colorFillTertiary,
  });

  return [
    genBaseStyle(tagToken),
    genPresetStyle(tagToken),
    genTagStatusStyle(tagToken, "success", "Success"),
    genTagStatusStyle(tagToken, "processing", "Info"),
    genTagStatusStyle(tagToken, "error", "Error"),
    genTagStatusStyle(tagToken, "warning", "Warning"),
    // size
    genSizeSmallTagStyle(tagToken), // size small
    genSizeLargeTagStyle(tagToken), // size large
  ];
});

然后再将对应的样式加上即可:

ts 复制代码
// =============================== Size ===============================
const genSizeTagStyle = (
  token: TagToken,
  sizePrefixCls: string = ""
): CSSInterpolation => {
  const { componentCls, tagPaddingHorizontal, lineWidth } = token;
  const paddingInline = tagPaddingHorizontal - lineWidth;

  return [
    // Size
    {
      [`${componentCls}${sizePrefixCls}`]: {
        // 对应的 class 覆盖基础样式
        lineHeight: `${token.tagLineHeight}px`,
        paddingInline,
        fontSize: token.tagFontSize,
      },
    },
  ];
};

const genSizeSmallTagStyle: GenerateStyle<TagToken> = (token) => {
  const { fontSizeSM, lineHeightSM, lineWidth } = token;

  const tagHeight = Math.round(fontSizeSM * lineHeightSM); // small 的用到了 fontSizeSM lineHeightSM Token
  const tagLineHeight = tagHeight - lineWidth * 2;

  const smallToken = mergeToken<TagToken>(token, {
    tagLineHeight,
    tagPaddingHorizontal: 4,
  });
  return genSizeTagStyle(smallToken, `${token.componentCls}-sm`);
};

const genSizeLargeTagStyle: GenerateStyle<TagToken> = (token) => {
  const { fontSizeLG, lineHeightLG, lineWidth } = token;

  const tagHeight = Math.round(fontSizeLG * lineHeightLG); // large 的用到了 fontSizeLG lineHeightLG Token
  const tagLineHeight = tagHeight - lineWidth * 2;

  const largeToken = mergeToken<TagToken>(token, {
    tagFontSize: token.fontSizeLG,
    tagLineHeight,
    tagPaddingHorizontal: 12,
  });
  return genSizeTagStyle(largeToken, `${token.componentCls}-lg`);
};

以上就是 AntDesignVue 主题应用的逻辑和主题修改的过程,Seed Token 的设计真的很巧妙。

参考资料:

相关推荐
小阮的学习笔记11 分钟前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜12 分钟前
Vue实现登录功能
前端·javascript·vue.js
杨荧14 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck16 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
嚣张农民2 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
落魄小二3 小时前
el-table 表格索引不展示问题
javascript·vue.js·elementui
neter.asia3 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js
十一吖i3 小时前
前端将后端返回的文件下载到本地
vue.js·elementplus
光影少年3 小时前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
熊的猫5 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js