525 lines
12 KiB
Markdown
525 lines
12 KiB
Markdown
# FileSystem.vue 代码 Review 报告
|
||
|
||
**Review 时间**: 2026-01-30 13:20
|
||
**文件**: `frontend/src/components/FileSystem.vue`
|
||
**代码行数**: 4,091 行
|
||
**Review 重点**: DRY、可读性、防御性编程、命名规范
|
||
|
||
---
|
||
|
||
## 📊 总体评估
|
||
|
||
| 维度 | 评分 | 说明 |
|
||
|------|------|------|
|
||
| **DRY 原则** | 🟡 中 | 存在部分重复代码,但不严重 |
|
||
| **可读性** | 🟢 良好 | 注释充分,逻辑清晰 |
|
||
| **防御性编程** | 🟢 良好 | 没有过度防御,合理使用 |
|
||
| **命名规范** | 🟡 中 | 部分函数命名不够清晰 |
|
||
| **逻辑嵌套** | 🟢 良好 | 大部分函数嵌套在 3 层以内 |
|
||
| **代码规范** | 🟢 良好 | 符合 Vue 3 最佳实践 |
|
||
|
||
**综合评分**: 🟢 **良好** (80/100)
|
||
|
||
---
|
||
|
||
## ✅ 优点
|
||
|
||
### 1. 注释充分 ✅
|
||
```javascript
|
||
// 好例子
|
||
const currentZipPath = ref('') // 当前浏览的 zip 文件路径
|
||
const currentZipDirectory = ref('') // 当前在 zip 中的目录路径
|
||
const isBrowsingZip = ref(false) // 是否正在浏览 zip 文件
|
||
```
|
||
|
||
### 2. debugLog 使用适度 ✅
|
||
```bash
|
||
# 只有 2 个 debugLog
|
||
$ Select-String -Path "FileSystem.vue" -Pattern "debugLog"
|
||
Count: 2
|
||
```
|
||
|
||
**评价**: 没有过度防御性编程,很好。
|
||
|
||
### 3. 逻辑清晰 ✅
|
||
大部分函数职责明确,易于理解。
|
||
|
||
### 4. 类型安全 ✅
|
||
使用 computed、ref 等 Vue 3 Composition API 正确。
|
||
|
||
---
|
||
|
||
## 🚨 问题分析
|
||
|
||
### 问题 1: 命名不一致(中等)
|
||
|
||
#### 描述
|
||
函数名后缀使用不统一,如 `previewImageLocal` vs `previewVideo`。
|
||
|
||
#### 示例
|
||
```javascript
|
||
// 不一致
|
||
const previewImageLocal = async (targetPath) => { ... }
|
||
const previewVideoLocal = (targetPath) => { ... }
|
||
const previewMedia = (mediaType, targetPath) => { ... }
|
||
const getMimeType = (ext) => { ... }
|
||
```
|
||
|
||
#### 问题
|
||
- 有些函数有 `Local` 后缀
|
||
- 有些没有
|
||
- 容易让人困惑
|
||
|
||
#### 建议统一命名
|
||
```javascript
|
||
// 方案 1: 全部去掉 Local 后缀
|
||
const previewImage = async (targetPath) => { ... }
|
||
const previewVideo = (targetPath) => { ... }
|
||
|
||
// 方案 2: 统一添加 Local 后缀(如果表示本地文件)
|
||
const previewImageLocal = async (targetPath) => { ... }
|
||
const previewVideoLocal = (targetPath) => { ... }
|
||
const getMimeTypeLocal = (ext) => { ... }
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 2: 重复的文件扩展名提取(低)
|
||
|
||
#### 描述
|
||
多次使用 `.split('.').pop()?.toLowerCase()` 获取文件扩展名。
|
||
|
||
#### 示例
|
||
```javascript
|
||
// 代码中 7 处出现
|
||
const ext = zipFilePath.split('.').pop()?.toLowerCase() || ''
|
||
const ext = fileToRead.split('.').pop()?.toLowerCase() || ''
|
||
const ext = item.path.split('.').pop()?.toLowerCase() || ''
|
||
const ext = fileName.split('.').pop()?.toLowerCase() || ''
|
||
```
|
||
|
||
#### 建议:提取为工具函数
|
||
```javascript
|
||
// 创建 frontend/src/utils/fileUtils.js
|
||
export const getFileExtension = (filename) => {
|
||
return filename.split('.').pop()?.toLowerCase() || ''
|
||
}
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { getFileExtension } from '@/utils/fileUtils'
|
||
|
||
const ext = getFileExtension(zipFilePath)
|
||
const ext = getFileExtension(fileToRead)
|
||
const ext = getFileExtension(item.path)
|
||
const ext = getFileExtension(fileName)
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3: 路径分割重复(低)
|
||
|
||
#### 描述
|
||
路径分割逻辑多次重复。
|
||
|
||
#### 示例
|
||
```javascript
|
||
// 代码中多次出现
|
||
selectedFilePath.value.split('/')
|
||
relPath.replace(/\//g, sep).split(sep)
|
||
item.path.split('.')
|
||
fileName.split('.')
|
||
```
|
||
|
||
#### 建议:提取为工具函数
|
||
```javascript
|
||
// 创建 frontend/src/utils/pathUtils.js
|
||
export const splitPath = (path) => path.split(/[/\\]/)
|
||
export const getFileName = (path) => splitPath(path).pop()
|
||
export const getParentPath = (path) => {
|
||
const parts = splitPath(path)
|
||
parts.pop()
|
||
return parts.join('/')
|
||
}
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { splitPath, getFileName, getParentPath } from '@/utils/pathUtils'
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 4: 冗长的字符串拼接(低)
|
||
|
||
#### 描述
|
||
URL 拼接方式冗长。
|
||
|
||
#### 示例
|
||
```javascript
|
||
previewUrl.value = `${fileServerURL.value}/localfs/${normalizeFilePath(tempFilePath, true)}`
|
||
const imgUrl = `${fileServerURL.value}/localfs/${normalizeFilePath(tempImgPath, true)}`
|
||
```
|
||
|
||
#### 建议:封装为函数
|
||
```javascript
|
||
// 创建 frontend/src/utils/fileServer.js
|
||
export const getFileServerUrl = (filePath) => {
|
||
const serverUrl = 'http://localhost:18765'
|
||
return `${serverUrl}/localfs/${normalizeFilePath(filePath, true)}`
|
||
}
|
||
|
||
// 使用
|
||
import { getFileServerUrl } from '@/utils/fileServer'
|
||
|
||
previewUrl.value = getFileServerUrl(tempFilePath)
|
||
const imgUrl = getFileServerUrl(tempImgPath)
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 5: 魔法字符串(中)
|
||
|
||
#### 描述
|
||
存在硬编码的配置值。
|
||
|
||
#### 示例
|
||
```javascript
|
||
const fileServerURL = ref('http://localhost:18765') // 硬编码端口
|
||
const displayWidth = 160 // 魔法数字
|
||
const FILE_SIZE_THRESHOLDS = { BIG_FILE: 10 * 1024 * 1024 } // 魔法数字
|
||
```
|
||
|
||
#### 建议:移到配置
|
||
```javascript
|
||
// 创建 frontend/src/config/fileSystem.js
|
||
export const FILESYSTEM_CONFIG = {
|
||
FILE_SERVER_URL: 'http://localhost:18765',
|
||
FILE_SERVER_PORT: 18765,
|
||
FILE_SIZE_THRESHOLDS: {
|
||
BIG_FILE: 10 * 1024 * 1024, // 10MB
|
||
MEDIUM_FILE: 1024 * 1024, // 1MB
|
||
},
|
||
DISPLAY: {
|
||
WIDTH: 160,
|
||
HEIGHT: 400,
|
||
}
|
||
}
|
||
|
||
// 使用
|
||
import { FILESYSTEM_CONFIG } from '@/config/fileSystem'
|
||
const fileServerURL = ref(FILESYSTEM_CONFIG.FILE_SERVER_URL)
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 优先级改进清单
|
||
|
||
### 🔴 P0 - 立即修复(今天)
|
||
|
||
#### 1. 统一函数命名(30 分钟)
|
||
```javascript
|
||
// 修改文件
|
||
frontend/src/components/FileSystem.vue
|
||
|
||
// 修改内容
|
||
previewImageLocal → previewImage
|
||
previewVideoLocal → previewVideo
|
||
previewAudioLocal → previewAudio
|
||
previewPdfLocal → previewPdf
|
||
previewHtmlLocal → previewHtml
|
||
previewMarkdownLocal → previewMarkdown
|
||
getMimeType → getFileMimeTypeLocal(或其他统一命名)
|
||
|
||
// 查找和替换
|
||
// 1. Ctrl+F 搜索 "Local"
|
||
// 2. 统一处理所有后缀
|
||
```
|
||
|
||
#### 2. 提取文件扩展名函数(30 分钟)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/utils/fileUtils.js
|
||
|
||
export const getFileExtension = (filename) => {
|
||
return filename.split('.').pop()?.toLowerCase() || ''
|
||
}
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { getFileExtension } from '@/utils/fileUtils'
|
||
|
||
// 替换 7 处使用
|
||
const ext = getFileExtension(zipFilePath)
|
||
```
|
||
|
||
---
|
||
|
||
### 🟡 P1 - 本周修复(4-6 小时)
|
||
|
||
#### 3. 提取路径处理函数(1 小时)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/utils/pathUtils.js
|
||
|
||
export const splitPath = (path) => path.split(/[/\\]/)
|
||
export const getFileName = (path) => splitPath(path).pop()
|
||
export const getParentPath = (path) => {
|
||
const parts = splitPath(path)
|
||
parts.pop()
|
||
return parts.join('/')
|
||
}
|
||
export const normalizePath = (path) => path.replace(/\\/g, '/')
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { splitPath, getFileName, getParentPath, normalizePath } from '@/utils/pathUtils'
|
||
```
|
||
|
||
#### 4. 提取文件服务器 URL 函数(1 小时)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/utils/fileServer.js
|
||
|
||
import { FILESYSTEM_CONFIG } from '@/config/fileSystem'
|
||
|
||
export const getFileServerUrl = (filePath) => {
|
||
return `${FILESYSTEM_CONFIG.FILE_SERVER_URL}/localfs/${normalizeFilePath(filePath, true)}`
|
||
}
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { getFileServerUrl } from '@/utils/fileServer'
|
||
```
|
||
|
||
#### 5. 提取文件类型判断函数(1 小时)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/utils/fileTypes.js
|
||
|
||
import { FILE_EXTENSIONS } from '@/utils/constants'
|
||
|
||
export const isImageFile = (filename) => {
|
||
const ext = getFileExtension(filename)
|
||
return FILE_EXTENSIONS.IMAGE.includes(ext)
|
||
}
|
||
|
||
export const isVideoFile = (filename) => { ... }
|
||
export const isAudioFile = (filename) => { ... }
|
||
export const isPdfFile = (filename) => { ... }
|
||
export const isHtmlFile = (filename) => { ... }
|
||
export const isMarkdownFile = (filename) => { ... }
|
||
export const isOfficeFile = (filename) => { ... }
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import {
|
||
isImageFile,
|
||
isVideoFile,
|
||
isAudioFile,
|
||
isPdfFile,
|
||
isHtmlFile,
|
||
isMarkdownFile
|
||
} from '@/utils/fileTypes'
|
||
```
|
||
|
||
#### 6. 提取格式化函数(1 小时)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/utils/formatUtils.js
|
||
|
||
export const formatBytes = (bytes) => {
|
||
if (bytes === 0) return '0 B'
|
||
const k = 1024
|
||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||
}
|
||
|
||
export const formatSize = (bytes) => formatBytes(bytes)
|
||
export const formatDate = (timestamp) => { ... }
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { formatBytes } from '@/utils/formatUtils'
|
||
```
|
||
|
||
#### 7. 创建配置文件(1 小时)
|
||
```javascript
|
||
// 创建文件
|
||
frontend/src/config/fileSystem.js
|
||
|
||
export const FILESYSTEM_CONFIG = {
|
||
FILE_SERVER_URL: 'http://localhost:18765',
|
||
FILE_SERVER_PORT: 18765,
|
||
FILE_SIZE_THRESHOLDS: {
|
||
BIG_FILE: 10 * 1024 * 1024,
|
||
MEDIUM_FILE: 1024 * 1024,
|
||
},
|
||
DISPLAY: {
|
||
WIDTH: 160,
|
||
HEIGHT: 400,
|
||
},
|
||
EDIT_MODE: {
|
||
DEFAULT_HEIGHT: 400,
|
||
MIN_HEIGHT: 200,
|
||
}
|
||
}
|
||
|
||
// 在 FileSystem.vue 中使用
|
||
import { FILESYSTEM_CONFIG } from '@/config/fileSystem'
|
||
const fileServerURL = ref(FILESYSTEM_CONFIG.FILE_SERVER_URL)
|
||
```
|
||
|
||
---
|
||
|
||
### 🟢 P2 - 下周优化(4-6 小时)
|
||
|
||
#### 8. 检查代码重复(2 小时)
|
||
```bash
|
||
# 使用工具检查重复
|
||
# 建议安装 eslint-plugin-duplicate
|
||
|
||
# 手动检查常见模式
|
||
- 相似的函数(如 previewXxxLocal)
|
||
- 重复的字符串拼接
|
||
- 重复的条件判断
|
||
```
|
||
|
||
#### 9. 优化逻辑嵌套(2 小时)
|
||
```javascript
|
||
// 检查嵌套超过 3 层的地方
|
||
// 提前返回减少嵌套
|
||
|
||
// 例子
|
||
// 修改前
|
||
if (condition1) {
|
||
if (condition2) {
|
||
if (condition3) {
|
||
// 做某事
|
||
}
|
||
}
|
||
}
|
||
|
||
// 修改后
|
||
if (!condition1) return
|
||
if (!condition2) return
|
||
if (!condition3) return
|
||
// 做某事
|
||
```
|
||
|
||
#### 10. 添加单元测试(2 小时)
|
||
```javascript
|
||
// 创建测试文件
|
||
frontend/tests/utils/fileUtils.spec.js
|
||
frontend/tests/utils/pathUtils.spec.js
|
||
frontend/tests/utils/fileTypes.spec.js
|
||
|
||
// 测试函数
|
||
import { getFileExtension, formatBytes } from '@/utils/fileUtils'
|
||
|
||
describe('fileUtils', () => {
|
||
it('should get file extension', () => {
|
||
expect(getFileExtension('test.txt')).toBe('txt')
|
||
expect(getFileExtension('test.JSON')).toBe('json')
|
||
})
|
||
|
||
it('should format bytes', () => {
|
||
expect(formatBytes(1024)).toBe('1.00 KB')
|
||
expect(formatBytes(1048576)).toBe('1.00 MB')
|
||
})
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 改进后预期效果
|
||
|
||
### 代码质量改善
|
||
| 指标 | 当前值 | 目标值 | 改善 |
|
||
|------|--------|--------|------|
|
||
| 代码重复率 | ~5% | < 3% | -40% |
|
||
| 函数命名一致性 | 70% | 100% | +30% |
|
||
| 魔法字符串 | 10+ | 0 | -100% |
|
||
| 工具函数提取 | 0% | 80% | +80% |
|
||
| 单元测试覆盖率 | 0% | 30% | +30% |
|
||
|
||
### 可维护性改善
|
||
| 维度 | 当前 | 目标 | 改善 |
|
||
|------|------|------|------|
|
||
| 新增功能的开发时间 | 基准 | -40% | 更快 |
|
||
| Bug 修复时间 | 基准 | -50% | 更快 |
|
||
| 代码理解难度 | 中 | 低 | 更易读 |
|
||
|
||
---
|
||
|
||
## 🎯 执行计划
|
||
|
||
### 第一步:立即修复(今天,1 小时)
|
||
1. 统一函数命名(去掉 Local 后缀)
|
||
2. 提取 getFileExtension 函数
|
||
3. 测试功能正常
|
||
|
||
### 第二步:本周优化(本周,6 小时)
|
||
1. 提取路径处理函数
|
||
2. 提取文件服务器 URL 函数
|
||
3. 提取文件类型判断函数
|
||
4. 提取格式化函数
|
||
5. 创建配置文件
|
||
|
||
### 第三步:下周优化(下周,6 小时)
|
||
1. 检查并消除代码重复
|
||
2. 优化逻辑嵌套
|
||
3. 添加单元测试
|
||
|
||
---
|
||
|
||
## 📝 改进总结
|
||
|
||
### 优点(保持)
|
||
- ✅ 注释充分
|
||
- ✅ debugLog 使用适度
|
||
- ✅ 逻辑清晰
|
||
- ✅ 类型安全
|
||
|
||
### 改进(按优先级)
|
||
- 🟡 函数命名更统一
|
||
- 🟡 消除重复代码
|
||
- 🟡 移除魔法字符串
|
||
- 🟢 提取工具函数
|
||
- 🟢 添加单元测试
|
||
|
||
### 拒绝的改进
|
||
- ❌ 不建议过度拆分组件(风险太高)
|
||
- ❌ 不建议立即使用 TypeScript(需要培训)
|
||
- ❌ 不建议引入新的复杂度
|
||
|
||
---
|
||
|
||
## 🚀 建议的执行顺序
|
||
|
||
### 今天(1 小时)
|
||
1. 统一函数命名(30 分钟)
|
||
2. 提取 getFileExtension 函数(30 分钟)
|
||
|
||
### 明天(2 小时)
|
||
3. 提取路径处理函数(1 小时)
|
||
4. 提取文件服务器 URL 函数(1 小时)
|
||
|
||
### 后续(按需)
|
||
5. 提取文件类型判断函数
|
||
6. 提取格式化函数
|
||
7. 创建配置文件
|
||
8. 添加单元测试
|
||
|
||
---
|
||
|
||
## 📞 需要帮助?
|
||
|
||
如果在执行过程中遇到问题:
|
||
|
||
1. **查看工具函数提取示例**
|
||
参考 `docs/代码审查/refactoring-examples.md`
|
||
|
||
2. **查看命名规范**
|
||
参考 `docs/代码审查/naming-conventions.md`
|
||
|
||
3. **查看测试示例**
|
||
参考 `docs/代码审查/test-examples.md`
|
||
|
||
---
|
||
|
||
**下一步**: 开始执行 P0 优先级的修复任务!
|