forked from zhongyidian/super_video
完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
236 lines
6.7 KiB
Vue
236 lines
6.7 KiB
Vue
<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>
|