# 🔄 技能树功能逻辑关系完整梳理 ## 📊 核心状态管理 ### 状态变量 ```typescript selectedModuleId: string | null // 当前选中的模块ID(null = 总览模式) selectedTagId: string | null // 当前选中的标签ID selectedSubTagId: string | null // 当前选中的子标签ID expandedTags: Set // 展开的标签集合 search: string // 搜索文本 ``` --- ## 🎯 功能模块详解 ### 1. 模块切换与技能树标题 #### 逻辑流程 ``` 用户点击模块图标 ↓ handleModuleListItemClick(moduleId) ↓ 如果点击的是已选中的模块 → 取消选择(回到总览) 如果点击的是新模块 → 选中该模块 ↓ 更新 selectedModuleId 清空 selectedTagId、selectedSubTagId 清空 expandedTags ↓ 技能树标题更新: - selectedModuleId = null → "技能树总览" - selectedModuleId = "module-1" → "新手导航" - selectedModuleId = "module-2" → "vlog发现与记录" - selectedModuleId = "module-3" → "访谈聆听与对话" - selectedModuleId = "module-4" → "电影创作与表达" ``` #### 实现位置 - **SkillTree.vue**: `getTreeTitle()` 函数 - **index.vue**: `handleModuleListItemClick()` 函数 --- ### 2. 标签点击逻辑(核心) #### 逻辑分支 ``` 用户点击技能树标签 ↓ handleTagClick(moduleId, tagId) ↓ 检查是否有子标签 ↓ ┌─────────────────────────────────────────────────────────┐ │ 分支A: 有子标签 │ ├─────────────────────────────────────────────────────────┤ │ 如果点击的是已选中的标签: │ │ - 已展开 → 折叠(删除 tagId from expandedTags) │ │ - 未展开 → 展开(添加 tagId to expandedTags) │ │ │ │ 如果点击的是新标签: │ │ - 设置 selectedModuleId = moduleId │ │ - 设置 selectedTagId = tagId │ │ - 清空 selectedSubTagId = null │ │ - 自动展开(添加 tagId to expandedTags) │ │ │ │ 结果:展开/折叠子节点,不触发搜索 │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ 分支B: 无子标签 │ ├─────────────────────────────────────────────────────────┤ │ - 设置 selectedModuleId = moduleId │ │ - 设置 selectedTagId = tagId │ │ - 清空 selectedSubTagId = null │ │ - 清空 expandedTags │ │ │ │ 结果:高亮标签 + 触发搜索,其他标签保持可见 │ └─────────────────────────────────────────────────────────┘ ``` #### 关键特性 - ✅ 有子标签:只展开/折叠,不搜索 - ✅ 无子标签:高亮并搜索,不隐藏其他标签 - ✅ 其他标签始终可见(不会因为选择而隐藏) #### 实现位置 - **index.vue**: `handleTagClick()` 函数 --- ### 3. 子标签点击逻辑 #### 逻辑流程 ``` 用户点击子标签 ↓ handleSubTagClick(moduleId, tagId, subTagId) ↓ 如果点击的是已选中的子标签 → 取消选择 如果点击的是新子标签 → 选中该子标签 ↓ 更新状态: - selectedModuleId = moduleId - selectedTagId = tagId - selectedSubTagId = subTagId - 确保父标签展开(添加 tagId to expandedTags) ↓ 触发资源过滤(显示该子标签的资源) ``` #### 实现位置 - **index.vue**: `handleSubTagClick()` 函数 --- ### 4. 资源过滤逻辑 #### 过滤优先级 ``` 1. 子标签过滤(最精确) ↓ 2. 标签过滤 ↓ 3. 模块过滤 ↓ 4. 全部资源(默认) ↓ 5. 文本搜索(叠加过滤) ``` #### 详细流程 ```typescript filteredResources = computed(() => { let resources = [] // 第一步:根据选择获取资源 if (selectedSubTagId) { resources = getResourcesBySubTag(moduleId, tagId, subTagId) } else if (selectedTagId) { resources = getResourcesByTag(moduleId, tagId) } else if (selectedModuleId) { resources = getResourcesByModule(moduleId) } else { resources = getAllResources() } // 第二步:文本搜索(如果有搜索文本) if (search.trim()) { resources = resources.filter( r => r.title.includes(search) || r.moduleName.includes(search) || r.tagName.includes(search) || r.description?.includes(search) ) } return resources }) ``` #### 搜索字段 - ✅ `title` - 资源标题 - ✅ `moduleName` - 模块名称 - ✅ `tagName` - 标签名称 - ✅ `description` - 资源描述 #### 实现位置 - **index.vue**: `filteredResources` 计算属性 --- ### 5. 搜索标签(面包屑) #### 显示逻辑 ``` searchTags = computed(() => { const tags = [] if (selectedModuleId) { tags.push({ type: 'module', name: moduleName }) } if (selectedTagId) { tags.push({ type: 'tag', name: tagName }) } if (selectedSubTagId) { tags.push({ type: 'subTag', name: subTagName }) } return tags }) ``` #### 标签样式 - **模块标签**:蓝色(`bg-blue-500/80`) - **标签标签**:橙色(`bg-orange-500/80`) - **子标签标签**:紫色(`bg-purple-500/80`) #### 删除功能 ``` 用户点击标签的 × 按钮 ↓ handleRemoveTag(tagType) ↓ 根据 tagType 清除对应状态: - 'subTag' → 清除 selectedSubTagId - 'tag' → 清除 selectedTagId + selectedSubTagId + expandedTags - 'module' → 清除所有选择状态 ↓ 资源列表自动更新 ``` #### 实现位置 - **index.vue**: `searchTags` 计算属性, `handleRemoveTag()` 函数 - **SearchTags.vue**: 标签显示和删除按钮 --- ### 6. 文本搜索 #### 搜索框行为 ``` 用户在搜索框输入文本 ↓ v-model 绑定到 search 变量 ↓ filteredResources 自动重新计算 ↓ 资源列表实时更新 ``` #### 搜索特性 - ✅ 实时搜索(无需点击搜索按钮) - ✅ 多字段搜索(title、moduleName、tagName、description) - ✅ 大小写不敏感 - ✅ 叠加过滤(在已选择的基础上进一步过滤) - ✅ 搜索文本为空时,不影响选择过滤 #### 实现位置 - **index.vue**: 搜索框 `v-model="search"` --- ## 🎨 UI 交互状态 ### 标签高亮状态 ```typescript isTagSelected(moduleId, tagId) { return selectedModuleId === moduleId && selectedTagId === tagId } ``` ### 标签展开状态 ```typescript isTagExpanded(tagId) { return expandedTags.has(tagId) } ``` ### 子标签选中状态 ```typescript isSubTagSelected(moduleId, tagId, subTagId) { return selectedModuleId === moduleId && selectedTagId === tagId && selectedSubTagId === subTagId } ``` --- ## 📈 数据流向图 ``` 用户操作 ↓ ┌─────────────────────────────────────────────────────────┐ │ 点击模块图标 → handleModuleListItemClick │ │ 点击标签 → handleTagClick │ │ 点击子标签 → handleSubTagClick │ │ 输入搜索文本 → v-model:search │ │ 删除面包屑标签 → handleRemoveTag │ └─────────────────────────────────────────────────────────┘ ↓ 更新状态变量 - selectedModuleId - selectedTagId - selectedSubTagId - expandedTags - search ↓ 触发计算属性重新计算 - searchTags(面包屑) - filteredResources(资源列表) - displayedTags(技能树标签) - displayedModules(显示的模块) ↓ UI 自动更新 - 技能树标题 - 标签高亮状态 - 子节点展开/折叠 - 面包屑标签 - 资源列表 ``` --- ## 🔍 关键场景示例 ### 场景1:从总览到具体资源 ``` 1. 初始状态:总览模式 - selectedModuleId = null - 显示 "技能树总览" - 显示所有模块的标签(去重) 2. 点击 "新手导航" 模块 - selectedModuleId = "module-1" - 显示 "新手导航" - 只显示新手导航的标签 3. 点击 "视觉素养" 标签(有子标签) - selectedTagId = "tag-1-1" - 展开子标签:构图、光线、运镜 - 资源列表显示视觉素养的所有资源 4. 点击 "构图" 子标签 - selectedSubTagId = "subtag-1-1-1" - 资源列表只显示构图相关资源 - 面包屑:新手导航 > 视觉素养 > 构图 ``` ### 场景2:搜索功能 ``` 1. 在搜索框输入 "构图" - search = "构图" - 资源列表实时过滤,显示所有包含"构图"的资源 2. 点击面包屑标签的删除按钮 - 清除对应的选择 - 资源列表更新 3. 清空搜索框 - search = "" - 资源列表恢复到选择过滤的结果 ``` ### 场景3:标签点击行为 ``` 有子标签的标签(如"视觉素养"): - 第一次点击 → 展开子节点 - 第二次点击 → 折叠子节点 - 不触发搜索,其他标签保持可见 无子标签的标签(如"摄影基础"): - 点击 → 高亮标签 + 过滤资源 - 其他标签保持可见 - 可以继续点击其他标签 ``` --- ## ✅ 功能清单 ### 已实现功能 - ✅ 模块切换(总览 ↔ 具体模块) - ✅ 技能树标题动态更新 - ✅ 标签点击逻辑(有/无子标签分支) - ✅ 子标签展开/折叠 - ✅ 标签高亮显示 - ✅ 资源多级过滤(模块 → 标签 → 子标签) - ✅ 文本搜索(多字段) - ✅ 面包屑导航 - ✅ 标签删除功能 - ✅ 实时搜索 - ✅ 标签去重(总览模式) ### 核心特性 - 🎯 **智能标签点击**:根据是否有子标签自动选择行为 - 🔍 **多维度搜索**:选择过滤 + 文本搜索 - 🏷️ **面包屑导航**:清晰显示当前位置 - 🗑️ **灵活删除**:可以删除任意层级的选择 - 👁️ **保持可见**:选择标签后其他标签不隐藏 --- ## 🎯 总结 这是一个**多层级、多维度的资源浏览和搜索系统**,核心特点: 1. **灵活的导航**:支持模块 → 标签 → 子标签的层级导航 2. **智能的交互**:根据标签类型自动选择展开或搜索 3. **强大的搜索**:选择过滤 + 文本搜索双重过滤 4. **清晰的反馈**:面包屑、高亮、展开状态等多种视觉反馈 5. **用户友好**:可以随时删除选择,标签始终可见 整个系统通过 Vue 的响应式系统自动同步状态和 UI,用户的每个操作都会立即反映在界面上。