# 文件内容区显示问题修复(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 { 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` - 之前的改进文档