Skip to content

xtf-linkui 自定义主题指南

本文档说明业务方如何在当前 xtf-linkui 主题系统中新增或覆盖主题。

适用对象:

  • 想新增一套完整业务主题
  • 想在运行时切换到自定义主题
  • 想只在某个页面或某个区域临时覆盖主题变量

1. 当前是否支持自定义主题

支持,现有主题系统已经具备以下能力:

  • 通过 themeManager.registerTheme(name, themeConfig) 注册新主题
  • 通过 themeManager.setTheme(name, overrides) 在运行时切换主题
  • 通过 themeManager.initTheme() 读取本地缓存主题
  • 通过 xtf-config-providerthemeVars 做局部主题覆盖

相关文件:

  • uni_modules/xtf-linkui/libs/theme/theme-manager.js
  • uni_modules/xtf-linkui/libs/theme/themes.js
  • uni_modules/xtf-linkui/components/xtf-config-provider/xtf-config-provider.vue
  • uni_modules/xtf-linkui/libs/config/default-config.js

2. 主题系统的两种扩展方式

推荐分成两种使用场景。

2.1 新增完整主题

适合:

  • 品牌换肤
  • 运营活动主题
  • 项目级主题方案

做法:通过 registerTheme 注册一个新主题,再通过 setTheme 切换。

2.2 局部覆盖当前主题

适合:

  • 某个页面做轻量视觉定制
  • 某个弹层、卡片区域临时改变主色
  • 不希望影响全局主题

做法:在 xtf-config-provider 上使用 themethemeVars

3. 快速开始

先确保应用已经引入全局主题样式,并初始化主题管理器。

3.1 引入主题样式

App.vue 中引入:

scss
@import "@/uni_modules/xtf-linkui/theme/index.scss";

3.2 初始化主题系统

在应用启动时执行:

js
import { themeManager } from '@/uni_modules/xtf-linkui'

themeManager.initTheme()

4. 新增完整主题

4.1 最简单的注册方式

js
import { themeManager } from '@/uni_modules/xtf-linkui'

themeManager.registerTheme('brandRed', {
  colors: {
    primary: '#c62828',
    primarySoft: 'rgba(198, 40, 40, 0.14)',
    success: '#2e7d32',
    warning: '#ed6c02',
    danger: '#c62828',
    info: '#8d6e63',
    background: '#fff8f7',
    surface: '#ffffff',
    surfaceMuted: '#fff1ef',
    surfaceElevated: 'rgba(255, 255, 255, 0.86)',
    text: '#2c1616',
    textHeading: '#1f0d0d',
    textSecondary: '#6d4c4c',
    textCaption: '#9b7a7a',
    textLabel: '#5a3f3f',
    textLink: '#c62828',
    textInverse: '#ffffff',
    textDisabled: '#c8b0b0',
    border: '#f0d6d4',
    overlay: 'rgba(44, 22, 22, 0.36)',
    mask: 'rgba(44, 22, 22, 0.52)',
    glass: 'rgba(255, 255, 255, 0.58)'
  }
})

themeManager.setTheme('brandRed')

这个示例的视觉方向是:

  • 主色明显偏大红色
  • 背景是轻微暖白和淡粉白
  • 文字不是纯黑,而是带一点红棕色,避免和品牌红割裂

4.2 这个示例里每个字段是什么意思

下面用上面的 brandRed 为例,逐项解释字段含义。

4.2.1 colors 是什么

colors 是语义层 token,决定整个主题的基础视觉角色。

它不是“某个按钮怎么画”,而是“整个系统里主色、正文色、背景色分别是什么”。

js
colors: {
  primary: '#c62828',
  primarySoft: 'rgba(198, 40, 40, 0.14)',
  success: '#2e7d32',
  warning: '#ed6c02',
  danger: '#c62828',
  info: '#8d6e63',
  background: '#fff8f7',
  surface: '#ffffff',
  surfaceMuted: '#fff1ef',
  surfaceElevated: 'rgba(255, 255, 255, 0.86)',
  text: '#2c1616',
  textHeading: '#1f0d0d',
  textSecondary: '#6d4c4c',
  textCaption: '#9b7a7a',
  textLabel: '#5a3f3f',
  textLink: '#c62828',
  textInverse: '#ffffff',
  textDisabled: '#c8b0b0',
  border: '#f0d6d4',
  overlay: 'rgba(44, 22, 22, 0.36)',
  mask: 'rgba(44, 22, 22, 0.52)',
  glass: 'rgba(255, 255, 255, 0.58)'
}

每个字段的作用:

  • primary
    • 主色。按钮主态、激活态、重点强调态的核心颜色。
  • primarySoft
    • 主色的浅背景版。常用于轻按钮、选中背景、focus ring、弱强调块。
  • success
    • 成功状态色。用于成功提示、完成状态、正向反馈。
  • warning
    • 警告状态色。用于提醒、待处理、注意类信息。
  • danger
    • 危险状态色。用于删除、报错、风险提示。
  • info
    • 中性信息色。用于较弱提示、辅助状态、信息标签。
  • background
    • 页面大背景色。通常是整个页面最底层背景。
  • surface
    • 标准内容面颜色。卡片、输入框、弹层主体经常会基于它派生。
  • surfaceMuted
    • 更弱一层的面色。适合分区块、浅底高亮、骨架底色。
  • surfaceElevated
    • 浮层或抬升内容面颜色。适合悬浮、浮卡、半透明高级面板。
  • text
    • 正文主文字颜色。默认最常用文本色。
  • textHeading
    • 标题文字颜色。一般对比度更高、更稳。
  • textSecondary
    • 次级说明文字。比如副标题、表单说明、附加信息。
  • textCaption
    • 更弱说明文字。比如时间、标签说明、辅助元信息。
  • textLabel
    • 标签型文字。适合字段名、说明标签、控制项标题。
  • textLink
    • 链接文字颜色。一般和主色接近。
  • textInverse
    • 反白文字。用于深色按钮、深色标签、主色按钮内部文字。
  • textDisabled
    • 禁用文字颜色。用于 disabled 态。
  • border
    • 通用边框色。卡片、输入框、分隔结构常用。
  • overlay
    • 覆盖层颜色。用于局部遮罩、图片信息遮层等。
  • mask
    • 更强的遮罩色。通常用于弹窗背景遮罩。
  • glass
    • 玻璃态底色。用于半透明场景。

4.2.2 componentTokens 是什么

componentTokens 是组件层 token。

当你发现只改 colors 还不够时,就用它精细控制具体组件。

它的作用是:

  • 不改组件源码结构
  • 只改组件“吃到的视觉变量”
  • 让某些高视觉权重组件更贴合你的品牌风格

例如:

js
themeManager.registerTheme('brandRed', {
  colors: {
    primary: '#c62828',
    primarySoft: 'rgba(198, 40, 40, 0.14)',
    text: '#2c1616',
    border: '#f0d6d4'
  },
  componentTokens: {
    card: {
      bg: 'rgba(255, 255, 255, 0.84)',
      border: 'rgba(255, 255, 255, 0.72)'
    },
    popup: {
      bg: 'rgba(255, 255, 255, 0.94)'
    },
    toast: {
      lightBg: 'rgba(255, 255, 255, 0.98)',
      lightBorder: 'rgba(198, 40, 40, 0.12)'
    },
    tabs: {
      active: '#c62828',
      soft: 'rgba(198, 40, 40, 0.12)'
    }
  }
})

逐项解释:

  • card.bg
    • 卡片主体背景色。
    • 影响 xtf-card、某些分组容器类视觉。
  • card.border
    • 卡片边框或玻璃卡片边缘的颜色。
  • popup.bg
    • 弹层主体背景色。
    • 影响 xtf-popup,并间接影响很多基于 popup 的组件。
  • toast.lightBg
    • light 风格 toast 的背景色。
  • toast.lightBorder
    • light 风格 toast 的边框色。
  • tabs.active
    • tabs 当前激活色。
  • tabs.soft
    • tabs 的浅色背景或弱强调态颜色。

4.3 什么时候只改 colors,什么时候还要改 componentTokens

可以这样理解:

  • 只想整体偏红、偏蓝、偏绿:先改 colors
  • 想让卡片、弹层、toast、tabs 也明显更像你的品牌:再改 componentTokens

经验建议:

  • 先写 colors
  • 看 demo
  • 哪些组件还是“像默认主题”,再给它补 componentTokens

4.4 推荐补齐的字段

虽然可以只改 primary,但更推荐至少补这些语义色:

  • primary
  • primarySoft
  • background
  • surface
  • surfaceMuted
  • text
  • textSecondary
  • textInverse
  • border
  • overlay
  • mask

这样按钮、卡片、输入框、弹层和文字层级会更统一。

4.5 补组件级 token

如果你的主题风格明显不同,建议再补 componentTokens。例如:

js
themeManager.registerTheme('brandRed', {
  colors: {
    primary: '#c62828',
    primarySoft: 'rgba(198, 40, 40, 0.14)',
    text: '#2c1616',
    border: '#f0d6d4'
  },
  componentTokens: {
    card: {
      bg: 'rgba(255, 255, 255, 0.84)',
      border: 'rgba(255, 255, 255, 0.72)'
    },
    popup: {
      bg: 'rgba(255, 255, 255, 0.94)'
    },
    toast: {
      lightBg: 'rgba(255, 255, 255, 0.98)',
      lightBorder: 'rgba(198, 40, 40, 0.12)'
    },
    tabs: {
      active: '#c62828',
      soft: 'rgba(198, 40, 40, 0.12)'
    }
  }
})

推荐优先关注这些组件 token:

  • card
  • popup
  • toast
  • tabs
  • tag
  • input
  • button

5. 运行时切换主题

5.1 全局切换

js
import { themeManager } from '@/uni_modules/xtf-linkui'

themeManager.setTheme('brandRed')

5.2 切换时同时传覆盖值

js
import { themeManager } from '@/uni_modules/xtf-linkui'

themeManager.setTheme('brandRed', {
  colors: {
    primary: '#b71c1c'
  },
  componentTokens: {
    badge: {
      bubbleEnd: '#e53935'
    }
  }
})

说明:

  • 第一个参数是主题名
  • 第二个参数是覆盖对象
  • 覆盖对象会参与持久化,重启应用后仍可恢复

6. 局部覆盖主题

如果你只想让某个页面或某个区域使用不同主色,不必注册新主题。

vue
<xtf-config-provider
  theme="light"
  :theme-vars="{
    colors: {
      primary: '#0f766e',
      primarySoft: 'rgba(15, 118, 110, 0.14)'
    },
    componentTokens: {
      card: {
        bg: 'rgba(255, 255, 255, 0.9)'
      }
    }
  }"
>
  <view>这里会使用局部覆盖后的主题变量</view>
</xtf-config-provider>

适合:

  • 某个运营专题页
  • 某个组件演示区
  • 某个弹窗局部改色

7. 如何查看当前可覆盖的字段

最直接的方式是查看:

  • uni_modules/xtf-linkui/libs/theme/themes.js

重点看两个层级:

  • colors
  • componentTokens

theme-manager 会把它们扁平化成 --xtf-* CSS 变量后注入组件。

例如:

  • colors.primary 会映射成 --xtf-color-primary
  • componentTokens.card.bg 会映射成 --xtf-card-bg
  • componentTokens.toast.lightBg 会映射成 --xtf-toast-light-bg

8. 自定义主题的推荐流程

推荐按下面顺序做:

  1. 先确定主题名称和风格方向,例如“品牌红”“冷蓝科技”“奶油暖白”
  2. 先补 colors 里的语义色
  3. 跑页面看整体感觉
  4. 再补强视觉组件的 componentTokens
  5. 最后补 demo 和文档

推荐优先验证这些页面:

  • pages/components/input/index.vue
  • pages/components/tabs/index.vue
  • pages/components/toast/index.vue
  • pages/components/card/index.vue
  • pages/components/form-controls/index.vue
  • pages/components/advanced/index.vue

9. 常见问题

9.1 只改了 primary,为什么很多组件看起来还是不协调?

因为很多组件除了主色,还依赖:

  • 背景层级
  • 边框色
  • 文字色
  • 遮罩色
  • 组件级 token

建议至少同时补 background / surface / text / border / mask

9.2 为什么文档示例改成“品牌红”?

因为自定义主题文档更适合使用一个视觉辨识度很强的例子。

相比自然绿,品牌红更容易让使用者一眼看出:

  • 主色变了
  • 次级背景也跟着变了
  • 卡片和 tabs 的组件级 token 也确实生效了

如果你把示例跑起来,通常会更容易确认主题系统是否按预期工作。

9.3 我需要手动改 theme/*.scss 吗?

通常不需要。

当前项目的主要主题来源是:

  • uni_modules/xtf-linkui/libs/theme/themes.js

只有当你想把这个主题作为正式内建主题长期维护时,才建议再补对应的 theme/*.scss 文件做静态样式兜底。

9.4 我应该从哪里查“还能覆盖哪些字段”?

最直接的方式是看:

  • uni_modules/xtf-linkui/libs/theme/themes.js

你看到的对象层级,就是你最适合覆盖的层级。

例如:

  • colors.primary
  • colors.textSecondary
  • componentTokens.toast.lightBg
  • componentTokens.tabs.active

如果某个组件你已经做过主题 token 收口,通常都能在 componentTokens 里找到它对应的分组。

9.5 自定义主题会不会影响命令式组件?

会同步生效。

因为 toast / dialog / loading / notify / action-sheet 都和普通组件共用同一套 themeManager 状态源。

9.6 如何恢复默认主题?

js
import { themeManager } from '@/uni_modules/xtf-linkui'

themeManager.resetTheme()

10. 推荐约定

为了后续维护更稳,建议业务主题遵守以下约定:

  • 主题名统一使用小写短横线或小写驼峰
  • 不直接覆盖组件内部结构 class
  • 优先覆盖 colors
  • 只有在视觉差异明显时才补 componentTokens
  • 把主题注册逻辑集中到一个文件里统一维护

例如可以新建:

  • common/theme/register-business-themes.js

集中处理所有业务主题注册。

MIT Licensed