forked from zhongyidian/super_video
feat: 完善访谈与vlog模块页面,重组设备与图片公共资源,新增音频管理与一班对接工具
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c4c9be9d35
commit
fc9633f371
159 files changed
+4823
-2535
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<script lang="tsx" setup>
|
||||
import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||
import { GuideSteps, GuideStepsItem, type GuideStep } from '@/components/Guide'
|
||||
import { ref, computed } from 'vue'
|
||||
import { getBackgroundImage, getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||
import { GuideSteps } from '@/components/Guide'
|
||||
import { onBeforeUnmount, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
|
||||
@@ -11,39 +11,102 @@ const back = () => {
|
||||
// 跳转设备准备
|
||||
router.back()
|
||||
}
|
||||
const steps = [
|
||||
{ id: 'step1', content: '先认识 Vlog:点击右侧「1.vlog初识」继续' },
|
||||
{ id: 'step2', content: '这是主要内容区域的边框' }
|
||||
]
|
||||
const stepIndex = ref(0)
|
||||
const steps = ref([
|
||||
{
|
||||
id: 'step1',
|
||||
content:
|
||||
'设备准备妥当后,我们就该走进真实场景,开启实地取景的环节啦,一起去捕捉那些适合拍摄的画面吧!',
|
||||
audioSrc: getPublicPath('audio/py_2_013.MP3')
|
||||
}
|
||||
])
|
||||
|
||||
const showGuide = ref(false)
|
||||
const guideTriggered = ref(false)
|
||||
const guideTimer = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
const viewedMenuKeys = ref<Set<string>>(new Set())
|
||||
const hiddenbar = ref(false)
|
||||
const currentIndex = ref(0) // 当前显示的图片索引
|
||||
const selectedMenuIndex = ref(0) // 当前选中的菜单项索引
|
||||
|
||||
// 菜单项数据
|
||||
const menuItems = ref([
|
||||
{ id: 'phone', name: '手机', disabled: false },
|
||||
{ id: 'phone-stand', name: '手机支架', disabled: true },
|
||||
{ id: 'bluetooth-remote', name: '蓝牙遥控器', disabled: true },
|
||||
{ id: 'stabilizer', name: '稳定器', disabled: true },
|
||||
{ id: 'props', name: '常用道具', disabled: true },
|
||||
{ id: 'phone-lens', name: '手机辅助箱头', disabled: true }
|
||||
type EquipmentCategoryId = 'phone' | 'phone-stabilizer' | 'wireless-lavalier-mic'
|
||||
interface EquipmentProduct {
|
||||
img: string
|
||||
}
|
||||
interface EquipmentCategory {
|
||||
id: EquipmentCategoryId
|
||||
name: string
|
||||
disabled: boolean
|
||||
titleImg: string
|
||||
contentImg: string
|
||||
products: EquipmentProduct[]
|
||||
}
|
||||
|
||||
const categories = ref<EquipmentCategory[]>([
|
||||
{
|
||||
id: 'phone',
|
||||
name: '手机',
|
||||
disabled: false,
|
||||
titleImg: getStaticImage('equipment/标题-苹果.png'),
|
||||
contentImg: getStaticImage('equipment/内容-苹果.png'),
|
||||
products: [
|
||||
{ img: getStaticImage('equipment/设备-手机1.png') },
|
||||
{ img: getStaticImage('equipment/设备-手机2.png') }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'phone-stabilizer',
|
||||
name: '手机稳定器',
|
||||
disabled: false,
|
||||
titleImg: getStaticImage('equipment/手机稳定器标题.png'),
|
||||
contentImg: getStaticImage('equipment/手机稳定器图文.png'),
|
||||
products: [{ img: getStaticImage('equipment/手机稳定器.png') }]
|
||||
},
|
||||
{
|
||||
id: 'wireless-lavalier-mic',
|
||||
name: '无线领夹麦克风',
|
||||
disabled: false,
|
||||
titleImg: getStaticImage('equipment/无线领夹麦克风标题.png'),
|
||||
contentImg: getStaticImage('equipment/无线领夹麦克风图文.png'),
|
||||
products: [{ img: getStaticImage('equipment/无线领夹麦克风.png') }]
|
||||
}
|
||||
])
|
||||
|
||||
const phoneList = ref([
|
||||
{ phoneImg: getStaticImage('equipment/设备-手机1.png') },
|
||||
{ phoneImg: getStaticImage('equipment/设备-手机2.png') }
|
||||
])
|
||||
categories.value = categories.value.map(c => ({
|
||||
...c,
|
||||
disabled: (c.products?.length ?? 0) === 0
|
||||
}))
|
||||
|
||||
const platetitleList = ref([
|
||||
{ title: getStaticImage('equipment/标题-苹果.png') },
|
||||
{ title: getStaticImage('equipment/标题-安卓小米.png') }
|
||||
])
|
||||
// 默认选中的菜单也算作已查看
|
||||
if (categories.value[0] && !categories.value[0].disabled) {
|
||||
viewedMenuKeys.value.add(categories.value[0].id)
|
||||
}
|
||||
|
||||
const platetextList = ref([
|
||||
{ text: getStaticImage('equipment/内容-苹果.png') },
|
||||
{ text: getStaticImage('equipment/内容-安卓小米.png') }
|
||||
])
|
||||
const maybeTriggerGuide = () => {
|
||||
if (guideTriggered.value) return
|
||||
const enabledIds = categories.value.filter(c => !c.disabled).map(c => c.id)
|
||||
if (enabledIds.length === 0) return
|
||||
const allViewed = enabledIds.every(id => viewedMenuKeys.value.has(id))
|
||||
if (!allViewed) return
|
||||
guideTriggered.value = true
|
||||
guideTimer.value = setTimeout(() => {
|
||||
showGuide.value = true
|
||||
guideTimer.value = null
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (guideTimer.value) {
|
||||
clearTimeout(guideTimer.value)
|
||||
guideTimer.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const currentCategory = computed(
|
||||
() => categories.value[selectedMenuIndex.value] ?? categories.value[0]
|
||||
)
|
||||
const currentProducts = computed(() => currentCategory.value?.products ?? [])
|
||||
const currentTitleImg = computed(() => currentCategory.value?.titleImg ?? '')
|
||||
const currentContentImg = computed(() => currentCategory.value?.contentImg ?? '')
|
||||
const hiddenbarclick = () => {
|
||||
hiddenbar.value = !hiddenbar.value
|
||||
}
|
||||
@@ -51,15 +114,17 @@ const hiddenbarclick = () => {
|
||||
// 选择菜单项
|
||||
const selectMenuItem = (index: number, item: { id: string; name: string; disabled: boolean }) => {
|
||||
if (item.disabled) {
|
||||
console.log(`菜单项 "${item.name}" 已被禁用`)
|
||||
return
|
||||
}
|
||||
selectedMenuIndex.value = index
|
||||
currentIndex.value = 0
|
||||
viewedMenuKeys.value.add(item.id)
|
||||
maybeTriggerGuide()
|
||||
}
|
||||
|
||||
// 下一张图片
|
||||
const nextPhone = () => {
|
||||
if (currentIndex.value < phoneList.value.length - 1) {
|
||||
if (currentIndex.value < currentProducts.value.length - 1) {
|
||||
currentIndex.value++
|
||||
}
|
||||
}
|
||||
@@ -70,17 +135,9 @@ const prevPhone = () => {
|
||||
currentIndex.value--
|
||||
}
|
||||
}
|
||||
|
||||
const handleStepChange = (_step: GuideStep | null, index: number) => {
|
||||
stepIndex.value = index
|
||||
}
|
||||
|
||||
const currentHighlight = computed(() =>
|
||||
stepIndex.value === 0 ? '.img-switch-item--vlog' : '.work-border'
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div class="equipment-index">
|
||||
<div class="equipment-index overflow-hidden">
|
||||
<!-- 背景图片 -->
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat equipment-bg"
|
||||
@@ -93,7 +150,7 @@ const currentHighlight = computed(() =>
|
||||
<div class="equipment-item" :hidden="hiddenbar">
|
||||
<div class="menu-list">
|
||||
<div
|
||||
v-for="(item, index) in menuItems"
|
||||
v-for="(item, index) in categories"
|
||||
:key="item.id"
|
||||
class="menu-item"
|
||||
:class="{
|
||||
@@ -118,7 +175,10 @@ const currentHighlight = computed(() =>
|
||||
<div class="equipment-bt">
|
||||
<img src="@/assets/images/equipment/按钮选中-设备准备.png" alt="" />
|
||||
</div>
|
||||
<div :class="hiddenbar ? 'equipment-unfold1' : 'equipment-unfold2'" @click="hiddenbarclick()">
|
||||
<div
|
||||
:class="['cursor-pointer', hiddenbar ? 'equipment-unfold1' : 'equipment-unfold2']"
|
||||
@click="hiddenbarclick()"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
hiddenbar
|
||||
@@ -139,12 +199,12 @@ const currentHighlight = computed(() =>
|
||||
<img src="@/assets/images/equipment/按钮-左滑.png" alt="" />
|
||||
</div>
|
||||
<div class="equipment-phoneImg">
|
||||
<img :src="phoneList[currentIndex].phoneImg" alt="" />
|
||||
<img :src="currentProducts[currentIndex]?.img" alt="" />
|
||||
</div>
|
||||
<div
|
||||
class="equipment-rightbutton"
|
||||
@click="nextPhone"
|
||||
:style="{ visibility: currentIndex < phoneList.length - 1 ? 'visible' : 'hidden' }"
|
||||
:style="{ visibility: currentIndex < currentProducts.length - 1 ? 'visible' : 'hidden' }"
|
||||
>
|
||||
<img src="@/assets/images/equipment/按钮-右滑.png" alt="" />
|
||||
</div>
|
||||
@@ -155,11 +215,11 @@ const currentHighlight = computed(() =>
|
||||
</div>
|
||||
|
||||
<div class="title">
|
||||
<img :src="platetitleList[currentIndex].title" alt="" />
|
||||
<img :src="currentTitleImg" alt="" />
|
||||
</div>
|
||||
<div class="plate-content">
|
||||
<div class="text">
|
||||
<img :src="platetextList[currentIndex].text" alt="" />
|
||||
<img :src="currentContentImg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,17 +235,12 @@ const currentHighlight = computed(() =>
|
||||
|
||||
<!-- 引导步骤 -->
|
||||
<GuideSteps
|
||||
:visible="showGuide"
|
||||
:steps="steps"
|
||||
:highlight-selector="currentHighlight"
|
||||
:highlight-padding="12"
|
||||
:highlight-radius="20"
|
||||
@step-change="handleStepChange"
|
||||
position-offset-y="85%"
|
||||
>
|
||||
<GuideStepsItem>
|
||||
<img src="@/assets/images/equipment/设备准备引导1.png" alt="" />
|
||||
</GuideStepsItem>
|
||||
</GuideSteps>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -1,113 +1,135 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { GuideSteps, GuideStepsItem, StepNavigation } from '@/components/Guide'
|
||||
import { Video } from '@/components'
|
||||
import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
const playing = ref(false)
|
||||
const showStepNavigation = ref(false)
|
||||
const currentVideoIndex = ref(0)
|
||||
|
||||
const navigationSteps = [
|
||||
{
|
||||
id: 'opening-shot',
|
||||
label: '拍摄执行',
|
||||
content: '开场建立',
|
||||
type: 'shooting' as const
|
||||
},
|
||||
{
|
||||
id: 'lens-language',
|
||||
label: '拍摄执行',
|
||||
content: '镜头语言',
|
||||
type: 'shooting' as const
|
||||
},
|
||||
{
|
||||
id: 'motion-capture',
|
||||
label: '拍摄执行',
|
||||
content: '动态跟拍与抓拍',
|
||||
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 handleClose = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const goTimeLapse = () => {
|
||||
router.push({ name: 'VlogOnsiteTimeLapse' })
|
||||
const handleStepClick = (index: number) => {
|
||||
currentVideoIndex.value = index
|
||||
nextTick(() => {
|
||||
playing.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const goSlider = () => {
|
||||
router.push({ name: 'VlogOnsiteSlider' })
|
||||
const handleStepNavigationClose = () => {
|
||||
showStepNavigation.value = false
|
||||
}
|
||||
|
||||
const goSlowShutterDay = () => {
|
||||
router.push({ name: 'VlogOnsiteSlowShutterDay' })
|
||||
const handleEnded = () => {
|
||||
if (currentVideoIndex.value >= videoList.length - 1) {
|
||||
playing.value = false
|
||||
return
|
||||
}
|
||||
|
||||
currentVideoIndex.value++
|
||||
nextTick(() => {
|
||||
playing.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const goSlowShutterNight = () => {
|
||||
router.push({ name: 'VlogOnsiteSlowShutterNight' })
|
||||
}
|
||||
|
||||
const goLowAngle = () => {
|
||||
router.push({ name: 'VlogOnsiteLowAngle' })
|
||||
}
|
||||
|
||||
const goRandom = () => {
|
||||
router.push({ name: 'VlogOnsiteRandom' })
|
||||
const handleIntroGuideComplete = () => {
|
||||
nextTick(() => {
|
||||
playing.value = true
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<!-- 背景 -->
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
<div class="relative w-full h-full overflow-hidden bg-black">
|
||||
<Video
|
||||
:src="currentVideo"
|
||||
v-model="playing"
|
||||
:muted="false"
|
||||
:volume="1"
|
||||
:controls="true"
|
||||
:hideNativeControls="true"
|
||||
:anchors="[]"
|
||||
@ended="handleEnded"
|
||||
>
|
||||
<template #overlay>
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20 pointer-events-auto"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleClose"
|
||||
/>
|
||||
<button
|
||||
class="absolute right-4 top-1/2 z-20 -translate-y-1/2 w-12 h-12 rounded-full bg-blue-500/80 text-white transition-all duration-200 hover:bg-blue-500 pointer-events-auto flex items-center justify-center"
|
||||
title="资源列表"
|
||||
@click="showStepNavigation = true"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path
|
||||
d="M3 4H17M3 8H17M3 12H17M3 16H17"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
</Video>
|
||||
|
||||
<!-- 内容卡片 + 按钮 -->
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-6xl w-full bg-black/35 backdrop-blur-md rounded-3xl border border-white/15 p-10 space-y-8 text-white pointer-events-auto"
|
||||
>
|
||||
<div class="flex items-baseline justify-between gap-6">
|
||||
<h1 class="text-5xl font-extrabold tracking-wide">现场拍摄</h1>
|
||||
<span class="text-xl opacity-80">从不同维度练习你的镜头语言</span>
|
||||
</div>
|
||||
<p class="text-xl leading-8 opacity-90">
|
||||
选择下面任意一个练习模块,进入对应的现场拍摄场景,按提示完成小任务,逐步建立自己的拍摄手感和创作节奏。
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<button type="button" class="onsite-btn" @click="goTimeLapse">
|
||||
<span class="onsite-btn-title">延时拍摄</span>
|
||||
<span class="onsite-btn-desc">记录时间流动,让平凡场景变得有节奏、有变化。</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="onsite-btn" @click="goSlider">
|
||||
<span class="onsite-btn-title">滑轨拍摄</span>
|
||||
<span class="onsite-btn-desc">通过平稳移动镜头,营造空间感和叙事推进感。</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="onsite-btn" @click="goSlowShutterDay">
|
||||
<span class="onsite-btn-title">慢门拍摄 · 白天</span>
|
||||
<span class="onsite-btn-desc">在白天环境下练习慢门,营造水流、车流等运动轨迹。</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="onsite-btn" @click="goSlowShutterNight">
|
||||
<span class="onsite-btn-title">慢门拍摄 · 夜晚</span>
|
||||
<span class="onsite-btn-desc">利用城市灯光,拍出具有节奏感和氛围感的夜景画面。</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="onsite-btn" @click="goLowAngle">
|
||||
<span class="onsite-btn-title">低角度拍摄</span>
|
||||
<span class="onsite-btn-desc">从低视角观察世界,突出主体气势,改变日常视角。</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="onsite-btn" @click="goRandom">
|
||||
<span class="onsite-btn-title">创作随机性</span>
|
||||
<span class="onsite-btn-desc">跟随直觉自由取景,捕捉那些意料之外的画面惊喜。</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关闭 -->
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleClose"
|
||||
<StepNavigation
|
||||
:visible="showStepNavigation"
|
||||
:background-image="getStaticImage('work/vlog背景全屏待定')"
|
||||
:steps="navigationSteps"
|
||||
:current-step-index="currentVideoIndex"
|
||||
@step-click="handleStepClick"
|
||||
@close="handleStepNavigationClose"
|
||||
/>
|
||||
|
||||
<GuideSteps position-offset-y="85%" @complete="handleIntroGuideComplete">
|
||||
<GuideStepsItem :audio-src="getPublicPath('audio/py_2_015.MP3')">
|
||||
接下来,我们进入最核心的实拍环节。看看一条真实 Vlog,是如何一步步被拍出来的。
|
||||
</GuideStepsItem>
|
||||
</GuideSteps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.onsite-btn {
|
||||
@apply relative flex flex-col items-start justify-between rounded-2xl px-6 py-5 text-left bg-white/5 hover:bg-white/10 border border-white/10 hover:border-white/30 transition-all duration-200 cursor-pointer;
|
||||
}
|
||||
|
||||
.onsite-btn-title {
|
||||
@apply text-2xl font-bold mb-2;
|
||||
}
|
||||
|
||||
.onsite-btn-desc {
|
||||
@apply text-base leading-7 opacity-80;
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-5xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-10 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<h1 class="text-4xl font-extrabold tracking-wide">低角度拍摄</h1>
|
||||
<p class="text-lg leading-8 opacity-90">
|
||||
尝试把镜头放低,接近地面或桌面,从下往上看主体,观察画面气势和空间的变化。
|
||||
</p>
|
||||
<p class="text-base leading-7 opacity-80">
|
||||
你可以拍摄人物、建筑或生活小物件,注意前景、主体与背景的组合,让画面更具故事感和张力。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-5xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-10 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<h1 class="text-4xl font-extrabold tracking-wide">创作随机性</h1>
|
||||
<p class="text-lg leading-8 opacity-90">
|
||||
不预设太多规则,带着手机走一圈,只要看到有感觉的光线、人物或场景,就果断按下录制键。
|
||||
</p>
|
||||
<p class="text-base leading-7 opacity-80">
|
||||
可以从“声音”“光线”“动作”这三个维度去寻找灵感,把这些零散片段记录下来,之后再在剪辑中重新组合,形成属于你的故事。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-5xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-10 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<h1 class="text-4xl font-extrabold tracking-wide">滑轨拍摄</h1>
|
||||
<p class="text-lg leading-8 opacity-90">
|
||||
使用滑轨或模拟滑轨移动(例如沿桌面平移手机),练习横向、前后、环绕等不同方向的移动镜头。
|
||||
</p>
|
||||
<p class="text-base leading-7 opacity-80">
|
||||
重点关注移动速度的稳定性,以及前景、主体和背景在画面中的关系,尝试拍出有空间层次感的镜头。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-5xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-10 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<h1 class="text-4xl font-extrabold tracking-wide">慢门拍摄 · 白天</h1>
|
||||
<p class="text-lg leading-8 opacity-90">
|
||||
在白天选择有连续运动元素的场景(如喷泉、水流、人群、车流等),尝试使用慢门制造“运动模糊”的效果。
|
||||
</p>
|
||||
<p class="text-base leading-7 opacity-80">
|
||||
建议:固定机位,尽量降低感光度,必要时使用减光配件;注意避免画面过曝,同时保留主体的清晰度与背景的动感。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center px-16 pointer-events-none">
|
||||
<div
|
||||
class="max-w-5xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-10 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<h1 class="text-4xl font-extrabold tracking-wide">慢门拍摄 · 夜晚</h1>
|
||||
<p class="text-lg leading-8 opacity-90">
|
||||
在夜晚的城市路口、桥上或高处,尝试记录车流灯轨、行人轨迹等富有节奏感的光线运动。
|
||||
</p>
|
||||
<p class="text-base leading-7 opacity-80">
|
||||
建议:寻找安全稳定的拍摄点,将设备固定好,控制好曝光时间与
|
||||
ISO,避免过度抖动,让光轨清晰流畅。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,157 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { Video, QuestionQuiz, type Question } from '@/components'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 延时拍摄视频 URL(占位,后续可替换)
|
||||
const timeLapseVideoUrl =
|
||||
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
|
||||
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
// 交互一 & 交互二:选择题
|
||||
const questions = ref<Question[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: '交互一|观察判断(引导问题)',
|
||||
description:
|
||||
'画面状态:展示一张「江边建筑 + 天空」的静态画面截图(不播放视频)。如果你只用一条普通视频来拍这个场景,你觉得画面最难被感知的是什么?',
|
||||
options: [
|
||||
{ id: 'a', label: '建筑的外形结构' },
|
||||
{ id: 'b', label: '天空的颜色' },
|
||||
{ id: 'c', label: '时间的变化' },
|
||||
{ id: 'd', label: '画面的清晰度' }
|
||||
],
|
||||
correctOptionId: 'c',
|
||||
mediaSrc: 'shooting/延时场景静帧.png' // 占位:请按需替换为真实图片
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '交互二|方法选择(技术判断)',
|
||||
description:
|
||||
'画面状态:同一场景的短视频首帧(仍未播放)。如果希望在很短时间内,让观众明显感知"时间正在流动",你会选择哪种拍摄方式?',
|
||||
options: [
|
||||
{ id: 'a', label: '手持拍摄' },
|
||||
{ id: 'b', label: '慢动作拍摄' },
|
||||
{ id: 'c', label: '延时拍摄' },
|
||||
{ id: 'd', label: '推拉镜头' }
|
||||
],
|
||||
correctOptionId: 'c',
|
||||
mediaSrc: 'shooting/延时场景静帧.png' // 占位:请按需替换为真实图片
|
||||
}
|
||||
])
|
||||
|
||||
const showObserveStep = ref(false)
|
||||
const showSummary = ref(false) // 是否显示交互四总结
|
||||
const videoPlaying = ref(false) // 视频播放状态
|
||||
|
||||
// 答题完成,进入交互三(视频)
|
||||
const handleQuestionsComplete = () => {
|
||||
showObserveStep.value = true
|
||||
showSummary.value = false // 先显示交互三(视频)
|
||||
}
|
||||
|
||||
// 视频播放完成,进入交互四总结
|
||||
const handleVideoEnded = () => {
|
||||
showSummary.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<!-- 背景 -->
|
||||
<div
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.SHOOTING.BG) }"
|
||||
></div>
|
||||
|
||||
<!-- 交互一 & 交互二:选择题 -->
|
||||
<QuestionQuiz
|
||||
v-if="!showObserveStep"
|
||||
:questions="questions"
|
||||
@complete="handleQuestionsComplete"
|
||||
/>
|
||||
<!-- 交互三:观察型 - 播放视频 -->
|
||||
<div
|
||||
v-if="showObserveStep && !showSummary"
|
||||
class="absolute inset-0 flex items-center justify-center px-20 pointer-events-none"
|
||||
>
|
||||
<div
|
||||
class="max-w-7xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-12 space-y-8 text-white pointer-events-auto"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold mb-4">交互三|观察型(无选项)</h2>
|
||||
<p class="text-xl leading-7 opacity-90 mb-5">
|
||||
播放延时拍摄视频(延时拍摄 .mp4)。在这个延时画面中,请注意天空变化与光影移动的节奏 ——
|
||||
它们在短时间内被连续表现出来,这就是延时拍摄让「时间流动」变得可见的关键。
|
||||
</p>
|
||||
<div class="rounded-2xl overflow-hidden border border-white/20 bg-black/60 min-h-[500px]">
|
||||
<Video
|
||||
:src="timeLapseVideoUrl"
|
||||
v-model="videoPlaying"
|
||||
:muted="false"
|
||||
:volume="1"
|
||||
:controls="true"
|
||||
:hideNativeControls="true"
|
||||
fit="contain"
|
||||
@ended="handleVideoEnded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交互四:总结 - 显示文本内容 -->
|
||||
<div
|
||||
v-if="showObserveStep && showSummary"
|
||||
class="absolute inset-0 flex items-center justify-center px-20 pointer-events-none"
|
||||
>
|
||||
<div
|
||||
class="max-w-6xl w-full bg-black/40 backdrop-blur-md rounded-3xl border border-white/20 p-12 space-y-6 text-white pointer-events-auto"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold mb-5">交互四|总结</h2>
|
||||
|
||||
<!-- 总结文本内容 -->
|
||||
<div class="space-y-3">
|
||||
<p class="text-xl leading-7 opacity-90">
|
||||
<span class="px-1 rounded">延时拍摄的主要目的</span>,<span class="px-1 rounded"
|
||||
>是交代时间流逝与环境状态</span
|
||||
>。
|
||||
</p>
|
||||
<p class="text-xl leading-7 opacity-90">
|
||||
<span class="px-1 rounded">这里的拍摄对象是江边的建筑群与天空</span>。<span
|
||||
class="px-1 rounded"
|
||||
>通过固定机位的连续拍摄</span
|
||||
>,<span class="px-1 rounded">将原本需要较长时间才能观察到的</span
|
||||
><span class="px-1 rounded">天空变化和光影移动</span>,<span class="px-1 rounded"
|
||||
>压缩到一个较短的镜头中呈现</span
|
||||
>。
|
||||
</p>
|
||||
<p class="text-xl leading-7 opacity-90">
|
||||
<span class="px-1 rounded">这种处理方式</span>,<span class="px-1 rounded"
|
||||
>非常适合放置在视频片头阶段</span
|
||||
>,<span class="px-1 rounded">用于快速建立时间感与空间环境</span>,<span
|
||||
class="px-1 rounded"
|
||||
>而不会占用过多时长</span
|
||||
>。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返回按钮 -->
|
||||
<ImgButton
|
||||
class="absolute right-0 top-0 z-20"
|
||||
:image="IMAGE_PATHS.COMPONENTS.BACK"
|
||||
@click="handleBack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,7 +9,6 @@ import StartOverlay from '../components/StartOverlay.vue'
|
||||
import LoadingOverlay from '@/components/Loading/LoadingOverlay.vue'
|
||||
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useVlogStore } from '@/stores'
|
||||
|
||||
const router = useRouter()
|
||||
const start = ref(false)
|
||||
@@ -114,7 +113,6 @@ const captureScene = async (): Promise<void> => {
|
||||
*/
|
||||
const handleComplete = (): void => {
|
||||
showGuide.value = false
|
||||
useVlogStore().setVlogUnlockProgress(3)
|
||||
router.push({
|
||||
name: 'Vlog'
|
||||
})
|
||||
@@ -283,7 +281,7 @@ const handleComplete = (): void => {
|
||||
|
||||
<!-- 引导步骤 -->
|
||||
<GuideSteps v-if="showGuide" positionOffsetY="80%" @complete="handleComplete">
|
||||
<GuideStepsItem> 实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~ </GuideStepsItem>
|
||||
<GuideStepsItem>实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~</GuideStepsItem>
|
||||
</GuideSteps>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'
|
||||
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
|
||||
import { Overlay } from '@/components/Overlay'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
||||
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'
|
||||
@@ -102,18 +102,24 @@ const back = () => {
|
||||
<ImgButton image="shooting/按钮-取景地B.png" @click="handleClickB" />
|
||||
</div>
|
||||
<div class="shooting-composition">
|
||||
<ImgButton image="shooting/构图指南.png" @click="showModal = true" />
|
||||
<ImgButton
|
||||
class="shooting-composition-btn"
|
||||
image="shooting/构图指南.png"
|
||||
@click="showModal = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 引导步骤 -->
|
||||
<GuideSteps
|
||||
:highlight-selector="'.mybutton'"
|
||||
:highlight-selector="'.shooting-composition-btn'"
|
||||
:highlight-padding="12"
|
||||
:highlight-radius="20"
|
||||
position-offset-y="85%"
|
||||
position-offset-y="60%"
|
||||
>
|
||||
<GuideStepsItem> 在走进拍摄场景之前,我们先来查看一下构图指南。 </GuideStepsItem>
|
||||
<GuideStepsItem :audio-src="getPublicPath('audio/py_2_014.MP3')">
|
||||
在走进拍摄场景之前,我们先来回顾一下三种常见的构图方法。
|
||||
</GuideStepsItem>
|
||||
</GuideSteps>
|
||||
|
||||
<!-- 保存图片弹窗 -->
|
||||
|
||||
Reference in new issue
Block a user