Private
Public Access
1
0
Files
u-desk/docs/04-功能迭代/GO-DESK-6.文件操作增强/文件操作增强方案.md

11 KiB
Raw Blame History

文件操作增强:多选 / 复制粘贴剪切 / 移动

状态:方案设计完成,待实施 日期2026-04-12

一、现状

功能 前端 后端 状态
多选 (Ctrl+Click / Shift+Click) 无,单选 selectedFileItem: FileItem | null N/A 缺失
全选 (Ctrl+A) N/A 缺失
复制文件 (Ctrl+C) TODO stub弹"暂未实现" 无 API 缺失
剪切文件 (Ctrl+X) 无 API 缺失
粘贴文件 (Ctrl+V) 仅支持图片粘贴 无 API 部分
移动文件 TODO stub弹"暂未实现" 无 API 缺失
重命名 (F2) 完整 完整 已完成
删除 (Del) 完整(含回收站) 完整 已完成
右键菜单 Copy/Cut/Paste 无菜单项 N/A 缺失

关键发现

  1. useFileOperations.tscopy()move() 已有函数签名但都是 TODO stub
  2. 回收站模块 (recycle_bin.go) 内部有 copyRecursively/copyFile/copyDirectory 私有方法可复用
  3. 快捷键已有 F5/F2/Del/Ctrl+S 等 12 个,缺 Ctrl+C/V/X/A 四个
  4. 右键菜单只有:新建文件、新建文件夹、系统打开、重命名、删除 — 缺复制/剪切/粘贴/移动

二、方案架构

┌─────────────────────────────────────────────┐
│              前端 (Vue 3)                    │
│                                              │
│  1. 多选状态管理 (selectedFiles: FileItem[]) │
│  2. 剪贴板状态 (clipboard: {op, files[]})   │
│  3. FileListPanel: Ctrl+Click / Shift+Click │
│  4. ContextMenu: +Copy / Cut / Paste 项     │
│  5. 快捷键: Ctrl+C / V / X / A             │
└──────────────────┬──────────────────────────┘
                   │ window.go.main.App.*
┌──────────────────▼──────────────────────────┐
│              后端 (Go)                       │
│                                              │
│  6. App.CopyPath(src, dst) → FileSystemService │
│  7. App.MovePath(src, dst) → os.Rename       │
│  8. 复用 recycle_bin.go 的 copy 辅助函数      │
└─────────────────────────────────────────────┘

三、实施步骤

Step 1后端 — 新增 Copy / Move API

文件: internal/filesystem/service.go

新增两个公开方法(复用已有的私有 copy 函数):

// CopyPath 复制文件或目录
func (s *FileSystemService) CopyPath(src, dst string) error {
    return copyRecursively(src, dst)
}

// MovePath 移动文件或目录(跨盘需 copy+delete
func (s *FileSystemService) MovePath(src, dst string) error {
    // 同盘: os.Rename (原子操作)
    // 跨盘: copyRecursively + DeletePathWithContext
}

文件: app.go

新增两个绑定方法 + 请求结构体:

type CopyMoveRequest struct {
    Src string `json:"src"`
    Dst string `json:"dst"`
}

func (a *App) CopyPath(req CopyMoveRequest) error {
    return a.filesystem.CopyPath(req.Src, req.Dst)
}

func (a *App) MovePath(req CopyMoveRequest) error {
    return a.filesystem.MovePath(req.Src, req.Dst)
}

文件: frontend/src/api/system.ts

export async function copyPath(src: string, dst: string): Promise<void>
export async function movePath(src: string, dst: string): Promise<void>

Step 2前端 — 多选状态管理

文件: frontend/src/components/FileSystem/index.vue

核心改动:selectedFileItem: FileItem | nullselectedFiles: FileItem[]

// 改前
const selectedFileItem = ref<FileItem | null>(null)

// 改后
const selectedFiles = ref<FileItem[]>([])
const selectedFileItem = computed(() => selectedFiles.value[0] || null) // 兼容现有单选逻辑

文件: frontend/src/types/file-system.ts

FileListPanelConfig.selectedFileItem 类型改为 selectedFiles: FileItem[]

文件: frontend/src/components/FileSystem/components/FileListPanel.vue

改造行点击支持多选:

const handleRowClick = (record: FileItem, ev: MouseEvent) => {
  if (target.closest('.arco-btn') || target.closest('.arco-input-wrapper')) return
  
  if (ev.ctrlKey || ev.metaKey) {
    emit('toggleSelect', record)       // Ctrl+Click: 切换选中
  } else if (ev.shiftKey && props.selectedFiles.length > 0) {
    emit('rangeSelect', record)        // Shift+Click: 范围选择
  } else {
    emit('fileClick', record)          // 普通点击: 单选
  }
}

getRowClassName 改为匹配数组:

const getRowClassName = (record: FileItem): string => [
  props.selectedFiles.some(f => f.path === record.path) && 'row-selected',
  props.config.editingFilePath === record.path && 'row-editing'
].filter(Boolean).join(' ')

Props 变更:

// 改前
selectedFileItem: FileItem | null
// 改后
selectedFiles: FileItem[]

新增 Emits:

(e: 'toggleSelect', file: FileItem): void
(e: 'rangeSelect', file: FileItem): void

Step 3前端 — 剪贴板状态(应用级)

新建文件: frontend/src/components/FileSystem/composables/useClipboard.ts

type ClipboardOp = 'copy' | 'cut'

interface ClipboardState {
  op: ClipboardOp | null
  files: FileItem[]     // 源文件列表
  sourceDir: string     // 来源目录
}

const clipboard = reactive<ClipboardState>({
  op: null, files: [], sourceDir: '',
})

export function useClipboard() {
  const copy = (files: FileItem[]) => { /* ... */ }
  const cut = (files: FileItem[]) => { /* ... */ }
  const clear = () => { clipboard.op = null; clipboard.files = [] }
  const canPaste = computed(() => clipboard.op !== null && clipboard.files.length > 0)
  return { clipboard, copy, cut, clear, canPaste }
}

Step 4前端 — 右键菜单扩展

文件: frontend/src/components/FileSystem/components/ContextMenu.vue

文件菜单追加 Copy / Cut

<div class="context-menu-item" @click="handleCopy">
  <span>📋</span><span>复制</span><span class="shortcut">Ctrl+C</span>
</div>
<div class="context-menu-item" @click="handleCut">
  <span>✂️</span><span>剪切</span><span class="shortcut">Ctrl+X</span>
</div>
<div class="context-menu-divider"></div>
<!-- 现有重命名/删除保持不变 -->

空白区域菜单追加 Paste

<div class="context-menu-divider"></div>
<div class="context-menu-item" :disabled="!canPaste" @click="handlePaste">
  <span>📌</span><span>粘贴</span><span class="shortcut">Ctrl+V</span>
</div>

新增 Props: selectedCount?: number, canPaste?: boolean 新增 Emits: 'action': 'copy' | 'cut' | 'paste'

Step 5前端 — 快捷键扩展

文件: frontend/src/components/FileSystem/index.vuehandleKeyDown

在 Delete 处理之后追加:

// Ctrl+C 复制
if ((event.ctrlKey || event.metaKey) && event.key === 'c' && selectedFiles.value.length > 0) {
  event.preventDefault(); handleCopy(); return
}
// Ctrl+X 剪切
if ((event.ctrlKey || event.metaKey) && event.key === 'x' && selectedFiles.value.length > 0) {
  event.preventDefault(); handleCut(); return
}
// Ctrl+V 粘贴
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
  event.preventDefault(); await handlePaste(); return
}
// Ctrl+A 全选
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
  event.preventDefault()
  selectedFiles.value = [...fileList.value]
  return
}

Step 6前端 — 操作执行逻辑

文件: frontend/src/components/FileSystem/composables/useFileOperations.ts

实现 copy()move()(替换 TODO stub

const copy = async (fromPath: string, toPath: string): Promise<void> => {
  await copyPathApi(fromPath, toPath)
  Message.success('复制完成')
}
const move = async (fromPath: string, toPath: string): Promise<void> => {
  await movePathApi(fromPath, toPath)
  Message.success('移动完成')
}

文件: frontend/src/components/FileSystem/index.vue

核心处理函数:

const { clipboard, copy: clipCopy, cut: clipCut, canPaste } = useClipboard()

const handleCopy = () => {
  clipCopy(selectedFiles.value)
  Message.info(`已复制 ${selectedFiles.value.length} 项`)
}
const handleCut = () => {
  clipCut(selectedFiles.value)
  Message.info(`已剪切 ${selectedFiles.value.length} 项`)
}
const handlePaste = async () => {
  for (const file of clipboard.files) {
    const dst = filePath.value + '/' + file.name
    clipboard.op === 'cut'
      ? await fileOps.move(file.path, dst)
      : await fileOps.copy(file.path, dst)
  }
  if (clipboard.op === 'cut') clipClear()
  loadDirectory(filePath.value)
}
const handleToggleSelect = (file: FileItem) => { /* 切换单项 */ }
const handleRangeSelect = (file: FileItem) => { /* 范围选择 */ }

// handleContextMenuAction 扩展
case 'copy': handleCopy(); break
case 'cut': handleCut(); break
case 'paste': await handlePaste(); break

四、改动文件清单

文件 改动类型 说明
internal/filesystem/service.go 修改 新增 CopyPath()MovePath()
app.go 修改 新增绑定方法 + CopyMoveRequest 结构体
frontend/src/api/system.ts 修改 新增 copyPath()movePath()
frontend/src/types/file-system.ts 修改 selectedFileItemselectedFiles 数组
frontend/src/components/FileSystem/composables/useClipboard.ts 新建 剪贴板 composable
frontend/src/components/FileSystem/composables/useFileOperations.ts 修改 实现 copy/move TODO
frontend/src/components/FileSystem/components/FileListPanel.vue 修改 多选行点击 + 行样式
frontend/src/components/FileSystem/components/ContextMenu.vue 修改 追加 Copy/Cut/Paste 菜单项
frontend/src/components/FileSystem/index.vue 修改 多选状态 + 快捷键 + 操作函数

9 个文件1 个新建 + 8 个修改)

五、验证标准

  1. Ctrl+Click 切换单文件选中状态,高亮多行
  2. Shift+Click 选中范围文件
  3. Ctrl+A 全选当前目录所有文件
  4. Ctrl+C 复制选中文件 → 提示"N 项已复制"
  5. Ctrl+X 剪切选中文件 → 提示"N 项已剪切"
  6. Ctrl+V 在目标目录粘贴 → 文件出现
  7. 右键菜单 显示 Copy / Cut / Paste空白区
  8. 跨目录粘贴 正确复制到目标路径
  9. 剪切粘贴 源文件消失(移动效果)
  10. 大文件夹 复制不卡顿Go io.Copy 流式)

六、不做(明确边界)

  • 拖拽移动文件 — 本轮不做,后续可加
  • 外部文件粘贴 — 不支持从系统资源管理器粘贴到 u-deskWails 限制)
  • 进度条 — 大文件复制暂不加进度条
  • 撤销(Ctrl+Z) — 仅保留编辑器内容重置,不做操作历史撤销