Files
super_video/src/views/home/action.vue
T

444 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
defineOptions({
name: 'Action'
})
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { ModuleSwitcher } from './components/ModuleSwitcher'
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore, useFilmStore, useVlogStore } from '@/stores'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
//点击电视机按钮引导状态
const status = ref(false)
const status2 = ref(false)
const status3 = ref(false)
const stepIndex = ref(0)
const handleComplete = () => {
status.value = false
}
const router = useRouter()
const user = useUserStore()
const film = useFilmStore()
const vlog = useVlogStore()
// 响应式状态
const guideShow = ref(false)
const selectedModule = ref<'vlog' | 'film' | 'interview'>('film')
const isImageLoading = ref(false)
// 预加载图片映射
const imageMap = {
vlog: IMAGE_PATHS.ACTION.VIDEO1,
film: IMAGE_PATHS.ACTION.VIDEO2,
interview: IMAGE_PATHS.ACTION.VIDEO3
}
//获取新手导航引导状态
const isGuideNewUser = computed(() => user.isGuideNewUser)
// 预加载所有图片
const preloadImages = () => {
const imageUrls = Object.values(imageMap).map(path => getBackgroundImage(path))
imageUrls.forEach(url => {
const img = new Image()
img.src = url
})
}
// 事件处理函数
const handleOpenGuide = () => {
status2.value = true
guideShow.value = true
}
const handleCloseGuide = () => {
guideShow.value = false
if (isGuideNewUser.value) {
user.setIsGuideNewUser(false)
}
}
const handleModuleChange = (module: 'vlog' | 'film' | 'interview') => {
status.value = true
if (selectedModule.value === module) return
isImageLoading.value = true
selectedModule.value = module
// 模拟图片加载完成
setTimeout(() => {
isImageLoading.value = false
}, 100)
}
const handleClick = () => {
if (selectedModule.value === 'interview') return
// console.log(selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1))
router.push({
//首字母大写
name: selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1)
})
}
// 计算属性
const selectedImage = computed(() => {
return getBackgroundImage(imageMap[selectedModule.value])
})
const handleSkill = () => {
router.push({
name: 'Skill'
})
}
// 清除所有与课程相关的缓存(Film / Vlog / User)
const handleClearCache = () => {
// 直接通过 $patch 重置各模块的状态,避免热更新导致的方法缺失问题
film.$patch({
isGuideFilmTask: true,
hasShownGuideFilmTask: false,
isFilmUnlock: false,
FilmUnlockProgress: 0
})
vlog.$patch({
isGuideVlogTask: true,
isVlogUnlock: false,
vlogUnlockProgress: 0
})
user.$patch({
isGuideNewUser: true
})
}
// 新手导航数据
const nodes = [
{
id: 1,
num: '1',
label: '三大基础要素',
x: 20,
y: 30,
hasChildren: true,
children: [
{ id: 1, label: '构图', icon: 'home/action/新手导航构图', routeName: 'Composition' },
{ id: 2, label: '光', icon: 'home/action/新手导航光', routeName: 'Light' },
{ id: 3, label: '运镜', icon: 'home/action/新手导航运镜', routeName: 'CameraMovement' }
]
},
{
id: 2,
num: '2',
label: '基础拍摄技巧',
x: 50,
y: 60,
hasChildren: false,
routeName: 'BasicShootingSkills'
},
{
id: 3,
num: '3',
label: '短视频剪辑技巧',
x: 80,
y: 30,
hasChildren: false,
routeName: 'ShortVideoEditingSkills'
}
]
// 显示子节点的节点ID
const showChildrenNodeId = ref<number | null>(null)
// 处理节点点击
const handleNodeClick = (node: any, event?: MouseEvent) => {
if (event) {
event.stopPropagation()
}
if (node.hasChildren) {
// 如果点击的是有子节点的节点,切换显示状态
if (showChildrenNodeId.value === node.id) {
showChildrenNodeId.value = null
} else {
showChildrenNodeId.value = node.id
status3.value = true
}
} else {
// 如果点击的是没有子节点的节点,直接跳转
if (node.routeName) {
router.push({ name: node.routeName })
}
}
}
// 处理子节点点击
const handleChildNodeClick = (childNode: any, event: MouseEvent) => {
event.stopPropagation()
if (childNode.routeName) {
router.push({ name: childNode.routeName })
}
}
// 点击外部关闭子节点
const handleClickOutside = () => {
showChildrenNodeId.value = null
}
// 生命周期
onMounted(() => {
preloadImages()
})
onUnmounted(() => {
// 清理工作
})
</script>
<template>
<div class="w-full h-full relative">
<!-- 图片引导 -->
<div
class="absolute top-0 left-0 w-full h-full z-50"
v-if="stepIndex < 4 && isGuideNewUser"
@click="stepIndex++"
>
<img v-if="stepIndex === 0" :src="getStaticImage('home/1')" alt="" />
<img v-else-if="stepIndex === 1" :src="getStaticImage('home/2')" alt="" />
<img v-else-if="stepIndex === 2" :src="getStaticImage('home/3')" alt="" />
<img v-else-if="stepIndex === 3" :src="getStaticImage('home/4')" alt="" />
<div v-else class="absolute top-0 left-0 w-full h-full bg-black/50"></div>
</div>
<!-- 背景图片 -->
<div
:class="['absolute inset-0 bg-cover bg-center bg-no-repeat']"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.ACTION_BG) }"
></div>
<div
class="absolute top-4 left-4 flex items-center text-white text-xl bg-black/10 px-4 py-2 rounded-xl"
>
!测试阶段友情提示:课程全部完成后,若需重新观看之前的内容,请按
<img
class="w-10 cursor-pointer mx-2"
src="@/assets/svg/clear.svg"
@click="handleClearCache"
/>清空缓存。
</div>
<!-- 选择模块 -->
<div :class="['absolute left-0 top-[-140px] inset-0 z-[8] pointer-events-none']">
<ModuleSwitcher v-model="selectedModule" @change="handleModuleChange" />
</div>
<!-- 电视机 -->
<div
class="absolute w-[765px] h-[768px] right-0 top-0 z-[5] transition-all duration-300 hover:scale-105 hover:brightness-110 cursor-pointer tvGuide"
:class="{ 'opacity-50': isImageLoading }"
:style="{ backgroundImage: selectedImage }"
@click.stop="handleClick"
></div>
<!-- 技能树 按钮 -->
<ImgButton
v-if="!isGuideNewUser"
@click="handleSkill"
:image="IMAGE_PATHS.ACTION.SKILLS_BTN"
class="absolute bottom-0 right-0 z-30"
/>
<!-- 新手指导遮罩 -->
<div v-if="isGuideNewUser" class="absolute inset-0 bg-black/50 z-10"></div>
<!-- 新手指导 -->
<div
class="absolute bottom-0 left-0 w-full h-[497px] bg-cover bg-center bg-no-repeat z-20 pointer-events-none"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.ACTION.FOOTER_BTN) }"
v-if="!guideShow"
>
<div
class="absolute bottom-20 right-[calc(50%-20px)] translate-x-1/2 w-20 h-20 cursor-pointer pointer-events-auto newUserGuide"
@click="handleOpenGuide"
></div>
</div>
<div
class="absolute bottom-0 left-0 w-full h-[911px] bg-cover bg-center bg-no-repeat z-40 flex flex-col items-center justify-center pt-40"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.ACTION.FOOTER_BG) }"
v-else
>
<ImgButton
:image="IMAGE_PATHS.ACTION.DOWN"
class="absolute top-60 right-1/2 translate-x-1/2 z-50"
@click="handleCloseGuide"
/>
<!-- 新手导航内容 -->
<div class="relative w-[880px] h-[400px] mt-20">
<div class="w-full h-full relative" @click="handleClickOutside">
<!-- 标题区域 -->
<div class="text-center mt-10 mb-8">
<h2 class="text-5xl font-bold mb-3 text-[#7bfffd] tracking-wider drop-shadow-lg">
新手导航
</h2>
<p class="text-4xl text-white font-bold drop-shadow-md">点击节点即可开始学习</p>
</div>
<!-- 节点图表 -->
<div class="absolute inset-0 top-[60px] btn-items">
<!-- 连线 (SVG) -->
<svg class="absolute inset-0 w-full h-full pointer-events-none">
<!-- 左侧装饰线 -> 节点1 -->
<line
x1="0%"
y1="45%"
:x2="nodes[0].x + '%'"
:y2="nodes[0].y + '%'"
stroke="#7bfffd"
stroke-width="5"
stroke-linecap="round"
/>
<!-- 节点1 -> 节点2 -->
<line
:x1="nodes[0].x + '%'"
:y1="nodes[0].y + '%'"
:x2="nodes[1].x + '%'"
:y2="nodes[1].y + '%'"
stroke="#7bfffd"
stroke-width="5"
stroke-linecap="round"
/>
<!-- 节点2 -> 节点3 -->
<line
:x1="nodes[1].x + '%'"
:y1="nodes[1].y + '%'"
:x2="nodes[2].x + '%'"
:y2="nodes[2].y + '%'"
stroke="#7bfffd"
stroke-width="5"
stroke-linecap="round"
/>
<!-- 节点3 -> 右侧装饰线 -->
<line
:x1="nodes[2].x + '%'"
:y1="nodes[2].y + '%'"
x2="100%"
y2="45%"
stroke="#7bfffd"
stroke-width="5"
stroke-linecap="round"
/>
</svg>
<!-- 节点 -->
<div
v-for="node in nodes"
:key="node.id"
class="absolute transform -translate-x-1/2 -translate-y-1/2"
:style="{ left: node.x + '%', top: node.y + '%' }"
@click="handleNodeClick(node, $event)"
>
<div
class="cursor-pointer flex flex-col items-center group transition-all duration-300 hover:scale-110"
>
<!-- 圆圈 -->
<div
class="w-24 h-24 rounded-full flex items-center justify-center text-5xl font-bold text-white z-10 relative transition-all duration-300 group-hover:bg-[#ff4d8c]"
:class="
showChildrenNodeId === node.id
? 'bg-[#ff4d8c] shadow-[0_0_20px_rgba(255,77,140,0.6)]'
: 'bg-[#60c8db] shadow-[0_0_20px_rgba(96, 200, 219,0.6)]'
"
>
{{ node.num }}
<!-- 选中状态的光晕 -->
<div
v-if="showChildrenNodeId === node.id"
class="absolute inset-0 rounded-full border-4 border-white/20 animate-pulse"
></div>
</div>
<!-- 标签 -->
<div
class="mt-2 text-4xl font-bold text-white tracking-wider drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)] whitespace-nowrap"
>
{{ node.label }}
</div>
</div>
<!-- 子节点图标(当点击三大基础要素时显示) -->
<div
v-if="node.hasChildren && showChildrenNodeId === node.id"
class="absolute -top-40 left-1/2 transform -translate-x-1/2 flex z-20 btn"
>
<div
v-for="childNode in node.children"
:key="childNode.id"
class="flex flex-col items-center cursor-pointer"
@click.stop="handleChildNodeClick(childNode, $event)"
>
<!-- 图标 -->
<div
class="w-36 flex items-center justify-center transition-all duration-300 hover:scale-110"
>
<img :src="getStaticImage(childNode.icon)" :alt="childNode.label" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 引导与高亮 -->
<GuideSteps
v-if="status"
highlight-mode="gradient"
highlight-selector=".tvGuide"
position-offset-y="85%"
@complete="handleComplete"
>
<GuideStepsItem>
<div
class="flex items-center justify-start"
v-if="selectedModule === 'interview' || selectedModule === 'vlog'"
>
[{{
selectedModule == 'interview'
? '访谈-聆听与对话'
: selectedModule == 'vlog'
? 'vlog-创作与表达'
: ''
}}]模块建设中...
</div>
<div class="flex items-center justify-start" v-else>
请点击<img
class="w-20"
:src="getStaticImage(imageMap[selectedModule])"
alt=""
/>,继续学习[{{
selectedModule == 'film'
? '电影-创作与表达'
: selectedModule == 'vlog'
? 'vlog-创作与表达'
: ''
}}]模块!
</div>
</GuideStepsItem>
</GuideSteps>
<GuideSteps :visible="status2" highlight-selector=".btn-items" position-offset-y="20%">
<GuideStepsItem> 选择任意节点开始学习 </GuideStepsItem>
</GuideSteps>
<GuideSteps :visible="status3" highlight-selector=".btn" position-offset-y="20%">
<GuideStepsItem> 选择任意要素开始学习 </GuideStepsItem>
</GuideSteps>
</div>
</template>