xtf-linkui 自定义主题指南
本文档说明业务方如何在当前 xtf-linkui 主题系统中新增或覆盖主题。
适用对象:
- 想新增一套完整业务主题
- 想在运行时切换到自定义主题
- 想只在某个页面或某个区域临时覆盖主题变量
1. 当前是否支持自定义主题
支持,现有主题系统已经具备以下能力:
- 通过
themeManager.registerTheme(name, themeConfig)注册新主题 - 通过
themeManager.setTheme(name, overrides)在运行时切换主题 - 通过
themeManager.initTheme()读取本地缓存主题 - 通过
xtf-config-provider的themeVars做局部主题覆盖
相关文件:
uni_modules/xtf-linkui/libs/theme/theme-manager.jsuni_modules/xtf-linkui/libs/theme/themes.jsuni_modules/xtf-linkui/components/xtf-config-provider/xtf-config-provider.vueuni_modules/xtf-linkui/libs/config/default-config.js
2. 主题系统的两种扩展方式
推荐分成两种使用场景。
2.1 新增完整主题
适合:
- 品牌换肤
- 运营活动主题
- 项目级主题方案
做法:通过 registerTheme 注册一个新主题,再通过 setTheme 切换。
2.2 局部覆盖当前主题
适合:
- 某个页面做轻量视觉定制
- 某个弹层、卡片区域临时改变主色
- 不希望影响全局主题
做法:在 xtf-config-provider 上使用 theme 和 themeVars。
3. 快速开始
先确保应用已经引入全局主题样式,并初始化主题管理器。
3.1 引入主题样式
在 App.vue 中引入:
@import "@/uni_modules/xtf-linkui/theme/index.scss";3.2 初始化主题系统
在应用启动时执行:
import { themeManager } from '@/uni_modules/xtf-linkui'
themeManager.initTheme()4. 新增完整主题
4.1 最简单的注册方式
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,决定整个主题的基础视觉角色。
它不是“某个按钮怎么画”,而是“整个系统里主色、正文色、背景色分别是什么”。
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 还不够时,就用它精细控制具体组件。
它的作用是:
- 不改组件源码结构
- 只改组件“吃到的视觉变量”
- 让某些高视觉权重组件更贴合你的品牌风格
例如:
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.lightBglight风格 toast 的背景色。
toast.lightBorderlight风格 toast 的边框色。
tabs.active- tabs 当前激活色。
tabs.soft- tabs 的浅色背景或弱强调态颜色。
4.3 什么时候只改 colors,什么时候还要改 componentTokens
可以这样理解:
- 只想整体偏红、偏蓝、偏绿:先改
colors - 想让卡片、弹层、toast、tabs 也明显更像你的品牌:再改
componentTokens
经验建议:
- 先写
colors - 看 demo
- 哪些组件还是“像默认主题”,再给它补
componentTokens
4.4 推荐补齐的字段
虽然可以只改 primary,但更推荐至少补这些语义色:
primaryprimarySoftbackgroundsurfacesurfaceMutedtexttextSecondarytextInverseborderoverlaymask
这样按钮、卡片、输入框、弹层和文字层级会更统一。
4.5 补组件级 token
如果你的主题风格明显不同,建议再补 componentTokens。例如:
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:
cardpopuptoasttabstaginputbutton
5. 运行时切换主题
5.1 全局切换
import { themeManager } from '@/uni_modules/xtf-linkui'
themeManager.setTheme('brandRed')5.2 切换时同时传覆盖值
import { themeManager } from '@/uni_modules/xtf-linkui'
themeManager.setTheme('brandRed', {
colors: {
primary: '#b71c1c'
},
componentTokens: {
badge: {
bubbleEnd: '#e53935'
}
}
})说明:
- 第一个参数是主题名
- 第二个参数是覆盖对象
- 覆盖对象会参与持久化,重启应用后仍可恢复
6. 局部覆盖主题
如果你只想让某个页面或某个区域使用不同主色,不必注册新主题。
<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
重点看两个层级:
colorscomponentTokens
theme-manager 会把它们扁平化成 --xtf-* CSS 变量后注入组件。
例如:
colors.primary会映射成--xtf-color-primarycomponentTokens.card.bg会映射成--xtf-card-bgcomponentTokens.toast.lightBg会映射成--xtf-toast-light-bg
8. 自定义主题的推荐流程
推荐按下面顺序做:
- 先确定主题名称和风格方向,例如“品牌红”“冷蓝科技”“奶油暖白”
- 先补
colors里的语义色 - 跑页面看整体感觉
- 再补强视觉组件的
componentTokens - 最后补 demo 和文档
推荐优先验证这些页面:
pages/components/input/index.vuepages/components/tabs/index.vuepages/components/toast/index.vuepages/components/card/index.vuepages/components/form-controls/index.vuepages/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.primarycolors.textSecondarycomponentTokens.toast.lightBgcomponentTokens.tabs.active
如果某个组件你已经做过主题 token 收口,通常都能在 componentTokens 里找到它对应的分组。
9.5 自定义主题会不会影响命令式组件?
会同步生效。
因为 toast / dialog / loading / notify / action-sheet 都和普通组件共用同一套 themeManager 状态源。
9.6 如何恢复默认主题?
import { themeManager } from '@/uni_modules/xtf-linkui'
themeManager.resetTheme()10. 推荐约定
为了后续维护更稳,建议业务主题遵守以下约定:
- 主题名统一使用小写短横线或小写驼峰
- 不直接覆盖组件内部结构 class
- 优先覆盖
colors - 只有在视觉差异明显时才补
componentTokens - 把主题注册逻辑集中到一个文件里统一维护
例如可以新建:
common/theme/register-business-themes.js
集中处理所有业务主题注册。