Private
Public Access
1
0

新增:文档体系重构+CHANGELOG补充+发布产物清理

This commit is contained in:
2026-05-01 22:22:06 +08:00
parent 3e1a540b83
commit 6eaaa56eb6
164 changed files with 40346 additions and 64 deletions

View File

@@ -0,0 +1,320 @@
# 文件操作增强:多选 / 复制粘贴剪切 / 移动
> 状态:方案设计完成,待实施
> 日期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.ts``copy()``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 函数):
```go
// 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`
新增两个绑定方法 + 请求结构体:
```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`
```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 | null``selectedFiles: FileItem[]`
```ts
// 改前
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`
改造行点击支持多选:
```ts
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` 改为匹配数组:
```ts
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 变更:
```ts
// 改前
selectedFileItem: FileItem | null
// 改后
selectedFiles: FileItem[]
```
新增 Emits:
```ts
(e: 'toggleSelect', file: FileItem): void
(e: 'rangeSelect', file: FileItem): void
```
### Step 3前端 — 剪贴板状态(应用级)
**新建文件**: `frontend/src/components/FileSystem/composables/useClipboard.ts`
```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
```vue
<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
```vue
<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.vue``handleKeyDown`
在 Delete 处理之后追加:
```ts
// 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
```ts
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`
核心处理函数:
```ts
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` | 修改 | `selectedFileItem``selectedFiles` 数组 |
| `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)** — 仅保留编辑器内容重置,不做操作历史撤销