Private
Public Access
1
0

优化:文件操作精确更新,避免占用问题

后端改进:
- API 返回 FileOperationResult 结构体(类型安全)
- 所有操作返回文件信息,支持精确更新
- 删除过度抽象的接口和全局函数包装器(桌面程序不需要)

前端改进:
- 精确更新文件列表(避免整目录刷新)
- 分离 add/remove/update 三个独立函数
- 重命名前智能关闭文件/文件夹,解决占用问题
- 优化错误提示,用户友好提示

技术细节:
- 定义 FileOperationResult 结构体替代 map[string]interface{}
- 前端 API 返回类型从 void 改为 any
- 保留运行时状态(如 is_favorite)
- 智能识别文件占用错误并给出解决建议
This commit is contained in:
2026-02-04 12:13:12 +08:00
parent d7de60b02c
commit edd5b7c869
8 changed files with 230 additions and 218 deletions

View File

@@ -81,41 +81,41 @@ export async function writeFile(path: string, content: string): Promise<void> {
/**
* 删除文件或目录
*/
export async function deletePath(path: string): Promise<void> {
export async function deletePath(path: string): Promise<any> {
if (!window.go?.main?.App?.DeletePath) {
throw new Error('DeletePath API 不可用')
}
await window.go.main.App.DeletePath(path)
return await window.go.main.App.DeletePath(path)
}
/**
* 创建目录
*/
export async function createDir(path: string): Promise<void> {
export async function createDir(path: string): Promise<any> {
if (!window.go?.main?.App?.CreateDir) {
throw new Error('CreateDir API 不可用')
}
await window.go.main.App.CreateDir(path)
return await window.go.main.App.CreateDir(path)
}
/**
* 创建文件
*/
export async function createFile(path: string): Promise<void> {
export async function createFile(path: string): Promise<any> {
if (!window.go?.main?.App?.CreateFile) {
throw new Error('CreateFile API 不可用')
}
await window.go.main.App.CreateFile(path)
return await window.go.main.App.CreateFile(path)
}
/**
* 重命名文件或目录
*/
export async function renamePath(oldPath: string, newPath: string): Promise<void> {
export async function renamePath(oldPath: string, newPath: string): Promise<any> {
if (!window.go?.main?.App?.RenamePath) {
throw new Error('RenamePath API 不可用')
}
await window.go.main.App.RenamePath({
return await window.go.main.App.RenamePath({
oldPath: String(oldPath),
newPath: String(newPath)
})

View File

@@ -80,12 +80,13 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
}
/**
* 删除路径(文件或目录)
* 删除路径(文件或目录),返回被删除的文件信息
*/
const deletePath = async (path: string): Promise<void> => {
const deletePath = async (path: string): Promise<FileItem> => {
try {
await deletePathApi(path)
onSuccess?.('deletePath', { path })
const result = await deletePathApi(path)
onSuccess?.('deletePath', { path, result })
return result as FileItem
} catch (error) {
const err = error as Error
onError?.('deletePath', err)
@@ -94,16 +95,17 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
}
/**
* 创建新文件
* 创建新文件,返回创建的文件信息
*/
const createNewFile = async (
dirPath: string,
filename: string,
content: string = ''
): Promise<void> => {
): Promise<FileItem> => {
try {
await createFile(dirPath, filename, content)
onSuccess?.('createFile', { dirPath, filename })
const result = await createFile(dirPath, filename, content)
onSuccess?.('createFile', { dirPath, filename, result })
return result as FileItem
} catch (error) {
const err = error as Error
onError?.('createFile', err)
@@ -112,12 +114,13 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
}
/**
* 创建新目录
* 创建新目录,返回创建的目录信息
*/
const createNewDir = async (parentPath: string, dirname: string): Promise<void> => {
const createNewDir = async (parentPath: string, dirname: string): Promise<FileItem> => {
try {
await createDir(parentPath, dirname)
onSuccess?.('createDir', { parentPath, dirname })
const result = await createDir(parentPath, dirname)
onSuccess?.('createDir', { parentPath, dirname, result })
return result as FileItem
} catch (error) {
const err = error as Error
onError?.('createDir', err)
@@ -126,9 +129,9 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
}
/**
* 重命名文件或目录
* 重命名文件或目录,返回新文件信息
*/
const rename = async (oldPath: string, newName: string): Promise<void> => {
const rename = async (oldPath: string, newName: string): Promise<FileItem> => {
// 构造新路径
const separator = oldPath.includes('\\') ? '\\' : '/'
const parentPath = oldPath.substring(
@@ -138,8 +141,9 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
const newPath = parentPath + separator + newName
try {
await renamePathApi(oldPath, newPath)
onSuccess?.('rename', { oldPath, newPath })
const result = await renamePathApi(oldPath, newPath)
onSuccess?.('rename', { oldPath, newPath, result })
return result as FileItem
} catch (error) {
const err = error as Error
onError?.('rename', err)

View File

@@ -532,54 +532,67 @@ const handleSaveEditing = async (oldPath: string, newName: string) => {
}
try {
await fileOps.rename(oldPath, trimmedName)
// 如果重命名的是当前打开的文件,更新其路径
// 如果重命名的是当前打开的文件,先关闭编辑器和预览
if (selectedFileItem.value?.path === oldPath) {
selectedFileItem.value = {
...selectedFileItem.value,
path: newPath,
name: trimmedName
// 如果是文件(不是文件夹),才需要关闭编辑器
if (!selectedFileItem.value.is_dir) {
// 清空编辑器内容
await clearContent()
// 清空预览URL
if (previewUrl.value) {
previewUrl.value = ''
}
}
// 取消选中状态
selectedFileItem.value = null
// 等待文件句柄释放(文件需要更长时间)
await new Promise(resolve => setTimeout(resolve, 300))
}
// 刷新文件列表(重命名成功后必须刷新)
await loadDirectory(filePath.value)
const renamedFile = await fileOps.rename(oldPath, trimmedName)
// 更新文件列表(保留收藏状态)
updateFileInList(oldPath, renamedFile)
// 如果重命名的是收藏的文件,更新收藏夹中的路径
// 注意:必须在刷新文件列表后才能找到新文件
if (isFavorite(oldPath)) {
// 移除旧路径
removeFav(oldPath)
// 添加新路径(保持收藏状态)
const newFile = fileList.value.find(f => f.path === newPath)
if (newFile) {
toggleFav(newFile)
}
toggleFav(renamedFile)
}
Message.success(`✓ 重命名成功: ${trimmedName}`)
} catch (error: any) {
// 解析并清理错误
// 提取错误
let errorMsg = error?.message || error?.toString() || '未知错误'
// 清理后端返回的错误消息(去除命令和路径部分)
// 格式rename oldPath newPath: actual error message
if (errorMsg.includes(': ')) {
const parts = errorMsg.split(': ')
if (parts.length > 1) {
// 取最后一部分作为真正的错误信息
errorMsg = parts.slice(1).join(': ')
}
}
// 清理常见的错误前缀
// 清理后端返回的错误前缀
errorMsg = errorMsg
.replace(/^rename\s+.*?:\s*/i, '') // 移除 "rename path: " 前缀
.replace(/^create\s+.*?:\s*/i, '') // 移除 "create path: " 前缀
.replace(/^rename\s+.*?:\s*/i, '')
.replace(/^create\s+.*?:\s*/i, '')
.replace(/^delete\s+.*?:\s*/i, '')
.trim()
// 针对常见错误提供友好提示
if (errorMsg.includes('being used by another process') ||
errorMsg.includes('being used by another process') ||
errorMsg.includes('被另一个进程占用')) {
errorMsg = '文件正在被其他程序使用,请先关闭该文件后重试'
if (selectedFileItem.value?.is_dir) {
errorMsg = '文件夹正在被其他程序使用(如文件管理器、终端等),请先关闭后重试'
}
} else if (errorMsg.includes('access is denied') ||
errorMsg.includes('permission denied')) {
errorMsg = '权限不足,无法重命名该文件'
} else if (errorMsg.includes('no such file') ||
errorMsg.includes('cannot find')) {
errorMsg = '文件不存在,可能已被删除或移动'
}
Message.error(`重命名失败: ${errorMsg}`)
// 失败时恢复编辑状态
editingFilePath.value = oldPath
editingFileName.value = oldName
@@ -730,9 +743,9 @@ const handleCreateFile = async () => {
const fullPath = `${filePath.value}\\${fileName}`
try {
await fileOps.createNewFile(fullPath)
const newFile = await fileOps.createNewFile(fullPath)
Message.success(`✓ 文件 "${fileName}" 创建成功`)
await loadDirectory(filePath.value)
addFileToList(newFile)
} catch (error: any) {
Message.error(`创建文件失败: ${error.message || error}`)
}
@@ -786,9 +799,9 @@ const handleCreateDir = async () => {
const fullPath = `${filePath.value}\\${folderName}`
try {
await fileOps.createNewDir(fullPath)
const newDir = await fileOps.createNewDir(fullPath)
Message.success(`✓ 文件夹 "${folderName}" 创建成功`)
await loadDirectory(filePath.value)
addFileToList(newDir)
} catch (error: any) {
Message.error(`创建文件夹失败: ${error.message || error}`)
}
@@ -827,6 +840,9 @@ const handleDeleteFile = async (file: FileItem) => {
await fileOps.deletePath(targetPath)
Message.success('删除成功')
// 从文件列表中移除
removeFileFromList(targetPath)
// 如果删除的是收藏的文件,从收藏夹中移除
if (isFavorite(targetPath)) {
removeFav(targetPath)
@@ -836,9 +852,6 @@ const handleDeleteFile = async (file: FileItem) => {
if (selectedFileItem.value?.path === targetPath) {
selectedFileItem.value = null
}
// 刷新文件列表
await loadDirectory(filePath.value)
} catch (error: any) {
Message.error(`删除失败: ${error.message || error}`)
}
@@ -988,6 +1001,35 @@ const loadDirectory = async (path: string) => {
}
}
/**
* 添加文件到列表(保持排序)
*/
const addFileToList = (item: FileItem) => {
fileList.value = sortFileList([...fileList.value, { ...item, is_favorite: false }])
}
/**
* 从列表中移除文件
*/
const removeFileFromList = (path: string) => {
fileList.value = fileList.value.filter(f => f.path !== path)
}
/**
* 更新列表中的文件信息(保留运行时状态如 is_favorite
*/
const updateFileInList = (oldPath: string, newItem: FileItem) => {
const index = fileList.value.findIndex(f => f.path === oldPath)
if (index !== -1) {
// 保留原有属性(如 is_favorite更新其他字段
fileList.value[index] = {
...fileList.value[index], // 保留原有所有属性
...newItem, // 覆盖新字段
is_favorite: fileList.value[index].is_favorite // 确保保留收藏状态
}
}
}
// 加载 ZIP 目录内容
const loadZipDirectoryContents = async (zipPath: string, currentDir: string): Promise<FileItem[]> => {
fileLoading.value = true

View File

@@ -17,6 +17,10 @@ export interface FileItem {
is_dir: boolean
/** 修改时间 */
modified_time?: string
/** 是否被收藏(运行时属性) */
is_favorite?: boolean
/** 旧路径(仅重命名操作时存在) */
old_path?: string
}
/**