diff --git a/src/components/PresetManager.vue b/src/components/PresetManager.vue index 2bd0f6b..8d800d6 100644 --- a/src/components/PresetManager.vue +++ b/src/components/PresetManager.vue @@ -105,6 +105,82 @@ const folderTree = computed(() => { return buildTree(rootFolders); }); +// 展开/折叠状态 +const expandedFolderIds = ref>(new Set()); + +function toggleFolderExpand(id: string) { + const set = new Set(expandedFolderIds.value); + if (set.has(id)) set.delete(id); else set.add(id); + expandedFolderIds.value = set; +} + +// 计算后代 ID,用于筛选与排除循环 +function getDescendantIds(folderId: string): string[] { + const all = store.presetFolders || []; + const result: string[] = []; + function walk(id: string) { + const children = all.filter(f => f.parentId === id); + for (const c of children) { + result.push(c.id); + walk(c.id); + } + } + walk(folderId); + return result; +} + +// 扁平化文件夹用于选择(面包屑路径标签) +const flattenedFolders = computed(() => { + type FlatItem = { id: string; name: string; label: string; level: number; presetCount: number; hasChildren: boolean }; + const res: FlatItem[] = []; + function walk(nodes: any[], level: number, parentPath: string) { + nodes.forEach((node: any) => { + const label = parentPath ? `${parentPath} / ${node.name}` : node.name; + res.push({ + id: node.id, + name: node.name, + label, + level, + presetCount: node.presetCount, + hasChildren: !!(node.children && node.children.length) + }); + if (node.children && node.children.length) { + walk(node.children, level + 1, label); + } + }); + } + walk(folderTree.value, 0, ''); + return res; +}); + +// 父文件夹可选项(编辑时排除自身及其所有子孙) +const flattenedParentOptions = computed(() => { + const exclude = new Set(); + if (editingFolder.value) { + exclude.add(editingFolder.value.id); + getDescendantIds(editingFolder.value.id).forEach(id => exclude.add(id)); + } + return flattenedFolders.value.filter(f => !exclude.has(f.id)); +}); + +// 可见的文件夹树列表(尊重展开状态) +const visibleFolderList = computed(() => { + type VisibleItem = { node: any; level: number; hasChildren: boolean; expanded: boolean }; + const res: VisibleItem[] = []; + function walk(nodes: any[], level: number) { + nodes.forEach((node: any) => { + const hasChildren = !!(node.children && node.children.length); + const expanded = expandedFolderIds.value.has(node.id); + res.push({ node, level, hasChildren, expanded }); + if (hasChildren && expanded) { + walk(node.children, level + 1); + } + }); + } + walk(folderTree.value, 0); + return res; +}); + // 预设操作 function createPreset() { resetPresetForm(); @@ -400,8 +476,8 @@ onMounted(() => { @@ -503,23 +579,26 @@ onMounted(() => { -
-
+
+
+
-
📁
+
📁
-

{{ folder.name }}

- {{ folder.presetCount }} 个预设 · {{ formatDate(folder.updatedAt) }} +

{{ item.node.name }}

+ {{ item.node.presetCount }} 个预设 · {{ formatDate(item.node.updatedAt) }}
- -
-
- {{ folder.description }} +
+ {{ item.node.description }}
@@ -563,8 +642,8 @@ onMounted(() => {
@@ -617,8 +696,8 @@ onMounted(() => {
@@ -1001,6 +1080,24 @@ onMounted(() => { border-color: var(--color-error); } +/* 文件夹树展开按钮 */ +.pm-expander { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border: none; + background: transparent; + color: var(--color-text-tertiary); + cursor: pointer; + margin-right: 0.25rem; +} + +.pm-expander:hover { + color: var(--color-text-secondary); +} + .pm-preset-description, .pm-folder-description { margin-bottom: 0.75rem; font-size: 0.875rem;