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

14 KiB
Raw Blame History

文件内容区显示问题修复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 方法:

// 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

// ReadFileAsBase64 读取二进制文件并返回 base64 编码的字符串
func (a *App) ReadFileAsBase64(path string) (string, error) {
    return a.filesystem.ReadFileAsBase64(path)
}

3. 前端:添加 API 调用

位置: frontend/src/api/system.ts

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 服务器):

const response = await fetch(`${serverURL}/localfs/${encodedPath}`)
const blob = await response.blob()

修改后(通过 Wails IPC

// 直接通过 Wails API 读取文件base64 编码)
const dataUri = await readFileAsBase64(filePath)

// 将 data URI 转换为 Blob
const response = await fetch(dataUri)
const blob = await response.blob()

优点

  • 更可靠:直接通过 Wails IPC 通信,不依赖网络请求
  • 更安全:在后端进行文件类型验证和大小限制
  • 更简洁:移除了复杂的重试逻辑和错误处理

测试验证

  • Excel 文件预览正常显示
  • Word 文件预览正常显示
  • 文件大小限制生效
  • 不允许的文件类型被正确拒绝

修复记录:文件内容区状态管理

修复日期

2026-01-28

问题描述

用户报告了三个问题:

  1. 闪烁问题:打开新的目录或文件后,整个文件管理区域闪烁刷新
  2. 文件名不显示:文件内容区上面之前有文件名现在没有了
  3. 切换目录后文件名消失:点击切换到别的目录后,文件名消失了
  4. 二进制文件乱码:点击没有后缀的文件时,加载了一堆乱码字符(实际是二进制文件)

根本原因

问题1函数名错误

在计算属性 isFileInCurrentDirectory 中使用了不存在的 normalizePath 函数,应该使用 normalizeFilePath。这导致了运行时错误,使得计算失败并返回空值。

错误代码:

return normalizePath(fileDir) === normalizePath(filePath.value)

问题2频繁的计算和重新渲染

  • isFileInCurrentDirectory 计算属性频繁调用 normalizeFilePath,性能较差
  • listDirectory 函数在目录切换时立即清空 selectedFileItem,导致视觉闪烁

修复方案

修复1使用正确的函数名

位置: frontend/src/components/FileSystem.vue:1437-1449

修复内容:

// 判断当前打开的文件是否在当前目录中(优化性能,减少计算)
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

修复内容:

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

修复内容:

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

修复内容:

.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

修复内容:

if (item.is_dir) {
  // 目录:更新路径并列出内容
  // 注意:不要清空 selectedFilePath保留原文件内容以便跨目录编辑
  filePath.value = path
  addToHistory(path)
  listDirectory()
}

同时修复收藏目录跳转: 位置: frontend/src/components/FileSystem.vue:2866-2875

if (fav && fav.is_dir) {
  // 目录:列出内容
  // 注意:不要清空 selectedFilePath保留原文件内容以便跨目录编辑
  filePath.value = path
  addToHistory(path)
  listDirectory()
}

改进点:

  • 移除了 selectedFilePath.value = '' 语句
  • 保留了跨目录编辑的文件内容
  • 用户可以在浏览其他目录时继续编辑原文件

修复6二进制文件检测

位置: frontend/src/components/FileSystem.vue:2008-2045

问题描述: 点击没有后缀的文件时,加载了一堆乱码字符。这些文件实际上是二进制文件,但代码没有检测,直接当作文本显示。

修复内容: 添加二进制内容检测逻辑:

// 检查前 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. 文件名显示测试

  • 打开文件后,文件名正确显示在内容区标题
  • 切换目录后,文件名仍然显示(如果之前打开了文件)
  • 文件在当前目录时,只显示文件名
  • 文件不在当前目录时,显示完整路径和提示

2. 性能测试

  • 切换目录时无明显闪烁
  • 打开文件时响应迅速
  • 计算属性不会频繁触发重新渲染

3. 错误处理测试

  • 即使路径计算失败,文件名仍能显示
  • 控制台不会有运行时错误

4. 跨目录编辑测试

  • 打开文件A
  • 切换到目录B
  • 文件A的内容和文件名仍然保留
  • 可以继续编辑文件A
  • 保存时正确保存到文件A的原位置

5. 二进制文件检测测试

  • 打开无后缀的二进制文件,显示友好提示
  • 不会显示乱码字符
  • 检测算法不会误判文本文件
  • 有后缀的二进制文件也能正确识别

用户体验改进

修复前

  • 文件名完全不显示
  • 切换目录时整个区域闪烁
  • 控制台有函数未定义错误
  • 切换目录后文件名消失
  • 二进制文件显示乱码

修复后

  • 文件名正常显示
  • 切换目录时流畅无闪烁
  • 性能优化,响应更快
  • 错误处理更健壮
  • 切换目录后保留文件名和内容
  • 二进制文件显示友好提示

技术要点

  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 - 之前的改进文档