# 重构缺漏检查报告 **日期**: 2025-01-30 **审查范围**: FileSystem.vue + 3个Composables --- ## 一、严重问题 🔴 ### 1. **重构目标未达成 - FileSystem.vue 仍然过大** | 文件 | 当前行数 | 目标行数 | 差距 | 状态 | |------|----------|----------|------|------| | FileSystem.vue | 4047 | < 500 | +3547 | 🔴 | | useNavigation.js | 273 | - | - | ✅ | | useFileEdit.js | 369 | - | - | ✅ | | useFilePreview.js | 611 | - | - | ✅ | | **总计** | 5300 | < 1500 | +3800 | 🔴 | **问题**: - Composables已创建(1253行),但**未真正集成** - FileSystem.vue仍然包含所有原始逻辑(4047行) - **代码总量增加**:从4241行 → 5300行(+25%) **根本原因**: - 之前因20+个重复函数声明错误,撤销了composable集成 - 保留了所有本地实现,导致双重代码存在 --- ### 2. **重复的计算属性(DRY违反)** #### 问题1: `isFileModified` 重复定义 **FileSystem.vue:2977-2988** ```javascript const isFileModified = computed(() => { const hasContent = fileContent.value !== '' && fileContent.value.trim() !== '' const hasModified = selectedFilePath.value && fileContent.value !== originalContent.value const isNewFile = !selectedFilePath.value && hasContent return isEditableView.value && (hasModified || isNewFile) }) ``` **useFileEdit.js:71-74** (未使用) ```javascript const isFileModified = computed(() => { return originalContent.value !== undefined && originalContent.value !== fileContent.value }) ``` **差异**:FileSystem.vue版本包含"新建文件"逻辑,useFileEdit版本更简单 --- #### 问题2: 文件名计算属性重复 **FileSystem.vue:1437-1460** ```javascript const currentFileNameDisplay = computed(() => { if (!selectedFilePath.value && !filePath.value) return '无文件' const path = selectedFilePath.value || filePath.value const parts = path.split(/[/\\]/) const fileName = parts[parts.length - 1] if (fileName.length > 30) { return fileName.substring(0, 15) + '...' + fileName.substring(fileName.length - 10) } return fileName }) ``` **useFilePreview.js:122-126** (未使用) ```javascript const currentFileName = computed(() => { if (!filePath.value) return '' const parts = filePath.value.split(/[/\\]/) return parts[parts.length - 1] }) ``` **重复**:都做路径分割取文件名,但Display版本有截断逻辑 --- #### 问题3: 文件路径计算属性重复 **FileSystem.vue:1462-1485** ```javascript const currentFileFullPathDisplay = computed(() => { if (isBrowsingZip.value) { return `ZIP: ${currentZipPath.value} → ${currentZipDirectory.value || '/'}` } if (!selectedFilePath.value) { return filePath.value || '未选择文件' } const path = selectedFilePath.value if (path.length > 50) { return '...' + path.substring(path.length - 50) } return path }) ``` **useFilePreview.js:131** (未使用) ```javascript const currentFileFullPath = computed(() => filePath.value || '') ``` **重复**:获取文件路径,但Display版本有ZIP模式和截断逻辑 --- #### 问题4: 内容修改检测重复 **FileSystem.vue:2991-2994** ```javascript const contentChanged = computed(() => { return fileContent.value !== '' && fileContent.value !== originalContent.value }) ``` **useFileEdit.js:79-82** (未使用) ```javascript const contentChanged = computed(() => { return fileContent.value !== '' && fileContent.value !== originalContent.value }) ``` **完全相同**:100%重复代码 --- #### 问题5: 保存/重置按钮状态重复 **FileSystem.vue:2997-3004** ```javascript const canSaveFile = computed(() => isEditableView.value && contentChanged.value) const canResetContent = computed(() => isEditableView.value && contentChanged.value && originalContent.value !== undefined ) ``` **useFileEdit.js:87-98** (未使用) ```javascript const canSaveFile = computed(() => { return isEditMode.value && contentChanged.value }) const canResetContent = computed(() => { return isEditMode.value && contentChanged.value && originalContent.value !== undefined }) ``` **差异**:FileSystem.vue用`isEditableView`,useFileEdit用`isEditMode` --- ### 3. **调试日志仍然过多 - 65个** ```bash $ grep -c "debug(Log|Warn|Error|Info)" web/src/components/FileSystem.vue 65 ``` **分布**: - `debugLog`: ~45处 - `debugWarn`: ~12处 - `debugError`: ~8处 **问题**: - 已从raw console替换为debugLog,但**数量仍然过多** - 过度防御性编程,每个分支都记录日志 - 影响代码可读性和运行时性能 --- ## 二、中等问题 🟡 ### 4. **currentFileExtension 逻辑嵌套过多** **FileSystem.vue:2941-2960** (19行) ```javascript const currentFileExtension = computed(() => { const path = selectedFilePath.value || filePath.value if (!path) return '' const fileName = path.split(/[/\\]/).pop()?.toLowerCase() || '' const specialFiles = { 'dockerfile': 'dockerfile', 'containerfile': 'dockerfile', 'makefile': 'makefile', 'cmakelists.txt': 'cmake', '.gitignore': 'gitignore', '.env': 'properties', } if (specialFiles[fileName]) return specialFiles[fileName] return getExt(path) }) ``` **可以改进为**(使用fileHelpers.js中的函数): ```javascript const currentFileExtension = computed(() => { const path = selectedFilePath.value || filePath.value return getExtensionForHighlight(path) // 复用现有工具函数 }) ``` --- ### 5. **函数命名不一致** | FileSystem.vue | useFilePreview.js | 用途 | |----------------|-------------------|------| | `currentFileNameDisplay` | `currentFileName` | 获取文件名 | | `currentFileFullPathDisplay` | `currentFileFullPath` | 获取完整路径 | | `currentImageDimensionsLocal` | `currentImageDimensions` | 图片尺寸 | **问题**: - 有的带`Display`后缀,有的不带 - 有的带`Local`后缀,含义不明 - 命名不一致导致维护困难 --- ### 6. **Go代码配置函数重复** **internal/filesystem/config.go:256-295** ```go func getAllowedExtensions() map[string]bool { return map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, // ... 30+ 个硬编码扩展名 } } ``` **web/src/utils/constants.js:27-73** (重复定义) ```javascript export const FILE_EXTENSIONS = { IMAGE: ['jpg', 'jpeg', 'png', /* ... */], VIDEO_BROWSER: ['mp4', 'webm', /* ... */], // ... 类似的30+个扩展名 } ``` **问题**:前后端用不同格式重复定义相同的数据 **建议**:后端从配置文件加载,或生成JSON供前端使用 --- ## 三、代码规范问题 ⚠️ ### 7. **路径分隔符正则重复** **出现次数**: 15+ ```javascript // FileSystem.vue 多处 path.split(/[/\\]/) // 行 719, 798, 819, 833, 845, 2946, ... // useFilePreview.js:124 path.split(/[/\\/]/) // useNavigation.js:304 const parts = path.split(/[/\\]/) ``` **建议**:提取为共享常量 ```javascript // utils/pathConstants.js export const PATH_SEPARATOR_REGEX = /[/\\]/ export const splitPath = (path) => path.split(PATH_SEPARATOR_REGEX) ``` --- ### 8. **文件类型判断分散** **FileSystem.vue:857-869** ```javascript const previewableTypes = [ ...FILE_EXTENSIONS.IMAGE, ...FILE_EXTENSIONS.VIDEO_BROWSER, ...FILE_EXTENSIONS.AUDIO, 'pdf', 'html', 'htm', 'md', 'markdown' ] const knownBinaryTypes = [ 'exe', 'dll', 'so', 'bin', 'zip', 'rar', '7z', 'tar', 'gz', 'iso', 'img', 'dmg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx' ] ``` **问题**: - 内联定义在函数内部 - 应该定义在constants.js中复用 --- ### 9. **localStorage键名分散** **多处重复定义**: - FileSystem.vue: 使用`STORAGE_KEYS.FILESYSTEM.*` - useFileEdit.js: 直接定义`DRAFT_STORAGE_KEY` - useNavigation.js: 直接定义`STORAGE_KEY_PATH_HISTORY` **应该统一使用**:`STORAGE_KEYS`常量对象 --- ## 四、DRY原则违反统计 ### 重复代码统计 | 类型 | 重复次数 | 总行数 | 浪费 | |------|----------|--------|------| | 计算属性 | 5组 | ~80行 | 40行 | | 路径分割正则 | 15+次 | ~15行 | 14行 | | 文件类型判断 | 8+次 | ~50行 | 40行 | | localStorage键 | 6+处 | ~12行 | 8行 | | **总计** | **34+处** | **~157行** | **102行** | --- ## 五、优化建议 ### 优先级1: 立即修复 🔴 #### 1.1 移除未使用的Composables ```bash # 由于composables未被实际使用,应该删除或文档化 rm web/src/composables/useNavigation.js rm web/src/composables/useFileEdit.js rm web/src/composables/useFilePreview.js ``` **理由**:如果不用,就不应该存在,避免混淆 --- #### 1.2 删除重复计算属性 **FileSystem.vue - 保留更完整的版本,删除useFileEdit/useFilePreview中的**: ```javascript // 保留 FileSystem.vue:1437 - currentFileNameDisplay (有截断逻辑) // 保留 FileSystem.vue:1462 - currentFileFullPathDisplay (有ZIP模式) // 保留 FileSystem.vue:2977 - isFileModified (有新建文件逻辑) // 删除 useFileEdit.js:71, useFilePreview.js:122-126 等重复定义 ``` **或者相反**:如果决定使用composables,则删除FileSystem.vue中的重复定义 --- #### 1.3 大幅减少调试日志 **策略A: 环境变量控制**(已部分实现) ```javascript // utils/debugLog.js const ENABLE_DEBUG = import.meta.env.DEV export const debugLog = ENABLE_DEBUG ? console.log : () => {} export const debugWarn = ENABLE_DEBUG ? console.warn : () => {} export const debugError = console.error // 始终保留错误日志 ``` **策略B: 删除非关键日志**(推荐) ```javascript // 删除这些类型的日志: debugLog('[readFile] 开始读取文件') // 显而易见的操作 debugLog('[handleKeyDown] F2 pressed') // 用户操作 debugLog('[startResizeHorizontal] 开始拖拽') // UI交互 // 保留这些: debugError('[readFile] 读取失败:', error) // 错误 debugWarn('[loadCommonPaths] Wails API未就绪') // 降级场景 ``` **目标**: 从65个 → < 10个(只保留错误和关键警告) --- ### 优先级2: 短期优化 🟡 #### 2.1 提取共享工具函数 **创建 web/src/utils/pathHelpers.js**: ```javascript export const PATH_SEPARATOR_REGEX = /[/\\]/ export const splitPath = (path) => path.split(PATH_SEPARATOR_REGEX) export const getFileName = (path) => { if (!path) return '' const parts = splitPath(path) return parts[parts.length - 1] || path } export const getParentPath = (path) => { if (!path) return '' const lastSep = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')) return lastSep > 0 ? path.substring(0, lastSep) : path } ``` **替换所有** `path.split(/[/\\]/)` 为 `splitPath(path)` --- #### 2.2 统一文件类型常量 **创建 web/src/utils/fileTypeCategories.js**: ```javascript import { FILE_EXTENSIONS } from './constants' export const PREVIEWABLE_TYPES = [ ...FILE_EXTENSIONS.IMAGE, ...FILE_EXTENSIONS.VIDEO_BROWSER, ...FILE_EXTENSIONS.AUDIO, 'pdf', 'html', 'htm', 'md', 'markdown' ] export const KNOWN_BINARY_TYPES = [ 'exe', 'dll', 'so', 'bin', 'zip', 'rar', '7z', 'tar', 'gz', 'iso', 'img', 'dmg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx' ] export const TEXT_EDITABLE_TYPES = [ ...FILE_EXTENSIONS.TEXT, ...FILE_EXTENSIONS.CODE ] ``` **替换所有内联定义** --- #### 2.3 统一localStorage键名 **只在 constants.js 中定义一次**: ```javascript export const STORAGE_KEYS = { FILESYSTEM: { PATH_HISTORY: 'app-filesystem-path-history', EDIT_MODE: 'app-filesystem-edit-mode', PANEL_WIDTH: 'app-filesystem-panel-width', DRAFT_CONTENT: 'filesystem-draft-content', DRAFT_TIME: 'filesystem-draft-time', FAVORITE_FILES: 'filesystem-favorite-files', } } // 删除所有其他文件中的重复定义 ``` --- ### 优先级3: 长期重构 🔵 #### 3.1 真正拆分FileSystem.vue **目标**: 从4047行 → < 500行 **策略**: 1. **提取子组件** (~1500行) - `FileListPanel.vue` (文件列表, ~300行) - `CodeEditorPanel.vue` (编辑器面板, ~400行) - `PreviewPanel.vue` (预览面板, ~300行) - `FavoriteSidebar.vue` (收藏夹侧边栏, ~200行) - `Toolbar.vue` (顶部工具栏, ~150行) - `ContextMenu.vue` (右键菜单, ~150行) 2. **提取composables** (~1000行) - `useFileSystem.js` (核心文件系统操作, ~300行) - `useFileEditor.js` (编辑器逻辑, ~200行) - `useFilePreview.js` (预览逻辑, ~250行) - `useFavoriteFiles.js` (收藏夹管理, ~150行) - `useKeyboardShortcuts.js` (快捷键, ~100行) 3. **主组件保留** (~500行) - 布局和状态协调 - 子组件通信 - 生命周期管理 **时间估算**: 2-3周 --- #### 3.2 TypeScript迁移 **目标**: 添加类型安全,减少运行时错误 ```typescript // types/file.ts export interface FileItem { path: string name: string is_dir: boolean size: number modified: string } export interface PreviewState { isImageView: boolean isVideoView: boolean isAudioView: boolean isPdfFile: boolean isHtmlFile: boolean isMarkdownFile: boolean isBinaryFile: boolean } ``` --- #### 3.3 统一前后端文件类型定义 **方案A: 后端生成JSON** ```go // internal/filesystem/export_types.go func ExportFileTypes() string { types := map[string][]string{ "image": getAllowedExtensions(), "binary": getForbiddenExtensions(), } json, _ := json.Marshal(types) return string(json) } ``` **方案B: 独立配置文件** ```yaml # config/file_types.yaml image: - jpg - jpeg - png binary: - exe - dll ``` 前后端都从同一配置读取 --- ## 六、检查清单 ### 立即执行(本周) - [ ] **决定**: 删除还是使用composables - [ ] **删除重复**: 移除5组重复计算属性(102行) - [ ] **减少日志**: 从65个debugLog → < 10个 - [ ] **提取工具**: 创建pathHelpers.js - [ ] **统一常量**: 合并文件类型定义 - [ ] **统一键名**: 只使用STORAGE_KEYS ### 短期计划(2周) - [ ] **提取子组件**: FileListPanel, Toolbar, ContextMenu - [ ] **提效composables**: 真正集成useFileSystem, useFilePreview - [ ] **优化函数**: 简化currentFileExtension逻辑 - [ ] **命名统一**: 统一Display/Local后缀规则 ### 长期优化(1个月) - [ ] **组件化**: 完成所有子组件提取 - [ ] **TypeScript**: 添加类型定义 - [ ] **前后端统一**: 文件类型配置共享 - [ ] **单元测试**: 覆盖核心逻辑 --- ## 七、代码质量指标(更新后) | 指标 | 当前值 | 目标值 | 评级 | |------|--------|--------|------| | 单文件最大行数 | 4047 | < 500 | 🔴 | | 函数平均行数 | ~50 | < 30 | 🟡 | | 代码重复率 | ~8% | < 3% | 🔴 | | 调试语句数量 | 65 | < 10 | 🔴 | | 圈复杂度 | 15+ | < 10 | 🟡 | | 未使用代码 | 1253行 | 0 | 🔴 | --- ## 八、总结 ### 关键发现 1. **重构未完成**: Composables已创建但未使用,反而增加了总代码量 2. **重复代码严重**: 5组计算属性重复,102行浪费 3. **过度防御性编程**: 65个调试日志,远超必要数量 4. **命名不一致**: Display/Local后缀混乱 ### 下一步行动 **推荐方案A: 激进重构** - 删除3个未使用的composables - 立即开始拆分子组件 - 1个月内完成组件化 **推荐方案B: 渐进优化(更稳妥)** - 先清理重复代码和日志 - 提取共享工具函数 - 逐步拆分子组件 ### 风险提示 ⚠️ **当前状态**: 代码库处于"半重构"状态,既有旧实现又有新参考,容易造成混淆 **建议**: 尽快决定方向(彻底重构 vs 回滚到重构前),避免技术债务累积