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() + }) +}