forked from zhongyidian/super_video
完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
276 lines
7.5 KiB
Vue
276 lines
7.5 KiB
Vue
<script lang="tsx" name="Interview" setup>
|
||
defineOptions({ name: 'Interview' })
|
||
import { getBackgroundImage, getPublicPath, IMAGE_PATHS } from '@/utils'
|
||
import { ImgButton } from '@/components/Button'
|
||
|
||
import { computed, ref, watch, type Ref } from 'vue'
|
||
import { ProgressBar, Overlay } from '@/components'
|
||
import { GuideSteps, GuideStepsItem, type GuideStep } from '@/components/Guide'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { useInterviewStore } from '@/stores'
|
||
import InterviewMenu from './components/menu.vue'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const interview = useInterviewStore()
|
||
|
||
const showReturnGuide = ref(
|
||
route.query.guide === 'return' && interview.isGuideInterviewReturn,
|
||
)
|
||
|
||
// 实战操作台一次性引导(currentStep === 4 时解锁)
|
||
const practiceGuideKey = 'interview-practice-guide'
|
||
const hasShownPracticeGuide = computed(
|
||
() => interview.guideShown[practiceGuideKey] === true,
|
||
)
|
||
const showPracticeGuide = computed(
|
||
() => currentStep.value === 4 && !hasShownPracticeGuide.value,
|
||
)
|
||
|
||
const returnSteps: GuideStep[] = [
|
||
{
|
||
id: 'return-step1',
|
||
content:
|
||
'接下来,我们将进入真实项目流程,体验一条人物访谈作品是如何一步步完成的。',
|
||
audioSrc: getPublicPath('audio/py_3_007.MP3'),
|
||
},
|
||
]
|
||
|
||
const handleReturnGuideComplete = () => {
|
||
showReturnGuide.value = false
|
||
interview.setIsGuideInterviewReturn(false)
|
||
router.replace({ name: 'Interview' })
|
||
}
|
||
|
||
const handlePracticeGuideComplete = () => {
|
||
interview.markGuideShown(practiceGuideKey)
|
||
}
|
||
|
||
const stageGuideMap = {
|
||
2: {
|
||
flagKey: 'isGuideInterviewStage2',
|
||
setter: 'setIsGuideInterviewStage2',
|
||
steps: [
|
||
{
|
||
id: 'stage2-step1',
|
||
content: '前期准备阶段,我们已经完成了主题确认、脚本结构与镜头规划。',
|
||
audioSrc: getPublicPath('audio/py_3_010.MP3'),
|
||
},
|
||
{
|
||
id: 'stage2-step2',
|
||
content:
|
||
'进入中期拍摄后就要按照既定方案,把后期需要用的素材尽可能拍完整。',
|
||
audioSrc: getPublicPath('audio/py_3_011.MP3'),
|
||
},
|
||
],
|
||
},
|
||
3: {
|
||
flagKey: 'isGuideInterviewStage3',
|
||
setter: 'setIsGuideInterviewStage3',
|
||
steps: [
|
||
{
|
||
id: 'stage3-step1',
|
||
content: '拍摄完成啦!现在我们正式进入剪辑环节。',
|
||
audioSrc: getPublicPath('audio/py_3_012.MP3'),
|
||
},
|
||
],
|
||
},
|
||
} as const
|
||
|
||
type StageKey = keyof typeof stageGuideMap
|
||
|
||
const stageGuideStep = ref<StageKey | null>(null)
|
||
const stageGuideSteps = computed(() =>
|
||
stageGuideStep.value ? stageGuideMap[stageGuideStep.value].steps : [],
|
||
) as Ref<GuideStep[]>
|
||
|
||
const triggerStageGuide = (step: number) => {
|
||
if (step !== 2 && step !== 3) return
|
||
const stage = step as StageKey
|
||
const config = stageGuideMap[stage]
|
||
if (!(interview as any)[config.flagKey]) return
|
||
stageGuideStep.value = stage
|
||
}
|
||
|
||
const handleStageGuideComplete = () => {
|
||
if (!stageGuideStep.value) return
|
||
const config = stageGuideMap[stageGuideStep.value]
|
||
;(interview as any)[config.setter](false)
|
||
stageGuideStep.value = null
|
||
}
|
||
|
||
const visible = computed({
|
||
get: () => interview.interviewMenuVisible,
|
||
set: (value) => interview.setInterviewMenuVisible(value),
|
||
})
|
||
|
||
const currentStep = computed(() => interview.InterviewUnlockProgress)
|
||
const progress = computed(() => interview.progressPercent)
|
||
|
||
const handleComplete = () => {
|
||
// console.log('完成前置条件')
|
||
// 处理完成逻辑
|
||
router.push({ name: 'InterviewPractice' })
|
||
}
|
||
|
||
const handleStepChange = (step: number) => {
|
||
interview.setInterviewUnlockProgress(step)
|
||
visible.value = true
|
||
}
|
||
|
||
const handleWorkClick = () => {
|
||
console.log('任务取景按钮点击')
|
||
// 处理任务取景按钮点击逻辑
|
||
router.push({ name: 'InterviewWork' })
|
||
}
|
||
|
||
const handleClose = () => {
|
||
router.push({ name: 'Action' })
|
||
}
|
||
|
||
watch(
|
||
showPracticeGuide,
|
||
(val) => {
|
||
if (val) {
|
||
interview.markGuideShown(practiceGuideKey)
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
watch(
|
||
[visible, currentStep],
|
||
([isVisible, step]) => {
|
||
if (isVisible && step < 4) {
|
||
triggerStageGuide(step)
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
</script>
|
||
|
||
<template>
|
||
<div class="w-full h-full relative">
|
||
<!-- 背景图片-center -->
|
||
<div
|
||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||
:style="{ backgroundImage: getBackgroundImage('interview/访谈背景.png') }"
|
||
></div>
|
||
|
||
<!-- 关闭 -->
|
||
<div class="absolute top-[140px] left-[calc(50%)] -translate-x-1/2">
|
||
<div
|
||
class="flex items-center justify-center text-white font-[1000] [text-shadow:_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1]"
|
||
>
|
||
<span class="text-6xl">学习进度:</span>
|
||
<span class="text-7xl">{{ progress }}%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 关闭 -->
|
||
<ImgButton
|
||
class="absolute right-0 top-0 z-20"
|
||
:image="IMAGE_PATHS.COMPONENTS.CLOSE"
|
||
@click="handleClose"
|
||
/>
|
||
|
||
<!-- 背景图片 -->
|
||
<div
|
||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.VLOG.BG) }"
|
||
></div>
|
||
|
||
<!-- 标题 -->
|
||
<div class="absolute top-2 left-1/2 -translate-x-1/2">
|
||
<ImgButton :image="'interview/访谈-聆听与对话.png'" />
|
||
</div>
|
||
|
||
<!-- 任务取景按钮 -->
|
||
<div
|
||
class="absolute left-24 top-24"
|
||
:style="{
|
||
backgroundImage: getBackgroundImage(IMAGE_PATHS.VLOG.TASK_CAMERA),
|
||
}"
|
||
>
|
||
<ImgButton
|
||
:image="
|
||
currentStep > 0
|
||
? IMAGE_PATHS.VLOG.TASK_BTN_OK
|
||
: IMAGE_PATHS.VLOG.TASK_BTN
|
||
"
|
||
@click="handleWorkClick"
|
||
/>
|
||
</div>
|
||
|
||
<GuideSteps
|
||
:visible="showPracticeGuide"
|
||
highlight-selector=".right-section"
|
||
position-offset-y="50%"
|
||
@complete="handlePracticeGuideComplete"
|
||
>
|
||
<GuideStepsItem>点击【实战操作台】进入实战操作!</GuideStepsItem>
|
||
</GuideSteps>
|
||
|
||
<!-- 引导任务 -->
|
||
<ImgButton
|
||
v-if="currentStep !== 4"
|
||
v-show="!showReturnGuide"
|
||
class="absolute right-24 bottom-24"
|
||
:image="IMAGE_PATHS.VLOG.TASK_PERSON"
|
||
/>
|
||
|
||
<Overlay
|
||
content-class="w-full h-full"
|
||
:visible="visible && currentStep < 4"
|
||
>
|
||
<!-- 关闭按钮 -->
|
||
<ImgButton
|
||
class="absolute right-0 top-0 z-20"
|
||
:image="IMAGE_PATHS.COMPONENTS.CLOSE"
|
||
@click="visible = false"
|
||
/>
|
||
|
||
<!-- 菜单 -->
|
||
<InterviewMenu :current-step="currentStep" />
|
||
|
||
<!-- 进度条 -->
|
||
<div class="absolute left-0 bottom-0 w-full">
|
||
<ProgressBar
|
||
module-name="interview"
|
||
:current-step="currentStep"
|
||
:completed-step="interview.completedStep"
|
||
@step-change="handleStepChange"
|
||
/>
|
||
</div>
|
||
</Overlay>
|
||
|
||
<div class="absolute bottom-0 w-full">
|
||
<ProgressBar
|
||
module-name="interview"
|
||
:current-step="currentStep"
|
||
:completed-step="interview.completedStep"
|
||
@complete="handleComplete"
|
||
@step-change="handleStepChange"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 思路提点完成回首页的一次性引导 -->
|
||
<GuideSteps
|
||
v-if="showReturnGuide"
|
||
:steps="returnSteps"
|
||
position-offset-y="85%"
|
||
@complete="handleReturnGuideComplete"
|
||
@skip="handleReturnGuideComplete"
|
||
/>
|
||
|
||
<!-- 阶段菜单一次性引导 -->
|
||
<GuideSteps
|
||
v-if="stageGuideStep"
|
||
:steps="stageGuideSteps"
|
||
position-offset-y="85%"
|
||
@complete="handleStageGuideComplete"
|
||
@skip="handleStageGuideComplete"
|
||
/>
|
||
</div>
|
||
</template>
|