From 09be964d536d49ae507ed89cc6b37e5edf3016c0 Mon Sep 17 00:00:00 2001 From: jie Date: Wed, 11 Mar 2026 19:02:36 +0800 Subject: [PATCH] feat: optimize frontend performance and update dependencies --- .../.ai/specs/frontend-perf-loading-spec.md | 133 +++++++++++ frontend/package.json | 2 + frontend/pnpm-lock.yaml | 223 ++++++++++++++++++ frontend/src/assets/styles/variables.scss | 2 +- frontend/src/main.ts | 12 +- frontend/vite.config.ts | 39 ++- 6 files changed, 391 insertions(+), 20 deletions(-) create mode 100644 frontend/.ai/specs/frontend-perf-loading-spec.md diff --git a/frontend/.ai/specs/frontend-perf-loading-spec.md b/frontend/.ai/specs/frontend-perf-loading-spec.md new file mode 100644 index 0000000..135c231 --- /dev/null +++ b/frontend/.ai/specs/frontend-perf-loading-spec.md @@ -0,0 +1,133 @@ +# 技术契约与架构设计书:前端加载性能优化 + +**版本**:v1.0 +**日期**:2026-03-11 +**作者**:system-architect +**状态**:待 task-coder 实施 + +--- + +## 一、问题诊断(根因分析) + +### 1.1 每次页面跳转都重新加载 Element Plus CSS + +**根因**:`vite.config.ts` 中 `ElementPlusResolver({ importStyle: 'css' })` 配置让 `unplugin-vue-components` 在每个使用到 Element Plus 组件的 `.vue` 文件中注入独立的 CSS import(`el-dropdown-menu.css`、`el-button-group.css` 等)。与此同时 `main.ts` 还保留了 `import 'element-plus/dist/index.css'`(全量 CSS),两者并存形成**双重加载**。 + +**表现**:网络面板中看到 `el-dropdown-menu.css`、`el-button-group.css`、`element_plus_es_components_loading_s...` 等碎片 CSS 在每次路由切换时被重新请求。 + +**正确做法**:二选一—— +- **方案 A(推荐)**:继续使用 `element-plus/dist/index.css` 全量 CSS(已在 main.ts 中),关闭 resolver 的 `importStyle`。全量 CSS 首次加载后长期缓存,零碎的 per-component CSS 完全消失。 +- 方案 B:彻底删除 `element-plus/dist/index.css`,完全依赖 resolver 按需注入。需要确保每个组件都被 resolver 覆盖,否则样式丢失。 + +### 1.2 字体每次导航重复加载(视觉上的误判) + +**根因**:浏览器缓存策略问题。`@fontsource/noto-sans-sc` 输出的 woff2 文件名**不含内容 hash**(如 `noto-sans-sc-116-400-normal.woff2`),而 Vite dev 服务器对字体文件**不设置强缓存 header**(`Cache-Control: no-cache`)。结果是浏览器每次导航都发起带 If-None-Match 的条件请求,即使返回 304,devtools 也会显示为"加载"。 + +**生产环境额外问题**:vite.config.ts 的 `assetFileNames` 对字体匹配正确会加 hash,但 `@fontsource` 的 CSS 中 `src` 路径是相对路径,构建时 Vite 自动处理后会有 hash,缓存正常。 + +### 1.3 PangMenZhengDao.ttf:1004 KB,仅 LicenseSearch.vue 使用 + +这是最严重的单点问题。一个装饰性标题字体(仅 `LicenseSearch.vue` 的 `.platform-title` 用到)以 **1 MB 的 TTF 格式**全局加载。 + +- 全局加载:`main.ts` → `fonts.css` → 预加载所有字体 +- 即使用户从未访问授权查询页,这 1 MB 也会被下载 + +### 1.4 Noto Sans SC:两个字重 = 每字重 ~15 个 woff2 分片 + +400 + 700 两个字重,每次页面含中文时浏览器匹配 unicode-range 触发下载,共约 20-30 个分片文件(每个 25-33 KB)。这是 CJK 字体的固有代价,无法消除,但可以: +- 替换为系统字体(零网络开销) +- 转为自托管 WOFF2 子集(仅保留常用汉字) + +--- + +## 二、优化方案设计 + +### 方案一(必做):移除 ElementPlusResolver CSS 双重加载 + +**影响层**:构建配置层 +**改动意图**:将 `importStyle: 'css'` 改为 `importStyle: false`,消除每个组件的碎片 CSS 请求,改由全局 `element-plus/dist/index.css` 统一提供样式。 + +**数据收益**:消除约 10-20 个 CSS 请求(每次路由跳转)。 + +--- + +### 方案二(必做):PangMenZhengDao 懒加载隔离 + +**影响层**:CSS 层 + 字体加载策略层 +**改动意图**: +1. 从 `fonts.css` 中删除 `@font-face` 全局声明 +2. 将 `@font-face` 声明移入 `LicenseSearch.vue` 的 `