From ea350fb2e04354588113baea18c0e8b1352a880a Mon Sep 17 00:00:00 2001 From: qubiaobiao <3294694717@qq.com> Date: Tue, 29 Sep 2026 11:44:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20Element=20Plus=20=E6=94=B9?= =?UTF-8?q?=E6=8C=89=E9=9C=80=E5=BC=95=E5=85=A5=EF=BC=8C=E9=A6=96=E5=B1=8F?= =?UTF-8?q?=20425.1->158.8KB=EF=BC=88R36=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vite.config.js: 新增 unplugin-vue-components + ElementPlusResolver; optimizeDeps.include 动态生成 118 项无扩展名 specifier(glob/.mjs 皆静默失效) - main.js: 删 app.use(ElementPlus) 与全量 CSS;改 provideGlobalConfig(locale, app, true); 显式引入 4 个函数式组件样式(message/message-box/notification/loading) - views/Upload.vue: 3 处 h(resolveComponent('el-xxx')) 改显式组件引用;清 2 处死导入 - devDep: unplugin-vue-components@^32.1.0 - .gitignore: 忽略 vite.config.js.timestamp-*.mjs 验证:dist 渲染 9/9 · dist 登录后 9/9 · dev 通道致命错误 0/告警 0 瘦身:首屏 425.1->158.8KB(-62.6%);主包 JS 1112.9->281.09KB --- .gitignore | 4 + package-lock.json | 372 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/components.d.ts | 20 ++- src/main.js | 27 +++- src/views/Upload.vue | 10 +- vite.config.js | 53 ++++++ 7 files changed, 471 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index c60af9d..a208aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ dist dist-ssr *.local +# Vite 在 dev server 因配置变化重启时会生成该临时打包文件; +# 若进程异常退出(例如被沙箱 safe-delete 钩子中断)会残留,不应进入版本库。 +vite.config.js.timestamp-*.mjs + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/package-lock.json b/package-lock.json index 9f6bdf3..02928fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", + "unplugin-vue-components": "^32.1.0", "vite": "^5.0.0" } }, @@ -475,10 +476,53 @@ "version": "0.2.11", "license": "MIT" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@popperjs/core": { "name": "@sxzz/popperjs-es", "version": "2.11.8", @@ -870,6 +914,19 @@ "snabbdom": "^3.1.0" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "license": "MIT", @@ -909,6 +966,22 @@ "node": ">= 0.4" } }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "license": "MIT", @@ -924,6 +997,13 @@ "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" }, + "node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "license": "MIT" @@ -1211,6 +1291,13 @@ "es5-ext": "~0.10.14" } }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "license": "MIT" + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", @@ -1219,6 +1306,24 @@ "type": "^2.7.2" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/follow-redirects": { "version": "1.16.0", "funding": [ @@ -1389,6 +1494,24 @@ "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/lodash": { "version": "4.18.1", "license": "MIT" @@ -1485,6 +1608,38 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "license": "MIT" @@ -1519,10 +1674,44 @@ "version": "1.2.0", "license": "BSD-3-Clause" }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pinia": { "version": "2.3.1", "license": "MIT", @@ -1543,6 +1732,18 @@ } } }, + "node_modules/pkg-types": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.1", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.15", "funding": [ @@ -1601,6 +1802,37 @@ "node": ">=10" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/rollup": { "version": "4.62.0", "dev": true, @@ -1708,6 +1940,23 @@ "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/type": { "version": "2.7.3", "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", @@ -1726,6 +1975,122 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-3.4.0.tgz", + "integrity": "sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.7", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rsbuild/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rsbuild/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "32.1.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz", + "integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "local-pkg": "^1.2.0", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, "node_modules/vite": { "version": "5.4.21", "dev": true, @@ -1844,6 +2209,13 @@ "vue": "^3.5.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/wildcard": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", diff --git a/package.json b/package.json index 4d11a37..b8709a5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", + "unplugin-vue-components": "^32.1.0", "vite": "^5.0.0" } } diff --git a/src/components.d.ts b/src/components.d.ts index d9b7747..fea2558 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -12,39 +12,51 @@ export {} declare module 'vue' { export interface GlobalComponents { ElAlert: typeof import('element-plus/es')['ElAlert'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] + ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElButton: typeof import('element-plus/es')['ElButton'] ElCard: typeof import('element-plus/es')['ElCard'] + ElCarousel: typeof import('element-plus/es')['ElCarousel'] + ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCol: typeof import('element-plus/es')['ElCol'] + ElCollapse: typeof import('element-plus/es')['ElCollapse'] + ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'] ElProgress: typeof import('element-plus/es')['ElProgress'] ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElResult: typeof import('element-plus/es')['ElResult'] ElRow: typeof import('element-plus/es')['ElRow'] + ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElStep: typeof import('element-plus/es')['ElStep'] ElSteps: typeof import('element-plus/es')['ElSteps'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElUpload: typeof import('element-plus/es')['ElUpload'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } diff --git a/src/main.js b/src/main.js index 8d99e71..a4d6d8e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,10 +1,18 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' -import ElementPlus from 'element-plus' +// Element Plus 按需引入(见 vite.config.js 的 Components 插件): +// 不再全量注册组件,也不再引入 element-plus/dist/index.css。 +// 此处只保留「语言包」与「全局 locale 设置」两项职责。 +import { provideGlobalConfig } from 'element-plus' // Element Plus 中文语言包 import zhCn from 'element-plus/dist/locale/zh-cn.mjs' -// Element Plus 样式 -import 'element-plus/dist/index.css' +// 🔴 函数式组件不经过模板编译,按需插件(静态扫描)无法为它们注入样式 ⇒ 必须显式引入。 +// 这四类在项目中的调用次数:ElMessage 147 / ElMessageBox 23 / ElNotification 4 / ElLoading 2。 +// 漏掉任一条的后果是「弹窗能弹出但无样式」(裸文本),构建不报错,只有真浏览器可见。 +import 'element-plus/es/components/message/style/css' +import 'element-plus/es/components/message-box/style/css' +import 'element-plus/es/components/notification/style/css' +import 'element-plus/es/components/loading/style/css' // 全局样式(含 Element Plus 主题色变量,防止 getThemeColors 解构 undefined 报错) import './styles/global.css' @@ -34,10 +42,15 @@ app.use(router) // 启动主动 token 续期:活跃用户不会因 token 过期被强制踢出登录 initProactiveTokenRefresh() -// 注册 Element Plus(使用中文语言包) -app.use(ElementPlus, { - locale: zhCn, -}) +// 设置 Element Plus 全局配置(中文语言包)。 +// 与原 app.use(ElementPlus, { locale: zhCn }) 语义等价:Element Plus 的 install +// 在注册完组件后做的正是 provideGlobalConfig(options, app, true) +// (见 node_modules/element-plus/es/make-installer.mjs)。 +// 第三个参数 global=true 是关键 —— 它把 locale 写入 globalConfig, +// 使 ElMessageBox / ElPagination 等「脱离组件树的函数式 API」也能取到中文文案; +// 少了它,确认框按钮会退回英文 OK / Cancel。 +// 组件注册职责已移交 vite 的按需插件,此处不再注册任何组件。 +provideGlobalConfig({ locale: zhCn }, app, true) // 全局指令 v-guard:为按钮/可点击元素提供「防重入 + 节流」, // 防止狂点触发大量并发请求打爆连接池(2026-08-10 账号列表超时根因)。 diff --git a/src/views/Upload.vue b/src/views/Upload.vue index e93c83d..4d4e0a7 100644 --- a/src/views/Upload.vue +++ b/src/views/Upload.vue @@ -136,7 +136,7 @@ * 6. 上传历史列表 * 7. 教师组双上传区(教学方案 + 说课PPT) */ -import { ref, computed, onMounted, onUnmounted, h, resolveComponent } from 'vue' +import { ref, computed, onMounted, onUnmounted, h } from 'vue' import { ElMessage, ElMessageBox, ElUpload, ElProgress, ElButton, ElTag, ElIcon } from 'element-plus' import { ArrowLeft, HomeFilled, Clock, UploadFilled, Delete } from '@element-plus/icons-vue' import { useAuthStore } from '@/stores/auth' @@ -305,7 +305,7 @@ onUnmounted(() => { * file-upload-area 是一个内联组件 * 为了保持 Vue SFC 的简洁性,我们用 defineComponent */ -import { defineComponent, ref, reactive, h, resolveComponent, computed } from 'vue' +import { defineComponent, ref, reactive, h, computed } from 'vue' import { ElMessage, ElUpload, ElProgress, ElButton, ElTag, ElIcon } from 'element-plus' import { UploadFilled, Delete } from '@element-plus/icons-vue' import * as uploadApi from '@/api/upload' @@ -455,7 +455,7 @@ const FileUploadArea = defineComponent({ e.currentTarget.previousElementSibling?.click() }, }, [ - h(resolveComponent('el-icon'), { size: 48 }, () => h(UploadFilled)), + h(ElIcon, { size: 48 }, () => h(UploadFilled)), h('div', { class: 'drag-text' }, '拖拽文件到此处或 '), h('em', null, '点击上传'), h('div', { class: 'drag-hint' }, @@ -488,7 +488,7 @@ const FileUploadArea = defineComponent({ // 上传进度 uploading.value ? h('div', { class: 'upload-progress' }, [ - h(resolveComponent('el-progress'), { + h(ElProgress, { percentage: uploadPercent.value, status: uploadPercent.value >= 100 ? 'success' : undefined, }), @@ -496,7 +496,7 @@ const FileUploadArea = defineComponent({ // 已上传文件信息 uploadedInfo.value ? h('div', { class: 'uploaded-card' }, [ - h(resolveComponent('el-tag'), { type: 'success', size: 'small' }, () => '上传成功'), + h(ElTag, { type: 'success', size: 'small' }, () => '上传成功'), h('div', { class: 'uploaded-name' }, uploadedInfo.value.fileName), h('div', { class: 'uploaded-meta' }, [ h('span', null, formatFileSize(uploadedInfo.value.fileSize)), diff --git a/vite.config.js b/vite.config.js index aba1c34..26c0acf 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,11 +1,52 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import fs from 'node:fs' +import path from 'node:path' import { fileURLToPath, URL } from 'node:url' +// Element Plus 按需样式模块清单(供下方 optimizeDeps.include 使用)。 +// 为什么不能用 glob:Vite 的 include 需要「运行时请求的 specifier 形式」(无扩展名, +// 形如 element-plus/es/components/table/style/css),而 glob 匹配的是文件系统条目 —— +// `style/css` 在磁盘上是目录而非文件,glob 会静默匹配 0 项(实测 '*/style/css' → 0 项); +// 写成 '*/style/css.mjs' 虽能匹配 118 项,但其 key 与运行时 specifier 不一致, +// Vite 仍会把每个组件判定为「新依赖」⇒ 等于没配。 +// 因此改为扫描目录动态生成:既不硬编码 118 项,也不会随 element-plus 升级失效。 +const EP_COMPONENTS_DIR = fileURLToPath( + new URL('./node_modules/element-plus/es/components', import.meta.url) +) +const EP_STYLE_INCLUDES = (() => { + try { + return fs + .readdirSync(EP_COMPONENTS_DIR) + .filter((name) => fs.existsSync(path.join(EP_COMPONENTS_DIR, name, 'style', 'css.mjs'))) + .map((name) => `element-plus/es/components/${name}/style/css`) + } catch { + return [] // 依赖尚未安装时降级为空(vite build 不依赖此项) + } +})() + // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), + // Element Plus 按需引入(替代 main.js 中的 app.use(ElementPlus) 全量注册)。 + // 构建期静态扫描模板,只把「实际用到的组件」及其样式打进产物: + // → element-plus/es/components/table + table/style/css + // v-loading → ElLoadingDirective(resolver 的 directives 默认开启) + // 注意两处边界(改前已核对): + // ① 运行时字符串解析(h('el-button') / resolveComponent('el-icon'))不归它管 + // —— 已改为显式组件引用,见 views/Upload.vue; + // ② 函数式 API(ElMessage/ElMessageBox/...)不会被注入样式, + // —— 已在 main.js 里显式引入这四个样式。 + // dirs 置空 = 不额外扫描 src/components 目录,保持原有显式 import 行为不变, + // 只让本插件负责 Element Plus 一个职责(最小改动)。 + Components({ + dts: 'src/components.d.ts', + dirs: [], + resolvers: [ElementPlusResolver()], + }), // 修复 Vite 内部 viteTransformMiddleware 调用 decodeURI 时遇到格式错误的 URL // 导致 "URI malformed" 错误的问题。在 Vite 中间件之前拦截并安全处理。 { @@ -30,6 +71,18 @@ export default defineConfig({ }, ], + // 依赖预构建:显式声明「按需引入」注入的 Element Plus 样式模块。 + // 必要性:unplugin-vue-components 是在 transform 阶段「运行时注入」style import 的, + // 而 Vite 启动期的依赖扫描基于 index.html,看不到这些被注入的模块 ⇒ 运行中才发现 + // ⇒ 触发「二次预构建」(日志里的 new dependencies optimized + reloading)。 + // 后果不只是「访问新页面时整页重载」;在带 safe-delete 钩子的受限沙箱里, + // 二次预构建清理临时目录会被钩子拦下,导致 dev server 进程直接退出。 + // 显式声明后首次预构建即完整,dev 启动一次成型。 + // ⚠️ 本项只作用于 dev(vite build 不做依赖预构建),不影响任何构建产物。 + optimizeDeps: { + include: ['element-plus/es', ...EP_STYLE_INCLUDES], + }, + // 开发服务器配置 server: { host: '0.0.0.0',