Private
Public Access
1
0

重构:文件系统模块化架构,增强 Markdown 渲染

- 拆分 FileSystem.vue 为模块化组件架构
- 新增 Markdown Mermaid 图表渲染支持
- 新增 180+ 编程语言代码高亮
- 修复编辑/预览模式切换渲染问题
- 优化亮色/暗色模式主题适配
- 新增 TypeScript 类型定义
This commit is contained in:
2026-02-04 03:31:22 +08:00
parent eb2cbad17b
commit a5d30684ed
119 changed files with 11244 additions and 12042 deletions

View File

@@ -0,0 +1,628 @@
# 重构缺漏检查报告
**日期**: 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 回滚到重构前),避免技术债务累积