修复子文件夹不显示问题
This commit is contained in:
@@ -105,6 +105,82 @@ const folderTree = computed(() => {
|
|||||||
return buildTree(rootFolders);
|
return buildTree(rootFolders);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 展开/折叠状态
|
||||||
|
const expandedFolderIds = ref<Set<string>>(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<string>();
|
||||||
|
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() {
|
function createPreset() {
|
||||||
resetPresetForm();
|
resetPresetForm();
|
||||||
@@ -400,8 +476,8 @@ onMounted(() => {
|
|||||||
<select v-model="selectedFolder" class="pm-folder-filter">
|
<select v-model="selectedFolder" class="pm-folder-filter">
|
||||||
<option :value="null">所有预设</option>
|
<option :value="null">所有预设</option>
|
||||||
<option value="">未分类</option>
|
<option value="">未分类</option>
|
||||||
<option v-for="folder in folderTree" :key="folder.id" :value="folder.id">
|
<option v-for="f in flattenedFolders" :key="f.id" :value="f.id">
|
||||||
📁 {{ folder.name }} ({{ folder.presetCount }})
|
📁 {{ f.label }} ({{ f.presetCount }})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -503,23 +579,26 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="folder in folderTree" :key="folder.id" class="pm-folder-item">
|
<div v-for="item in visibleFolderList" :key="item.node.id" class="pm-folder-item">
|
||||||
<div class="pm-folder-header">
|
<div class="pm-folder-header" :style="{ paddingLeft: `${item.level * 16}px` }">
|
||||||
|
<button v-if="item.hasChildren" @click="toggleFolderExpand(item.node.id)" class="pm-expander" :title="item.expanded ? '折叠' : '展开'">
|
||||||
|
<span>{{ item.expanded ? '▼' : '▶' }}</span>
|
||||||
|
</button>
|
||||||
<div class="pm-folder-info">
|
<div class="pm-folder-info">
|
||||||
<div class="pm-folder-icon" :style="{ backgroundColor: folder.color }">📁</div>
|
<div class="pm-folder-icon" :style="{ backgroundColor: item.node.color }">📁</div>
|
||||||
<div class="pm-folder-details">
|
<div class="pm-folder-details">
|
||||||
<h4>{{ folder.name }}</h4>
|
<h4>{{ item.node.name }}</h4>
|
||||||
<span class="pm-folder-meta">{{ folder.presetCount }} 个预设 · {{ formatDate(folder.updatedAt) }}</span>
|
<span class="pm-folder-meta">{{ item.node.presetCount }} 个预设 · {{ formatDate(item.node.updatedAt) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pm-folder-actions">
|
<div class="pm-folder-actions">
|
||||||
<button @click="editFolder(folder)" class="pm-action-btn" title="编辑文件夹">
|
<button @click="editFolder(item.node)" class="pm-action-btn" title="编辑文件夹">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2"/>
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2"/>
|
||||||
<path d="m18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2"/>
|
<path d="m18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button @click="deleteFolder(folder)" class="pm-action-btn pm-delete" title="删除文件夹">
|
<button @click="deleteFolder(item.node)" class="pm-action-btn pm-delete" title="删除文件夹">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<polyline points="3,6 5,6 21,6" stroke="currentColor" stroke-width="2"/>
|
<polyline points="3,6 5,6 21,6" stroke="currentColor" stroke-width="2"/>
|
||||||
<path d="m19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2"/>
|
<path d="m19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2"/>
|
||||||
@@ -528,8 +607,8 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="folder.description" class="pm-folder-description">
|
<div v-if="item.node.description" class="pm-folder-description">
|
||||||
{{ folder.description }}
|
{{ item.node.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -563,8 +642,8 @@ onMounted(() => {
|
|||||||
<label>所属文件夹</label>
|
<label>所属文件夹</label>
|
||||||
<select v-model="presetForm.folderId">
|
<select v-model="presetForm.folderId">
|
||||||
<option value="">未分类</option>
|
<option value="">未分类</option>
|
||||||
<option v-for="folder in folderTree" :key="folder.id" :value="folder.id">
|
<option v-for="f in flattenedFolders" :key="f.id" :value="f.id">
|
||||||
📁 {{ folder.name }}
|
📁 {{ f.label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -617,8 +696,8 @@ onMounted(() => {
|
|||||||
<label>父文件夹</label>
|
<label>父文件夹</label>
|
||||||
<select v-model="folderForm.parentId">
|
<select v-model="folderForm.parentId">
|
||||||
<option value="">根目录</option>
|
<option value="">根目录</option>
|
||||||
<option v-for="folder in folderTree" :key="folder.id" :value="folder.id">
|
<option v-for="f in flattenedParentOptions" :key="f.id" :value="f.id">
|
||||||
📁 {{ folder.name }}
|
📁 {{ f.label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1001,6 +1080,24 @@ onMounted(() => {
|
|||||||
border-color: var(--color-error);
|
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 {
|
.pm-preset-description, .pm-folder-description {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user