feat: add useTDesignDesignTokens

pull/355/head^2
金毛88 2026-06-03 13:25:23 +08:00
parent 7fd4b1de04
commit 0eb72ca4fd
10 changed files with 1617 additions and 1857 deletions

View File

@ -3,6 +3,7 @@ import type { GlobalConfigProvider } from 'tdesign-vue-next';
import { watch } from 'vue'; import { watch } from 'vue';
import { useTDesignDesignTokens } from '@vben/hooks';
import { usePreferences } from '@vben/preferences'; import { usePreferences } from '@vben/preferences';
import { merge } from 'es-toolkit/compat'; import { merge } from 'es-toolkit/compat';
@ -12,10 +13,16 @@ import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
defineOptions({ name: 'App' }); defineOptions({ name: 'App' });
const { isDark } = usePreferences(); const { isDark } = usePreferences();
// Vben CSS TDesign
useTDesignDesignTokens();
watch( watch(
() => isDark.value, () => isDark.value,
(dark) => { (dark) => {
document.documentElement.setAttribute('theme-mode', dark ? 'dark' : ''); document.documentElement.setAttribute(
'theme-mode',
dark ? 'dark' : 'light',
);
}, },
{ immediate: true }, { immediate: true },
); );

View File

@ -5,8 +5,6 @@ import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores'; import { initStores } from '@vben/stores';
import '@vben/styles'; import '@vben/styles';
// import '@vben/styles/antd';
// 引入组件库的少量全局样式变量
import { useTitle } from '@vueuse/core'; import { useTitle } from '@vueuse/core';
@ -17,6 +15,7 @@ import { initSetupVbenForm } from './adapter/form';
import App from './app.vue'; import App from './app.vue';
import { router } from './router'; import { router } from './router';
// 引入组件库的少量全局样式变量
import 'tdesign-vue-next/es/style/index.css'; import 'tdesign-vue-next/es/style/index.css';
async function bootstrap(namespace: string) { async function bootstrap(namespace: string) {

View File

@ -32,7 +32,7 @@ export async function node(): Promise<Linter.Config[]> {
'error', 'error',
{ {
ignores: [], ignores: [],
version: '>=20.12.0', version: '>=22.18.0',
}, },
], ],
'n/prefer-global/buffer': ['error', 'never'], 'n/prefer-global/buffer': ['error', 'never'],

View File

@ -106,5 +106,5 @@
"node": "^22.18.0 || ^24.0.0", "node": "^22.18.0 || ^24.0.0",
"pnpm": ">=11.0.0" "pnpm": ">=11.0.0"
}, },
"packageManager": "pnpm@11.4.0" "packageManager": "pnpm@11.5.0"
} }

View File

@ -28,3 +28,21 @@ it('updateCSSVariables should update CSS variables in :root selector', () => {
updatedStyleContent?.includes('fontSize: 16px;'), updatedStyleContent?.includes('fontSize: 16px;'),
).toBe(true); ).toBe(true);
}); });
it('updateCSSVariables should support a custom selector', () => {
document.head.innerHTML = `<style id="tdesign-styles"></style>`;
// 使用自定义选择器(如 TDesign 的 theme-mode 选择器)更新 CSS 变量
updateCSSVariables(
{ '--td-brand-color': 'rgb(0, 82, 217)' },
'tdesign-styles',
":root[theme-mode='dark']",
);
const styleElement = document.querySelector('#tdesign-styles');
const content = styleElement?.textContent ?? '';
// 选择器与变量都应正确写入
expect(content.startsWith(":root[theme-mode='dark'] {")).toBe(true);
expect(content.includes('--td-brand-color: rgb(0, 82, 217);')).toBe(true);
});

View File

@ -1,10 +1,15 @@
/** /**
* CSS * CSS
* @param variables CSS * @param variables CSS
* @param id id便
* @param selector CSS `:root`
* TDesign `:root[theme-mode='dark']`
*
*/ */
function updateCSSVariables( function updateCSSVariables(
variables: { [key: string]: string }, variables: { [key: string]: string },
id = '__vben-styles__', id = '__vben-styles__',
selector = ':root',
): void { ): void {
// 获取或创建内联样式表元素 // 获取或创建内联样式表元素
const styleElement = const styleElement =
@ -13,7 +18,7 @@ function updateCSSVariables(
styleElement.id = id; styleElement.id = id;
// 构建要更新的 CSS 变量的样式文本 // 构建要更新的 CSS 变量的样式文本
let cssText = ':root {'; let cssText = `${selector} {`;
for (const key in variables) { for (const key in variables) {
if (Object.prototype.hasOwnProperty.call(variables, key)) { if (Object.prototype.hasOwnProperty.call(variables, key)) {
cssText += `${key}: ${variables[key]};`; cssText += `${key}: ${variables[key]};`;

View File

@ -37,7 +37,7 @@ import SubMenu from './sub-menu.vue';
interface Props extends MenuProps {} interface Props extends MenuProps {}
defineOptions({ name: 'Menu' }); defineOptions({ name: 'MenuUI' });
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
accordion: true, accordion: true,

View File

@ -319,3 +319,103 @@ export function useElementPlusDesignTokens() {
{ immediate: true }, { immediate: true },
); );
} }
export function useTDesignDesignTokens() {
const { isDark } = usePreferences();
const rootStyles = getComputedStyle(document.documentElement);
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
const value = rootStyles.getPropertyValue(variable);
return isColor ? convertToRgb(`hsl(${value})`) : value;
};
/**
* TDesign + 1~10
* TDesign 1 10
* Vben 50~900
* @param tdName TDesign `brand``error`
* @param vbenName Vben `primary``destructive`
*/
const getColorTokens = (tdName: string, vbenName: string) => {
const dark = isDark.value;
const getColor = (level?: number) =>
getCssVariableValue(
level === undefined ? `--${vbenName}` : `--${vbenName}-${level}`,
);
// 亮色模式 1 最浅、10 最深;暗色模式相反
const lightScale = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
const scaleLevels = dark ? [...lightScale].toReversed() : lightScale;
const scale: Record<string, string> = {};
scaleLevels.forEach((level, index) => {
scale[`--td-${tdName}-color-${index + 1}`] = getColor(level);
});
return {
[`--td-${tdName}-color`]: getColor(),
[`--td-${tdName}-color-active`]: getColor(dark ? 300 : 700),
[`--td-${tdName}-color-disabled`]: getColor(dark ? 800 : 200),
[`--td-${tdName}-color-focus`]: getColor(dark ? 900 : 100),
[`--td-${tdName}-color-hover`]: getColor(dark ? 400 : 600),
[`--td-${tdName}-color-light`]: getColor(dark ? 950 : 50),
[`--td-${tdName}-color-light-hover`]: getColor(dark ? 900 : 100),
...scale,
};
};
watch(
() => preferences.theme,
() => {
const variables: Record<string, string> = {
// 品牌色(主题色)、功能色
...getColorTokens('brand', 'primary'),
...getColorTokens('error', 'destructive'),
...getColorTokens('warning', 'warning'),
...getColorTokens('success', 'success'),
// 文字颜色
'--td-text-color-anti': getCssVariableValue('--primary-foreground'),
'--td-text-color-brand': getCssVariableValue('--primary'),
'--td-text-color-disabled': getCssVariableValue('--muted-foreground'),
'--td-text-color-link': getCssVariableValue('--primary'),
'--td-text-color-placeholder': getCssVariableValue(
'--input-placeholder',
),
'--td-text-color-primary': getCssVariableValue('--foreground'),
'--td-text-color-secondary': getCssVariableValue('--muted-foreground'),
// 背景颜色
'--td-bg-color-component': getCssVariableValue('--accent'),
'--td-bg-color-component-active':
getCssVariableValue('--accent-darker'),
'--td-bg-color-component-disabled': getCssVariableValue('--muted'),
'--td-bg-color-component-hover': getCssVariableValue('--accent-hover'),
'--td-bg-color-container': getCssVariableValue('--background'),
'--td-bg-color-container-active':
getCssVariableValue('--accent-darker'),
'--td-bg-color-container-hover': getCssVariableValue('--accent'),
'--td-bg-color-container-select': getCssVariableValue('--accent'),
'--td-bg-color-page': getCssVariableValue('--background-deep'),
'--td-bg-color-secondarycontainer': getCssVariableValue('--card'),
// 边框颜色
'--td-border-level-1-color': getCssVariableValue('--border'),
'--td-border-level-2-color': getCssVariableValue('--border'),
'--td-component-border': getCssVariableValue('--border'),
'--td-component-stroke': getCssVariableValue('--border'),
// 圆角
'--td-radius-default': getCssVariableValue('--radius', false),
};
// TDesign 将亮/暗变量定义在 `:root[theme-mode='light'|'dark']` 下,
// 需要写入相同优先级的选择器才能覆盖默认值
const selector = isDark.value
? `:root[theme-mode='dark']`
: `:root[theme-mode='light']`;
updateCSSVariables(variables, `__vben_design_tdesign_styles__`, selector);
},
{ immediate: true },
);
}

File diff suppressed because it is too large Load Diff

View File

@ -41,20 +41,20 @@ catalog:
'@changesets/changelog-github': ^0.7.0 '@changesets/changelog-github': ^0.7.0
'@changesets/cli': ^2.31.0 '@changesets/cli': ^2.31.0
'@changesets/git': ^3.0.4 '@changesets/git': ^3.0.4
'@clack/prompts': ^1.4.0 '@clack/prompts': ^1.5.0
'@commitlint/cli': ^21.0.1 '@commitlint/cli': ^21.0.2
'@commitlint/config-conventional': ^21.0.1 '@commitlint/config-conventional': ^21.0.2
'@ctrl/tinycolor': ^4.2.0 '@ctrl/tinycolor': ^4.2.0
'@eslint-community/eslint-plugin-eslint-comments': ^4.7.2 '@eslint-community/eslint-plugin-eslint-comments': ^4.7.2
'@eslint/js': ^10.0.1 '@eslint/js': ^10.0.1
'@faker-js/faker': ^10.4.0 '@faker-js/faker': ^10.4.0
'@iconify/json': ^2.2.479 '@iconify/json': ^2.2.481
'@iconify/tailwind4': ^1.2.3 '@iconify/tailwind4': ^1.2.3
'@iconify/vue': ^5.0.1 '@iconify/vue': ^5.0.1
'@intlify/core-base': ^11.4.4 '@intlify/core-base': ^11.4.4
'@intlify/unplugin-vue-i18n': ^11.2.3 '@intlify/unplugin-vue-i18n': ^11.2.3
'@jspm/generator': ^2.16.1 '@jspm/generator': ^2.16.1
'@lucide/vue': ^1.16.0 '@lucide/vue': ^1.17.0
'@manypkg/get-packages': ^3.1.0 '@manypkg/get-packages': ^3.1.0
'@nolebase/vitepress-plugin-git-changelog': ^2.18.2 '@nolebase/vitepress-plugin-git-changelog': ^2.18.2
'@playwright/test': ^1.60.0 '@playwright/test': ^1.60.0
@ -64,19 +64,19 @@ catalog:
'@tailwindcss/vite': ^4.3.0 '@tailwindcss/vite': ^4.3.0
'@tanstack/vue-query': ^5.100.14 '@tanstack/vue-query': ^5.100.14
'@tanstack/vue-store': ^0.11.0 '@tanstack/vue-store': ^0.11.0
'@tiptap/core': ^3.23.6 '@tiptap/core': ^3.24.0
'@tiptap/extension-document': ^3.23.6 '@tiptap/extension-document': ^3.24.0
'@tiptap/extension-highlight': ^3.23.6 '@tiptap/extension-highlight': ^3.24.0
'@tiptap/extension-image': ^3.23.6 '@tiptap/extension-image': ^3.24.0
'@tiptap/extension-link': ^3.23.6 '@tiptap/extension-link': ^3.24.0
'@tiptap/extension-placeholder': ^3.23.6 '@tiptap/extension-placeholder': ^3.24.0
'@tiptap/extension-text-align': ^3.23.6 '@tiptap/extension-text-align': ^3.24.0
'@tiptap/extension-text-style': ^3.23.6 '@tiptap/extension-text-style': ^3.24.0
'@tiptap/extension-underline': ^3.23.6 '@tiptap/extension-underline': ^3.24.0
'@tiptap/pm': ^3.23.6 '@tiptap/pm': ^3.24.0
'@tiptap/starter-kit': ^3.23.6 '@tiptap/starter-kit': ^3.24.0
'@tiptap/vue-3': ^3.23.6 '@tiptap/vue-3': ^3.24.0
'@tsdown/css': ^0.22.0 '@tsdown/css': ^0.22.1
'@types/archiver': ^7.0.0 '@types/archiver': ^7.0.0
'@types/html-minifier-terser': ^7.0.2 '@types/html-minifier-terser': ^7.0.2
'@types/json-bigint': ^1.0.4 '@types/json-bigint': ^1.0.4
@ -87,8 +87,8 @@ catalog:
'@types/qrcode': ^1.5.6 '@types/qrcode': ^1.5.6
'@types/qs': ^6.15.1 '@types/qs': ^6.15.1
'@types/sortablejs': ^1.15.9 '@types/sortablejs': ^1.15.9
'@typescript-eslint/eslint-plugin': ^8.60.0 '@typescript-eslint/eslint-plugin': ^8.60.1
'@typescript-eslint/parser': ^8.60.0 '@typescript-eslint/parser': ^8.60.1
'@vee-validate/zod': ^4.15.1 '@vee-validate/zod': ^4.15.1
'@vite-pwa/vitepress': ^1.1.0 '@vite-pwa/vitepress': ^1.1.0
'@vitejs/plugin-vue': ^6.0.7 '@vitejs/plugin-vue': ^6.0.7
@ -112,26 +112,26 @@ catalog:
commitlint-plugin-function-rules: ^5.0.1 commitlint-plugin-function-rules: ^5.0.1
consola: ^3.4.2 consola: ^3.4.2
cross-env: ^10.1.0 cross-env: ^10.1.0
cspell: ^10.0.0 cspell: ^10.0.1
cz-git: ^1.13.1 cz-git: ^1.13.1
czg: ^1.13.1 czg: ^1.13.1
dayjs: ^1.11.21 dayjs: ^1.11.21
defu: ^6.1.7 defu: ^6.1.7
dotenv: ^17.4.2 dotenv: ^17.4.2
echarts: ^6.1.0 echarts: ^6.1.0
element-plus: ^2.14.0 element-plus: ^2.14.1
es-toolkit: ^1.47.0 es-toolkit: ^1.47.0
eslint: ^10.4.0 eslint: ^10.4.1
eslint-plugin-better-tailwindcss: ^4.5.0 eslint-plugin-better-tailwindcss: ^4.5.0
eslint-plugin-command: ^3.5.2 eslint-plugin-command: ^3.5.2
eslint-plugin-jsonc: ^3.1.2 eslint-plugin-jsonc: ^3.2.0
eslint-plugin-n: ^18.0.1 eslint-plugin-n: ^18.0.1
eslint-plugin-perfectionist: ^5.9.0 eslint-plugin-perfectionist: ^5.9.0
eslint-plugin-pnpm: ^1.6.1 eslint-plugin-pnpm: ^1.6.1
eslint-plugin-unicorn: ^64.0.0 eslint-plugin-unicorn: ^64.0.0
eslint-plugin-unused-imports: ^4.4.1 eslint-plugin-unused-imports: ^4.4.1
eslint-plugin-vue: ^10.9.1 eslint-plugin-vue: ^10.9.1
eslint-plugin-yml: ^3.3.2 eslint-plugin-yml: ^3.4.0
execa: ^9.6.1 execa: ^9.6.1
find-up: ^8.0.0 find-up: ^8.0.0
get-port: ^7.2.0 get-port: ^7.2.0
@ -142,16 +142,16 @@ catalog:
is-ci: ^4.1.0 is-ci: ^4.1.0
json-bigint: ^1.0.0 json-bigint: ^1.0.0
jsonwebtoken: ^9.0.3 jsonwebtoken: ^9.0.3
knip: ^6.14.2 knip: ^6.15.0
lefthook: ^2.1.8 lefthook: ^2.1.9
lodash.clonedeep: ^4.5.0 lodash.clonedeep: ^4.5.0
medium-zoom: ^1.1.0 medium-zoom: ^1.1.0
naive-ui: ^2.44.1 naive-ui: ^2.44.1
nitropack: ^2.13.4 nitropack: ^2.13.4
nprogress: ^0.2.0 nprogress: ^0.2.0
ora: ^9.4.0 ora: ^9.4.0
oxfmt: ^0.52.0 oxfmt: ^0.53.0
oxlint: ^1.67.0 oxlint: ^1.68.0
oxlint-tsgolint: ^0.23.0 oxlint-tsgolint: ^0.23.0
pinia: ^3.0.4 pinia: ^3.0.4
pinia-plugin-persistedstate: ^4.7.1 pinia-plugin-persistedstate: ^4.7.1
@ -184,31 +184,31 @@ catalog:
tdesign-vue-next: ^1.20.0 tdesign-vue-next: ^1.20.0
theme-colors: ^0.1.0 theme-colors: ^0.1.0
tippy.js: ^6.3.7 tippy.js: ^6.3.7
tsdown: ^0.22.0 tsdown: ^0.22.1
turbo: ^2.9.15 turbo: ^2.9.16
tw-animate-css: ^1.4.0 tw-animate-css: ^1.4.0
typescript: ^6.0.3 typescript: ^6.0.3
unplugin-dts: ^1.0.1 unplugin-dts: ^1.0.2
unplugin-element-plus: ^0.11.2 unplugin-element-plus: ^0.11.2
unplugin-vue: ^7.2.0 unplugin-vue: ^7.2.0
vee-validate: ^4.15.1 vee-validate: ^4.15.1
vite: 8.0.10 vite: ^8.0.16
vite-plugin-compression: ^0.5.1 vite-plugin-compression: ^0.5.1
vite-plugin-lazy-import: ^1.0.7 vite-plugin-lazy-import: ^1.0.7
vite-plugin-pwa: ^1.3.0 vite-plugin-pwa: ^1.3.0
vite-plugin-vue-devtools: ^8.1.2 vite-plugin-vue-devtools: ^8.1.2
vitepress: ^2.0.0-alpha.17 vitepress: ^2.0.0-alpha.17
vitepress-plugin-group-icons: ^1.7.5 vitepress-plugin-group-icons: ^1.7.5
vitest: ^4.1.7 vitest: ^4.1.8
vue: ^3.5.35 vue: ^3.5.35
vue-eslint-parser: ^10.4.0 vue-eslint-parser: ^10.4.0
vue-i18n: ^11.4.4 vue-i18n: ^11.4.4
vue-json-pretty: ^2.6.0 vue-json-pretty: ^2.6.0
vue-router: ^5.0.7 vue-router: ^5.1.0
vue-tippy: ^6.7.1 vue-tippy: ^6.7.1
vue-tsc: ^3.3.2 vue-tsc: ^3.3.3
vxe-pc-ui: ^4.14.24 vxe-pc-ui: ^4.14.26
vxe-table: ^4.19.3 vxe-table: ^4.19.6
watermark-js-plus: ^1.6.3 watermark-js-plus: ^1.6.3
yaml-eslint-parser: ^2.0.0 yaml-eslint-parser: ^2.0.0
zod: ^3.25.76 zod: ^3.25.76