feat: 完善视频学习进度缓存

完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.7 committed 2026-05-11 09:05:29 +08:00
1 parent 1f8723aabd
commit 6f289959be
63 files changed
+3139 -1126

No files matched your search

@@ -1,9 +1,10 @@
<script lang="ts" setup>
import { computed, nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
import { onBeforeRouteLeave, useRouter } from 'vue-router'
import ImgButton from '@/components/Button/ImgButton.vue'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
import StepNavigation from '@/components/Guide/StepNavigation.vue'
import UnfinishedLearningPrompt from '@/components/UnfinishedLearningPrompt'
import { Video } from '@/components'
import QuestionQuiz, {
type Question,
@@ -79,11 +80,35 @@ const navigationSteps = ref(rawSteps)
// 步骤导航相关状态
const showStepNavigation = ref(false)
const showUnfinishedPrompt = ref(false)
const pendingLeavePath = ref<string | null>(null)
const allowRouteLeave = ref(false)
const currentNavigationStep = ref(0) // 对应视频索引,从0开始
// 视频列表
const currentVideoIndex = ref(0)
const getLearningVideoKey = (index: number) =>
`basic-shooting-skills:${navigationSteps.value[index]?.id ?? index}`
const completedNavigationSteps = computed(() =>
navigationSteps.value.map(
(_, index) =>
basicElementsStore.learningVideosWatched[getLearningVideoKey(index)] ===
true,
),
)
const hasUnfinishedLearningVideos = computed(() =>
completedNavigationSteps.value.some((completed) => !completed),
)
const markCurrentLearningVideoWatched = () => {
basicElementsStore.markLearningVideoWatched(
getLearningVideoKey(currentVideoIndex.value),
)
}
//按顺序播放
const currentVideo = computed(
() => navigationSteps.value[currentVideoIndex.value].videoUrl,
@@ -101,7 +126,7 @@ const getQuestionsForVideo = (index: number): Question[] => {
id: 1,
title: '',
description:
'(单选)如果你想让观众“看清环境、理解空间关系”,更适合用哪种镜头?',
'【单选题】如果你想让观众“看清环境、理解空间关系”,更适合用哪种镜头?',
options: [
{
id: 'a',
@@ -129,7 +154,7 @@ const getQuestionsForVideo = (index: number): Question[] => {
id: 2,
title: '',
description:
'(多选题)下面这些拍摄场景中,哪些更适合使用"慢动作"来表现?',
'【多选题】下面这些拍摄场景中,哪些更适合使用"慢动作"来表现?',
options: [
{
id: 'a',
@@ -163,7 +188,8 @@ const getQuestionsForVideo = (index: number): Question[] => {
{
id: 3,
title: '',
description: '(单选)你觉得下面哪句话,更接近“新手拍视频的正确思路”?',
description:
'【单选题】你觉得下面哪句话,更接近“新手拍视频的正确思路”?',
options: [
{
id: 'a',
@@ -209,6 +235,8 @@ const handleClose = () => {
const handlePlay = () => {}
const onEnded = () => {
markCurrentLearningVideoWatched()
// 在指定视频结束时弹出答题
if (
currentVideoIndex.value === 1 ||
@@ -250,6 +278,25 @@ const handleQuestionsComplete = () => {
// 答题完成后继续播放下一个视频
handleVideoChange()
}
const handleContinueLearning = () => {
showUnfinishedPrompt.value = false
pendingLeavePath.value = null
}
const handleConfirmLeave = () => {
if (!pendingLeavePath.value) return
allowRouteLeave.value = true
showUnfinishedPrompt.value = false
router.push(pendingLeavePath.value)
}
onBeforeRouteLeave((to) => {
if (allowRouteLeave.value || !hasUnfinishedLearningVideos.value) return true
pendingLeavePath.value = to.fullPath
showUnfinishedPrompt.value = true
return false
})
</script>
<template>
@@ -313,10 +360,17 @@ const handleQuestionsComplete = () => {
:background-image="getStaticImage('work/vlog背景全屏待定')"
:steps="navigationSteps"
:current-step-index="currentNavigationStep"
:completed-steps="completedNavigationSteps"
@step-click="handleStepClick"
@close="handleStepNavigationClose"
/>
<UnfinishedLearningPrompt
:visible="showUnfinishedPrompt"
@continue="handleContinueLearning"
@leave="handleConfirmLeave"
/>
<GuideSteps
:visible="showIntroGuide"
position-offset-y="85%"
@@ -326,7 +380,7 @@ const handleQuestionsComplete = () => {
欢迎来到基础拍摄训练区!
</GuideStepsItem>
<GuideStepsItem :audio-src="getPublicPath('audio/py_1_019.MP3')">
我会带你依次认识手机摄像头、看懂景别、用好慢动作与延时。
我会带你认识手机摄像头、看懂景别、了解慢动作与延迟摄影。
</GuideStepsItem>
<GuideStepsItem :audio-src="getPublicPath('audio/py_1_020.MP3')">
还会教你拍出更稳的微距和更亮的夜景。