优化:文件操作精确更新,避免占用问题
后端改进:
- API 返回 FileOperationResult 结构体(类型安全)
- 所有操作返回文件信息,支持精确更新
- 删除过度抽象的接口和全局函数包装器(桌面程序不需要)
前端改进:
- 精确更新文件列表(避免整目录刷新)
- 分离 add/remove/update 三个独立函数
- 重命名前智能关闭文件/文件夹,解决占用问题
- 优化错误提示,用户友好提示
技术细节:
- 定义 FileOperationResult 结构体替代 map[string]interface{}
- 前端 API 返回类型从 void 改为 any
- 保留运行时状态(如 is_favorite)
- 智能识别文件占用错误并给出解决建议
This commit is contained in:
8
app.go
8
app.go
@@ -270,17 +270,17 @@ func (a *App) ListDir(path string) ([]map[string]interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDir 创建目录
|
// CreateDir 创建目录
|
||||||
func (a *App) CreateDir(path string) error {
|
func (a *App) CreateDir(path string) (*filesystem.FileOperationResult, error) {
|
||||||
return a.filesystem.CreateDir(path)
|
return a.filesystem.CreateDir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFile 创建文件
|
// CreateFile 创建文件
|
||||||
func (a *App) CreateFile(path string) error {
|
func (a *App) CreateFile(path string) (*filesystem.FileOperationResult, error) {
|
||||||
return a.filesystem.CreateFile(path)
|
return a.filesystem.CreateFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePath 删除文件或目录
|
// DeletePath 删除文件或目录
|
||||||
func (a *App) DeletePath(path string) error {
|
func (a *App) DeletePath(path string) (*filesystem.FileOperationResult, error) {
|
||||||
return a.filesystem.DeletePath(path)
|
return a.filesystem.DeletePath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ type RenamePathRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenamePath 重命名文件或目录
|
// RenamePath 重命名文件或目录
|
||||||
func (a *App) RenamePath(req RenamePathRequest) error {
|
func (a *App) RenamePath(req RenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||||||
return a.filesystem.RenamePath(req.OldPath, req.NewPath)
|
return a.filesystem.RenamePath(req.OldPath, req.NewPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,90 +8,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ========== 向后兼容的全局函数包装器 ==========
|
// ========== 辅助函数 ==========
|
||||||
// 这些函数提供向后兼容性,内部委托给 FileSystemService
|
|
||||||
// 新代码应该使用 FileSystemService 而不是这些全局函数
|
|
||||||
|
|
||||||
// ReadFile 读取文件内容(向后兼容包装器)
|
|
||||||
func ReadFile(path string) (string, error) {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.ReadFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile 写入文件(向后兼容包装器)
|
|
||||||
func WriteFile(path, content string) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.WriteFile(path, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDir 列出目录内容(向后兼容包装器)
|
|
||||||
func ListDir(path string) ([]map[string]interface{}, error) {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.ListDir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDir 创建目录(向后兼容包装器)
|
|
||||||
func CreateDir(path string) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.CreateDir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFile 创建空文件(向后兼容包装器)
|
|
||||||
func CreateFile(path string) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.CreateFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePath 删除文件或目录(向后兼容包装器)
|
|
||||||
func DeletePath(path string) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.DeletePath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePathWithConfig 使用指定配置删除文件或目录(向后兼容包装器)
|
|
||||||
func DeletePathWithConfig(path string, config *Config) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 临时替换服务的配置
|
|
||||||
originalConfig := service.config
|
|
||||||
service.config = config
|
|
||||||
defer func() { service.config = originalConfig }()
|
|
||||||
|
|
||||||
return service.DeletePath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileInfo 获取文件信息(向后兼容包装器)
|
|
||||||
func GetFileInfo(path string) (map[string]interface{}, error) {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.GetFileInfo(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenPath 打开文件或目录(使用系统默认程序)
|
// OpenPath 打开文件或目录(使用系统默认程序)
|
||||||
// 这是一个核心工具函数,保留为独立函数
|
|
||||||
func OpenPath(path string) error {
|
func OpenPath(path string) error {
|
||||||
// 使用 path.validator 进行验证
|
// 使用 path.validator 进行验证
|
||||||
validator := NewPathValidator(DefaultConfig())
|
validator := NewPathValidator(DefaultConfig())
|
||||||
@@ -132,16 +51,7 @@ func OpenPath(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenamePath 重命名文件或目录(向后兼容包装器)
|
// ========== 工具函数 ==========
|
||||||
func RenamePath(oldPath, newPath string) error {
|
|
||||||
service, err := GetGlobalService()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("服务未初始化: %v", err)
|
|
||||||
}
|
|
||||||
return service.RenamePath(oldPath, newPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 辅助函数 ==========
|
|
||||||
|
|
||||||
// formatBytes 格式化字节大小为人类可读格式
|
// formatBytes 格式化字节大小为人类可读格式
|
||||||
func formatBytes(bytes int64) string {
|
func formatBytes(bytes int64) string {
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import (
|
|||||||
"u-desk/internal/common"
|
"u-desk/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FileOperationResult 文件操作结果
|
||||||
|
type FileOperationResult struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
SizeStr string `json:"size_str,omitempty"`
|
||||||
|
IsDir bool `json:"is_dir"`
|
||||||
|
ModTime string `json:"mod_time,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
OldPath string `json:"old_path,omitempty"` // 仅重命名操作时有值
|
||||||
|
Deleted bool `json:"deleted,omitempty"` // 仅删除操作时有值
|
||||||
|
}
|
||||||
|
|
||||||
// FileSystemService 文件系统服务
|
// FileSystemService 文件系统服务
|
||||||
// 统一管理所有文件系统相关的功能,使用依赖注入而非全局变量
|
// 统一管理所有文件系统相关的功能,使用依赖注入而非全局变量
|
||||||
type FileSystemService struct {
|
type FileSystemService struct {
|
||||||
@@ -173,52 +186,52 @@ func (s *FileSystemService) Open(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除文件或目录(实现 FileService 接口)
|
// Delete 删除文件或目录(实现 FileService 接口)
|
||||||
func (s *FileSystemService) Delete(path string) error {
|
func (s *FileSystemService) Delete(path string) (*FileOperationResult, error) {
|
||||||
return s.DeletePathWithContext(context.Background(), path)
|
return s.DeletePathWithContext(context.Background(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePath 删除文件或目录
|
// DeletePath 删除文件或目录
|
||||||
func (s *FileSystemService) DeletePath(path string) error {
|
func (s *FileSystemService) DeletePath(path string) (*FileOperationResult, error) {
|
||||||
return s.DeletePathWithContext(context.Background(), path)
|
return s.DeletePathWithContext(context.Background(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePathWithContext 带上下文的删除操作
|
// DeletePathWithContext 带上下文的删除操作,返回被删除文件的信息
|
||||||
func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path string) error {
|
func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path string) (*FileOperationResult, error) {
|
||||||
// 路径验证
|
// 路径验证
|
||||||
if err := s.validatePath(path); err != nil {
|
if err := s.validatePath(path); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件信息
|
// 获取文件信息(在删除前保存)
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return fmt.Errorf("文件或目录不存在")
|
return nil, fmt.Errorf("文件或目录不存在")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("获取文件信息失败: %v", err)
|
return nil, fmt.Errorf("获取文件信息失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查删除限制
|
// 检查删除限制
|
||||||
exceeds, details, checkErr := CheckDeleteRestrictions(path, info, s.config)
|
exceeds, details, checkErr := CheckDeleteRestrictions(path, info, s.config)
|
||||||
if checkErr != nil {
|
if checkErr != nil {
|
||||||
return checkErr
|
return nil, checkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if exceeds {
|
if exceeds {
|
||||||
if s.config.Security.DeleteRestrictions.RequireConfirm {
|
if s.config.Security.DeleteRestrictions.RequireConfirm {
|
||||||
return &DeleteRestrictionWarning{
|
return nil, &DeleteRestrictionWarning{
|
||||||
Path: path,
|
Path: path,
|
||||||
Details: details,
|
Details: details,
|
||||||
Info: info,
|
Info: info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("删除限制: %s", details)
|
return nil, fmt.Errorf("删除限制: %s", details)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件锁检查(可选)
|
// 文件锁检查(可选)
|
||||||
if s.lockChecker != nil {
|
if s.lockChecker != nil {
|
||||||
if err := s.lockChecker.SafeDeleteWithLockCheck(path); err != nil {
|
if err := s.lockChecker.SafeDeleteWithLockCheck(path); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +246,7 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
|
|||||||
s.logDelete(path, info.IsDir(), info.Size(), deleteErr)
|
s.logDelete(path, info.IsDir(), info.Size(), deleteErr)
|
||||||
|
|
||||||
if deleteErr != nil {
|
if deleteErr != nil {
|
||||||
return fmt.Errorf("删除失败: %v", deleteErr)
|
return nil, fmt.Errorf("删除失败: %v", deleteErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果启用回收站,移动到回收站而非永久删除
|
// 如果启用回收站,移动到回收站而非永久删除
|
||||||
@@ -247,7 +260,17 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// 返回被删除的文件信息,用于前端更新
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: path,
|
||||||
|
Name: info.Name(),
|
||||||
|
Size: info.Size(),
|
||||||
|
SizeStr: formatBytes(info.Size()),
|
||||||
|
IsDir: info.IsDir(),
|
||||||
|
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
|
||||||
|
Mode: info.Mode().String(),
|
||||||
|
Deleted: true,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDir 列出目录内容
|
// ListDir 列出目录内容
|
||||||
@@ -292,14 +315,14 @@ func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, erro
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDir 创建目录
|
// CreateDir 创建目录,返回创建的目录信息
|
||||||
func (s *FileSystemService) CreateDir(path string) error {
|
func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error) {
|
||||||
if err := s.validatePath(path); err != nil {
|
if err := s.validatePath(path); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(path, DefaultDirPermissions); err != nil {
|
if err := os.MkdirAll(path, DefaultDirPermissions); err != nil {
|
||||||
return fmt.Errorf("创建目录失败: %v", err)
|
return nil, fmt.Errorf("创建目录失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logAudit(AuditLogEntry{
|
s.logAudit(AuditLogEntry{
|
||||||
@@ -310,23 +333,42 @@ func (s *FileSystemService) CreateDir(path string) error {
|
|||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
// 获取创建的目录信息
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// 创建成功但获取信息失败,返回基本信息
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: path,
|
||||||
|
Name: filepath.Base(path),
|
||||||
|
IsDir: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: path,
|
||||||
|
Name: info.Name(),
|
||||||
|
Size: info.Size(),
|
||||||
|
SizeStr: formatBytes(info.Size()),
|
||||||
|
IsDir: true,
|
||||||
|
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
|
||||||
|
Mode: info.Mode().String(),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFile 创建空文件
|
// CreateFile 创建空文件,返回创建的文件信息
|
||||||
func (s *FileSystemService) CreateFile(path string) error {
|
func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error) {
|
||||||
if err := s.validatePath(path); err != nil {
|
if err := s.validatePath(path); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查文件是否已存在
|
// 检查文件是否已存在
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
return fmt.Errorf("文件已存在")
|
return nil, fmt.Errorf("文件已存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(path)
|
file, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("创建文件失败: %v", err)
|
return nil, fmt.Errorf("创建文件失败: %v", err)
|
||||||
}
|
}
|
||||||
file.Close()
|
file.Close()
|
||||||
|
|
||||||
@@ -338,7 +380,27 @@ func (s *FileSystemService) CreateFile(path string) error {
|
|||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
// 获取创建的文件信息
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// 创建成功但获取信息失败,返回基本信息
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: path,
|
||||||
|
Name: filepath.Base(path),
|
||||||
|
IsDir: false,
|
||||||
|
Size: 0,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: path,
|
||||||
|
Name: info.Name(),
|
||||||
|
Size: info.Size(),
|
||||||
|
SizeStr: formatBytes(info.Size()),
|
||||||
|
IsDir: false,
|
||||||
|
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
|
||||||
|
Mode: info.Mode().String(),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInfo 获取文件信息(实现 FileService 接口)
|
// GetInfo 获取文件信息(实现 FileService 接口)
|
||||||
@@ -380,21 +442,21 @@ func (s *FileSystemService) OpenPath(path string) error {
|
|||||||
return OpenPath(path)
|
return OpenPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenamePath 重命名文件或目录
|
// RenamePath 重命名文件或目录,返回新文件信息
|
||||||
func (s *FileSystemService) RenamePath(oldPath, newPath string) error {
|
func (s *FileSystemService) RenamePath(oldPath, newPath string) (*FileOperationResult, error) {
|
||||||
// 验证旧路径
|
// 验证旧路径
|
||||||
if err := s.validatePath(oldPath); err != nil {
|
if err := s.validatePath(oldPath); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证新路径
|
// 验证新路径
|
||||||
if err := s.validatePath(newPath); err != nil {
|
if err := s.validatePath(newPath); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行重命名
|
// 执行重命名
|
||||||
if err := os.Rename(oldPath, newPath); err != nil {
|
if err := os.Rename(oldPath, newPath); err != nil {
|
||||||
return fmt.Errorf("重命名失败: %v", err)
|
return nil, fmt.Errorf("重命名失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logAudit(AuditLogEntry{
|
s.logAudit(AuditLogEntry{
|
||||||
@@ -405,7 +467,27 @@ func (s *FileSystemService) RenamePath(oldPath, newPath string) error {
|
|||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
// 获取新文件信息
|
||||||
|
info, err := os.Stat(newPath)
|
||||||
|
if err != nil {
|
||||||
|
// 重命名成功但获取信息失败,返回基本信息
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: newPath,
|
||||||
|
Name: filepath.Base(newPath),
|
||||||
|
OldPath: oldPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileOperationResult{
|
||||||
|
Path: newPath,
|
||||||
|
Name: info.Name(),
|
||||||
|
Size: info.Size(),
|
||||||
|
SizeStr: formatBytes(info.Size()),
|
||||||
|
IsDir: info.IsDir(),
|
||||||
|
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
|
||||||
|
Mode: info.Mode().String(),
|
||||||
|
OldPath: oldPath,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== ZIP操作接口 ==========
|
// ========== ZIP操作接口 ==========
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package filesystem
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileService 文件操作核心接口
|
|
||||||
// 定义所有文件操作的基本功能,便于mock测试
|
|
||||||
type FileService interface {
|
|
||||||
// 基本操作
|
|
||||||
Read(path string) (string, error)
|
|
||||||
Write(path, content string) error
|
|
||||||
Delete(path string) error
|
|
||||||
List(path string) ([]map[string]interface{}, error)
|
|
||||||
CreateDir(path string) error
|
|
||||||
CreateFile(path string) error
|
|
||||||
GetInfo(path string) (map[string]interface{}, error)
|
|
||||||
Open(path string) error
|
|
||||||
|
|
||||||
// 快捷方式
|
|
||||||
ResolveShortcut(lnkPath string) (targetPath string, err error)
|
|
||||||
|
|
||||||
// 配置
|
|
||||||
GetConfig() *Config
|
|
||||||
Close(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保实现接口
|
|
||||||
var _ FileService = (*FileSystemService)(nil)
|
|
||||||
|
|
||||||
@@ -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) {
|
if (!window.go?.main?.App?.DeletePath) {
|
||||||
throw new Error('DeletePath API 不可用')
|
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) {
|
if (!window.go?.main?.App?.CreateDir) {
|
||||||
throw new Error('CreateDir API 不可用')
|
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) {
|
if (!window.go?.main?.App?.CreateFile) {
|
||||||
throw new Error('CreateFile API 不可用')
|
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) {
|
if (!window.go?.main?.App?.RenamePath) {
|
||||||
throw new Error('RenamePath API 不可用')
|
throw new Error('RenamePath API 不可用')
|
||||||
}
|
}
|
||||||
await window.go.main.App.RenamePath({
|
return await window.go.main.App.RenamePath({
|
||||||
oldPath: String(oldPath),
|
oldPath: String(oldPath),
|
||||||
newPath: String(newPath)
|
newPath: String(newPath)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -80,12 +80,13 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除路径(文件或目录)
|
* 删除路径(文件或目录),返回被删除的文件信息
|
||||||
*/
|
*/
|
||||||
const deletePath = async (path: string): Promise<void> => {
|
const deletePath = async (path: string): Promise<FileItem> => {
|
||||||
try {
|
try {
|
||||||
await deletePathApi(path)
|
const result = await deletePathApi(path)
|
||||||
onSuccess?.('deletePath', { path })
|
onSuccess?.('deletePath', { path, result })
|
||||||
|
return result as FileItem
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error
|
const err = error as Error
|
||||||
onError?.('deletePath', err)
|
onError?.('deletePath', err)
|
||||||
@@ -94,16 +95,17 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新文件
|
* 创建新文件,返回创建的文件信息
|
||||||
*/
|
*/
|
||||||
const createNewFile = async (
|
const createNewFile = async (
|
||||||
dirPath: string,
|
dirPath: string,
|
||||||
filename: string,
|
filename: string,
|
||||||
content: string = ''
|
content: string = ''
|
||||||
): Promise<void> => {
|
): Promise<FileItem> => {
|
||||||
try {
|
try {
|
||||||
await createFile(dirPath, filename, content)
|
const result = await createFile(dirPath, filename, content)
|
||||||
onSuccess?.('createFile', { dirPath, filename })
|
onSuccess?.('createFile', { dirPath, filename, result })
|
||||||
|
return result as FileItem
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error
|
const err = error as Error
|
||||||
onError?.('createFile', err)
|
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 {
|
try {
|
||||||
await createDir(parentPath, dirname)
|
const result = await createDir(parentPath, dirname)
|
||||||
onSuccess?.('createDir', { parentPath, dirname })
|
onSuccess?.('createDir', { parentPath, dirname, result })
|
||||||
|
return result as FileItem
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error
|
const err = error as Error
|
||||||
onError?.('createDir', err)
|
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 separator = oldPath.includes('\\') ? '\\' : '/'
|
||||||
const parentPath = oldPath.substring(
|
const parentPath = oldPath.substring(
|
||||||
@@ -138,8 +141,9 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) {
|
|||||||
const newPath = parentPath + separator + newName
|
const newPath = parentPath + separator + newName
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await renamePathApi(oldPath, newPath)
|
const result = await renamePathApi(oldPath, newPath)
|
||||||
onSuccess?.('rename', { oldPath, newPath })
|
onSuccess?.('rename', { oldPath, newPath, result })
|
||||||
|
return result as FileItem
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error
|
const err = error as Error
|
||||||
onError?.('rename', err)
|
onError?.('rename', err)
|
||||||
|
|||||||
@@ -532,54 +532,67 @@ const handleSaveEditing = async (oldPath: string, newName: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fileOps.rename(oldPath, trimmedName)
|
// 如果重命名的是当前打开的文件,先关闭编辑器和预览
|
||||||
|
|
||||||
// 如果重命名的是当前打开的文件,更新其路径
|
|
||||||
if (selectedFileItem.value?.path === oldPath) {
|
if (selectedFileItem.value?.path === oldPath) {
|
||||||
selectedFileItem.value = {
|
// 如果是文件(不是文件夹),才需要关闭编辑器
|
||||||
...selectedFileItem.value,
|
if (!selectedFileItem.value.is_dir) {
|
||||||
path: newPath,
|
// 清空编辑器内容
|
||||||
name: trimmedName
|
await clearContent()
|
||||||
|
|
||||||
|
// 清空预览URL
|
||||||
|
if (previewUrl.value) {
|
||||||
|
previewUrl.value = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消选中状态
|
||||||
|
selectedFileItem.value = null
|
||||||
|
|
||||||
|
// 等待文件句柄释放(文件需要更长时间)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新文件列表(重命名成功后必须刷新)
|
const renamedFile = await fileOps.rename(oldPath, trimmedName)
|
||||||
await loadDirectory(filePath.value)
|
|
||||||
|
// 更新文件列表(保留收藏状态)
|
||||||
|
updateFileInList(oldPath, renamedFile)
|
||||||
|
|
||||||
// 如果重命名的是收藏的文件,更新收藏夹中的路径
|
// 如果重命名的是收藏的文件,更新收藏夹中的路径
|
||||||
// 注意:必须在刷新文件列表后才能找到新文件
|
|
||||||
if (isFavorite(oldPath)) {
|
if (isFavorite(oldPath)) {
|
||||||
// 移除旧路径
|
|
||||||
removeFav(oldPath)
|
removeFav(oldPath)
|
||||||
// 添加新路径(保持收藏状态)
|
toggleFav(renamedFile)
|
||||||
const newFile = fileList.value.find(f => f.path === newPath)
|
|
||||||
if (newFile) {
|
|
||||||
toggleFav(newFile)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.success(`✓ 重命名成功: ${trimmedName}`)
|
Message.success(`✓ 重命名成功: ${trimmedName}`)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 解析并清理错误消息
|
// 提取错误信息
|
||||||
let errorMsg = error?.message || error?.toString() || '未知错误'
|
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
|
errorMsg = errorMsg
|
||||||
.replace(/^rename\s+.*?:\s*/i, '') // 移除 "rename path: " 前缀
|
.replace(/^rename\s+.*?:\s*/i, '')
|
||||||
.replace(/^create\s+.*?:\s*/i, '') // 移除 "create path: " 前缀
|
.replace(/^create\s+.*?:\s*/i, '')
|
||||||
|
.replace(/^delete\s+.*?:\s*/i, '')
|
||||||
.trim()
|
.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}`)
|
Message.error(`重命名失败: ${errorMsg}`)
|
||||||
|
|
||||||
// 失败时恢复编辑状态
|
// 失败时恢复编辑状态
|
||||||
editingFilePath.value = oldPath
|
editingFilePath.value = oldPath
|
||||||
editingFileName.value = oldName
|
editingFileName.value = oldName
|
||||||
@@ -730,9 +743,9 @@ const handleCreateFile = async () => {
|
|||||||
const fullPath = `${filePath.value}\\${fileName}`
|
const fullPath = `${filePath.value}\\${fileName}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fileOps.createNewFile(fullPath)
|
const newFile = await fileOps.createNewFile(fullPath)
|
||||||
Message.success(`✓ 文件 "${fileName}" 创建成功`)
|
Message.success(`✓ 文件 "${fileName}" 创建成功`)
|
||||||
await loadDirectory(filePath.value)
|
addFileToList(newFile)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Message.error(`创建文件失败: ${error.message || error}`)
|
Message.error(`创建文件失败: ${error.message || error}`)
|
||||||
}
|
}
|
||||||
@@ -786,9 +799,9 @@ const handleCreateDir = async () => {
|
|||||||
const fullPath = `${filePath.value}\\${folderName}`
|
const fullPath = `${filePath.value}\\${folderName}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fileOps.createNewDir(fullPath)
|
const newDir = await fileOps.createNewDir(fullPath)
|
||||||
Message.success(`✓ 文件夹 "${folderName}" 创建成功`)
|
Message.success(`✓ 文件夹 "${folderName}" 创建成功`)
|
||||||
await loadDirectory(filePath.value)
|
addFileToList(newDir)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Message.error(`创建文件夹失败: ${error.message || error}`)
|
Message.error(`创建文件夹失败: ${error.message || error}`)
|
||||||
}
|
}
|
||||||
@@ -827,6 +840,9 @@ const handleDeleteFile = async (file: FileItem) => {
|
|||||||
await fileOps.deletePath(targetPath)
|
await fileOps.deletePath(targetPath)
|
||||||
Message.success('删除成功')
|
Message.success('删除成功')
|
||||||
|
|
||||||
|
// 从文件列表中移除
|
||||||
|
removeFileFromList(targetPath)
|
||||||
|
|
||||||
// 如果删除的是收藏的文件,从收藏夹中移除
|
// 如果删除的是收藏的文件,从收藏夹中移除
|
||||||
if (isFavorite(targetPath)) {
|
if (isFavorite(targetPath)) {
|
||||||
removeFav(targetPath)
|
removeFav(targetPath)
|
||||||
@@ -836,9 +852,6 @@ const handleDeleteFile = async (file: FileItem) => {
|
|||||||
if (selectedFileItem.value?.path === targetPath) {
|
if (selectedFileItem.value?.path === targetPath) {
|
||||||
selectedFileItem.value = null
|
selectedFileItem.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新文件列表
|
|
||||||
await loadDirectory(filePath.value)
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Message.error(`删除失败: ${error.message || error}`)
|
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 目录内容
|
// 加载 ZIP 目录内容
|
||||||
const loadZipDirectoryContents = async (zipPath: string, currentDir: string): Promise<FileItem[]> => {
|
const loadZipDirectoryContents = async (zipPath: string, currentDir: string): Promise<FileItem[]> => {
|
||||||
fileLoading.value = true
|
fileLoading.value = true
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export interface FileItem {
|
|||||||
is_dir: boolean
|
is_dir: boolean
|
||||||
/** 修改时间 */
|
/** 修改时间 */
|
||||||
modified_time?: string
|
modified_time?: string
|
||||||
|
/** 是否被收藏(运行时属性) */
|
||||||
|
is_favorite?: boolean
|
||||||
|
/** 旧路径(仅重命名操作时存在) */
|
||||||
|
old_path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user