feat(frontend): Element Plus 改按需引入,首屏 425.1->158.8KB(R36)

- 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
This commit is contained in:
2026-09-29 11:44:15 +08:00
parent 042e0a8e1f
commit ea350fb2e0
7 changed files with 471 additions and 16 deletions

4
.gitignore vendored
View File

@@ -12,6 +12,10 @@ dist
dist-ssr dist-ssr
*.local *.local
# Vite 在 dev server 因配置变化重启时会生成该临时打包文件;
# 若进程异常退出(例如被沙箱 safe-delete 钩子中断)会残留,不应进入版本库。
vite.config.js.timestamp-*.mjs
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json

372
package-lock.json generated
View File

@@ -21,6 +21,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"unplugin-vue-components": "^32.1.0",
"vite": "^5.0.0" "vite": "^5.0.0"
} }
}, },
@@ -475,10 +476,53 @@
"version": "0.2.11", "version": "0.2.11",
"license": "MIT" "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": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"license": "MIT" "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": { "node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es", "name": "@sxzz/popperjs-es",
"version": "2.11.8", "version": "2.11.8",
@@ -870,6 +914,19 @@
"snabbdom": "^3.1.0" "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": { "node_modules/agent-base": {
"version": "6.0.2", "version": "6.0.2",
"license": "MIT", "license": "MIT",
@@ -909,6 +966,22 @@
"node": ">= 0.4" "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": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"license": "MIT", "license": "MIT",
@@ -924,6 +997,13 @@
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" "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": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"license": "MIT" "license": "MIT"
@@ -1211,6 +1291,13 @@
"es5-ext": "~0.10.14" "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": { "node_modules/ext": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
@@ -1219,6 +1306,24 @@
"type": "^2.7.2" "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": { "node_modules/follow-redirects": {
"version": "1.16.0", "version": "1.16.0",
"funding": [ "funding": [
@@ -1389,6 +1494,24 @@
"resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" "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": { "node_modules/lodash": {
"version": "4.18.1", "version": "4.18.1",
"license": "MIT" "license": "MIT"
@@ -1485,6 +1608,38 @@
"node": ">= 0.6" "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": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"license": "MIT" "license": "MIT"
@@ -1519,10 +1674,44 @@
"version": "1.2.0", "version": "1.2.0",
"license": "BSD-3-Clause" "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": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"license": "ISC" "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": { "node_modules/pinia": {
"version": "2.3.1", "version": "2.3.1",
"license": "MIT", "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": { "node_modules/postcss": {
"version": "8.5.15", "version": "8.5.15",
"funding": [ "funding": [
@@ -1601,6 +1802,37 @@
"node": ">=10" "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": { "node_modules/rollup": {
"version": "4.62.0", "version": "4.62.0",
"dev": true, "dev": true,
@@ -1708,6 +1940,23 @@
"resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" "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": { "node_modules/type": {
"version": "2.7.3", "version": "2.7.3",
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz",
@@ -1726,6 +1975,122 @@
"node": ">=14.17" "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": { "node_modules/vite": {
"version": "5.4.21", "version": "5.4.21",
"dev": true, "dev": true,
@@ -1844,6 +2209,13 @@
"vue": "^3.5.0" "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": { "node_modules/wildcard": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz",

View File

@@ -23,6 +23,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"unplugin-vue-components": "^32.1.0",
"vite": "^5.0.0" "vite": "^5.0.0"
} }
} }

20
src/components.d.ts vendored
View File

@@ -12,39 +12,51 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
ElAlert: typeof import('element-plus/es')['ElAlert'] 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'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] 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'] 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'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider'] ElDivider: typeof import('element-plus/es')['ElDivider']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 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'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult'] ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElStep: typeof import('element-plus/es')['ElStep'] ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps'] ElSteps: typeof import('element-plus/es')['ElSteps']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElUpload: typeof import('element-plus/es')['ElUpload']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }

View File

@@ -1,10 +1,18 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' 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 中文语言包 // Element Plus 中文语言包
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' 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 报错) // 全局样式(含 Element Plus 主题色变量,防止 getThemeColors 解构 undefined 报错)
import './styles/global.css' import './styles/global.css'
@@ -34,10 +42,15 @@ app.use(router)
// 启动主动 token 续期:活跃用户不会因 token 过期被强制踢出登录 // 启动主动 token 续期:活跃用户不会因 token 过期被强制踢出登录
initProactiveTokenRefresh() initProactiveTokenRefresh()
// 注册 Element Plus(使用中文语言包) // 设置 Element Plus 全局配置(中文语言包)。
app.use(ElementPlus, { // 与原 app.use(ElementPlus, { locale: zhCn }) 语义等价:Element Plus 的 install
locale: zhCn, // 在注册完组件后做的正是 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:为按钮/可点击元素提供「防重入 + 节流」, // 全局指令 v-guard:为按钮/可点击元素提供「防重入 + 节流」,
// 防止狂点触发大量并发请求打爆连接池(2026-08-10 账号列表超时根因)。 // 防止狂点触发大量并发请求打爆连接池(2026-08-10 账号列表超时根因)。

View File

@@ -136,7 +136,7 @@
* 6. 上传历史列表 * 6. 上传历史列表
* 7. 教师组双上传区(教学方案 + 说课PPT) * 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 { ElMessage, ElMessageBox, ElUpload, ElProgress, ElButton, ElTag, ElIcon } from 'element-plus'
import { ArrowLeft, HomeFilled, Clock, UploadFilled, Delete } from '@element-plus/icons-vue' import { ArrowLeft, HomeFilled, Clock, UploadFilled, Delete } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
@@ -305,7 +305,7 @@ onUnmounted(() => {
* file-upload-area 是一个内联组件 * file-upload-area 是一个内联组件
* 为了保持 Vue SFC 的简洁性,我们用 defineComponent * 为了保持 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 { ElMessage, ElUpload, ElProgress, ElButton, ElTag, ElIcon } from 'element-plus'
import { UploadFilled, Delete } from '@element-plus/icons-vue' import { UploadFilled, Delete } from '@element-plus/icons-vue'
import * as uploadApi from '@/api/upload' import * as uploadApi from '@/api/upload'
@@ -455,7 +455,7 @@ const FileUploadArea = defineComponent({
e.currentTarget.previousElementSibling?.click() e.currentTarget.previousElementSibling?.click()
}, },
}, [ }, [
h(resolveComponent('el-icon'), { size: 48 }, () => h(UploadFilled)), h(ElIcon, { size: 48 }, () => h(UploadFilled)),
h('div', { class: 'drag-text' }, '拖拽文件到此处或 '), h('div', { class: 'drag-text' }, '拖拽文件到此处或 '),
h('em', null, '点击上传'), h('em', null, '点击上传'),
h('div', { class: 'drag-hint' }, h('div', { class: 'drag-hint' },
@@ -488,7 +488,7 @@ const FileUploadArea = defineComponent({
// 上传进度 // 上传进度
uploading.value ? h('div', { class: 'upload-progress' }, [ uploading.value ? h('div', { class: 'upload-progress' }, [
h(resolveComponent('el-progress'), { h(ElProgress, {
percentage: uploadPercent.value, percentage: uploadPercent.value,
status: uploadPercent.value >= 100 ? 'success' : undefined, status: uploadPercent.value >= 100 ? 'success' : undefined,
}), }),
@@ -496,7 +496,7 @@ const FileUploadArea = defineComponent({
// 已上传文件信息 // 已上传文件信息
uploadedInfo.value ? h('div', { class: 'uploaded-card' }, [ 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-name' }, uploadedInfo.value.fileName),
h('div', { class: 'uploaded-meta' }, [ h('div', { class: 'uploaded-meta' }, [
h('span', null, formatFileSize(uploadedInfo.value.fileSize)), h('span', null, formatFileSize(uploadedInfo.value.fileSize)),

View File

@@ -1,11 +1,52 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' 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' 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/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
// Element Plus 按需引入(替代 main.js 中的 app.use(ElementPlus) 全量注册)。
// 构建期静态扫描模板,只把「实际用到的组件」及其样式打进产物:
// <el-table> → 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 // 修复 Vite 内部 viteTransformMiddleware 调用 decodeURI 时遇到格式错误的 URL
// 导致 "URI malformed" 错误的问题。在 Vite 中间件之前拦截并安全处理。 // 导致 "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: { server: {
host: '0.0.0.0', host: '0.0.0.0',