From 86c05e71a25ca5dc5330328e4884b33983c039c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 14 Sep 2026 11:21:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E8=A1=A5=E9=BD=90=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=BC=8F=E8=B0=83=E7=94=A8=E7=9A=84=20EP=20=E6=8C=89?= =?UTF-8?q?=E9=9C=80=E6=A0=B7=E5=BC=8F=EF=BC=8C=E4=BF=AE=E6=8B=A6=E6=88=AA?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E8=B4=B4=E5=88=B0=E6=96=87=E6=A1=A3=E5=B7=A6?= =?UTF-8?q?=E4=B8=8A=E8=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Element Plus 走按需引入,只有模板里用到的组件才会注入样式;ElMessage / ElMessageBox 都是 import 后函数式调用,构建产物里一条 .el-message-box 规则都没有, 弹窗因此没有定位与遮罩,渲染成普通块元素压在页头文字上(ElMessage 提示条同样受影响)。 入口显式引入两个样式模块,并加测试钉住「显式 import 的 EP 组件必须有样式引入」。 --- frontend-vue/src/main.ts | 5 ++ .../tests/ep-imperative-style.test.ts | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 frontend-vue/tests/ep-imperative-style.test.ts diff --git a/frontend-vue/src/main.ts b/frontend-vue/src/main.ts index 14daf2fe..c51a7d9d 100644 --- a/frontend-vue/src/main.ts +++ b/frontend-vue/src/main.ts @@ -1,5 +1,10 @@ import { createApp } from 'vue' import '@/styles/main.css' +// 命令式调用的组件样式必须在这里显式引入:ElMessage / ElMessageBox 由函数调用触发, +// 不出现在任何模板里,ElementPlusResolver 的按需注入看不到它们。漏掉时弹窗没有定位 +// 与遮罩样式,会渲染成普通块元素贴到文档左上角、与页头文字叠在一起。 +import 'element-plus/es/components/message/style/css' +import 'element-plus/es/components/message-box/style/css' import App from '@/App.vue' import router from '@/router' diff --git a/frontend-vue/tests/ep-imperative-style.test.ts b/frontend-vue/tests/ep-imperative-style.test.ts new file mode 100644 index 00000000..db81eb0f --- /dev/null +++ b/frontend-vue/tests/ep-imperative-style.test.ts @@ -0,0 +1,57 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync, readdirSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { resolve, dirname } from 'node:path' + +const here = dirname(fileURLToPath(import.meta.url)) +const repoRoot = resolve(here, '..') + +/** + * Element Plus 组件名 → 按需样式目录(ElMessageBox → message-box)。 + * 与 element-plus/es/components/<目录>/style/css 的目录名一致。 + */ +function styleDirOf(component: string) { + return component.replace(/^El/, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase() +} + +/** 扫 src 下所有「显式 from 'element-plus' 具名导入的 El* 组件」及其出处 */ +function explicitElementPlusImports(): Map { + const used = new Map() + for (const file of readdirSync(resolve(repoRoot, 'src'), { recursive: true }) as string[]) { + if (!/\.(ts|vue)$/.test(file)) continue + const source = readFileSync(resolve(repoRoot, 'src', file), 'utf-8') + for (const match of source.matchAll(/import\s+(?!type\b)\{([^}]*)\}\s*from\s*['"]element-plus['"]/g)) { + for (const raw of match[1].split(',')) { + const name = raw.trim().split(/\s+as\s+/)[0].trim() + if (!/^El[A-Z]/.test(name)) continue + const files = used.get(name) || [] + if (!files.includes(file)) files.push(file) + used.set(name, files) + } + } + } + return used +} + +/** + * 显式 import 的 EP 组件不会被 ElementPlusResolver 注入样式(resolver 只处理 + * 模板里未解析的组件和自动导入的标识符),漏在入口引入样式就会出现 + * 「弹窗跑到文档左上角、和页头文字叠在一起」这类无定位样式的故障。 + */ +test('test_element_plus_explicit_imports_have_on_demand_style', () => { + const entry = readFileSync(resolve(repoRoot, 'src/main.ts'), 'utf-8') + // 锚定行首:注释掉的 import 不算数 + const styleDirs = [ + ...entry.matchAll(/^import\s+['"]element-plus\/es\/components\/([\w-]+)\/style\/css['"]/gm), + ].map((match) => match[1]) + + for (const [component, files] of explicitElementPlusImports()) { + const dir = styleDirOf(component) + assert.ok( + styleDirs.includes(dir), + `${component} 在 ${files.join('、')} 被显式 import,按需样式不会自动注入;` + + `请在 src/main.ts 增加 import 'element-plus/es/components/${dir}/style/css'`, + ) + } +})