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:
20
src/components.d.ts
vendored
20
src/components.d.ts
vendored
@@ -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']
|
||||
}
|
||||
|
||||
27
src/main.js
27
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 账号列表超时根因)。
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user