Private
Public Access
1
0
Files
u-desk/docs/代码审查/refactoring-review-2026-01-30.md
绝尘 a5d30684ed 重构:文件系统模块化架构,增强 Markdown 渲染
- 拆分 FileSystem.vue 为模块化组件架构
- 新增 Markdown Mermaid 图表渲染支持
- 新增 180+ 编程语言代码高亮
- 修复编辑/预览模式切换渲染问题
- 优化亮色/暗色模式主题适配
- 新增 TypeScript 类型定义
2026-02-04 03:32:46 +08:00

15 KiB
Raw Blame History

重构缺漏检查报告

日期: 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

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 (未使用)

const isFileModified = computed(() => {
  return originalContent.value !== undefined &&
         originalContent.value !== fileContent.value
})

差异FileSystem.vue版本包含"新建文件"逻辑useFileEdit版本更简单


问题2: 文件名计算属性重复

FileSystem.vue:1437-1460

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 (未使用)

const currentFileName = computed(() => {
  if (!filePath.value) return ''
  const parts = filePath.value.split(/[/\\]/)
  return parts[parts.length - 1]
})

重复都做路径分割取文件名但Display版本有截断逻辑


问题3: 文件路径计算属性重复

FileSystem.vue:1462-1485

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 (未使用)

const currentFileFullPath = computed(() => filePath.value || '')

重复获取文件路径但Display版本有ZIP模式和截断逻辑


问题4: 内容修改检测重复

FileSystem.vue:2991-2994

const contentChanged = computed(() => {
  return fileContent.value !== '' &&
         fileContent.value !== originalContent.value
})

useFileEdit.js:79-82 (未使用)

const contentChanged = computed(() => {
  return fileContent.value !== '' &&
         fileContent.value !== originalContent.value
})

完全相同100%重复代码


问题5: 保存/重置按钮状态重复

FileSystem.vue:2997-3004

const canSaveFile = computed(() => isEditableView.value && contentChanged.value)
const canResetContent = computed(() =>
  isEditableView.value &&
  contentChanged.value &&
  originalContent.value !== undefined
)

useFileEdit.js:87-98 (未使用)

const canSaveFile = computed(() => {
  return isEditMode.value && contentChanged.value
})

const canResetContent = computed(() => {
  return isEditMode.value &&
         contentChanged.value &&
         originalContent.value !== undefined
})

差异FileSystem.vue用isEditableViewuseFileEdit用isEditMode


3. 调试日志仍然过多 - 65个

$ 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行)

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中的函数

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

func getAllowedExtensions() map[string]bool {
  return map[string]bool{
    ".jpg": true, ".jpeg": true, ".png": true,
    // ... 30+ 个硬编码扩展名
  }
}

web/src/utils/constants.js:27-73 (重复定义)

export const FILE_EXTENSIONS = {
  IMAGE: ['jpg', 'jpeg', 'png', /* ... */],
  VIDEO_BROWSER: ['mp4', 'webm', /* ... */],
  // ... 类似的30+个扩展名
}

问题:前后端用不同格式重复定义相同的数据

建议后端从配置文件加载或生成JSON供前端使用


三、代码规范问题 ⚠️

7. 路径分隔符正则重复

出现次数: 15+

// FileSystem.vue 多处
path.split(/[/\\]/)  // 行 719, 798, 819, 833, 845, 2946, ...

// useFilePreview.js:124
path.split(/[/\\/]/)

// useNavigation.js:304
const parts = path.split(/[/\\]/)

建议:提取为共享常量

// utils/pathConstants.js
export const PATH_SEPARATOR_REGEX = /[/\\]/
export const splitPath = (path) => path.split(PATH_SEPARATOR_REGEX)

8. 文件类型判断分散

FileSystem.vue:857-869

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

# 由于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中的

// 保留 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: 环境变量控制(已部分实现)

// 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: 删除非关键日志(推荐)

// 删除这些类型的日志:
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

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

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 中定义一次

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迁移

目标: 添加类型安全,减少运行时错误

// 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

// internal/filesystem/export_types.go
func ExportFileTypes() string {
  types := map[string][]string{
    "image": getAllowedExtensions(),
    "binary": getForbiddenExtensions(),
  }
  json, _ := json.Marshal(types)
  return string(json)
}

方案B: 独立配置文件

# 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 回滚到重构前),避免技术债务累积