Private
Public Access
1
0
Files
u-desk/docs/03-模块文档/文件内容/file-content-fix-bugfixes.md

441 lines
14 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.
# 文件内容区显示问题修复Bug Fixes
---
## 修复记录Excel/Word 预览失败
### 修复日期
2026-02-16
### 问题描述
用户点击 Excel 文件时,预览区域显示错误:
```
❌ Excel 预览失败
Failed to fetch
💡 提示:尝试使用外部应用打开文件
```
### 根本原因
在 Wails 应用中,前端通过 `fetch` 请求本地 HTTP 服务器 (`http://localhost:18765`) 获取 Office 文件内容。这种方式在 Wails 的 webview 环境中可能会失败:
1. Wails webview 对外部 HTTP 请求有限制
2. 本地文件服务器的启动时机可能存在竞态条件
3. 网络请求在桌面应用环境中不够可靠
### 解决方案
改用 Wails IPC 机制直接读取二进制文件,不再依赖本地 HTTP 服务器。
### 修改的文件
#### 1. 后端:添加二进制文件读取 API
**位置:** `internal/filesystem/service.go`
添加 `ReadFileAsBase64` 方法:
```go
// ReadFileAsBase64 读取二进制文件并返回 base64 编码的字符串
func (s *FileSystemService) ReadFileAsBase64(path string) (string, error) {
// 路径验证
if err := s.validatePath(path); err != nil {
return "", err
}
// 检查文件扩展名是否在允许列表中
ext := strings.ToLower(filepath.Ext(path))
if !s.fileTypeManager.IsAllowed(ext) {
return "", fmt.Errorf("不允许的文件类型: %s", ext)
}
// 限制文件大小(最大 100MB
const maxFileSize = 100 * 1024 * 1024
// ... 读取文件并编码为 base64 ...
// 返回 data URI 格式
return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded), nil
}
```
#### 2. 后端:暴露 API 接口
**位置:** `app.go`
```go
// ReadFileAsBase64 读取二进制文件并返回 base64 编码的字符串
func (a *App) ReadFileAsBase64(path string) (string, error) {
return a.filesystem.ReadFileAsBase64(path)
}
```
#### 3. 前端:添加 API 调用
**位置:** `frontend/src/api/system.ts`
```typescript
export async function readFileAsBase64(path: string): Promise<string> {
if (!window.go?.main?.App?.ReadFileAsBase64) {
throw new Error('ReadFileAsBase64 API 不可用')
}
return await window.go.main.App.ReadFileAsBase64(path)
}
```
#### 4. 前端:修改预览函数
**位置:** `frontend/src/components/FileSystem/components/FileEditorPanel.vue`
修改前(通过 HTTP 服务器):
```javascript
const response = await fetch(`${serverURL}/localfs/${encodedPath}`)
const blob = await response.blob()
```
修改后(通过 Wails IPC
```javascript
// 直接通过 Wails API 读取文件base64 编码)
const dataUri = await readFileAsBase64(filePath)
// 将 data URI 转换为 Blob
const response = await fetch(dataUri)
const blob = await response.blob()
```
### 优点
- **更可靠**:直接通过 Wails IPC 通信,不依赖网络请求
- **更安全**:在后端进行文件类型验证和大小限制
- **更简洁**:移除了复杂的重试逻辑和错误处理
### 测试验证
- [x] Excel 文件预览正常显示
- [x] Word 文件预览正常显示
- [x] 文件大小限制生效
- [x] 不允许的文件类型被正确拒绝
---
## 修复记录:文件内容区状态管理
### 修复日期
2026-01-28
## 问题描述
用户报告了三个问题:
1. **闪烁问题**:打开新的目录或文件后,整个文件管理区域闪烁刷新
2. **文件名不显示**:文件内容区上面之前有文件名现在没有了
3. **切换目录后文件名消失**:点击切换到别的目录后,文件名消失了
4. **二进制文件乱码**:点击没有后缀的文件时,加载了一堆乱码字符(实际是二进制文件)
## 根本原因
### 问题1函数名错误
在计算属性 `isFileInCurrentDirectory` 中使用了不存在的 `normalizePath` 函数,应该使用 `normalizeFilePath`。这导致了运行时错误,使得计算失败并返回空值。
**错误代码:**
```javascript
return normalizePath(fileDir) === normalizePath(filePath.value)
```
### 问题2频繁的计算和重新渲染
- `isFileInCurrentDirectory` 计算属性频繁调用 `normalizeFilePath`,性能较差
- `listDirectory` 函数在目录切换时立即清空 `selectedFileItem`,导致视觉闪烁
## 修复方案
### 修复1使用正确的函数名
**位置:** `frontend/src/components/FileSystem.vue:1437-1449`
**修复内容:**
```javascript
// 判断当前打开的文件是否在当前目录中(优化性能,减少计算)
const isFileInCurrentDirectory = computed(() => {
if (!selectedFilePath.value || !filePath.value) return false
// 提取文件的父目录
const lastBackslash = selectedFilePath.value.lastIndexOf('\\')
const lastSlash = selectedFilePath.value.lastIndexOf('/')
const lastSeparator = Math.max(lastBackslash, lastSlash)
if (lastSeparator === -1) return false
const fileDir = selectedFilePath.value.substring(0, lastSeparator)
// 直接比较路径,避免频繁调用 normalizeFilePath
// 只在必要时才进行路径标准化
const fileDirNormalized = fileDir.replace(/\\/g, '/').replace(/\/$/, '')
const currentPathNormalized = filePath.value.replace(/\\/g, '/').replace(/\/$/, '')
return fileDirNormalized.toLowerCase() === currentPathNormalized.toLowerCase()
})
```
**改进点:**
- 不再调用 `normalizeFilePath` 函数,改用简单的字符串替换
- 性能优化:直接进行字符串比较而不是函数调用
- 统一路径格式:将反斜杠转换为正斜杠,并移除尾部斜杠
- 忽略大小写:使用 `toLowerCase()` 进行大小写不敏感比较
### 修复2添加错误处理
**位置:** `frontend/src/components/FileSystem.vue:1452-1473`
**修复内容:**
```javascript
const currentFileName = computed(() => {
if (isBrowsingZip.value && selectedFilePath.value) {
const parts = selectedFilePath.value.split('/')
return parts[parts.length - 1] || parts[parts.length - 2] || ''
}
if (selectedFilePath.value) {
try {
if (isFileInCurrentDirectory.value) {
return getFileName(selectedFilePath.value)
} else {
return selectedFilePath.value
}
} catch (error) {
debugWarn('[currentFileName] 计算失败,返回文件名:', error)
return getFileName(selectedFilePath.value)
}
}
return ''
})
```
**改进点:**
- 添加 try-catch 错误处理
- 即使计算失败也能返回基本的文件名
- 防止因计算错误导致文件名完全不显示
### 修复3减少视觉闪烁
**位置:** `frontend/src/components/FileSystem.vue:847-883`
**修复内容:**
```javascript
const listDirectory = async () => {
if (!filePath.value) return
if (isBrowsingZip.value && filePath.value !== originalPathBeforeZip.value) {
debugLog('检测到路径切换,退出 ZIP 模式')
exitZipMode()
}
addToHistory(filePath.value)
pushToNavigationHistory(filePath.value)
fileLoading.value = true
try {
fileList.value = await listDir(filePath.value)
// 目录加载完成后,检查原选中的文件是否还在新目录中
// 如果不在,清空 selectedFileItem避免视觉闪烁
if (selectedFileItem.value) {
const stillExists = fileList.value.some(f => f.path === selectedFileItem.value.path)
if (!stillExists) {
selectedFileItem.value = null
}
}
if (selectedFilePath.value) {
debugLog('[listDirectory] 目录已切换,保留原文件引用:', selectedFilePath.value)
}
} catch (error) {
Message.error('列出目录失败: ' + error.message)
selectedFileItem.value = null
} finally {
fileLoading.value = false
}
}
```
**改进点:**
- 延迟清空 `selectedFileItem`,等到新目录加载完成后再检查
- 如果原文件仍在新目录中,保持选中状态
- 如果原文件不在新目录中,再清空选中状态
- 减少了不必要的视觉闪烁
### 修复4优化样式
**位置:** `frontend/src/components/FileSystem.vue:3389-3406`
**修复内容:**
```css
.panel-filename {
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 500px; /* 从 300px 增加到 500px */
display: inline-block;
vertical-align: middle;
}
.panel-filename.file-outside-dir {
color: rgb(var(--warning-6));
font-weight: 500;
}
.file-location-hint {
font-size: 11px;
color: var(--color-text-3);
font-weight: normal;
white-space: nowrap;
display: inline;
}
```
**改进点:**
- 增加最大宽度到 500px可以显示更长的路径
- 添加 `display: inline-block` 确保布局正确
- 提示文字使用 `display: inline` 避免换行
### 修复5切换目录时保留文件名
**位置:** `frontend/src/components/FileSystem.vue:958-967`
**问题描述:**
当用户点击切换到其他目录时,文件名消失了。这是因为在 `selectFile` 函数中,当点击目录时会清空 `selectedFilePath`
**修复内容:**
```javascript
if (item.is_dir) {
// 目录:更新路径并列出内容
// 注意:不要清空 selectedFilePath保留原文件内容以便跨目录编辑
filePath.value = path
addToHistory(path)
listDirectory()
}
```
**同时修复收藏目录跳转:**
**位置:** `frontend/src/components/FileSystem.vue:2866-2875`
```javascript
if (fav && fav.is_dir) {
// 目录:列出内容
// 注意:不要清空 selectedFilePath保留原文件内容以便跨目录编辑
filePath.value = path
addToHistory(path)
listDirectory()
}
```
**改进点:**
- 移除了 `selectedFilePath.value = ''` 语句
- 保留了跨目录编辑的文件内容
- 用户可以在浏览其他目录时继续编辑原文件
### 修复6二进制文件检测
**位置:** `frontend/src/components/FileSystem.vue:2008-2045`
**问题描述:**
点击没有后缀的文件时,加载了一堆乱码字符。这些文件实际上是二进制文件,但代码没有检测,直接当作文本显示。
**修复内容:**
添加二进制内容检测逻辑:
```javascript
// 检查前 1000 个字符中二进制字符的比例
const checkLength = Math.min(content.length, 1000)
let binaryCharCount = 0
for (let i = 0; i < checkLength; i++) {
const charCode = content.charCodeAt(i)
// 检查是否为空字节或其他控制字符(除了常见的换行符、制表符等)
if (charCode === 0 || (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13)) {
binaryCharCount++
}
}
// 如果二进制字符超过 5%,认为是二进制文件
const binaryRatio = binaryCharCount / checkLength
if (binaryRatio > 0.05) {
// 显示友好的二进制文件提示信息
isBinaryFile.value = true
isEditMode.value = false
// ... 显示提示信息 ...
return
}
```
**改进点:**
- 自动检测二进制内容(不依赖文件扩展名)
- 检查前 1000 个字符中的二进制字符比例
- 阈值设置为 5%,平衡误报和漏报
- 显示友好的提示信息,而不是乱码
- 支持有后缀和无后缀的二进制文件检测
**检测算法:**
- 空字节charCode === 0肯定是二进制
- 控制字符charCode < 32除了 Tab(9)、LF(10)、CR(13) 外都是二进制
- 如果二进制字符比例超过 5%,判定为二进制文件
## 技术要点
1. **函数命名一致性**:确保所有函数调用都使用正确的名称
2. **性能优化**:避免在计算属性中进行昂贵的操作
3. **延迟更新**等到数据加载完成后再更新UI状态
4. **错误处理**:在关键路径添加 try-catch防止连锁失败
5. **样式优化**:合理的布局和宽度设置,确保内容正确显示
6. **状态保留**:跨目录操作时保留编辑状态,提升用户体验
7. **内容检测**:智能检测二进制文件,避免显示乱码
## 相关文件
- `frontend/src/components/FileSystem.vue` - 主要修改文件
- `frontend/src/utils/fileUtils.js` - 工具函数normalizeFilePath 等)
- `docs/file-content-state-fix.md` - 之前的改进文档
### 1. 文件名显示测试
- [x] 打开文件后,文件名正确显示在内容区标题
- [x] 切换目录后,文件名仍然显示(如果之前打开了文件)
- [x] 文件在当前目录时,只显示文件名
- [x] 文件不在当前目录时,显示完整路径和提示
### 2. 性能测试
- [x] 切换目录时无明显闪烁
- [x] 打开文件时响应迅速
- [x] 计算属性不会频繁触发重新渲染
### 3. 错误处理测试
- [x] 即使路径计算失败,文件名仍能显示
- [x] 控制台不会有运行时错误
### 4. 跨目录编辑测试
- [x] 打开文件A
- [x] 切换到目录B
- [x] 文件A的内容和文件名仍然保留
- [x] 可以继续编辑文件A
- [x] 保存时正确保存到文件A的原位置
### 5. 二进制文件检测测试
- [x] 打开无后缀的二进制文件,显示友好提示
- [x] 不会显示乱码字符
- [x] 检测算法不会误判文本文件
- [x] 有后缀的二进制文件也能正确识别
## 用户体验改进
### 修复前
- ❌ 文件名完全不显示
- ❌ 切换目录时整个区域闪烁
- ❌ 控制台有函数未定义错误
- ❌ 切换目录后文件名消失
- ❌ 二进制文件显示乱码
### 修复后
- ✅ 文件名正常显示
- ✅ 切换目录时流畅无闪烁
- ✅ 性能优化,响应更快
- ✅ 错误处理更健壮
- ✅ 切换目录后保留文件名和内容
- ✅ 二进制文件显示友好提示
## 技术要点
1. **函数命名一致性**:确保所有函数调用都使用正确的名称
2. **性能优化**:避免在计算属性中进行昂贵的操作
3. **延迟更新**等到数据加载完成后再更新UI状态
4. **错误处理**:在关键路径添加 try-catch防止连锁失败
5. **样式优化**:合理的布局和宽度设置,确保内容正确显示
## 相关文件
- `frontend/src/components/FileSystem.vue` - 主要修改文件
- `frontend/src/utils/fileUtils.js` - 工具函数normalizeFilePath 等)
- `docs/file-content-state-fix.md` - 之前的改进文档