diff --git a/apps/web-antdv-next/src/adapter/component/index.ts b/apps/web-antdv-next/src/adapter/component/index.ts index 4a505fdb..ac3ac5f6 100644 --- a/apps/web-antdv-next/src/adapter/component/index.ts +++ b/apps/web-antdv-next/src/adapter/component/index.ts @@ -726,9 +726,7 @@ async function initComponentAdapter() { modelValueProp: 'value', }), Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input', { - style: { width: '100%' }, - }), + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 diff --git a/apps/web-antdv-next/src/app.vue b/apps/web-antdv-next/src/app.vue index 518f5068..46366e91 100644 --- a/apps/web-antdv-next/src/app.vue +++ b/apps/web-antdv-next/src/app.vue @@ -4,7 +4,7 @@ import { computed, watch } from 'vue'; import { useAntdDesignTokens } from '@vben/hooks'; import { preferences, usePreferences } from '@vben/preferences'; -import { App, ConfigProvider, theme } from 'antdv-next'; +import { App, ConfigProvider, StyleProvider, theme } from 'antdv-next'; import { antdLocale } from '#/locales'; @@ -39,9 +39,12 @@ watch( diff --git a/apps/web-ele/src/adapter/component/index.ts b/apps/web-ele/src/adapter/component/index.ts index 28d87c56..d4ba55c1 100644 --- a/apps/web-ele/src/adapter/component/index.ts +++ b/apps/web-ele/src/adapter/component/index.ts @@ -284,9 +284,7 @@ async function initComponentAdapter() { inputComponent: ElInput, }), Input: withDefaultPlaceholder(ElInput, 'input'), - InputNumber: withDefaultPlaceholder(ElInputNumber, 'input', { - style: { width: '100%' }, - }), + InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), RadioGroup: (props, { attrs, slots }) => { let defaultSlot; if (Reflect.has(slots, 'default')) { diff --git a/apps/web-ele/vite.config.ts b/apps/web-ele/vite.config.ts index 9f1e7235..c50186cc 100644 --- a/apps/web-ele/vite.config.ts +++ b/apps/web-ele/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from '@vben/vite-config'; +import { defineConfig, viteCssLayerPlugin } from '@vben/vite-config'; import ElementPlus from 'unplugin-element-plus/vite'; @@ -7,9 +7,9 @@ export default defineConfig(async () => { application: {}, vite: { plugins: [ - ElementPlus({ - format: 'esm', - }), + // element-plus 的 css 包进 @layer el,使 Tailwind 工具类可覆盖组件样式 + viteCssLayerPlugin({ layerName: 'el', packageName: 'element-plus' }), + ElementPlus({ format: 'esm' }), ], server: { proxy: { diff --git a/apps/web-naive/src/adapter/component/index.ts b/apps/web-naive/src/adapter/component/index.ts index 9ee677f8..98601656 100644 --- a/apps/web-naive/src/adapter/component/index.ts +++ b/apps/web-naive/src/adapter/component/index.ts @@ -225,9 +225,7 @@ async function initComponentAdapter() { inputComponent: NInput, }), Input: withDefaultPlaceholder(NInput, 'input'), - InputNumber: withDefaultPlaceholder(NInputNumber, 'input', { - style: { width: '100%' }, - }), + InputNumber: withDefaultPlaceholder(NInputNumber, 'input'), RadioGroup: (props, { attrs, slots }) => { let defaultSlot; if (Reflect.has(slots, 'default')) { diff --git a/apps/web-tdesign/src/adapter/component/index.ts b/apps/web-tdesign/src/adapter/component/index.ts index 37a3055e..c2f9634d 100644 --- a/apps/web-tdesign/src/adapter/component/index.ts +++ b/apps/web-tdesign/src/adapter/component/index.ts @@ -239,9 +239,7 @@ async function initComponentAdapter() { modelValueProp: 'value', }), Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input', { - style: { width: '100%' }, - }), + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), // InputPassword: withDefaultPlaceholder(InputPassword, 'input'), // Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 diff --git a/apps/web-tdesign/vite.config.ts b/apps/web-tdesign/vite.config.ts index b6360f1d..c79ec9a4 100644 --- a/apps/web-tdesign/vite.config.ts +++ b/apps/web-tdesign/vite.config.ts @@ -1,9 +1,16 @@ -import { defineConfig } from '@vben/vite-config'; +import { defineConfig, viteCssLayerPlugin } from '@vben/vite-config'; export default defineConfig(async () => { return { application: {}, vite: { + plugins: [ + // tdesign 的 css 包进 @layer td,使 Tailwind 工具类可覆盖组件样式 + viteCssLayerPlugin({ + layerName: 'td', + packageName: 'tdesign-vue-next', + }), + ], server: { proxy: { '/api': { diff --git a/internal/tailwind-config/src/theme.css b/internal/tailwind-config/src/theme.css index 076c1cfb..2bf31bbe 100644 --- a/internal/tailwind-config/src/theme.css +++ b/internal/tailwind-config/src/theme.css @@ -1,3 +1,12 @@ +/* + * 级联层顺序声明(必须先于 @import 'tailwindcss' 首次出现): + * preflight(base) < UI 库组件样式(antd / el / td) < Tailwind 工具类, + * 使组件库样式(antdv-next 经 StyleProvider layer 注入 @layer antd; + * element-plus / tdesign 经各自应用的 vite css-layer 插件包入对应层) + * 能被工具类覆盖 + */ +@layer theme, base, ant, antd, el, td, components, utilities; + @import 'tailwindcss'; @import 'tw-animate-css'; @@ -301,7 +310,7 @@ } html { - @apply text-foreground bg-background font-sans; + @apply bg-background font-sans text-foreground; scroll-behavior: smooth; font-size: var(--font-size-base, 16px); @@ -369,7 +378,7 @@ } ::-webkit-scrollbar-thumb { - @apply bg-border rounded-sm border-none; + @apply rounded-sm border-none bg-border; } ::-webkit-scrollbar-track { @@ -407,7 +416,7 @@ * 见:https://tailwindcss.com/docs/adding-custom-styles#using-css-and-layering */ @layer components { .outline-box { - @apply outline-border relative cursor-pointer rounded-md p-1 outline-1; + @apply relative cursor-pointer rounded-md p-1 outline-1 outline-border; } .outline-box::after { @@ -415,7 +424,7 @@ } .outline-box.outline-box-active { - @apply outline-primary outline-2; + @apply outline-2 outline-primary; } .outline-box.outline-box-active::after { @@ -423,15 +432,15 @@ } .outline-box:not(.outline-box-active):hover::after { - @apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100; + @apply top-0 left-0 h-full w-full p-1 opacity-100 outline-primary; } .vben-link { - @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer; + @apply cursor-pointer text-primary hover:text-primary-hover active:text-primary-active; } .card-box { - @apply bg-card text-card-foreground border-border rounded-xl border; + @apply rounded-xl border border-border bg-card text-card-foreground; } } diff --git a/internal/vite-config/src/plugins/css-layer.ts b/internal/vite-config/src/plugins/css-layer.ts new file mode 100644 index 00000000..d12e60cb --- /dev/null +++ b/internal/vite-config/src/plugins/css-layer.ts @@ -0,0 +1,45 @@ +import type { Plugin } from 'vite'; + +interface CssLayerRule { + /** 层名,需在 css 中先于 @import 'tailwindcss' 声明层顺序(见 internal/tailwind-config/theme.css) */ + layerName: string; + /** 需要包层的包名(匹配该包在 node_modules 下的 css 模块 id) */ + packageName: string; +} + +function escapeRegExp(value: string): string { + return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); +} + +/** + * 把指定包内的 css 包进 @layer: + * 组件库的 css 是无层样式,无层样式在级联中永远压过 @layer 内的 Tailwind 工具类; + * 包层后由 theme.css 的层声明决定顺序(utilities 排在组件库层之后), + * 使 Tailwind 工具类可以覆盖组件库样式。 + * + * @example + * ```ts + * plugins: [viteCssLayerPlugin({ packageName: 'element-plus', layerName: 'el' })] + * ``` + */ +export function viteCssLayerPlugin( + rules: CssLayerRule | CssLayerRule[], +): Plugin { + const list = Array.isArray(rules) ? rules : [rules]; + const matchers = list.map(({ layerName, packageName }) => ({ + layerName, + regex: new RegExp(`${escapeRegExp(packageName)}[\\\\/].+\\.css$`, 'i'), + })); + return { + name: 'vite-plugin-css-layer', + enforce: 'pre', + transform(code, id) { + const [file] = id.split('?', 1); + if (!file) return; + const matched = matchers.find((m) => m.regex.test(file)); + if (matched) { + return { code: `@layer ${matched.layerName} {\n${code}\n}`, map: null }; + } + }, + }; +} diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index 7488ab32..f7bdfd28 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -248,6 +248,8 @@ async function loadLibraryPlugins( ]); } +export { viteCssLayerPlugin } from './css-layer'; + export { loadApplicationPlugins, loadLibraryPlugins, diff --git a/playground/src/adapter/component/index.ts b/playground/src/adapter/component/index.ts index 9a975a2d..bd41e361 100644 --- a/playground/src/adapter/component/index.ts +++ b/playground/src/adapter/component/index.ts @@ -726,9 +726,7 @@ async function initComponentAdapter() { modelValueProp: 'value', }), Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input', { - style: { width: '100%' }, - }), + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 diff --git a/playground/src/app.vue b/playground/src/app.vue index 518f5068..46366e91 100644 --- a/playground/src/app.vue +++ b/playground/src/app.vue @@ -4,7 +4,7 @@ import { computed, watch } from 'vue'; import { useAntdDesignTokens } from '@vben/hooks'; import { preferences, usePreferences } from '@vben/preferences'; -import { App, ConfigProvider, theme } from 'antdv-next'; +import { App, ConfigProvider, StyleProvider, theme } from 'antdv-next'; import { antdLocale } from '#/locales'; @@ -39,9 +39,12 @@ watch(