diff --git a/web/src/views/editor/Editor.vue b/web/src/views/editor/Editor.vue index 922d8c3..e7919b8 100644 --- a/web/src/views/editor/Editor.vue +++ b/web/src/views/editor/Editor.vue @@ -24,6 +24,21 @@ const fileTree = ref([]) const expandedDirs = ref>(new Set()) const selectedPath = ref(null) +const allDirs = computed(() => { + const dirs: string[] = [] + function traverse(nodes: FileNode[]) { + for (const node of nodes) { + if (node.isDir) { + dirs.push(node.path) + if (node.children) traverse(node.children) + } + } + } + traverse(fileTree.value) + return dirs +}) + + // State for Editor const selectedFile = ref(null) const fileContent = ref('') @@ -479,6 +494,7 @@ onUnmounted(() => { +import { ref, computed, watch } from 'vue' +import { Button } from '@/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Folder, ChevronsUpDown, ChevronDown, ChevronRight } from 'lucide-vue-next' +import type { FileNode } from '@/api' + +const props = defineProps<{ + fileTree?: FileNode[] + modelValue: string + defaultExpand?: string +}>() + +const emit = defineEmits<{ + 'update:modelValue': [value: string] +}>() + +const isPopoverOpen = ref(false) +const expandedDirs = ref>(new Set()) + +watch(() => props.defaultExpand, (newVal) => { + if (newVal) { + const parts = newVal.split('/') + let current = '' + for (const part of parts) { + current = current ? `${current}/${part}` : part + expandedDirs.value.add(current) + } + } +}, { immediate: true }) + +function toggleExpand(path: string) { + if (expandedDirs.value.has(path)) { + expandedDirs.value.delete(path) + } else { + expandedDirs.value.add(path) + } +} + +const visibleDirs = computed(() => { + const result: { path: string; name: string; depth: number; hasChildren: boolean }[] = [] + + function traverse(nodes: FileNode[], depth: number) { + for (const node of nodes) { + if (node.isDir) { + const hasChildren = !!node.children && node.children.some(c => c.isDir) + result.push({ path: node.path, name: node.name, depth, hasChildren }) + if (expandedDirs.value.has(node.path) && node.children) { + traverse(node.children, depth + 1) + } + } + } + } + + if (props.fileTree) { + traverse(props.fileTree, 0) + } + return result +}) + +function selectDir(path: string) { + emit('update:modelValue', path) + isPopoverOpen.value = false +} + + + diff --git a/web/src/views/editor/components/FileActionDialogs.vue b/web/src/views/editor/components/FileActionDialogs.vue index 4e517c2..1317a47 100644 --- a/web/src/views/editor/components/FileActionDialogs.vue +++ b/web/src/views/editor/components/FileActionDialogs.vue @@ -6,12 +6,19 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from ' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' import { Label } from '@/components/ui/label' import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' +import DirSelect from './DirSelect.vue' +import type { FileNode } from '@/api' + +const props = defineProps<{ + fileTree?: FileNode[] +}>() // Create Dialog State const showCreate = ref(false) const newItemName = ref('') const newItemType = ref<'file' | 'dir'>('file') const createInDir = ref('') +const createParent = ref('') // Delete Dialog State const showDelete = ref(false) @@ -31,7 +38,8 @@ const emit = defineEmits<{ function openCreate(parent = '') { newItemName.value = '' newItemType.value = 'file' - createInDir.value = parent + createInDir.value = parent || '/' + createParent.value = parent showCreate.value = true } @@ -57,8 +65,9 @@ defineExpose({ openCreate, openDelete, openRename, closeCreate: () => showCreate 新建
-
- 位置: {{ createInDir || '根目录' }} +
+ +
@@ -72,12 +81,12 @@ defineExpose({ openCreate, openDelete, openRename, closeCreate: () => showCreate
- +
- +