Files
super_video/src/views/film/video/index.vue
T

380 lines
11 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>
import { computed, nextTick, onMounted, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import ImgButton from '@/components/Button/ImgButton.vue'
import { GuideSteps, GuideStepsItem, StepNavigation } from '@/components/Guide'
import { Video, QuestionQuiz, type Question } from '@/components'
import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
import { useFilmStore } from '@/stores'
const router = useRouter()
const route = useRoute()
const film = useFilmStore()
const visibleGuide = ref(true)
const visibleGuide2 = ref(true)
// 引导步骤索引
const guideStepIndex = ref(0)
// 受控播放开关(用于切换到下一个视频时自动播放)
const playing = ref(false)
const status = ref(false)
const showQuiz = ref(false) // 控制是否显示答题窗口
//引导步骤
const steps = ref([
{
id: 'step1',
content: '试着用这些方法记录身边的小事,你会发现,原来平凡的日子里有这么多值得捕捉的瞬间。'
}
])
// 步骤导航数据
const navigationSteps = ref([
{
id: 'step1',
label: '后期制作',
content: '遮幅'
},
{
id: 'step2',
label: '拍摄执行',
content: '光线'
},
{
id: 'step3',
label: '拍摄执行',
content: '虚实'
},
{
id: 'step4',
label: '拍摄执行',
content: '特写镜头'
},
{
id: 'step5',
label: '拍摄执行',
content: '分镜头'
},
{
id: 'step5',
label: '后期制作',
content: '滤镜'
},
{
id: 'step5',
label: '拍摄执行',
content: '同期声'
}
])
// 步骤导航相关状态
const showStepNavigation = ref(false)
const currentNavigationStep = ref(0) // 对应视频索引,从0开始
// 视频列表
const videoList = [
getPublicPath('video/film/dygmk_zf_batch.mp4'),
getPublicPath('video/film/dygmk_gx_batch.mp4'),
getPublicPath('video/film/dygmk_xs_batch.mp4'),
getPublicPath('video/film/dygmk_txjt_batch.mp4'),
getPublicPath('video/film/dygmk_fjt_batch.mp4'),
getPublicPath('video/film/dygmk_lj_batch.mp4'),
getPublicPath('video/film/dygmk_tqs_batch.mp4')
]
const currentVideoIndex = ref(0)
// 题目数据:虚实(index 2)和分镜头(index 4)
const questions = ref<Question[]>([])
// 根据当前视频索引获取对应的题目
const getQuestionsForVideo = (index: number): Question[] => {
// 虚实(index 2)
if (index === 2) {
return [
{
id: 1,
title: '',
description: '(单选题)下面两个画面,哪一个更具电影叙事感?',
options: [
{
id: 'a',
label: '前景、人物、背景全部清晰'
},
{
id: 'b',
label: '前景虚化,人物清晰,背景有层次'
}
],
correctOptionId: 'b',
explanation:
'电影感的画面,往往会主动模糊一部分信息。这是为了把观众的注意力,留给正在发生的情绪。'
}
]
}
// 分镜头(index 4)
if (index === 4) {
return [
{
id: 2,
title: '',
description: '(单选题)下面哪一组镜头顺序,更符合电影叙事逻辑?',
options: [
{
id: 'a',
label: '特写→中景→ 全景'
},
{
id: 'b',
label: '全景→ 中景→ 特写'
}
],
correctOptionId: 'b',
explanation: '电影里的镜头, 往往是从环境进入人物,再进入情绪。'
}
]
}
return []
}
//按顺序播放
const currentVideo = computed(() => videoList[currentVideoIndex.value])
const resolvedSrc = computed(() => {
if (key.value === 'case') return getPublicPath('video/film/dygmk_zbbwg_batch.mp4')
if (key.value === 'profession') return currentVideo.value
return ''
})
const handleVideoChange = () => {
if (currentVideoIndex.value < videoList.length - 1) {
currentVideoIndex.value++
currentNavigationStep.value = currentVideoIndex.value
} else {
status.value = true
steps.value = [
{
id: 'step1',
content: '恭喜你,已完成专业剖析的学习,点击【查看脚本】回顾视频脚本吧!'
}
]
return
}
nextTick(() => {
// 使用受控播放来触发下一个视频自动播放
playing.value = true
})
}
const handleClose = () => {
if (key.value === 'idea') {
film.setFilmUnlockProgress(1)
router.push({
name: 'Film'
})
return
}
router.back()
}
const image = computed(() => route.query.image as string)
const key = computed(() => route.query.key as string)
// const label = computed(() => route.query.label as string)
const handleClick = () => {}
const handlePlay = () => {
console.log(1)
}
const onEnded = () => {
if (key.value === 'profession') {
// 检查是否是"虚实"(index 2)或"分镜头"(index 4)
if (currentVideoIndex.value === 2 || currentVideoIndex.value === 4) {
// 暂停播放并显示答题窗口
playing.value = false
questions.value = getQuestionsForVideo(currentVideoIndex.value)
showQuiz.value = true
} else {
// 其他视频正常切换
handleVideoChange()
}
}
}
const handleComplete = () => {
if (key.value === 'profession') {
if (!film.isFilmUnlock) film.setIsFilmUnlock(true)
router.push({
name: 'FilmWork'
})
} else {
// if (film.FilmUnlockProgress == 0) film.setFilmUnlockProgress(1)
// router.push({
// name: 'Film'
// })
}
}
// 步骤导航事件处理
const handleStepClick = (index: number) => {
// 关闭答题窗口(如果打开)
showQuiz.value = false
currentVideoIndex.value = index
currentNavigationStep.value = index
nextTick(() => {
playing.value = true
})
}
const handleStepNavigationClose = () => {
showStepNavigation.value = false
}
// 答题完成后的处理
const handleQuestionsComplete = () => {
showQuiz.value = false
// 答题完成后继续播放下一个视频
handleVideoChange()
}
onMounted(() => {
if (key.value === 'idea') {
status.value = true
}
})
</script>
<template>
<div class="w-full h-full relative bg-white/10" @click="handleClick">
<template v-if="key !== 'case' && key !== 'profession'">
<ImgButton
class="absolute right-0 top-0 z-10"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="handleClose"
/>
</template>
<!-- 步骤导航按钮 -->
<button
v-if="key === 'profession'"
class="step-navigation-btn absolute right-4 top-1/2 z-10 -translate-y-1/2 w-20 h-20 bg-blue-500/80 hover:bg-blue-500 rounded-full flex items-center justify-center text-white transition-all duration-200"
@click="showStepNavigation = true"
title="步骤导航"
>
<svg width="35" height="35" viewBox="0 0 20 20" fill="none">
<path
d="M3 4H17M3 8H17M3 12H17M3 16H17"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</button>
<Video
v-if="key === 'case' || key === 'profession'"
:src="resolvedSrc"
v-model="playing"
:muted="false"
:volume="1"
:controls="true"
:hideNativeControls="true"
@play="handlePlay"
@ended="onEnded"
>
<template #overlay>
<ImgButton
class="absolute right-0 top-0 z-10 pointer-events-auto"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="handleClose"
/>
<QuestionQuiz
v-if="showQuiz && key === 'profession'"
:show-question-number="false"
:questions="questions"
@complete="handleQuestionsComplete"
/>
</template>
</Video>
<div v-else-if="key === 'idea'" :src="image">
<img :src="getStaticImage('film/思路提点全屏')" alt="" />
</div>
</div>
<!-- 引导与高亮 -->
<GuideSteps v-if="status" :steps="steps" position-offset-y="85%" @complete="handleComplete" />
<!-- 步骤导航抽屉 -->
<StepNavigation
:visible="showStepNavigation && key === 'profession'"
:background-image="getStaticImage('work/vlog背景全屏待定')"
:steps="navigationSteps"
:current-step-index="currentNavigationStep"
@step-click="handleStepClick"
@close="handleStepNavigationClose"
/>
<!-- 引导与高亮 -->
<GuideSteps
:highlight-selector="guideStepIndex === 2 ? '.step-navigation-btn' : ''"
v-if="visibleGuide && key === 'profession'"
position-offset-y="85%"
@step-change="(_: any, index: number) => (guideStepIndex = index)"
>
<GuideStepsItem>
从刚才的案例可以看出,电影感画面自带一种独特的艺术魅力——或大气恢弘,扑朔迷离或充满故事感。
</GuideStepsItem>
<GuideStepsItem>
作为短视频创作者,我们可以借助这些方法,在拍摄与剪辑中打造更有质感的画面。
</GuideStepsItem>
<GuideStepsItem>
<div class="flex flex-wrap items-center justify-start">
<div>接下来,让我们一起看看打造电影感的几种关键技巧吧。</div>
<div class="text-yellow-400">
点击<button
class="w-10 h-10 bg-blue-500/80 hover:bg-blue-500 rounded-full inline-flex items-center justify-center text-white transition-all duration-200"
title="步骤导航"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<path
d="M3 4H17M3 8H17M3 12H17M3 16H17"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</button>
可以切换不同的学习内容!
</div>
</div>
</GuideStepsItem>
</GuideSteps>
<!-- 引导与高亮 -->
<GuideSteps
highlight-selector=""
v-if="visibleGuide2 && key === 'idea'"
position-offset-y="85%"
@complete="handleComplete"
>
<GuideStepsItem>
一个好镜头,既要好看,又要好懂 —— 既有视觉吸引力,又能把信息清晰地传达出来。
</GuideStepsItem>
<GuideStepsItem> 当这些条件被满足,一个镜头就有了“高级感”。 </GuideStepsItem>
<GuideStepsItem> 而优秀的短视频,就是靠无数这样的好镜头累积而成的。 </GuideStepsItem>
<GuideStepsItem>
接下来,我们就以徐汇滨江为例,一起动手练习如何拍出属于它的意义与故事吧!
</GuideStepsItem>
</GuideSteps>
</template>
<style scoped>
video {
width: 100%;
height: 100%;
/* 核心:保持视频比例,多余部分被裁剪 */
object-fit: cover;
}
</style>