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

629 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 重构缺漏检查报告
**日期**: 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 回滚到重构前),避免技术债务累积