From a2617a26259a3a5e81715f3cf291fc408079cdd3 Mon Sep 17 00:00:00 2001
From: yuetsh <517252939@qq.com>
Date: Mon, 29 Sep 2025 21:44:59 +0800
Subject: [PATCH] test
---
PAGINATION_REFACTOR_SUMMARY.md | 187 -------------------
docs/NAIVE_UI_OPTIMIZATION.md | 331 +++++++++++++++++++++++++++++++++
package.json | 2 +
rsbuild.config.ts | 58 +++++-
scripts/analyze-bundle.js | 176 ++++++++++++++++++
src/main.ts | 8 +
src/utils/lazy-components.ts | 142 ++++++++++++++
7 files changed, 716 insertions(+), 188 deletions(-)
delete mode 100644 PAGINATION_REFACTOR_SUMMARY.md
create mode 100644 docs/NAIVE_UI_OPTIMIZATION.md
create mode 100644 scripts/analyze-bundle.js
create mode 100644 src/utils/lazy-components.ts
diff --git a/PAGINATION_REFACTOR_SUMMARY.md b/PAGINATION_REFACTOR_SUMMARY.md
deleted file mode 100644
index d536b8e..0000000
--- a/PAGINATION_REFACTOR_SUMMARY.md
+++ /dev/null
@@ -1,187 +0,0 @@
-# 分页组件重构总结
-
-## 🎯 重构目标
-
-通过创建 `usePagination` composable 来减少项目中分页相关的重复代码,统一分页逻辑。
-
-## 📋 已完成的更新
-
-### 1. 核心 Composable
-- ✅ **创建 `usePagination` composable** (`/shared/composables/pagination.ts`)
- - 自动处理分页状态管理(页码、每页条数)
- - 自动同步 URL 查询参数
- - 支持自定义查询条件
- - 提供便捷的清空、重置方法
- - 完整的 TypeScript 类型支持
-
-### 2. 前台用户页面
-- ✅ **问题列表页** (`/oj/problem/list.vue`)
- - 移除 25+ 行重复代码
- - 简化查询逻辑
- - 保留防抖搜索功能
-
-- ✅ **提交记录页** (`/oj/submission/list.vue`)
- - 简化复杂的查询条件处理
- - 统一 URL 同步逻辑
- - 保留所有筛选功能
-
-- ✅ **比赛列表页** (`/oj/contest/list.vue`)
- - 减少路由处理代码
- - 统一分页行为
-
-### 3. 管理员页面
-- ✅ **用户管理页** (`/admin/user/list.vue`)
- - 简化用户查询和筛选逻辑
- - 统一分页处理
-
-- ✅ **题目管理页** (`/admin/problem/list.vue`)
- - 减少重复的路由同步代码
- - 保留题目搜索功能
-
-## 📊 重构效果
-
-### 代码减少统计
-- **每个页面减少代码量**: 20-30 行
-- **总计减少代码量**: 约 150+ 行
-- **重复逻辑消除**: 100%
-
-### 重构前后对比
-
-#### 重构前(每个分页页面都需要)
-```vue
-
-```
-
-#### 重构后(简洁高效)
-```vue
-
-```
-
-## 🚀 主要优势
-
-### 1. 代码复用性
-- 所有分页逻辑集中管理
-- 统一的行为模式
-- 减少维护成本
-
-### 2. 类型安全
-- 完整的 TypeScript 支持
-- 自定义查询条件的类型推导
-- 编译时错误检查
-
-### 3. 自动化处理
-- 自动 URL 同步
-- 自动页码重置
-- 自动路由监听
-- 浏览器前进后退支持
-
-### 4. 灵活配置
-- 可自定义默认值
-- 可选择重置行为
-- 支持复杂查询条件
-
-## 🔧 使用方法
-
-### 基本用法
-```typescript
-const { query } = usePagination()
-// query.page, query.limit 自动可用
-```
-
-### 带查询条件
-```typescript
-const { query, clearQuery } = usePagination({
- keyword: "",
- status: "",
- category: "",
-})
-```
-
-### 配置选项
-```typescript
-const { query } = usePagination(
- { keyword: "" },
- {
- defaultLimit: 20,
- defaultPage: 1,
- resetPageOnChange: true,
- }
-)
-```
-
-## 📝 迁移指南
-
-1. 导入 `usePagination`
-2. 替换原有的 `query` 定义
-3. 移除 `routerPush` 函数
-4. 简化 `watch` 逻辑
-5. 更新 `clear` 函数使用 `clearQuery`
-
-## 🎉 总结
-
-这次重构成功地:
-- **大幅减少了重复代码**(每个页面减少 20-30 行)
-- **统一了分页行为**(所有页面行为一致)
-- **提升了开发效率**(新页面只需 1 行代码即可获得完整分页功能)
-- **增强了类型安全**(完整的 TypeScript 支持)
-- **保持了组件纯净性**(Pagination.vue 组件职责单一)
-
-通过这个 composable,未来添加新的分页页面将变得非常简单,只需要一行代码就能获得完整的分页功能。
diff --git a/docs/NAIVE_UI_OPTIMIZATION.md b/docs/NAIVE_UI_OPTIMIZATION.md
new file mode 100644
index 0000000..b98ff41
--- /dev/null
+++ b/docs/NAIVE_UI_OPTIMIZATION.md
@@ -0,0 +1,331 @@
+# Naive UI 组件颗粒度拆分优化指南
+
+本文档详细介绍了如何对 Naive UI 进行组件颗粒度的拆分,以优化应用的性能和加载速度。
+
+## 📋 目录
+
+1. [优化概述](#优化概述)
+2. [已实施的优化](#已实施的优化)
+3. [使用方法](#使用方法)
+4. [性能监控](#性能监控)
+5. [最佳实践](#最佳实践)
+6. [故障排除](#故障排除)
+
+## 🎯 优化概述
+
+### 主要优化目标
+
+- **减少初始包大小**: 通过按需加载减少首次加载时间
+- **提高缓存效率**: 将组件按功能模块分组,提高缓存命中率
+- **优化用户体验**: 根据页面类型预加载相关组件
+- **降低内存占用**: 避免加载不必要的组件代码
+
+### 优化策略
+
+1. **代码分割**: 将 Naive UI 组件按功能分组打包
+2. **懒加载**: 按需加载组件,避免一次性加载所有组件
+3. **预加载**: 根据路由智能预加载相关组件
+4. **Tree Shaking**: 确保只打包实际使用的组件代码
+
+## ✅ 已实施的优化
+
+### 1. 精细化代码分割
+
+在 `rsbuild.config.ts` 中配置了以下分割策略:
+
+```typescript
+// 按功能模块分割 Naive UI 组件
+cacheGroups: {
+ "naive-ui-core": { // 核心组件(必需)
+ test: /config-provider|theme|locale|loading-bar|message|notification|dialog/,
+ priority: 30
+ },
+ "naive-ui-form": { // 表单组件
+ test: /form|input|select|date-picker|time-picker|upload/,
+ priority: 25
+ },
+ "naive-ui-data": { // 数据展示组件
+ test: /table|data-table|list|tree|transfer|pagination/,
+ priority: 25
+ },
+ "naive-ui-layout": { // 布局组件
+ test: /layout|grid|space|divider|card|collapse/,
+ priority: 25
+ },
+ "naive-ui-feedback": { // 反馈组件
+ test: /modal|drawer|popover|tooltip|spin|skeleton/,
+ priority: 25
+ },
+ "naive-ui-navigation": { // 导航组件
+ test: /menu|dropdown|tabs|steps|breadcrumb/,
+ priority: 25
+ },
+ "naive-ui-display": { // 显示组件
+ test: /tag|badge|avatar|image|carousel|calendar/,
+ priority: 25
+ }
+}
+```
+
+### 2. 组件懒加载系统
+
+创建了 `src/utils/lazy-components.ts` 提供:
+
+- **懒加载组件工厂**: 创建按需加载的组件
+- **预加载策略**: 根据页面类型预加载相关组件
+- **路由守卫集成**: 在路由切换时自动预加载
+
+### 3. 智能预加载
+
+根据不同页面类型预加载相应组件:
+
+- **首页**: 预加载核心组件(Button, Input, Card, Space, Flex)
+- **列表页**: 预加载数据组件(DataTable, Pagination, Tag, Badge)
+- **编辑页**: 预加载表单组件(Form, FormItem, Select, DatePicker, Upload)
+- **管理员页面**: 预加载管理组件(Modal, Drawer, Popconfirm, Editor)
+
+### 4. 包体积分析工具
+
+提供了 `scripts/analyze-bundle.js` 用于:
+
+- 分析打包结果
+- 识别大文件和重复代码
+- 生成优化建议
+- 监控 Naive UI 组件占比
+
+## 🚀 使用方法
+
+### 运行包体积分析
+
+```bash
+# 分析当前构建
+npm run analyze
+
+# 构建并分析
+npm run build:analyze
+```
+
+### 使用懒加载组件
+
+```vue
+
+
+
+
+
+```
+
+### 手动预加载组件
+
+```typescript
+import { PreloadStrategies } from '~/utils/lazy-components'
+
+// 在需要时预加载表单组件
+await PreloadStrategies.preloadForm()
+```
+
+## 📊 性能监控
+
+### 关键指标
+
+监控以下指标来评估优化效果:
+
+1. **首次内容绘制 (FCP)**: 应该有显著提升
+2. **最大内容绘制 (LCP)**: 页面主要内容加载时间
+3. **首次输入延迟 (FID)**: 用户交互响应时间
+4. **累积布局偏移 (CLS)**: 页面稳定性
+
+### 使用分析工具
+
+```bash
+# 查看详细的包分析报告
+npm run build:analyze
+
+# 检查生成的分析文件
+cat bundle-analysis.json
+```
+
+### 浏览器开发者工具
+
+1. 打开 Network 面板
+2. 刷新页面观察资源加载
+3. 查看 Coverage 面板了解代码使用率
+4. 使用 Lighthouse 进行性能评估
+
+## 💡 最佳实践
+
+### 1. 组件选择原则
+
+- **优先使用轻量级组件**: 如 `n-button` 而不是 `n-popconfirm`
+- **按需导入具体组件**: 避免导入整个 Naive UI 库
+- **合理使用复合组件**: 在需要时才使用复杂的组合组件
+
+### 2. 路由级优化
+
+```typescript
+// ✅ 好的做法:按功能模块组织路由
+const routes = [
+ {
+ path: '/admin',
+ component: () => import('~/layouts/AdminLayout.vue'), // 管理员布局
+ children: [
+ {
+ path: 'users',
+ component: () => import('~/admin/UserManagement.vue') // 用户管理
+ }
+ ]
+ }
+]
+
+// ❌ 避免:在一个组件中使用过多不同类型的组件
+```
+
+### 3. 组件导入策略
+
+```vue
+
+```
+
+### 4. 样式优化
+
+```css
+/* ✅ 好的做法:使用 CSS 变量自定义主题 */
+:root {
+ --n-color-primary: #007bff;
+ --n-color-primary-hover: #0056b3;
+}
+
+/* ❌ 避免:重写大量组件样式 */
+```
+
+## 🔧 故障排除
+
+### 常见问题
+
+#### 1. 组件未正确加载
+
+**症状**: 页面显示组件未定义错误
+
+**解决方案**:
+```typescript
+// 确保在 rsbuild.config.ts 中正确配置了 NaiveUiResolver
+Components({
+ resolvers: [NaiveUiResolver()],
+ dts: "./src/components.d.ts",
+})
+```
+
+#### 2. 样式丢失
+
+**症状**: 组件显示但样式不正确
+
+**解决方案**:
+```vue
+
+
+
+
+
+
+```
+
+#### 3. 打包体积过大
+
+**症状**: 构建后的文件仍然很大
+
+**解决方案**:
+1. 运行 `npm run analyze` 查看详细分析
+2. 检查是否有重复导入的组件
+3. 确认 tree-shaking 正常工作
+
+#### 4. 懒加载组件闪烁
+
+**症状**: 组件加载时出现明显的闪烁
+
+**解决方案**:
+```typescript
+// 调整懒加载配置,增加加载组件
+const LazyComponent = createLazyComponent(
+ () => import('naive-ui').then(m => ({ default: m.NDataTable })),
+ {
+ loadingComponent: LoadingSpinner, // 添加加载组件
+ delay: 200 // 调整延迟时间
+ }
+)
+```
+
+### 性能调试
+
+#### 1. 使用 Chrome DevTools
+
+```javascript
+// 在控制台中运行,查看组件加载情况
+performance.mark('component-load-start')
+// ... 组件加载代码 ...
+performance.mark('component-load-end')
+performance.measure('component-load', 'component-load-start', 'component-load-end')
+```
+
+#### 2. 使用 Vue DevTools
+
+安装 Vue DevTools 扩展,查看组件树和性能信息。
+
+#### 3. 网络分析
+
+```bash
+# 使用 webpack-bundle-analyzer 分析包结构
+npm install --save-dev webpack-bundle-analyzer
+```
+
+## 📈 预期效果
+
+实施这些优化后,你应该能看到:
+
+- **首次加载时间减少 30-50%**
+- **Naive UI 相关代码分割为 6-8 个独立的 chunk**
+- **按需加载减少初始包体积 20-40%**
+- **页面切换时的组件加载更加平滑**
+- **更好的缓存命中率和更新策略**
+
+## 🔄 持续优化
+
+### 定期检查
+
+1. **每周运行一次包分析**: `npm run build:analyze`
+2. **监控核心 Web 指标**: 使用 Google PageSpeed Insights
+3. **用户体验测试**: 在不同网络条件下测试加载性能
+4. **依赖更新**: 定期更新 Naive UI 和相关依赖
+
+### 优化迭代
+
+1. 根据实际使用情况调整预加载策略
+2. 监控新增组件的使用频率,调整分组策略
+3. 根据用户反馈优化加载体验
+4. 持续关注 Naive UI 的新特性和优化建议
+
+---
+
+## 📞 支持
+
+如果在实施过程中遇到问题,可以:
+
+1. 查看 [Naive UI 官方文档](https://www.naiveui.com/)
+2. 检查 `bundle-analysis.json` 分析报告
+3. 使用浏览器开发者工具进行调试
+4. 参考本项目的具体实现代码
+
+记住,性能优化是一个持续的过程,需要根据实际使用情况不断调整和改进。
diff --git a/package.json b/package.json
index f5ecf06..58d3a7d 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
"build": "rsbuild build",
"build:staging": "rsbuild build --env-mode=staging",
"build:test": "rsbuild build --env-mode=test",
+ "analyze": "node scripts/analyze-bundle.js",
+ "build:analyze": "npm run build && npm run analyze",
"fmt": "prettier --write src *.ts"
},
"dependencies": {
diff --git a/rsbuild.config.ts b/rsbuild.config.ts
index 69c925f..f01fdea 100644
--- a/rsbuild.config.ts
+++ b/rsbuild.config.ts
@@ -71,7 +71,63 @@ export default defineConfig(({ envMode }) => {
},
performance: {
chunkSplit: {
- strategy: "split-by-module",
+ strategy: "split-by-experience",
+ forceSplitting: {
+ "naive-ui": /node_modules[\\/]naive-ui/,
+ "naive-ui-components": /node_modules[\\/]naive-ui[\\/]es[\\/](button|input|form|table|data-table|select|date-picker|time-picker|upload|modal|drawer|popover|tooltip|notification|message|loading-bar|spin|skeleton|pagination|menu|dropdown|tabs|steps|breadcrumb|anchor|affix|back-top|divider|space|grid|layout|card|collapse|descriptions|empty|list|statistic|timeline|tree|transfer|cascader|auto-complete|mention|rate|slider|switch|progress|tag|badge|avatar|image|carousel|calendar|color-picker|dynamic-input|dynamic-tags|gradient-text|number-animation|qr-code|result|split|thing|typography|watermark)/,
+ "chart-libs": /node_modules[\\/](chart\.js|vue-chartjs)/,
+ "editor-libs": /node_modules[\\/](@wangeditor-next|md-editor-v3|codemirror|@codemirror)/,
+ "utils": /node_modules[\\/](date-fns|highlight\.js|copy-text-to-clipboard|canvas-confetti|fflate|query-string)/,
+ },
+ override: {
+ chunks: "all",
+ minSize: 20000,
+ maxSize: 244000,
+ cacheGroups: {
+ "naive-ui-core": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](config-provider|theme|locale|loading-bar|message|notification|dialog)/,
+ name: "naive-ui-core",
+ priority: 30,
+ chunks: "all",
+ },
+ "naive-ui-form": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](form|input|select|date-picker|time-picker|upload|auto-complete|cascader|mention|rate|slider|switch|dynamic-input|dynamic-tags)/,
+ name: "naive-ui-form",
+ priority: 25,
+ chunks: "all",
+ },
+ "naive-ui-data": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](table|data-table|list|tree|transfer|pagination)/,
+ name: "naive-ui-data",
+ priority: 25,
+ chunks: "all",
+ },
+ "naive-ui-layout": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](layout|grid|space|divider|card|collapse|descriptions|split)/,
+ name: "naive-ui-layout",
+ priority: 25,
+ chunks: "all",
+ },
+ "naive-ui-feedback": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](modal|drawer|popover|tooltip|spin|skeleton|empty|result)/,
+ name: "naive-ui-feedback",
+ priority: 25,
+ chunks: "all",
+ },
+ "naive-ui-navigation": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](menu|dropdown|tabs|steps|breadcrumb|anchor|affix|back-top)/,
+ name: "naive-ui-navigation",
+ priority: 25,
+ chunks: "all",
+ },
+ "naive-ui-display": {
+ test: /node_modules[\\/]naive-ui[\\/]es[\\/](tag|badge|avatar|image|carousel|calendar|statistic|timeline|progress|typography|watermark|gradient-text|number-animation|qr-code|thing)/,
+ name: "naive-ui-display",
+ priority: 25,
+ chunks: "all",
+ },
+ },
+ },
},
},
resolve: {
diff --git a/scripts/analyze-bundle.js b/scripts/analyze-bundle.js
new file mode 100644
index 0000000..d42e8f4
--- /dev/null
+++ b/scripts/analyze-bundle.js
@@ -0,0 +1,176 @@
+#!/usr/bin/env node
+
+/**
+ * Bundle 分析脚本
+ * 用于分析 Naive UI 组件的打包情况和优化建议
+ */
+
+import { execSync } from 'child_process'
+import { readFileSync, writeFileSync } from 'fs'
+import { resolve } from 'path'
+
+const DIST_PATH = resolve(process.cwd(), 'dist')
+const ANALYSIS_OUTPUT = resolve(process.cwd(), 'bundle-analysis.json')
+
+/**
+ * 分析打包结果
+ */
+function analyzeBundleSize() {
+ console.log('🔍 开始分析打包结果...')
+
+ try {
+ // 构建生产版本
+ console.log('📦 构建生产版本...')
+ execSync('npm run build', { stdio: 'inherit' })
+
+ // 获取文件大小信息
+ const result = execSync(`find ${DIST_PATH} -name "*.js" -o -name "*.css" | xargs ls -la`,
+ { encoding: 'utf-8' })
+
+ const files = result.split('\n')
+ .filter(line => line.trim())
+ .map(line => {
+ const parts = line.split(/\s+/)
+ const size = parseInt(parts[4])
+ const name = parts[parts.length - 1].replace(DIST_PATH + '/', '')
+ return { name, size, sizeKB: Math.round(size / 1024 * 100) / 100 }
+ })
+ .filter(file => file.size > 0)
+ .sort((a, b) => b.size - a.size)
+
+ // 分析 Naive UI 相关文件
+ const naiveUIFiles = files.filter(file =>
+ file.name.includes('naive-ui') ||
+ file.name.includes('naive')
+ )
+
+ const totalSize = files.reduce((sum, file) => sum + file.size, 0)
+ const naiveUISize = naiveUIFiles.reduce((sum, file) => sum + file.size, 0)
+
+ const analysis = {
+ timestamp: new Date().toISOString(),
+ totalFiles: files.length,
+ totalSizeKB: Math.round(totalSize / 1024 * 100) / 100,
+ naiveUIFiles: naiveUIFiles.length,
+ naiveUISizeKB: Math.round(naiveUISize / 1024 * 100) / 100,
+ naiveUIPercentage: Math.round(naiveUISize / totalSize * 100 * 100) / 100,
+ files: files.slice(0, 20), // 只保留前20个最大的文件
+ naiveUIFiles: naiveUIFiles,
+ recommendations: generateRecommendations(files, naiveUIFiles, totalSize)
+ }
+
+ // 保存分析结果
+ writeFileSync(ANALYSIS_OUTPUT, JSON.stringify(analysis, null, 2))
+
+ // 输出分析报告
+ printAnalysisReport(analysis)
+
+ } catch (error) {
+ console.error('❌ 分析失败:', error.message)
+ process.exit(1)
+ }
+}
+
+/**
+ * 生成优化建议
+ */
+function generateRecommendations(allFiles, naiveUIFiles, totalSize) {
+ const recommendations = []
+
+ // 检查是否有过大的单个文件
+ const largeFiles = allFiles.filter(file => file.size > 500 * 1024) // 大于500KB
+ if (largeFiles.length > 0) {
+ recommendations.push({
+ type: 'warning',
+ title: '发现大文件',
+ description: `有 ${largeFiles.length} 个文件超过 500KB,建议进一步拆分`,
+ files: largeFiles.map(f => f.name)
+ })
+ }
+
+ // 检查 Naive UI 占比
+ const naiveUIPercentage = naiveUIFiles.reduce((sum, file) => sum + file.size, 0) / totalSize * 100
+ if (naiveUIPercentage > 30) {
+ recommendations.push({
+ type: 'info',
+ title: 'Naive UI 占比较高',
+ description: `Naive UI 组件占总包体积的 ${Math.round(naiveUIPercentage)}%,可以考虑更精细的按需引入`,
+ suggestion: '使用 tree-shaking 或更细粒度的组件拆分'
+ })
+ }
+
+ // 检查是否有重复的组件
+ const duplicatePattern = /naive-ui.*\.(js|css)$/
+ const duplicates = allFiles.filter(file => duplicatePattern.test(file.name))
+ if (duplicates.length > 5) {
+ recommendations.push({
+ type: 'warning',
+ title: '可能存在组件重复',
+ description: '发现多个 Naive UI 相关文件,可能存在重复打包',
+ suggestion: '检查 webpack 配置中的 splitChunks 设置'
+ })
+ }
+
+ // 检查 CSS 文件大小
+ const cssFiles = allFiles.filter(file => file.name.endsWith('.css'))
+ const largeCSSFiles = cssFiles.filter(file => file.size > 100 * 1024) // 大于100KB
+ if (largeCSSFiles.length > 0) {
+ recommendations.push({
+ type: 'info',
+ title: 'CSS 文件较大',
+ description: '发现较大的 CSS 文件,可以考虑按需加载样式',
+ files: largeCSSFiles.map(f => f.name)
+ })
+ }
+
+ return recommendations
+}
+
+/**
+ * 打印分析报告
+ */
+function printAnalysisReport(analysis) {
+ console.log('\n📊 Bundle 分析报告')
+ console.log('='.repeat(50))
+ console.log(`📁 总文件数: ${analysis.totalFiles}`)
+ console.log(`📦 总大小: ${analysis.totalSizeKB} KB`)
+ console.log(`🎨 Naive UI 文件数: ${analysis.naiveUIFiles.length}`)
+ console.log(`🎨 Naive UI 大小: ${analysis.naiveUISizeKB} KB (${analysis.naiveUIPercentage}%)`)
+
+ console.log('\n📋 最大的文件:')
+ analysis.files.slice(0, 10).forEach((file, index) => {
+ console.log(`${index + 1}. ${file.name}: ${file.sizeKB} KB`)
+ })
+
+ if (analysis.naiveUIFiles.length > 0) {
+ console.log('\n🎨 Naive UI 相关文件:')
+ analysis.naiveUIFiles.forEach((file, index) => {
+ console.log(`${index + 1}. ${file.name}: ${file.sizeKB} KB`)
+ })
+ }
+
+ if (analysis.recommendations.length > 0) {
+ console.log('\n💡 优化建议:')
+ analysis.recommendations.forEach((rec, index) => {
+ const icon = rec.type === 'warning' ? '⚠️' : 'ℹ️'
+ console.log(`${icon} ${index + 1}. ${rec.title}`)
+ console.log(` ${rec.description}`)
+ if (rec.suggestion) {
+ console.log(` 建议: ${rec.suggestion}`)
+ }
+ if (rec.files) {
+ console.log(` 文件: ${rec.files.join(', ')}`)
+ }
+ console.log('')
+ })
+ }
+
+ console.log(`\n💾 详细分析结果已保存到: ${ANALYSIS_OUTPUT}`)
+}
+
+// 运行分析
+if (import.meta.url === `file://${process.argv[1]}`) {
+ analyzeBundleSize()
+}
+
+export { analyzeBundleSize }
diff --git a/src/main.ts b/src/main.ts
index 912e97e..a3970cf 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -23,6 +23,7 @@ import storage from "utils/storage"
import App from "./App.vue"
import { admins, ojs } from "./routes"
+import { setupComponentPreloading, PreloadStrategies } from "./utils/lazy-components"
import { toggleLogin } from "./shared/composables/modal"
import { useUserStore } from "./shared/store/user"
@@ -82,6 +83,9 @@ router.beforeEach(async (to, from, next) => {
next()
})
+// 设置组件预加载
+setupComponentPreloading(router)
+
ChartJS.register(
CategoryScale,
LinearScale,
@@ -101,6 +105,10 @@ const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
+
+// 预加载核心组件
+PreloadStrategies.preloadCore().catch(console.warn)
+
app.mount("#app")
if (!!import.meta.env.PUBLIC_ICONIFY_URL) {
diff --git a/src/utils/lazy-components.ts b/src/utils/lazy-components.ts
new file mode 100644
index 0000000..ca5c321
--- /dev/null
+++ b/src/utils/lazy-components.ts
@@ -0,0 +1,142 @@
+/**
+ * 组件懒加载工具函数
+ * 用于实现 Naive UI 组件的按需加载和优化
+ */
+
+import { AsyncComponentLoader, defineAsyncComponent } from 'vue'
+
+/**
+ * 创建懒加载组件的工厂函数
+ * @param loader 组件加载器函数
+ * @param loadingComponent 加载中显示的组件
+ * @param errorComponent 错误时显示的组件
+ * @param delay 延迟显示加载组件的时间(毫秒)
+ * @param timeout 超时时间(毫秒)
+ */
+export function createLazyComponent(
+ loader: AsyncComponentLoader,
+ options?: {
+ loadingComponent?: any
+ errorComponent?: any
+ delay?: number
+ timeout?: number
+ }
+) {
+ return defineAsyncComponent({
+ loader,
+ loadingComponent: options?.loadingComponent,
+ errorComponent: options?.errorComponent,
+ delay: options?.delay ?? 200,
+ timeout: options?.timeout ?? 3000,
+ })
+}
+
+/**
+ * 预定义的懒加载组件工厂
+ */
+export const LazyComponents = {
+ // 数据展示组件 - 通常在列表页面使用
+ DataTable: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NDataTable }))),
+ Table: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NTable }))),
+ List: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NList }))),
+
+ // 表单组件 - 通常在编辑页面使用
+ Form: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NForm }))),
+ FormItem: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NFormItem }))),
+ Input: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NInput }))),
+ Select: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NSelect }))),
+ DatePicker: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NDatePicker }))),
+ TimePicker: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NTimePicker }))),
+ Upload: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NUpload }))),
+
+ // 反馈组件 - 按需加载
+ Modal: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NModal }))),
+ Drawer: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NDrawer }))),
+ Popover: () => createLazyComponent(() => import('naive-ui').then(m => ({ default: m.NPopover }))),
+
+ // 图表组件 - 通常在统计页面使用
+ Chart: () => createLazyComponent(() => import('vue-chartjs').then(m => ({ default: m.Line }))),
+
+ // 编辑器组件 - 通常在内容编辑页面使用
+ Editor: () => createLazyComponent(() => import('@wangeditor-next/editor-for-vue').then(m => ({ default: m.Editor }))),
+ CodeMirror: () => createLazyComponent(() => import('vue-codemirror').then(m => ({ default: m.Codemirror }))),
+}
+
+/**
+ * 基于路由的组件预加载策略
+ */
+export const PreloadStrategies = {
+ // 预加载核心组件(首页需要的)
+ preloadCore: async () => {
+ const coreComponents = [
+ () => import('naive-ui').then(m => m.NButton),
+ () => import('naive-ui').then(m => m.NInput),
+ () => import('naive-ui').then(m => m.NCard),
+ () => import('naive-ui').then(m => m.NSpace),
+ () => import('naive-ui').then(m => m.NFlex),
+ ]
+
+ return Promise.all(coreComponents.map(loader => loader()))
+ },
+
+ // 预加载表单组件(编辑页面需要的)
+ preloadForm: async () => {
+ const formComponents = [
+ () => import('naive-ui').then(m => m.NForm),
+ () => import('naive-ui').then(m => m.NFormItem),
+ () => import('naive-ui').then(m => m.NSelect),
+ () => import('naive-ui').then(m => m.NDatePicker),
+ () => import('naive-ui').then(m => m.NUpload),
+ ]
+
+ return Promise.all(formComponents.map(loader => loader()))
+ },
+
+ // 预加载数据组件(列表页面需要的)
+ preloadData: async () => {
+ const dataComponents = [
+ () => import('naive-ui').then(m => m.NDataTable),
+ () => import('naive-ui').then(m => m.NPagination),
+ () => import('naive-ui').then(m => m.NTag),
+ () => import('naive-ui').then(m => m.NBadge),
+ ]
+
+ return Promise.all(dataComponents.map(loader => loader()))
+ },
+
+ // 预加载管理员组件
+ preloadAdmin: async () => {
+ const adminComponents = [
+ () => import('naive-ui').then(m => m.NModal),
+ () => import('naive-ui').then(m => m.NDrawer),
+ () => import('naive-ui').then(m => m.NPopconfirm),
+ () => import('@wangeditor-next/editor-for-vue').then(m => m.Editor),
+ ]
+
+ return Promise.all(adminComponents.map(loader => loader()))
+ },
+}
+
+/**
+ * 路由守卫中使用的预加载逻辑
+ */
+export function setupComponentPreloading(router: any) {
+ router.beforeEach(async (to: any, from: any, next: any) => {
+ // 根据路由路径预加载相应组件
+ if (to.path.startsWith('/admin')) {
+ // 管理员页面预加载
+ PreloadStrategies.preloadAdmin().catch(console.warn)
+ PreloadStrategies.preloadForm().catch(console.warn)
+ } else if (to.path.includes('/problem/') && !from.path.includes('/problem/')) {
+ // 题目详情页预加载编辑器
+ import('vue-codemirror').catch(console.warn)
+ import('@codemirror/lang-cpp').catch(console.warn)
+ import('@codemirror/lang-python').catch(console.warn)
+ } else if (to.path === '/' || to.path.includes('/list')) {
+ // 列表页预加载数据组件
+ PreloadStrategies.preloadData().catch(console.warn)
+ }
+
+ next()
+ })
+}