Files
super_video/src/views/film/work/index.vue
T
zhongyidianandClaude Opus 4.7 6f289959be feat: 完善视频学习进度缓存
完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:05:29 +08:00

236 lines
6.7 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="tsx" name="FilmWork" setup>
defineOptions({ name: 'FilmWork' })
import { ref, computed } from 'vue'
import {
getBackgroundImage,
IMAGE_PATHS,
getStaticImage,
getPublicPath,
} from '@/utils'
import { ImgButton } from '@/components/Button'
import WorkSideSwitcher from './components/WorkSideSwitcher.vue'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
import { useRouter } from 'vue-router'
import { useScale } from '@/composables/useScale'
import { ImagePreview } from '@/components'
import { useFilmStore } from '@/stores'
const router = useRouter()
const film = useFilmStore()
type WorkTab = 'case' | 'profession' | 'idea'
const selected = computed({
get: () => film.workSelectedTab as WorkTab,
set: (value: WorkTab) => film.setWorkSelectedTab(value),
})
const stepIndex = ref(0)
const workSideSwitcherRef = ref<InstanceType<typeof WorkSideSwitcher>>()
const isGuideFilmTask = computed(() => film.isGuideFilmTask)
const isFilmUnlock = computed(() => film.isFilmUnlock)
//切换图片
const sideSwitcherItems = ref([
{
key: 'case',
name: '案例赏析',
description: '物件春秋-上海钟表文化科普馆',
image: 'film/案件赏析小屏',
},
{
key: 'profession',
name: '专业剖析',
description: '物件春秋-上海钟表文化科普馆',
image: 'film/专业剖析',
},
{
key: 'idea',
name: '思路提点',
description: '物件春秋-上海钟表文化科普馆',
image: 'film/思路提点小屏',
},
])
//当前选中的item
const currentItem = computed(() => {
return sideSwitcherItems.value.find((item) => item.key === selected.value)
})
const currentHighlight = computed(() =>
stepIndex.value === 2 ? '.work-border' : null,
)
const handleStepChange = (_: any, index: number) => {
stepIndex.value = index
}
const handleSideSwitcherChange = (value: WorkTab) => {
if (selected.value === value) return
}
const handleImageClick = () => {
const currentItem = workSideSwitcherRef.value?.items.find(
(item) => item.key === selected.value,
)
router.push({
name: 'FilmVideo',
query: {
image: currentItem?.activeImage || '',
key: selected.value,
label: currentItem?.label || '',
},
})
}
const handleClose = () => {
router.push({ name: 'Film' })
}
const handleWorkClick = () => {
if (!isFilmUnlock.value) return
openImages([getStaticImage('film/paishejiaoben')])
}
const handleGuideComplete = () => {
film.setIsGuideFilmTask(false)
}
const handleOpenGuide = () => {
stepIndex.value = 0
film.setIsGuideFilmTask(true)
}
const { scale, offsetX, offsetY } = useScale()
function openImages(urls: string[], startIndex = 0) {
ImagePreview({
images: urls,
startIndex,
maskClosable: true,
getScaleMetrics: () => ({
scale: scale.value,
offsetX: offsetX.value,
offsetY: offsetY.value,
}),
})
}
</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('film/任务取景框背景') }"
></div>
<!-- 关闭 -->
<ImgButton
class="absolute right-0 top-0"
:image="IMAGE_PATHS.COMPONENTS.CLOSE"
@click="handleClose"
/>
<!-- border图片 -->
<div
class="work-border absolute top-[140px] left-24 inset-0 bg-cover bg-center bg-no-repeat z-10 w-[1360px] h-[800px] p-10"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.WORK.BORDER) }"
>
<div class="relative w-full h-full">
<!-- 图片 -->
<img
@click="handleImageClick"
class="rounded-[62px] cursor-pointer"
:src="getStaticImage(currentItem?.image || '')"
alt=""
/>
<!-- 手 -->
<div
class="absolute left-[46%] top-[45%] -translate-x-1/2 -translate-y-1/2 z-10 animate-bounce pointer-events-none"
>
<img
class="w-40"
:src="getStaticImage('work/小手')"
alt=""
/>
<!-- <span class="text-white text-2xl font-bold ml-5">点击进入</span> -->
</div>
</div>
<div
class="flex items-center pl-16 w-[1357px] h-[265px] absolute -bottom-16 left-0 bg-cover bg-center bg-no-repeat z-20"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.WORK.TITLE) }"
>
<span
class="mt-24 text-white font-bold text-6xl [text-shadow:0_2px_4px_rgba(0,0,0,0.2),0_0_6px_rgba(0,207,255,0.9),0_0_14px_rgba(0,207,255,0.7)]"
>
{{ currentItem?.description }}
</span>
</div>
<!-- <div class="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
<div
class="w-40 h-40 pointer-events-auto rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center cursor-pointer hover:bg-black/70 transition-colors"
>
<svg
class="w-22 h-22 text-white ml-1"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M8 5v14l11-7z" />
</svg>
</div>
</div> -->
</div>
<!-- 左侧选项切换 -->
<div class="absolute right-4 top-56 z-20">
<WorkSideSwitcher
ref="workSideSwitcherRef"
v-model="selected"
@change="handleSideSwitcherChange"
:item-class="'w-[359px] h-[96px]'"
/>
</div>
<!-- 任务取景按钮 -->
<ImgButton
class="absolute right-14 bottom-10"
:image="
isFilmUnlock ? IMAGE_PATHS.WORK.TASK_BTN_OK : IMAGE_PATHS.WORK.TASK_BTN
"
@click="handleWorkClick"
/>
<!-- 引导与高亮 -->
<GuideSteps
v-if="isGuideFilmTask"
:highlight-selector="currentHighlight"
:highlight-padding="12"
:highlight-radius="20"
@step-change="handleStepChange"
@complete="handleGuideComplete"
@skip="handleGuideComplete"
position-offset-y="85%"
>
<GuideStepsItem :audio-src="getPublicPath('audio/py_4_001.MP3')">
你是否也听过这样的评价——“这个镜头,看起来很高级。”
</GuideStepsItem>
<GuideStepsItem :audio-src="getPublicPath('audio/py_4_002.MP3')">
但所谓高级感,到底来自哪里?
</GuideStepsItem>
<GuideStepsItem :audio-src="getPublicPath('audio/py_4_003.MP3')">
带着这个问题,我们一起来看看“钟表文化科普馆”的案例,看看普通画面是如何被赋予质感与故事感的。
</GuideStepsItem>
</GuideSteps>
<!-- 引导词按钮 -->
<ImgButton
class="absolute w-[120px] bottom-0 right-0 z-10"
image="components/long"
@click="handleOpenGuide"
/>
</div>
</template>