This commit is contained in:
2026-07-02 00:47:03 +08:00
parent e4efa4b54d
commit 82a64787c3
12 changed files with 568 additions and 127 deletions

View File

@@ -1,64 +1,58 @@
<script setup lang="ts">
import { ref } from "vue";
interface DirEntry {
name: string;
path: string;
is_dir: boolean;
}
const props = defineProps<{
folderPath: string;
entries: DirEntry[];
}>();
const collapsed = ref(false);
const toggleCollapsed = () => {
collapsed.value = !collapsed.value;
};
// ---- file tree state ----
// ---- file tree state (for opened folder) ----
interface FileItem {
id: string;
name: string;
type: "file" | "folder";
path: string;
children?: FileItem[];
expanded?: boolean;
}
const files = ref<FileItem[]>([
{
id: "1",
name: "文档",
type: "folder",
expanded: true,
children: [
{ id: "2", name: "README.md", type: "file" },
{ id: "3", name: "笔记.md", type: "file" },
],
},
{
id: "4",
name: "工作",
type: "folder",
expanded: false,
children: [{ id: "5", name: "项目计划.md", type: "file" }],
},
]);
const expandedFolders = ref<Set<string>>(new Set());
const toggleFolder = (item: FileItem) => {
if (item.type === "folder") {
item.expanded = !item.expanded;
if (expandedFolders.value.has(item.path)) {
expandedFolders.value.delete(item.path);
} else {
expandedFolders.value.add(item.path);
}
// Trigger reactivity
expandedFolders.value = new Set(expandedFolders.value);
}
};
const newFile = () => {
const name = prompt("文件名称(含扩展名):");
if (name) {
files.value.push({ id: crypto.randomUUID(), name, type: "file" });
// files are managed externally
}
};
const newFolder = () => {
const name = prompt("文件夹名称:");
if (name) {
files.value.push({
id: crypto.randomUUID(),
name,
type: "folder",
expanded: true,
children: [],
});
// folders are managed externally
}
};
@@ -94,7 +88,7 @@ const removeTag = (tag: string) => {
:class="collapsed ? 'justify-center' : ''"
>
<span v-if="!collapsed" class="text-xs font-semibold text-gray-400 uppercase tracking-wider">
目录
语柔
</span>
<button
class="p-1 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
@@ -154,20 +148,34 @@ const removeTag = (tag: string) => {
<div v-if="!collapsed" class="flex flex-col flex-1 overflow-hidden">
<!-- File tree -->
<div class="flex-1 overflow-y-auto px-2 py-2 space-y-0.5">
<!-- Folder path indicator -->
<div
v-for="item in files"
:key="item.id"
v-if="folderPath"
class="px-2 py-1 text-xs text-gray-500 truncate mb-2 border-b border-gray-800/50 pb-2"
:title="folderPath"
>
📁 {{ folderPath.split(/[/\\]/).pop() || folderPath }}
</div>
<!-- Empty state -->
<div
v-if="!folderPath"
class="px-2 py-4 text-xs text-gray-500 text-center"
>
点击 文件 打开文件夹 或按 Ctrl+O
</div>
<template v-for="entry in entries" :key="entry.path">
<!-- Folder -->
<div
v-if="item.type === 'folder'"
v-if="entry.is_dir"
class="flex items-center gap-1 px-2 py-1 rounded-md text-sm text-gray-300 hover:bg-gray-800 cursor-pointer select-none"
@click="toggleFolder(item)"
@click="toggleFolder({ id: entry.path, name: entry.name, type: 'folder', path: entry.path })"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3.5 h-3.5 text-gray-500 transition-transform"
:class="item.expanded ? 'rotate-90' : ''"
:class="expandedFolders.has(entry.path) ? 'rotate-90' : ''"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
@@ -185,36 +193,12 @@ const removeTag = (tag: string) => {
>
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
<span class="truncate">{{ item.name }}</span>
<span class="truncate">{{ entry.name }}</span>
</div>
<!-- Folder children -->
<!-- File -->
<div
v-if="item.type === 'folder' && item.expanded"
class="ml-4 space-y-0.5"
>
<div
v-for="child in item.children"
:key="child.id"
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-sm text-gray-400 hover:bg-gray-800 hover:text-gray-200 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3.5 h-3.5 text-indigo-400/60"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span class="truncate">{{ child.name }}</span>
</div>
</div>
<!-- Top-level file -->
<div
v-else-if="item.type === 'file'"
v-else
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-sm text-gray-400 hover:bg-gray-800 hover:text-gray-200 cursor-pointer"
>
<svg
@@ -227,9 +211,9 @@ const removeTag = (tag: string) => {
>
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span class="truncate">{{ item.name }}</span>
<span class="truncate">{{ entry.name }}</span>
</div>
</div>
</template>
</div>
<!-- Action buttons -->