forked from zhongyidian/super_video
12 KiB
12 KiB
🔄 技能树功能逻辑关系完整梳理
📊 核心状态管理
状态变量
selectedModuleId: string | null // 当前选中的模块ID(null = 总览模式)
selectedTagId: string | null // 当前选中的标签ID
selectedSubTagId: string | null // 当前选中的子标签ID
expandedTags: Set<string> // 展开的标签集合
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. 文本搜索(叠加过滤)
详细流程
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 交互状态
标签高亮状态
isTagSelected(moduleId, tagId) {
return selectedModuleId === moduleId && selectedTagId === tagId
}
标签展开状态
isTagExpanded(tagId) {
return expandedTags.has(tagId)
}
子标签选中状态
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:标签点击行为
有子标签的标签(如"视觉素养"):
- 第一次点击 → 展开子节点
- 第二次点击 → 折叠子节点
- 不触发搜索,其他标签保持可见
无子标签的标签(如"摄影基础"):
- 点击 → 高亮标签 + 过滤资源
- 其他标签保持可见
- 可以继续点击其他标签
✅ 功能清单
已实现功能
- ✅ 模块切换(总览 ↔ 具体模块)
- ✅ 技能树标题动态更新
- ✅ 标签点击逻辑(有/无子标签分支)
- ✅ 子标签展开/折叠
- ✅ 标签高亮显示
- ✅ 资源多级过滤(模块 → 标签 → 子标签)
- ✅ 文本搜索(多字段)
- ✅ 面包屑导航
- ✅ 标签删除功能
- ✅ 实时搜索
- ✅ 标签去重(总览模式)
核心特性
- 🎯 智能标签点击:根据是否有子标签自动选择行为
- 🔍 多维度搜索:选择过滤 + 文本搜索
- 🏷️ 面包屑导航:清晰显示当前位置
- 🗑️ 灵活删除:可以删除任意层级的选择
- 👁️ 保持可见:选择标签后其他标签不隐藏
🎯 总结
这是一个多层级、多维度的资源浏览和搜索系统,核心特点:
- 灵活的导航:支持模块 → 标签 → 子标签的层级导航
- 智能的交互:根据标签类型自动选择展开或搜索
- 强大的搜索:选择过滤 + 文本搜索双重过滤
- 清晰的反馈:面包屑、高亮、展开状态等多种视觉反馈
- 用户友好:可以随时删除选择,标签始终可见
整个系统通过 Vue 的响应式系统自动同步状态和 UI,用户的每个操作都会立即反映在界面上。