diff --git a/SKILL_ARCHITECTURE.md b/SKILL_ARCHITECTURE.md new file mode 100644 index 0000000..a1855b0 --- /dev/null +++ b/SKILL_ARCHITECTURE.md @@ -0,0 +1,481 @@ +# 📚 Skill 功能架构完整分析 + +## 🗂️ 文件结构(已清理) + +``` +src/views/skill/ +├── index.vue # 主页面 - 技能树资源浏览器 +├── resourceData.ts # 数据层 - 资源数据管理 +├── video.vue # 视频播放页面 +└── components/ + ├── SkillTree.vue # 技能树可视化组件 + ├── SearchTags.vue # 搜索面包屑标签 + └── ResourceList.vue # 资源列表展示 +``` + +--- + +## 🎯 核心功能定位 + +**技能树资源浏览系统** - 一个基于树形结构的学习资源导航和筛选平台 + +--- + +## 📊 数据架构 + +### 1. 数据层级结构 + +``` +模块 (Module) + └─ 标签 (Tag) + └─ 子标签 (SubTag) + └─ 资源 (Resource) +``` + +### 2. 数据实体 + +#### ResourceItem 接口 + +```typescript +{ + id: string // 资源唯一标识 + title: string // 资源标题 + description?: string // 资源描述 + moduleId: string // 所属模块ID + moduleName: string // 所属模块名称 + tagId: string // 所属标签ID + tagName: string // 所属标签名称 + subTagId?: string // 所属子标签ID(可选) + subTagName?: string // 所属子标签名称(可选) +} +``` + +### 3. 数据统计 + +- **4个模块**: + 1. module-1: 新手导航 (5个资源) + 2. module-2: vlog发现与记录 (6个资源) + 3. module-3: 访谈聆听与对话 (3个资源) + 4. module-4: 电影创作与表达 (7个资源) + +- **8种标签类型**: + - 视觉素养 (橙色) + - 摄影基础 (蓝色) + - 剪映基础操作 (紫色) + - 内容策划 (橙色) + - 拍摄执行 (绿色) + - 后期制作 (蓝色) + - AI与版权伦理知识 (蓝色) + - 思路提点与案例赏析 (蓝色) + +- **总资源数**:21个学习资源 + +### 4. 工具函数 + +```typescript +getAllResources() // 获取所有资源 +getResourcesByModule(moduleId) // 按模块筛选 +getResourcesByTag(moduleId, tagId) // 按标签筛选 +getResourcesBySubTag(moduleId, tagId, subTagId) // 按子标签筛选 +getAllModules() // 获取所有模块 +getTagsByModule(moduleId) // 获取模块的标签 +getSubTagsByTag(moduleId, tagId) // 获取标签的子标签 +getTagResourceCount(moduleId, tagId) // 获取标签的资源数量 +``` + +--- + +## 🎨 UI 布局 + +### 页面布局(1920x1080) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [搜索框 + 面包屑] [模块1][模块2][模块3][模块4] [X] │ +│ │ +│ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ │ │ │ │ +│ │ 资源列表 │ │ 技能树 │ │ +│ │ 740x800 │ │ 860x820 │ │ +│ │ │ │ │ │ +│ │ │ │ [标签节点...] │ │ +│ │ [资源卡片] │ │ │ │ +│ │ [资源卡片] │ │ 技能树总览 │ │ +│ └──────────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 组件定位 + +1. **搜索框**:左上角 (left-10, top-10, 547x84px) +2. **模块图标**:顶部居中 (left-55%, top-10, 4个141x150px) +3. **资源列表**:左下角 (left-10, bottom-20, 740x800px) +4. **技能树**:右下角 (right-0, bottom-20, 860x820px) +5. **关闭按钮**:右上角 (right-0, top-0) + +--- + +## 🔄 交互逻辑流程 + +### 1. 状态管理 + +```typescript +// 核心状态 +selectedModuleId: string | null // 当前选中的模块 +selectedTagId: string | null // 当前选中的标签 +selectedSubTagId: string | null // 当前选中的子标签 +expandedTags: Set // 展开的标签集合 +search: string // 搜索文本(待实现) +``` + +### 2. 用户交互流程 + +#### 流程A:模块筛选 + +``` +用户点击模块图标 + ↓ +handleModuleListItemClick(moduleId) + ↓ +如果已选中同一模块 → 取消选择(显示所有) +如果选中新模块 → 设置 selectedModuleId + ↓ +清空标签和子标签选择 + ↓ +技能树只显示该模块的标签 +资源列表只显示该模块的资源 +``` + +#### 流程B:标签筛选 + +``` +用户点击技能树标签 + ↓ +handleTagClick(moduleId, tagId) + ↓ +如果已选中同一标签: + - 已展开 → 折叠并取消选择 + - 未展开 → 展开子标签 +如果选中新标签: + - 设置 selectedModuleId 和 selectedTagId + - 如果有子标签 → 自动展开 + ↓ +资源列表显示该标签的所有资源 +搜索框显示面包屑:[模块] > [标签] +``` + +#### 流程C:子标签筛选 + +``` +用户点击子标签 + ↓ +handleSubTagClick(moduleId, tagId, subTagId) + ↓ +如果已选中同一子标签 → 取消选择 +如果选中新子标签 → 设置 selectedSubTagId + ↓ +资源列表显示该子标签的资源 +搜索框显示面包屑:[模块] > [标签] > [子标签] +``` + +### 3. 计算属性逻辑 + +#### searchTags(面包屑) + +```typescript +根据选择状态生成面包屑数组: +- 选中模块 → 添加模块标签(蓝色) +- 选中标签 → 添加标签标签(橙色) +- 选中子标签 → 添加子标签标签(紫色) +``` + +#### filteredResources(资源过滤) + +```typescript +优先级:子标签 > 标签 > 模块 > 全部 +1. 如果选中子标签 → 返回子标签的资源 +2. 否则如果选中标签 → 返回标签的资源 +3. 否则如果选中模块 → 返回模块的资源 +4. 否则 → 返回所有资源 +``` + +--- + +## 🌳 技能树组件详解 + +### 1. 核心功能 + +- **标签去重**:使用 `displayedTags` 计算属性,通过 `moduleId-tagId` 作为唯一键去重 +- **位置布局**:14个预定义位置,形成树形分布 +- **颜色映射**:根据标签名称返回不同颜色 +- **展开/折叠**:有子标签的标签可以展开显示子标签列表 +- **数字徽章**:显示每个标签下的资源数量 + +### 2. 标签位置分布 + +```typescript +14个预定义位置(百分比定位): +顶部区域(树冠): + - (8%, 35%) - (12%, 55%) - (18%, 72%) +中上区域: + - (28%, 18%) - (32%, 82%) +中部区域: + - (42%, 10%) - (45%, 28%) - (48%, 68%) +中下区域: + - (58%, 22%) - (62%, 78%) - (68%, 85%) +底部区域(树干): + - (75%, 15%) - (78%, 38%) - (82%, 65%) +``` + +### 3. 标签样式 + +```css +基础样式: +- 圆角矩形:rounded-xl +- 内边距:px-4 py-2 +- 边框:border-2 +- 尺寸:min-w-[140px] max-w-[200px] +- 阴影:shadow-lg + +交互效果: +- hover:scale-105 +- 选中:橙色背景 + 白色光圈 + +颜色映射: +- 后期制作:蓝色 (bg-blue-500/85) +- 拍摄执行:绿色 (bg-green-500/85) +- 内容策划:橙色 (bg-orange-500/85) +- 视觉素养:橙色 (bg-orange-500/85) +- 摄影基础:蓝色 (bg-blue-500/85) +- 剪映基础操作:紫色 (bg-purple-500/85) +- AI与版权伦理知识:蓝色 (bg-blue-500/85) +- 思路提点与案例赏析:蓝色 (bg-blue-500/85) +``` + +### 4. 子标签展开 + +``` +父标签被点击且有子标签 + ↓ +在父标签下方显示子标签列表 + ↓ +子标签样式: +- 更小的尺寸 (px-3 py-1) +- 圆角矩形 (rounded-lg) +- 蓝色背景(未选中)/ 橙色背景(选中) +- 垂直排列 (space-y-1) +``` + +--- + +## 📋 资源列表组件 + +### 功能 + +- 显示过滤后的资源列表 +- 滚动容器(overflow-y-auto) +- 空状态提示 + +### 资源卡片样式 + +```css +- 半透明蓝色背景:bg-blue-500/20 +- 圆角:rounded +- 内边距:p-4 +- hover效果:bg-blue-500/30 +- 标题:text-2xl font-bold +- 描述:text-lg text-gray-300 +``` + +--- + +## 🔍 搜索标签组件 + +### 功能 + +- 显示当前选择路径(面包屑导航) +- 不同类型不同颜色 + +### 标签样式 + +```css +- 圆角胶囊:rounded-full +- 边框:border border-white/80 +- 内边距:px-3 py-1 +- 颜色映射: + - 模块:蓝色 (bg-blue-500/80) + - 标签:橙色 (bg-orange-500/80) + - 子标签:紫色 (bg-purple-500/80) +``` + +--- + +## 🎬 视频播放页面 + +### 功能 + +- 播放学习视频 +- 全屏视频播放器 +- 返回按钮 + +### 当前问题 + +- 硬编码视频路径:`/video/xinshou_jiuzhonggoutufa.mp4` +- 未接收路由参数 +- 无法动态切换视频 + +### 建议改进 + +```typescript +// 应该接收资源ID参数 +const route = useRoute() +const resourceId = route.params.id + +// 根据资源ID获取视频路径 +const videoSrc = getVideoByResourceId(resourceId) +``` + +--- + +## 🚀 功能特点 + +### ✅ 已实现 + +1. **多层级筛选**:模块 → 标签 → 子标签 +2. **可视化导航**:技能树形式展示 +3. **实时过滤**:点击即时更新资源列表 +4. **面包屑导航**:清晰显示当前位置 +5. **标签去重**:避免重复显示 +6. **展开/折叠**:子标签动态显示 +7. **资源计数**:显示每个标签的资源数量 +8. **响应式交互**:hover、选中等视觉反馈 + +### ⚠️ 待完善 + +1. **搜索功能**:文本搜索未实现 +2. **资源详情**:点击资源卡片无响应 +3. **视频播放**:未与资源数据关联 +4. **学习进度**:无进度追踪 +5. **数据持久化**:刷新后状态丢失 + +--- + +## 💡 优化建议 + +### 1. 完善搜索功能 + +```typescript +// 在 filteredResources 中添加文本搜索 +const filteredResources = computed(() => { + let resources = /* 现有筛选逻辑 */ + + // 添加文本搜索 + if (search.value.trim()) { + resources = resources.filter(r => + r.title.includes(search.value) || + r.description?.includes(search.value) + ) + } + + return resources +}) +``` + +### 2. 资源点击跳转 + +```typescript +// ResourceList.vue +const handleResourceClick = (resource: ResourceItem) => { + router.push({ + name: 'SkillVideo', + params: { id: resource.id } + }) +} +``` + +### 3. 动态视频播放 + +```typescript +// video.vue +const route = useRoute() +const resourceId = route.params.id as string +const resource = getAllResources().find(r => r.id === resourceId) +const videoSrc = `/video/${resource?.videoFile || 'default.mp4'}` +``` + +### 4. 学习进度追踪 + +```typescript +// 添加到 ResourceItem +interface ResourceItem { + // ... 现有字段 + completed?: boolean + progress?: number + lastViewedAt?: Date +} +``` + +### 5. 状态持久化 + +```typescript +// 使用 localStorage 或 Pinia +import { useLocalStorage } from '@vueuse/core' + +const selectedModuleId = useLocalStorage('skill-module', null) +const selectedTagId = useLocalStorage('skill-tag', null) +``` + +--- + +## 📈 数据扩展建议 + +### 1. 添加视频文件字段 + +```typescript +interface ResourceItem { + // ... 现有字段 + videoFile?: string // 视频文件名 + duration?: number // 视频时长(秒) + difficulty?: 'easy' | 'medium' | 'hard' // 难度 +} +``` + +### 2. 添加标签元数据 + +```typescript +interface TagMeta { + id: string + name: string + color: string + icon?: string + description?: string +} +``` + +### 3. 添加模块元数据 + +```typescript +interface ModuleMeta { + id: string + name: string + description: string + totalResources: number + completedResources: number + progress: number +} +``` + +--- + +## 🎯 总结 + +这是一个**结构清晰、功能完整的技能树资源浏览系统**,核心特点: + +1. **数据驱动**:基于一维数组的资源数据,通过工具函数灵活查询 +2. **层级筛选**:支持模块→标签→子标签的多层级筛选 +3. **可视化导航**:技能树形式直观展示资源结构 +4. **实时反馈**:点击即时更新,面包屑导航清晰 +5. **代码整洁**:已清理冗余代码,结构简洁明了 + +主要改进方向是完善搜索、资源详情、视频播放和学习进度追踪功能。 diff --git a/SKILL_LOGIC_FLOW.md b/SKILL_LOGIC_FLOW.md new file mode 100644 index 0000000..d71be28 --- /dev/null +++ b/SKILL_LOGIC_FLOW.md @@ -0,0 +1,429 @@ +# 🔄 技能树功能逻辑关系完整梳理 + +## 📊 核心状态管理 + +### 状态变量 + +```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,用户的每个操作都会立即反映在界面上。 diff --git a/src/assets/images/skill/icon/ai技术应用.png b/src/assets/images/skill/icon/ai技术应用.png new file mode 100644 index 0000000..47ca6ac Binary files /dev/null and b/src/assets/images/skill/icon/ai技术应用.png differ diff --git a/src/assets/images/skill/icon/光线.png b/src/assets/images/skill/icon/光线.png new file mode 100644 index 0000000..f9c4d69 Binary files /dev/null and b/src/assets/images/skill/icon/光线.png differ diff --git a/src/assets/images/skill/icon/光线运用.png b/src/assets/images/skill/icon/光线运用.png new file mode 100644 index 0000000..f5d6026 Binary files /dev/null and b/src/assets/images/skill/icon/光线运用.png differ diff --git a/src/assets/images/skill/icon/剪辑技巧.png b/src/assets/images/skill/icon/剪辑技巧.png new file mode 100644 index 0000000..c293608 Binary files /dev/null and b/src/assets/images/skill/icon/剪辑技巧.png differ diff --git a/src/assets/images/skill/icon/包装.png b/src/assets/images/skill/icon/包装.png new file mode 100644 index 0000000..bc495e2 Binary files /dev/null and b/src/assets/images/skill/icon/包装.png differ diff --git a/src/assets/images/skill/icon/思路提点.png b/src/assets/images/skill/icon/思路提点.png new file mode 100644 index 0000000..30bb71d Binary files /dev/null and b/src/assets/images/skill/icon/思路提点.png differ diff --git a/src/assets/images/skill/icon/拍摄技巧.png b/src/assets/images/skill/icon/拍摄技巧.png new file mode 100644 index 0000000..e4b12d4 Binary files /dev/null and b/src/assets/images/skill/icon/拍摄技巧.png differ diff --git a/src/assets/images/skill/icon/景别.png b/src/assets/images/skill/icon/景别.png new file mode 100644 index 0000000..23806f4 Binary files /dev/null and b/src/assets/images/skill/icon/景别.png differ diff --git a/src/assets/images/skill/icon/构图.png b/src/assets/images/skill/icon/构图.png new file mode 100644 index 0000000..d753f9c Binary files /dev/null and b/src/assets/images/skill/icon/构图.png differ diff --git a/src/assets/images/skill/icon/案例合集.png b/src/assets/images/skill/icon/案例合集.png new file mode 100644 index 0000000..5504c3c Binary files /dev/null and b/src/assets/images/skill/icon/案例合集.png differ diff --git a/src/assets/images/skill/icon/版权意识.png b/src/assets/images/skill/icon/版权意识.png new file mode 100644 index 0000000..3182f07 Binary files /dev/null and b/src/assets/images/skill/icon/版权意识.png differ diff --git a/src/assets/images/skill/icon/画面构图.png b/src/assets/images/skill/icon/画面构图.png new file mode 100644 index 0000000..0290ddf Binary files /dev/null and b/src/assets/images/skill/icon/画面构图.png differ diff --git a/src/assets/images/skill/icon/素材管理.png b/src/assets/images/skill/icon/素材管理.png new file mode 100644 index 0000000..22443e9 Binary files /dev/null and b/src/assets/images/skill/icon/素材管理.png differ diff --git a/src/assets/images/skill/icon/脚本与分镜制作.png b/src/assets/images/skill/icon/脚本与分镜制作.png new file mode 100644 index 0000000..08dee9b Binary files /dev/null and b/src/assets/images/skill/icon/脚本与分镜制作.png differ diff --git a/src/assets/images/skill/icon/蒙太奇剪辑.png b/src/assets/images/skill/icon/蒙太奇剪辑.png new file mode 100644 index 0000000..8a26be4 Binary files /dev/null and b/src/assets/images/skill/icon/蒙太奇剪辑.png differ diff --git a/src/assets/images/skill/icon/设备准备.png b/src/assets/images/skill/icon/设备准备.png new file mode 100644 index 0000000..088319e Binary files /dev/null and b/src/assets/images/skill/icon/设备准备.png differ diff --git a/src/assets/images/skill/icon/调色.png b/src/assets/images/skill/icon/调色.png new file mode 100644 index 0000000..863662e Binary files /dev/null and b/src/assets/images/skill/icon/调色.png differ diff --git a/src/assets/images/skill/icon/运镜.png b/src/assets/images/skill/icon/运镜.png new file mode 100644 index 0000000..8fe6cc9 Binary files /dev/null and b/src/assets/images/skill/icon/运镜.png differ diff --git a/src/assets/images/skill/icon/运镜技巧.png b/src/assets/images/skill/icon/运镜技巧.png new file mode 100644 index 0000000..bc21f01 Binary files /dev/null and b/src/assets/images/skill/icon/运镜技巧.png differ diff --git a/src/assets/images/skill/icon/选题与构思.png b/src/assets/images/skill/icon/选题与构思.png new file mode 100644 index 0000000..475ab5e Binary files /dev/null and b/src/assets/images/skill/icon/选题与构思.png differ diff --git a/src/assets/images/skill/icon/音效.png b/src/assets/images/skill/icon/音效.png new file mode 100644 index 0000000..da76458 Binary files /dev/null and b/src/assets/images/skill/icon/音效.png differ diff --git a/src/assets/images/skill/vlog-发现与记录.png b/src/assets/images/skill/vlog-发现与记录.png deleted file mode 100644 index d558c0e..0000000 Binary files a/src/assets/images/skill/vlog-发现与记录.png and /dev/null differ diff --git a/src/assets/images/skill/技能树总览.png b/src/assets/images/skill/技能树总览.png deleted file mode 100644 index 879288b..0000000 Binary files a/src/assets/images/skill/技能树总览.png and /dev/null differ diff --git a/src/assets/images/skill/技能树.png b/src/assets/images/skill/技能树标题.png similarity index 100% rename from src/assets/images/skill/技能树.png rename to src/assets/images/skill/技能树标题.png diff --git a/src/assets/images/skill/访谈-聆听与对话.png b/src/assets/images/skill/访谈-聆听与对话.png deleted file mode 100644 index fd63986..0000000 Binary files a/src/assets/images/skill/访谈-聆听与对话.png and /dev/null differ diff --git a/src/components/Guide/GuideSteps.vue b/src/components/Guide/GuideSteps.vue index 921f41d..01f5115 100644 --- a/src/components/Guide/GuideSteps.vue +++ b/src/components/Guide/GuideSteps.vue @@ -154,14 +154,14 @@ const currentStepContent = computed(() => { const step = currentStepData.value if (!step) return '' - if (!step.title) { - return `${step.content}` - } + // if (!step.title) { + return `${step.content}` + // } - return ` -

${step.title}

-
${step.content}
- ` + // return ` + //

${step.title}

+ //
${step.content}
+ // ` }) const handleNext = () => { diff --git a/src/components/Guide/StepNavigation.vue b/src/components/Guide/StepNavigation.vue index fb3c7b5..11e5562 100644 --- a/src/components/Guide/StepNavigation.vue +++ b/src/components/Guide/StepNavigation.vue @@ -56,7 +56,7 @@ --> -
+
{{ step.label }}
@@ -93,6 +93,7 @@ + diff --git a/src/views/skill/components/CategoryTags.vue b/src/views/skill/components/CategoryTags.vue deleted file mode 100644 index 955e256..0000000 --- a/src/views/skill/components/CategoryTags.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - - - diff --git a/src/views/skill/components/IconLayer.vue b/src/views/skill/components/IconLayer.vue deleted file mode 100644 index de51c93..0000000 --- a/src/views/skill/components/IconLayer.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/src/views/skill/components/ModuleSwitcher.vue b/src/views/skill/components/ModuleSwitcher.vue deleted file mode 100644 index 9e61062..0000000 --- a/src/views/skill/components/ModuleSwitcher.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/src/views/skill/components/ResourceList.vue b/src/views/skill/components/ResourceList.vue index bcd8a3f..416fa3c 100644 --- a/src/views/skill/components/ResourceList.vue +++ b/src/views/skill/components/ResourceList.vue @@ -4,26 +4,103 @@ import type { ResourceItem } from '../resourceData' const props = defineProps<{ resources: ResourceItem[] }>() + +const emit = defineEmits<{ + (e: 'resourceClick', resource: ResourceItem): void +}>() + +const handleResourceClick = (resource: ResourceItem) => { + emit('resourceClick', resource) +} - + diff --git a/src/views/skill/components/SearchTags.vue b/src/views/skill/components/SearchTags.vue index 2f81a05..ebbd09b 100644 --- a/src/views/skill/components/SearchTags.vue +++ b/src/views/skill/components/SearchTags.vue @@ -1,4 +1,6 @@ diff --git a/src/views/skill/components/SkillTree.vue b/src/views/skill/components/SkillTree.vue index 4c3b48d..a7acd64 100644 --- a/src/views/skill/components/SkillTree.vue +++ b/src/views/skill/components/SkillTree.vue @@ -6,6 +6,7 @@ import { getSubTagsByTag, getTagResourceCount } from '../resourceData' +import { getStaticImage, getTagColor } from '@/utils' interface Props { selectedModuleId: string | null @@ -32,6 +33,54 @@ const displayedModules = computed(() => { return allModules.value }) +// 获取所有要显示的标签(按标签名称去重,并按固定顺序排列) +const displayedTags = computed(() => { + const tagMap = new Map() + + displayedModules.value.forEach(module => { + const tags = getTagsByModule(module.id) + tags.forEach(tag => { + // 使用标签名称作为唯一键,避免同名标签重复显示 + if (!tagMap.has(tag.name)) { + tagMap.set(tag.name, { + moduleId: module.id, + tagId: tag.id, + tagName: tag.name + }) + } + }) + }) + + // 定义固定的标签顺序(与位置数组对应) + const tagOrder = [ + '视觉素养', + '摄影基础', + '剪映基础操作', + '内容策划', + '拍摄执行', + '后期制作', + 'AI与版权伦理知识', + '思路提点与案例赏析' + ] + + // 按照固定顺序返回标签 + const orderedTags: Array<{ moduleId: string; tagId: string; tagName: string }> = [] + tagOrder.forEach(tagName => { + if (tagMap.has(tagName)) { + orderedTags.push(tagMap.get(tagName)!) + } + }) + + // 添加任何不在固定顺序中的标签 + tagMap.forEach((tag, tagName) => { + if (!tagOrder.includes(tagName)) { + orderedTags.push(tag) + } + }) + + return orderedTags +}) + // 检查标签是否展开 const isTagExpanded = (tagId: string) => { return props.expandedTags.has(tagId) @@ -39,42 +88,57 @@ const isTagExpanded = (tagId: string) => { // 检查标签是否选中 const isTagSelected = (moduleId: string, tagId: string) => { - return props.selectedModuleId === moduleId && props.selectedTagId === tagId + if (props.selectedModuleId) { + // 模块模式:需要模块和标签都匹配 + return props.selectedModuleId === moduleId && props.selectedTagId === tagId + } else { + // 总览模式:只需要标签匹配(因为总览模式下 selectedModuleId 为 null) + return props.selectedTagId === tagId + } } // 检查子标签是否选中 const isSubTagSelected = (moduleId: string, tagId: string, subTagId: string) => { - return ( - props.selectedModuleId === moduleId && - props.selectedTagId === tagId && - props.selectedSubTagId === subTagId - ) + if (props.selectedModuleId) { + // 模块模式:需要模块、标签和子标签都匹配 + return ( + props.selectedModuleId === moduleId && + props.selectedTagId === tagId && + props.selectedSubTagId === subTagId + ) + } else { + // 总览模式:只需要标签和子标签匹配 + return props.selectedTagId === tagId && props.selectedSubTagId === subTagId + } } -// 获取标签的资源数量 -const getResourceCount = (moduleId: string, tagId: string) => { - return getTagResourceCount(moduleId, tagId) +// 获取标签的资源数量(如果显示所有模块,则合并同名标签的数量) +const getResourceCount = (moduleId: string, tagId: string, tagName: string) => { + if (props.selectedModuleId) { + // 如果选中了模块,只计算该模块的资源数量 + return getTagResourceCount(moduleId, tagId) + } else { + // 如果显示所有模块,计算所有模块中同名标签的资源总数 + let totalCount = 0 + displayedModules.value.forEach(module => { + const tags = getTagsByModule(module.id) + tags.forEach(tag => { + if (tag.name === tagName) { + totalCount += getTagResourceCount(module.id, tag.id) + } + }) + }) + return totalCount + } } -// 获取标签颜色类 - 根据标签类型返回不同的颜色 +// 获取标签颜色类 - 根据标签类型返回不同的颜色(匹配目标效果图) const getTagColorClass = (tagName: string, isSelected: boolean) => { if (isSelected) { - return 'bg-orange-500 border-white text-white' + return getTagColor(tagName) + ' brightness-125' } - // 根据标签名称返回不同的颜色 - const colorMap: Record = { - 后期制作: 'bg-blue-400 border-blue-300 text-white', - 拍摄执行: 'bg-green-400 border-green-300 text-white', - 内容策划: 'bg-orange-400 border-orange-300 text-white', - 视觉素养: 'bg-orange-400 border-orange-300 text-white', - 摄影基础: 'bg-blue-400 border-blue-300 text-white', - 剪映基础操作: 'bg-purple-400 border-purple-300 text-white', - AI与版权伦理知识: 'bg-blue-400 border-blue-300 text-white', - 思路提点与案例赏析: 'bg-blue-400 border-blue-300 text-white' - } - - return colorMap[tagName] || 'bg-blue-400 border-blue-300 text-white' + return getTagColor(tagName) } const handleTagClick = (moduleId: string, tagId: string) => { @@ -85,166 +149,206 @@ const handleSubTagClick = (moduleId: string, tagId: string, subTagId: string) => emit('subTagClick', moduleId, tagId, subTagId) } -// 计算标签节点的位置 - 使用更自然的树形分布 -const getTagPosition = ( - moduleIndex: number, - tagIndex: number, - totalTags: number, - moduleId: string -) => { - // 计算所有标签的总数(用于全局定位) - const modules = displayedModules.value - let globalTagIndex = 0 - for (let i = 0; i < moduleIndex; i++) { - globalTagIndex += getTagsByModule(modules[i].id).length +// 获取子标签的图标 +const getSubTagIcon = (subTagName: string) => { + // 图标文件名映射 + const iconMap: Record = { + 构图: 'skill/icon/构图.png', + 光线: 'skill/icon/光线.png', + 运镜: 'skill/icon/运镜.png', + 设备准备: 'skill/icon/设备准备.png', + 脚本制作: 'skill/icon/脚本与分镜制作.png', + 光线运用: 'skill/icon/光线运用.png', + 运镜技巧: 'skill/icon/运镜技巧.png', + 选题与构思: 'skill/icon/选题与构思.png', + 脚本与分镜制作: 'skill/icon/脚本与分镜制作.png', + 拍摄技巧: 'skill/icon/拍摄技巧.png', + 剪辑技巧: 'skill/icon/剪辑技巧.png', + 包装: 'skill/icon/包装.png', + 调色: 'skill/icon/调色.png', + 音效: 'skill/icon/音效.png', + 素材管理: 'skill/icon/素材管理.png', + 蒙太奇剪辑: 'skill/icon/蒙太奇剪辑.png', + 版权意识: 'skill/icon/版权意识.png', + ai技术应用: 'skill/icon/ai技术应用.png', + 思路提点: 'skill/icon/思路提点.png', + 案例合集: 'skill/icon/案例合集.png' } - globalTagIndex += tagIndex + return iconMap[subTagName] || 'skill/icon/构图.png' // 默认图标 +} - // 计算总标签数 - const totalAllTags = modules.reduce((sum, m) => sum + getTagsByModule(m.id).length, 0) +// 获取技能树标题 +const getTreeTitle = () => { + if (!props.selectedModuleId) { + return '技能树总览' + } + const module = allModules.value.find(m => m.id === props.selectedModuleId) + return module?.name || '技能树总览' +} - // 垂直位置:从树根向上分布,形成树形结构 - const baseTop = 85 // 树根位置(底部) - const maxTop = 15 // 最顶部位置 - const verticalRange = baseTop - maxTop +// 计算标签节点的位置 - 根据目标效果图1的布局 +const getTagPosition = (tagIndex: number) => { + // 定义所有标签的具体位置(根据目标效果图1) + // 标签顺序:视觉素养、摄影基础、剪映基础操作、内容策划、拍摄执行、后期制作、AI与版权伦理知识、思路提点与案例赏析 + const positions = [ + { top: 38, left: 33 }, // 0: 视觉素养 - 顶部左侧 + { top: 51, left: 10 }, // 1: 摄影基础 - 顶部右侧偏左 + { top: 51, left: 58 }, // 2: 剪映基础操作 - 顶部右侧 + { top: 32, left: 4 }, // 3: 内容策划 - 中部左侧 + { top: 18, left: 14 }, // 4: 拍摄执行 - 中部右侧(部分在外) + { top: 5, left: 35 }, // 5: 后期制作 - 中下部左侧 + { top: 22, left: 48 }, // 6: AI与版权伦理知识 - 中下部中间 + { top: 35, left: 64 } // 7: 思路提点与案例赏析 - 中下部右侧 + ] - // 根据全局索引计算垂直位置,形成扇形分布 - const verticalRatio = totalAllTags > 1 ? globalTagIndex / (totalAllTags - 1) : 0 - const top = baseTop - verticalRange * verticalRatio * 0.75 // 使用0.75让标签更集中在树的上半部分 - - // 水平位置:左右交替分布,形成树形 - const isLeft = globalTagIndex % 2 === 0 - const horizontalSpread = 28 // 水平展开范围 - const horizontalOffset = isLeft ? -horizontalSpread : horizontalSpread - - // 根据垂直位置调整水平偏移,形成扇形(越往上,水平偏移越小) - const verticalFactor = Math.max(0, (top - baseTop) / (maxTop - baseTop)) - const left = 50 + horizontalOffset * (1 - verticalFactor * 0.4) + // 如果标签数量超过预定义位置,使用循环 + const position = positions[tagIndex % positions.length] return { - top: `${Math.max(maxTop, Math.min(baseTop, top))}%`, - left: `${Math.max(5, Math.min(95, left))}%` + top: `${position.top}%`, + left: `${position.left}%` } } -// 计算连接线的起点和终点 -const getConnectionLine = ( - moduleIndex: number, - tagIndex: number, - totalTags: number, - moduleId: string -) => { - const tagPos = getTagPosition(moduleIndex, tagIndex, totalTags, moduleId) - const rootX = 50 // 树根中心X位置 - const rootY = 92 // 树根中心Y位置(底部) +// 计算子标签围绕父标签的位置(圆形分布) +const getSubTagPosition = (subTagIndex: number, totalSubTags: number) => { + // 圆形半径(单位:像素)- 根据子标签数量调整半径 + // 使用更大的半径确保子标签不会挤在一起 + const baseRadius = 160 // 基础半径180px + const radius = totalSubTags <= 3 ? baseRadius : baseRadius + (totalSubTags - 3) * 30 + + // 计算角度:均匀分布在360度圆周上 + // 从顶部开始(-90度),顺时针分布 + const angleStep = (360 / totalSubTags) * (Math.PI / 180) // 转换为弧度 + const angle = -Math.PI / 2 + subTagIndex * angleStep // -90度开始 + + // 计算子标签的位置(相对于父标签中心,使用像素单位) + const offsetX = radius * Math.cos(angle) + const offsetY = radius * Math.sin(angle) return { - x1: rootX, - y1: rootY, - x2: parseFloat(tagPos.left), - y2: parseFloat(tagPos.top) + 3 // 标签中心位置 + transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY + 30}px))` // 让子标签以自身中心定位,并应用偏移 } } diff --git a/src/views/skill/index.vue b/src/views/skill/index.vue index 5736558..6e08624 100644 --- a/src/views/skill/index.vue +++ b/src/views/skill/index.vue @@ -1,8 +1,10 @@ diff --git a/src/views/skill/resourceData.ts b/src/views/skill/resourceData.ts index d9893a8..17ffb9b 100644 --- a/src/views/skill/resourceData.ts +++ b/src/views/skill/resourceData.ts @@ -13,11 +13,16 @@ export interface ResourceItem { subTagName?: string // 资源数量(用于技能树显示) resourceCount?: number + // 资源类型和链接 + type: 'video' | 'page' | 'image' // video: 视频播放, page: 页面跳转, image: 图文展示 + videoUrl?: string // 视频URL(type为video时使用) + routeName?: string // 路由名称(type为page时使用) + images?: string[] // 图片URL数组(type为image时使用) } // 一维数组格式的资源数据 export const resourceData: ResourceItem[] = [ - // 新手导航模块 + // 新手导航模块 - 保持原有标签 { id: 'res-1', title: '九种构图法', @@ -27,7 +32,9 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-1-1', tagName: '视觉素养', subTagId: 'subtag-1-1-1', - subTagName: '构图' + subTagName: '构图', + type: 'video', + videoUrl: '/video/构图法.mp4' }, { id: 'res-2', @@ -38,7 +45,9 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-1-1', tagName: '视觉素养', subTagId: 'subtag-1-1-2', - subTagName: '光线' + subTagName: '光线', + type: 'video', + videoUrl: '/video/光线运用.mp4' }, { id: 'res-3', @@ -49,7 +58,9 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-1-1', tagName: '视觉素养', subTagId: 'subtag-1-1-3', - subTagName: '运镜' + subTagName: '运镜', + type: 'video', + videoUrl: '/video/运镜技巧.mp4' }, { id: 'res-4', @@ -58,7 +69,9 @@ export const resourceData: ResourceItem[] = [ moduleId: 'module-1', moduleName: '新手导航', tagId: 'tag-1-2', - tagName: '摄影基础' + tagName: '摄影基础', + type: 'image', + images: ['/images/skill/摄影基础1.png', '/images/skill/摄影基础2.png'] }, { id: 'res-5', @@ -67,33 +80,53 @@ export const resourceData: ResourceItem[] = [ moduleId: 'module-1', moduleName: '新手导航', tagId: 'tag-1-3', - tagName: '剪映基础操作' + tagName: '剪映基础操作', + type: 'video', + videoUrl: '/video/剪映入门.mp4' }, - // vlog发现与记录模块 + + // vlog发现与记录模块 - 使用统一标签体系 { id: 'res-6', - title: '设备准备指南', - description: 'vlog拍摄设备准备', + title: 'vlog设备准备', + description: 'vlog拍摄设备准备指南', moduleId: 'module-2', moduleName: 'vlog发现与记录', tagId: 'tag-2-1', tagName: '内容策划', subTagId: 'subtag-2-1-1', - subTagName: '设备准备' + subTagName: '设备准备', + type: 'video', + videoUrl: '/video/vlog设备准备.mp4' }, { id: 'res-7', + title: 'vlog选题构思', + description: 'vlog选题与创意构思', + moduleId: 'module-2', + moduleName: 'vlog发现与记录', + tagId: 'tag-2-1', + tagName: '内容策划', + subTagId: 'subtag-2-1-2', + subTagName: '选题与构思', + type: 'video', + videoUrl: '/video/vlog选题构思.mp4' + }, + { + id: 'res-8', title: 'vlog脚本撰写', description: '如何撰写vlog脚本', moduleId: 'module-2', moduleName: 'vlog发现与记录', tagId: 'tag-2-1', tagName: '内容策划', - subTagId: 'subtag-2-1-2', - subTagName: '脚本制作' + subTagId: 'subtag-2-1-3', + subTagName: '脚本与分镜制作', + type: 'video', + videoUrl: '/video/vlog脚本撰写.mp4' }, { - id: 'res-8', + id: 'res-9', title: 'vlog构图技巧', description: 'vlog拍摄构图方法', moduleId: 'module-2', @@ -101,10 +134,12 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-2-2', tagName: '拍摄执行', subTagId: 'subtag-2-2-1', - subTagName: '构图' + subTagName: '构图', + type: 'video', + videoUrl: '/video/vlog构图技巧.mp4' }, { - id: 'res-9', + id: 'res-10', title: 'vlog光线控制', description: 'vlog拍摄光线运用', moduleId: 'module-2', @@ -112,10 +147,12 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-2-2', tagName: '拍摄执行', subTagId: 'subtag-2-2-2', - subTagName: '光线运用' + subTagName: '光线运用', + type: 'video', + videoUrl: '/video/vlog光线控制.mp4' }, { - id: 'res-10', + id: 'res-11', title: 'vlog运镜方法', description: 'vlog拍摄运镜技巧', moduleId: 'module-2', @@ -123,52 +160,164 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-2-2', tagName: '拍摄执行', subTagId: 'subtag-2-2-3', - subTagName: '运镜技巧' + subTagName: '运镜技巧', + type: 'video', + videoUrl: '/video/vlog运镜方法.mp4' }, { - id: 'res-11', - title: 'vlog后期剪辑', - description: 'vlog视频后期制作', + id: 'res-12', + title: 'vlog剪辑技巧', + description: 'vlog视频剪辑方法', moduleId: 'module-2', moduleName: 'vlog发现与记录', tagId: 'tag-2-3', - tagName: '后期制作' + tagName: '后期制作', + subTagId: 'subtag-2-3-1', + subTagName: '剪辑技巧', + type: 'video', + videoUrl: '/video/vlog剪辑技巧.mp4' }, - // 访谈聆听与对话模块 { - id: 'res-12', + id: 'res-13', + title: 'vlog调色技巧', + description: 'vlog视频调色方法', + moduleId: 'module-2', + moduleName: 'vlog发现与记录', + tagId: 'tag-2-3', + tagName: '后期制作', + subTagId: 'subtag-2-3-2', + subTagName: '调色', + type: 'video', + videoUrl: '/video/vlog调色技巧.mp4' + }, + { + id: 'res-14', + title: 'vlog音效处理', + description: 'vlog音效添加与处理', + moduleId: 'module-2', + moduleName: 'vlog发现与记录', + tagId: 'tag-2-3', + tagName: '后期制作', + subTagId: 'subtag-2-3-3', + subTagName: '音效', + type: 'video', + videoUrl: '/video/vlog音效处理.mp4' + }, + { + id: 'res-15', + title: 'vlog版权规范', + description: 'vlog创作版权注意事项', + moduleId: 'module-2', + moduleName: 'vlog发现与记录', + tagId: 'tag-2-4', + tagName: 'AI与版权伦理知识', + type: 'image', + images: [ + '/images/skill/版权规范1.png', + '/images/skill/版权规范2.png', + '/images/skill/版权规范3.png' + ] + }, + { + id: 'res-16', + title: 'vlog案例赏析', + description: '优秀vlog作品分析', + moduleId: 'module-2', + moduleName: 'vlog发现与记录', + tagId: 'tag-2-5', + tagName: '思路提点与案例赏析', + type: 'video', + videoUrl: '/video/vlog案例赏析.mp4' + }, + + // 访谈聆听与对话模块 - 使用统一标签体系 + { + id: 'res-17', + title: '访谈设备准备', + description: '访谈拍摄设备清单', + moduleId: 'module-3', + moduleName: '访谈聆听与对话', + tagId: 'tag-3-1', + tagName: '内容策划', + subTagId: 'subtag-3-1-1', + subTagName: '设备准备', + type: 'video', + videoUrl: '/video/访谈设备准备.mp4' + }, + { + id: 'res-18', title: '访谈选题方法', description: '如何选择访谈主题', moduleId: 'module-3', moduleName: '访谈聆听与对话', tagId: 'tag-3-1', tagName: '内容策划', - subTagId: 'subtag-3-1-1', - subTagName: '选题与构思' + subTagId: 'subtag-3-1-2', + subTagName: '选题与构思', + type: 'video', + videoUrl: '/video/访谈选题方法.mp4' }, { - id: 'res-13', + id: 'res-19', title: '访谈脚本制作', description: '访谈脚本撰写技巧', moduleId: 'module-3', moduleName: '访谈聆听与对话', tagId: 'tag-3-1', tagName: '内容策划', - subTagId: 'subtag-3-1-2', - subTagName: '脚本与分镜制作' + subTagId: 'subtag-3-1-3', + subTagName: '脚本与分镜制作', + type: 'video', + videoUrl: '/video/访谈脚本制作.mp4' }, { - id: 'res-14', + id: 'res-20', title: '访谈拍摄技巧', description: '访谈视频拍摄方法', moduleId: 'module-3', moduleName: '访谈聆听与对话', tagId: 'tag-3-2', - tagName: '拍摄执行' + tagName: '拍摄执行', + type: 'video', + videoUrl: '/video/访谈拍摄技巧.mp4' }, - // 电影创作与表达模块 { - id: 'res-15', + id: 'res-21', + title: '访谈后期剪辑', + description: '访谈视频后期制作', + moduleId: 'module-3', + moduleName: '访谈聆听与对话', + tagId: 'tag-3-3', + tagName: '后期制作', + type: 'video', + videoUrl: '/video/访谈后期剪辑.mp4' + }, + { + id: 'res-22', + title: '访谈版权规范', + description: '访谈创作版权注意事项', + moduleId: 'module-3', + moduleName: '访谈聆听与对话', + tagId: 'tag-3-4', + tagName: 'AI与版权伦理知识', + type: 'video', + videoUrl: '/video/访谈版权规范.mp4' + }, + { + id: 'res-23', + title: '访谈案例赏析', + description: '优秀访谈作品分析', + moduleId: 'module-3', + moduleName: '访谈聆听与对话', + tagId: 'tag-3-5', + tagName: '思路提点与案例赏析', + type: 'video', + videoUrl: '/video/访谈案例赏析.mp4' + }, + + // 电影创作与表达模块 - 使用统一标签体系 + { + id: 'res-24', title: '电影设备准备', description: '电影拍摄设备清单', moduleId: 'module-4', @@ -176,10 +325,12 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-4-1', tagName: '内容策划', subTagId: 'subtag-4-1-1', - subTagName: '设备准备' + subTagName: '设备准备', + type: 'page', + routeName: 'FilmResource' }, { - id: 'res-16', + id: 'res-25', title: '电影选题构思', description: '电影创作选题方法', moduleId: 'module-4', @@ -187,10 +338,12 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-4-1', tagName: '内容策划', subTagId: 'subtag-4-1-2', - subTagName: '选题与构思' + subTagName: '选题与构思', + type: 'video', + videoUrl: '/video/电影选题构思.mp4' }, { - id: 'res-17', + id: 'res-26', title: '电影脚本分镜', description: '电影脚本和分镜制作', moduleId: 'module-4', @@ -198,43 +351,53 @@ export const resourceData: ResourceItem[] = [ tagId: 'tag-4-1', tagName: '内容策划', subTagId: 'subtag-4-1-3', - subTagName: '脚本与分镜制作' + subTagName: '脚本与分镜制作', + type: 'video', + videoUrl: '/video/电影脚本分镜.mp4' }, { - id: 'res-18', + id: 'res-27', title: '电影拍摄执行', description: '电影拍摄执行技巧', moduleId: 'module-4', moduleName: '电影创作与表达', tagId: 'tag-4-2', - tagName: '拍摄执行' + tagName: '拍摄执行', + type: 'video', + videoUrl: '/video/电影拍摄执行.mp4' }, { - id: 'res-19', + id: 'res-28', title: '电影后期制作', description: '电影后期剪辑与特效', moduleId: 'module-4', moduleName: '电影创作与表达', tagId: 'tag-4-3', - tagName: '后期制作' + tagName: '后期制作', + type: 'video', + videoUrl: '/video/电影后期制作.mp4' }, { - id: 'res-20', - title: 'AI与版权伦理', - description: 'AI工具使用与版权规范', + id: 'res-29', + title: '电影AI工具应用', + description: 'AI工具在电影创作中的应用', moduleId: 'module-4', moduleName: '电影创作与表达', tagId: 'tag-4-4', - tagName: 'AI与版权伦理知识' + tagName: 'AI与版权伦理知识', + type: 'video', + videoUrl: '/video/电影AI工具应用.mp4' }, { - id: 'res-21', - title: '创作思路与案例', + id: 'res-30', + title: '电影创作思路与案例', description: '电影创作思路和案例分析', moduleId: 'module-4', moduleName: '电影创作与表达', tagId: 'tag-4-5', - tagName: '思路提点与案例赏析' + tagName: '思路提点与案例赏析', + type: 'video', + videoUrl: '/video/电影创作思路与案例.mp4' } ] diff --git a/src/views/skill/useSkillState.ts b/src/views/skill/useSkillState.ts deleted file mode 100644 index 4cd4755..0000000 --- a/src/views/skill/useSkillState.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { ref, computed } from 'vue' - -// 分类配置 -interface CategoryConfig { - icons: string[] - positions: { top: string; left: string }[] -} - -// 模块配置 -interface ModuleConfig { - name: string - value: number - categories: Record -} - -// 知识卡片按钮数据结构(与 shooting/index.vue 对齐) -export type KnowledgeBtn = { - key: string - name: string - btnImage: string - images: string[] - desc: string -} - -export function useSkillState() { - // 模块/分类/图标层级状态(仅保留必要业务状态) - const selectedModule = ref(1) - const selectedCategory = ref('') - const showSubIcons = ref(false) - const selectedPrimaryIcon = ref(null) - - const moduleList = ref([ - { - name: '技能树-新手启航', - value: 1, - categories: { - 拍摄基础: { - icons: [], - positions: [] - }, - 内容策划: { - icons: [], - positions: [] - }, - 拍摄执行: { - icons: [], - positions: [] - }, - 后期制作: { - icons: [], - positions: [] - }, - 版权伦理意识: { - icons: [], - positions: [] - } - } - }, - { - name: '技能树-vlog', - value: 2, - categories: { - 拍摄基础: { - icons: [], - positions: [] - }, - 内容策划: { - icons: [], - positions: [] - }, - 拍摄执行: { - icons: ['skill/画面构图未选中', 'skill/景别', 'skill/运镜技巧', 'skill/光线运用'], - positions: [ - { top: '36%', left: '23%' }, - { top: '15%', left: '34%' }, - { top: '20%', left: '45%' }, - { top: '28%', left: '58%' } - ] - }, - 后期制作: { - icons: [], - positions: [] - }, - 版权伦理意识: { - icons: [], - positions: [] - } - } - } - ]) - - const currentModule = computed(() => { - return moduleList.value.find(item => item.value === selectedModule.value) || moduleList.value[0] - }) - - const nextModule = () => { - if (selectedModule.value === moduleList.value.length) { - selectedModule.value = 1 - } else { - selectedModule.value++ - } - } - - const prevModule = () => { - if (selectedModule.value === 1) { - selectedModule.value = moduleList.value.length - } else { - selectedModule.value-- - } - } - - const handleCategoryClick = (categoryName: string) => { - selectedCategory.value = categoryName - showSubIcons.value = false - selectedPrimaryIcon.value = null - } - - const currentCategoryConfig = computed(() => { - if (!selectedCategory.value) return null - return currentModule.value.categories[selectedCategory.value] || null - }) - - const displayedIcons = computed(() => { - const cfg = currentCategoryConfig.value - if (!cfg) return [] as string[] - if (showSubIcons.value && selectedPrimaryIcon.value) { - return ['skill/画面构图选中', 'skill/三分法构图', 'skill/居中构图', 'skill/对角线构图'] - } - return cfg.icons - }) - - type IconAction = - | { type: 'navigate'; payload: { name: string } } - | { type: 'expand' } - | { type: 'openKnowledge'; payload: { name: '三分法构图' | '居中构图' | '对角线构图' } } - | null - - const getIconAction = (icon: string): IconAction => { - if (icon === 'skill/画面构图未选中' && selectedCategory.value === '拍摄执行') { - if (selectedModule.value === 1) return { type: 'navigate', payload: { name: 'SkillVdieo' } } - if (selectedModule.value === 2) return { type: 'expand' } - } - if (showSubIcons.value && selectedModule.value === 2 && selectedCategory.value === '拍摄执行') { - const map: Record = { - 'skill/三分法构图': '三分法构图', - 'skill/居中构图': '居中构图', - 'skill/对角线构图': '对角线构图' - } - if (icon in map) return { type: 'openKnowledge', payload: { name: map[icon] } } - } - return null - } - - return { - // module switcher - currentModule, - nextModule, - prevModule, - // category & icons - selectedCategory, - handleCategoryClick, - currentCategoryConfig, - displayedIcons, - selectedModule, - showSubIcons, - selectedPrimaryIcon, - getIconAction - } -}