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-08 09:44:59 +08:00
1 parent fc9633f371
commit 6f8252472f
114 files changed
+2758 -1798

No files matched your search

@@ -5,40 +5,45 @@ import { ImgButton } from '@/components/Button'
import { GuideSteps, GuideStepsItem, StepNavigation } from '@/components/Guide'
import { Video } from '@/components'
import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
import { useVlogStore } from '@/stores/modules/vlog'
const router = useRouter()
const vlogStore = useVlogStore()
const guideKey = 'vlog-onsite-intro'
const showIntroGuide = ref(!vlogStore.guideShown[guideKey])
const playing = ref(false)
if (showIntroGuide.value) {
vlogStore.markGuideShown(guideKey)
}
const showStepNavigation = ref(false)
const currentVideoIndex = ref(0)
const navigationSteps = [
const navigationSteps = ref([
{
id: 'opening-shot',
label: '拍摄执行',
content: '开场建立',
videoUrl: getPublicPath('video/vlog/on-site/Vlogmk_kcjl_batch.mp4'),
type: 'shooting' as const
},
{
id: 'lens-language',
label: '拍摄执行',
content: '镜头语言',
videoUrl: getPublicPath('video/vlog/on-site/Vlogmk_jtyy_batch.mp4'),
type: 'shooting' as const
},
{
id: 'motion-capture',
label: '拍摄执行',
content: '动态跟拍与抓拍',
videoUrl: getPublicPath('video/vlog/on-site/Vlogmk_dtgpyzp_batch.mp4'),
type: 'shooting' as const
}
]
])
const videoList = [
getPublicPath('video/vlog_renwuqujing_huamiangoutu.mp4'),
getPublicPath('video/vlog_renwuqujing_jichujingbieyunyong.mp4'),
getPublicPath('video/vlog_renwuqujing_zhuapai.mp4')
]
const currentVideo = computed(() => videoList[currentVideoIndex.value])
const currentVideo = computed(() => navigationSteps.value[currentVideoIndex.value].videoUrl)
const handleClose = () => {
router.back()
@@ -56,7 +61,7 @@ const handleStepNavigationClose = () => {
}
const handleEnded = () => {
if (currentVideoIndex.value >= videoList.length - 1) {
if (currentVideoIndex.value >= navigationSteps.value.length - 1) {
playing.value = false
return
}
@@ -118,7 +123,7 @@ const handleIntroGuideComplete = () => {
@close="handleStepNavigationClose"
/>
<GuideSteps position-offset-y="85%" @complete="handleIntroGuideComplete">
<GuideSteps v-if="showIntroGuide" position-offset-y="85%" @complete="handleIntroGuideComplete">
<GuideStepsItem :audio-src="getPublicPath('audio/py_2_015.MP3')">
接下来,我们进入最核心的实拍环节。看看一条真实 Vlog,是如何一步步被拍出来的。
</GuideStepsItem>
@@ -156,9 +156,9 @@ const capturePhoto = async () => {
<div class="w-full h-full">
<div
class="relative w-full h-full bg-cover bg-center bg-no-repeat shooting-bg"
:style="{ backgroundImage: getBackgroundImage('shooting/取景地模糊背景.png') }"
:style="{ backgroundImage: getBackgroundImage('vlog/shooting/热带植物馆-模糊.png') }"
>
<ImgButton image="shooting/标题-取景地a.png" class="title-button absolute left-0" />
<ImgButton image="vlog/shooting/热带植物馆侧边.png" class="title-button absolute left-0" />
<ImgButton image="shooting/返回.png" class="back-button absolute right-0" @click="back" />
<p
@@ -192,7 +192,7 @@ const capturePhoto = async () => {
'cursor-grabbing': isDragging
}"
:style="{ transform: getTransform() }"
src="@/assets/images/shooting/取景大图.png"
src="@/assets/images/vlog/shooting/热带植物馆-清晰.png"
alt=""
crossorigin="anonymous"
@mousedown="startDrag"
@@ -125,12 +125,12 @@ const handleComplete = (): void => {
class="relative w-full h-full bg-cover bg-center bg-no-repeat shooting-bg"
:style="{
backgroundImage: start
? getBackgroundImage('shooting/取景大图.png')
: getBackgroundImage('shooting/取景地模糊背景.png')
? getBackgroundImage('vlog/shooting/沉水栈道-清晰.png')
: getBackgroundImage('vlog/shooting/沉水栈道-模糊.png')
}"
>
<!-- 标题 -->
<ImgButton image="shooting/标题-取景地b.png" class="title-button absolute left-0" />
<ImgButton image="vlog/shooting/沉水栈道侧边.png" class="title-button absolute left-0" />
<!-- 返回按钮 -->
<ImgButton
@@ -154,7 +154,7 @@ const handleComplete = (): void => {
<img
ref="backgroundImage"
class="absolute inset-0 w-full h-full object-cover pointer-events-none opacity-0"
src="@/assets/images/shooting/取景大图.png"
src="@/assets/images/vlog/shooting/沉水栈道-清晰.png"
alt=""
crossorigin="anonymous"
/>
@@ -4,10 +4,12 @@ import { useRouter } from 'vue-router'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
import { Overlay } from '@/components/Overlay'
import { ImgButton } from '@/components/Button'
import { Video } from '@/components/Video'
import { getBackgroundImage, getPublicPath, IMAGE_PATHS } from '@/utils'
import shootingtitle from '@/assets/images/shooting/标题-实地取景.png'
import videoSrc from '/video/xinshou_jiuzhonggoutufa.mp4'
import KnowledgeCard from '@/components/KnowledgeCard/KnowledgeCard.vue'
import { useVlogStore } from '@/stores/modules/vlog'
type Btn = {
key: string
@@ -18,6 +20,13 @@ type Btn = {
}
const router = useRouter()
const vlogStore = useVlogStore()
const guideKey = 'vlog-shooting-intro'
const showIntroGuide = ref(!vlogStore.guideShown[guideKey])
if (showIntroGuide.value) {
vlogStore.markGuideShown(guideKey)
}
const showModal = ref(false)
@@ -87,7 +96,7 @@ const back = () => {
<!-- 背景图片 -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat shooting-bg"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
:style="{ backgroundImage: getBackgroundImage('vlog/shooting/实地取景底图') }"
>
<div class="shooting-title">
<img :src="shootingtitle" alt="" />
@@ -95,23 +104,20 @@ const back = () => {
<div class="shooting-return">
<ImgButton image="shooting/返回.png" @click="back" />
</div>
<div class="shooting-left">
<ImgButton image="shooting/按钮-取景地A.png" @click="handleClickA" />
<div class="absolute left-[12%] top-1/2 -translate-y-[calc(50%-70px)]">
<ImgButton image="vlog/shooting/热带植物馆-入口.png" @click="handleClickA" />
</div>
<div class="shooting-right">
<ImgButton image="shooting/按钮-取景地B.png" @click="handleClickB" />
<div class="absolute right-[12%] top-1/2 -translate-y-[calc(50%-70px)]">
<ImgButton image="vlog/shooting/沉水栈道-入口.png" @click="handleClickB" />
</div>
<div class="shooting-composition">
<ImgButton
class="shooting-composition-btn"
image="shooting/构图指南.png"
@click="showModal = true"
/>
<div class="shooting-composition btn-1">
<ImgButton image="shooting/构图指南.png" @click="showModal = true" />
</div>
</div>
<!-- 引导步骤 -->
<GuideSteps
v-if="showIntroGuide"
:highlight-selector="'.shooting-composition-btn'"
:highlight-padding="12"
:highlight-radius="20"
@@ -170,7 +176,7 @@ const back = () => {
/>
<!-- 视频 -->
<Overlay :visible="showModal3" :clickable="false">
<video :src="videoSrc" controls></video>
<Video :src="videoSrc" controls :volume="1" :muted="false"></Video>
<!-- 返回 -->
<ImgButton
class="absolute right-0 top-0 z-10"