更新
This commit is contained in:
93
src/components/TreeEntry.vue
Normal file
93
src/components/TreeEntry.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
// Named export for recursive self-reference
|
||||
export default { name: "TreeEntry" };
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
interface DirEntry {
|
||||
name: string;
|
||||
path: string;
|
||||
is_dir: boolean;
|
||||
children?: DirEntry[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
entry: DirEntry;
|
||||
depth: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleFolder: [path: string];
|
||||
openFile: [path: string];
|
||||
}>();
|
||||
|
||||
const expanded = ref(false);
|
||||
|
||||
// Auto-expand when children are loaded for the first time
|
||||
watch(
|
||||
() => props.entry.children,
|
||||
(children, oldChildren) => {
|
||||
if (children !== undefined && oldChildren === undefined) {
|
||||
expanded.value = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function onClick() {
|
||||
if (props.entry.is_dir) {
|
||||
if (props.entry.children === undefined) {
|
||||
// Not loaded yet — ask parent to load
|
||||
emit("toggleFolder", props.entry.path);
|
||||
} else {
|
||||
// Already loaded — toggle visibility
|
||||
expanded.value = !expanded.value;
|
||||
}
|
||||
} else {
|
||||
emit("openFile", props.entry.path);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Folder -->
|
||||
<div
|
||||
v-if="entry.is_dir"
|
||||
class="flex items-center gap-1 px-2 py-1 rounded-md text-sm text-[#5c574e] hover:bg-[#ede8de] cursor-pointer select-none"
|
||||
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
||||
@click="onClick"
|
||||
>
|
||||
<i
|
||||
class="ri-arrow-right-s-line text-sm text-[#b8b3a8] transition-transform shrink-0"
|
||||
:class="expanded && entry.children ? 'rotate-90' : ''"
|
||||
></i>
|
||||
<i class="ri-folder-line text-base text-[#bf6a3b]/70 shrink-0"></i>
|
||||
<span class="truncate">{{ entry.name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Children (recursive) -->
|
||||
<template v-if="entry.is_dir && expanded && entry.children">
|
||||
<TreeEntry
|
||||
v-for="child in entry.children"
|
||||
:key="child.path"
|
||||
:entry="child"
|
||||
:depth="depth + 1"
|
||||
@toggle-folder="(path: string) => emit('toggleFolder', path)"
|
||||
@open-file="(path: string) => emit('openFile', path)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- File -->
|
||||
<div
|
||||
v-else-if="!entry.is_dir"
|
||||
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-sm text-[#8c877d] hover:bg-[#ede8de] hover:text-[#38342e] cursor-pointer"
|
||||
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
||||
@click="onClick"
|
||||
>
|
||||
<i class="ri-file-line text-sm text-[#bf6a3b]/60 shrink-0"></i>
|
||||
<span class="truncate">{{ entry.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user