feat: 完善访谈与vlog模块页面,重组设备与图片公共资源,新增音频管理与一班对接工具

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.7 committed 2026-05-05 15:41:41 +08:00
1 parent c4c9be9d35
commit fc9633f371
159 files changed
+4823 -2535

No files matched your search

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ImgButton } from '@/components/Button'
import { Video } from '@/components'
import { IMAGE_PATHS, getPublicPath } from '@/utils'
const router = useRouter()
const playing = ref(false)
const handleBack = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden bg-black">
<Video
class="w-full h-full"
:src="getPublicPath('video/editing/yingxiao_batch.mp4')"
v-model="playing"
:muted="false"
:volume="1"
:controls="true"
:hideNativeControls="true"
/>
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="handleBack"
/>
</div>
</template>
@@ -0,0 +1,38 @@
<script lang="ts" setup>
import { IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { useRouter } from 'vue-router'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
const router = useRouter()
const back = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden">
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<img class="absolute inset-0" :src="getStaticImage('img/背景.png')" alt="" />
<img class="absolute top-5 z-10" :src="getStaticImage('img/调色-标题.png')" alt="" />
<div class="relative h-full pt-28 overflow-y-scroll">
<img :src="getStaticImage('interview/img/调色实践.png')" alt="" />
<ImgButton
class="absolute top-[1180px] left-[51.5%] -translate-x-1/2"
image="after-editing/color/调色实践"
@click="router.push({ name: 'InterviewColor' })"
/>
</div>
<GuideSteps :visible="true" position-offset-y="85%">
<GuideStepsItem>这份长图文干货满满,滚动鼠标查看完整内容吧</GuideStepsItem>
</GuideSteps>
</div>
</template>
+12 -7
View File
@@ -5,7 +5,6 @@ import { ImgButton } from '@/components/Button'
import { Overlay } from '@/components/Overlay'
import { ref } from 'vue'
import ImageFilter from '@/components/ImageFilter/ImageFilter.vue'
import { useInterviewStore } from '@/stores'
const selected = ref<number | undefined>(1)
const btnState = ref(false)
@@ -25,11 +24,11 @@ const adjustmentList = ref([
const colorBarList = ref([
{ id: 1, name: '原图', filter: 'none' },
{ id: 2, name: '暖阳', filter: 'sepia(0.3) contrast(1.1) brightness(1.1)' },
{ id: 2, name: '暖阳', filter: 'saturate(2.5) contrast(2.1)' },
{ id: 3, name: '冷调', filter: 'hue-rotate(180deg) contrast(0.9)' },
{ id: 4, name: '复古', filter: 'sepia(0.6) contrast(1.2) brightness(0.9)' },
{ id: 5, name: '黑白', filter: 'grayscale(1)' },
{ id: 6, name: '鲜艳', filter: 'saturate(1.5) contrast(1.1)' }
{ id: 6, name: '鲜艳', filter: 'sepia(0.1) saturate(2.5) contrast(2.1) brightness(1)' }
])
// 获取指定参数的值
@@ -117,10 +116,8 @@ const handleTryOther = () => {
}
const handleFinish = () => {
useInterviewStore().setInterviewUnlockProgress(4)
router.push({
name: 'Interview'
name: 'Film'
})
}
@@ -157,7 +154,7 @@ const changeColor = (item: { id: number; name: string }) => {
class="relative w-[1273px] h-[732px] bg-no-repeat bg-cover mt-10 p-2 pb-[26px] pr-[26px]"
:style="{ backgroundImage: getBackgroundImage('after-editing/color/图片框.png') }"
>
<ImageFilter ref="imageFilterRef" :src="getStaticImage('after-editing/color/图片')" />
<ImageFilter ref="imageFilterRef" :src="getStaticImage('after-editing/color/film_img')" />
<!-- 开启对比 -->
<ImgButton
@@ -258,6 +255,14 @@ const changeColor = (item: { id: number; name: string }) => {
<!-- title -->
<img class="absolute top-0 left-0" :src="getStaticImage('after-editing/color/标题-调色实践')" />
<!-- 回到顶部按钮 - 最后一页显示 -->
<p class="absolute left-40 bottom-10 flex items-center">
<img class="rotate-0 w-28 mr-4" src="@/assets/svg/deng.svg" />
<span class="text-white text-2xl font-bold">
推荐参数:亮度 90、对比度 100、饱和度 100、曝光 - 90、色调 0、锐化 100、模糊 0
</span>
</p>
<!-- 保存图片弹窗 -->
<Overlay :visible="showModal" @close="handleModalClose">
<div class="relative w-[1000px] h-[600px] flex items-center justify-center">
+71 -66
View File
@@ -1,69 +1,63 @@
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import { nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
import ImgButton from '@/components/Button/ImgButton.vue'
// import { GuideSteps } from '@/components/Guide'
import { StepNavigation } from '@/components/Guide'
import { IMAGE_PATHS } from '@/utils'
import { Video } from '@/components'
const router = useRouter()
// 导入视频文件
import videoBasic from '/video/vlog_zhizuobianjiaojing_jichujianji.mp4'
import videoSoundeffect from '/video/vlog_renwuqujing_yingxiaochuli.mp4'
import videoPackaging from '/video/vlog_renwuqujing_baozhuang.mp4'
import videoCopyright from '/video/vlog_renwuqujing_banquanguifan.mp4'
const videoList = [videoBasic, videoSoundeffect, videoPackaging, videoCopyright]
const videoRef = ref<HTMLVideoElement>()
const navigationSteps = [
{
id: 'step1',
label: '后期制作',
content: '声画剪辑流程',
type: 'post' as const
},
{
id: 'step2',
label: '后期制作',
content: 'J-cut/L-cut运用',
type: 'post' as const
},
{
id: 'step3',
label: '后期制作',
content: '【Q3】节奏剪辑技巧',
type: 'post' as const
}
]
const currentVideoIndex = ref(0)
const playing = ref(false)
const showStepNavigation = ref(false)
onMounted(() => {
// 确保视频元素加载并自动播放
if (videoRef.value) {
videoRef.value.src = videoList[currentVideoIndex.value]
videoRef.value.load()
// 尝试自动播放
const playPromise = videoRef.value.play()
if (playPromise !== undefined) {
playPromise.catch(error => {
// 自动播放失败,可能是浏览器策略阻止,需要用户交互
console.log('自动播放被阻止:', error)
})
}
// 监听视频结束事件
videoRef.value.addEventListener('ended', playNextVideo)
}
})
// 播放下一个视频
const playNextVideo = () => {
if (currentVideoIndex.value < videoList.length - 1) {
currentVideoIndex.value++
if (videoRef.value) {
videoRef.value.src = videoList[currentVideoIndex.value]
videoRef.value.load()
// 自动播放下一个视频
const playPromise = videoRef.value.play()
if (playPromise !== undefined) {
playPromise.catch(error => {
console.log('自动播放被阻止:', error)
})
}
}
if (currentVideoIndex.value >= videoList.length - 1) {
playing.value = false
return
}
currentVideoIndex.value++
nextTick(() => {
playing.value = true
})
}
// 监听当前视频索引变化
watch(currentVideoIndex, newIndex => {
if (videoRef.value) {
videoRef.value.src = videoList[newIndex]
videoRef.value.load()
}
})
const handleStepClick = (index: number) => {
currentVideoIndex.value = index
// showStepNavigation.value = false
nextTick(() => {
playing.value = true
})
}
const handleClose = () => {
router.back()
@@ -71,29 +65,40 @@ const handleClose = () => {
</script>
<template>
<div>
<!--使用动态绑定和ref-->
<video
ref="videoRef"
<div class="relative w-full h-full overflow-hidden bg-black">
<Video
:src="videoList[currentVideoIndex]"
type="video/mp4"
controls
preload="auto"
class="video-player"
autoplay
></video>
v-model="playing"
:muted="false"
:volume="1"
@ended="playNextVideo"
/>
<ImgButton
class="absolute right-0 top-0 z-10"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="handleClose"
/>
<button
class="absolute right-4 top-1/2 z-10 flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-blue-500/80 text-white transition-all duration-200 hover:bg-blue-500"
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>
<StepNavigation
:visible="showStepNavigation"
:background-image="''"
:steps="navigationSteps"
:current-step-index="currentVideoIndex"
@step-click="handleStepClick"
@close="showStepNavigation = false"
/>
</div>
</template>
<style scoped>
.video-player {
width: 100%;
height: 100%;
object-fit: contain;
}
</style>
@@ -0,0 +1,33 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ImgButton } from '@/components/Button'
import { Video } from '@/components'
import { IMAGE_PATHS, getPublicPath } from '@/utils'
const router = useRouter()
const playing = ref(false)
const handleBack = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden bg-black">
<Video
class="w-full h-full"
:src="getPublicPath('video/on-site/dygmk_zlsxhbj.mp4')"
v-model="playing"
:muted="false"
:volume="1"
:controls="true"
:hideNativeControls="true"
/>
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="handleBack"
/>
</div>
</template>
+43 -23
View File
@@ -3,7 +3,8 @@ import { getStaticImage } from '@/utils'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { getBackgroundImage } from '@/utils'
defineOptions({ name: 'VlogMenu' })
import { useInterviewStore } from '@/stores'
defineOptions({ name: 'InterviewMenu' })
/**
* 菜单项 (前期准备、中期拍摄、后期剪辑)
@@ -33,13 +34,13 @@ const menuItems = ref([
label: '资源筹备',
routeName: 'InterviewResource',
parentValue: 1,
disabled: true
disabled: false
},
{
label: '创意构想',
routeName: 'InterviewCreative',
parentValue: 1,
disabled: true
disabled: false
},
{
label: '脚本撰写',
@@ -51,32 +52,32 @@ const menuItems = ref([
label: '分镜制作',
routeName: 'InterviewStoryboard',
parentValue: 1,
disabled: true
disabled: false
},
{
label: '设备准备',
routeName: 'InterviewMidtermEquipment',
routeName: 'InterviewEquipment',
parentValue: 2,
disabled: false
},
{
label: '实地取景',
routeName: 'InterviewMidtermShooting',
routeName: 'InterviewShooting',
parentValue: 2,
disabled: false
},
{
label: '现场拍摄',
routeName: 'Interview现场拍摄',
routeName: 'InterviewOnsiteShooting',
parentValue: 2,
disabled: true
},
{
label: '素材管理',
routeName: 'Interview素材管理',
parentValue: 3,
disabled: true
disabled: false
},
// {
// label: '素材管理',
// routeName: 'Interview素材管理',
// parentValue: 3,
// disabled: true
// },
{
label: '剪辑',
routeName: 'InterviewEditing',
@@ -85,15 +86,21 @@ const menuItems = ref([
},
{
label: '调色',
routeName: 'InterviewColor',
routeName: 'InterviewColorPractice',
parentValue: 3,
disabled: false
},
{
label: '音频',
routeName: 'InterviewAudio',
parentValue: 3,
disabled: false
},
{
label: '导出发布',
routeName: 'Interview导出发布',
routeName: 'InterviewExportPublish',
parentValue: 3,
disabled: true
disabled: false
}
])
@@ -102,12 +109,19 @@ const props = defineProps<{
}>()
const router = useRouter()
const interview = useInterviewStore()
const isViewed = (routeName: string) => {
return interview.viewed?.[routeName] === true
}
const currentParentValue = computed<number>(() => {
return props.currentStep
}) // 当前选中的父级菜单项
const currentItemValue = ref<string>('') // 当前选中的子级菜单项
const currentItemValue = computed<string>(() => {
return router.currentRoute.value.name?.toString() ?? ''
})
const getMenuItems = computed(() => {
return menuItems.value.filter(item => item.parentValue === currentParentValue.value)
@@ -115,7 +129,6 @@ const getMenuItems = computed(() => {
const handleItemClick = (item: { routeName: string; disabled: boolean }) => {
if (item.disabled) return
currentItemValue.value = item.routeName
router.push({ name: item.routeName })
}
</script>
@@ -124,9 +137,9 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => {
<div class="absolute left-20 top-0 bottom-0 w-[520px] pt-28" v-if="currentStep">
<!-- 分类名称 -->
<div class="absolute top-3 left-24 flex items-center space-x-2">
<span class="text-4xl text-[#fbbf24] font-black">{{
category[currentParentValue - 1].label
}}</span>
<span class="text-4xl text-[#fbbf24] font-black">
{{ category[currentParentValue - 1].label }}
</span>
<img class="filter-blue" :src="category[currentParentValue - 1].icon" alt="" />
</div>
<!-- 背景图片 -->
@@ -150,7 +163,14 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => {
class="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 group-hover:opacity-100 transition-opacity duration-300"
:style="{ backgroundImage: getBackgroundImage('components/menu-hover.png') }"
></div>
<span :class="[!item.disabled ? 'menu-glow-text' : '']">{{ item.label }}</span>
<span class="flex items-center gap-2" :class="[!item.disabled ? 'menu-glow-text' : '']">
<span>{{ item.label }}</span>
<img
:class="['w-10', isViewed(item.routeName) ? 'brightness-90' : 'brightness-50']"
src="@/assets/images/midterm/完成.png"
alt=""
/>
</span>
</div>
</template>
</div>
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { useRouter } from 'vue-router'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
const router = useRouter()
const back = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden">
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<img class="absolute inset-0" :src="getStaticImage('img/背景.png')" alt="" />
<img class="absolute top-5 z-10" :src="getStaticImage('img/创意构想-标题.png')" alt="" />
<div class="relative h-full pt-28 overflow-y-scroll">
<img :src="getStaticImage('interview/img/创意构想.png')" alt="" />
</div>
<GuideSteps :visible="true" position-offset-y="85%">
<GuideStepsItem>这份长图文干货满满,滚动鼠标查看完整内容吧</GuideStepsItem>
</GuideSteps>
</div>
</template>
+26 -8
View File
@@ -1,8 +1,9 @@
<script lang="tsx" setup>
<script lang="tsx" name="Interview" setup>
defineOptions({ name: 'Interview' })
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
import { ImgButton } from '@/components/Button'
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { ProgressBar, Overlay } from '@/components'
import { useRouter } from 'vue-router'
import { useInterviewStore } from '@/stores'
@@ -12,7 +13,8 @@ const router = useRouter()
const interview = useInterviewStore()
const visible = ref(false)
const currentStep = ref(interview.InterviewUnlockProgress)
const currentStep = computed(() => interview.InterviewUnlockProgress)
const progress = computed(() => interview.progressPercent)
const handleComplete = () => {
// console.log('完成前置条件')
@@ -21,8 +23,7 @@ const handleComplete = () => {
}
const handleStepChange = (step: number) => {
// 处理步骤变化逻辑
currentStep.value = step
interview.setInterviewUnlockProgress(step)
visible.value = true
}
@@ -48,9 +49,19 @@ onMounted(() => {
<!-- 背景图片-center -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.VLOG.BG_CENTER) }"
:style="{ backgroundImage: getBackgroundImage('interview/访谈背景.png') }"
></div>
<!-- 关闭 -->
<div class="absolute top-[140px] left-[calc(50%)] -translate-x-1/2">
<div
class="flex items-center justify-center text-white font-[1000] [text-shadow:_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1]"
>
<span class="text-6xl">学习进度:</span>
<span class="text-7xl">{{ progress }}%</span>
</div>
</div>
<!-- 关闭 -->
<ImgButton
class="absolute right-0 top-0 z-20"
@@ -66,7 +77,7 @@ onMounted(() => {
<!-- 标题 -->
<div class="absolute top-2 left-1/2 -translate-x-1/2">
<ImgButton :image="IMAGE_PATHS.VLOG.TITLE" />
<ImgButton :image="'interview/访谈-聆听与对话.png'" />
</div>
<!-- 任务取景按钮 -->
@@ -96,13 +107,20 @@ onMounted(() => {
<!-- 进度条 -->
<div class="absolute left-0 bottom-0 w-full">
<ProgressBar :current-step="currentStep" @step-change="handleStepChange" />
<ProgressBar
module-name="interview"
:current-step="currentStep"
:completed-step="interview.completedStep"
@step-change="handleStepChange"
/>
</div>
</Overlay>
<div class="absolute bottom-0 w-full">
<ProgressBar
module-name="interview"
:current-step="currentStep"
:completed-step="interview.completedStep"
@complete="handleComplete"
@step-change="handleStepChange"
/>
@@ -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 { GuideSteps, GuideStepsItem } from '@/components/Guide'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ImgButton } from '@/components/Button'
@@ -12,38 +12,59 @@ const back = () => {
router.back()
}
const steps = [
{ id: 'step1', content: '先认识 Vlog:点击右侧「1.vlog初识」继续' },
{ id: 'step2', content: '这是主要内容区域的边框' }
{
id: 'step1',
content: '先认识 Vlog:点击右侧「1.vlog初识」继续',
highlightSelector: '.img-switch-item--vlog'
},
{
id: 'step2',
content: '这是主要内容区域的边框',
highlightSelector: '.work-border'
}
]
const stepIndex = ref(0)
const hiddenbar = ref(false)
const currentIndex = ref(0) // 当前显示的图片索引
const selectedMenuIndex = ref(0) // 当前选中的菜单项索引
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 }
])
const equipmentItems = [
{
id: 'camera',
name: '相机 / 摄像机 / 微单',
productImages: [
getStaticImage('equipment/相机产品.png'),
getStaticImage('equipment/摄像机产品.png'),
getStaticImage('equipment/微单产品.png')
],
title: getStaticImage('equipment/相机标题.png'),
text: getStaticImage('equipment/相机内容.png')
},
{
id: 'tripod',
name: '三脚架',
productImages: [getStaticImage('equipment/三角架产品.png')],
title: getStaticImage('equipment/三脚架标题.png'),
text: getStaticImage('equipment/三脚架内容.png')
},
{
id: 'light',
name: '灯光',
productImages: [
getStaticImage('equipment/led补光灯.png'),
getStaticImage('equipment/环形灯产品.png')
],
title: getStaticImage('equipment/灯标题.png'),
text: getStaticImage('equipment/灯内容.png')
}
]
const phoneList = ref([
{ phoneImg: getStaticImage('equipment/设备-手机1.png') },
{ phoneImg: getStaticImage('equipment/设备-手机2.png') }
])
const menuItems = ref(
equipmentItems.map(item => ({ id: item.id, name: item.name, disabled: false }))
)
const platetitleList = ref([
{ title: getStaticImage('equipment/标题-苹果.png') },
{ title: getStaticImage('equipment/标题-安卓小米.png') }
])
const currentEquipment = computed(() => equipmentItems[selectedMenuIndex.value])
const currentProductImages = computed(() => currentEquipment.value.productImages)
const platetextList = ref([
{ text: getStaticImage('equipment/内容-苹果.png') },
{ text: getStaticImage('equipment/内容-安卓小米.png') }
])
const hiddenbarclick = () => {
hiddenbar.value = !hiddenbar.value
}
@@ -55,11 +76,12 @@ const selectMenuItem = (index: number, item: { id: string; name: string; disable
return
}
selectedMenuIndex.value = index
currentIndex.value = 0
}
// 下一张图片
const nextPhone = () => {
if (currentIndex.value < phoneList.value.length - 1) {
if (currentIndex.value < currentProductImages.value.length - 1) {
currentIndex.value++
}
}
@@ -70,14 +92,6 @@ 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">
@@ -139,12 +153,14 @@ const currentHighlight = computed(() =>
<img src="@/assets/images/equipment/按钮-左滑.png" alt="" />
</div>
<div class="equipment-phoneImg">
<img :src="phoneList[currentIndex].phoneImg" alt="" />
<img :src="currentProductImages[currentIndex]" alt="" />
</div>
<div
class="equipment-rightbutton"
@click="nextPhone"
:style="{ visibility: currentIndex < phoneList.length - 1 ? 'visible' : 'hidden' }"
:style="{
visibility: currentIndex < currentProductImages.length - 1 ? 'visible' : 'hidden'
}"
>
<img src="@/assets/images/equipment/按钮-右滑.png" alt="" />
</div>
@@ -155,11 +171,11 @@ const currentHighlight = computed(() =>
</div>
<div class="title">
<img :src="platetitleList[currentIndex].title" alt="" />
<img :src="currentEquipment.title" alt="" />
</div>
<div class="plate-content">
<div class="text">
<img :src="platetextList[currentIndex].text" alt="" />
<img :src="currentEquipment.text" alt="" />
</div>
</div>
</div>
@@ -176,10 +192,8 @@ const currentHighlight = computed(() =>
<!-- 引导步骤 -->
<GuideSteps
:steps="steps"
:highlight-selector="currentHighlight"
:highlight-padding="12"
:highlight-radius="20"
@step-change="handleStepChange"
position-offset-y="85%"
>
<GuideStepsItem>
@@ -0,0 +1,121 @@
<script lang="ts" setup>
import { computed, nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ImgButton, Video } from '@/components'
import { StepNavigation } from '@/components/Guide'
import { IMAGE_PATHS, getPublicPath, getStaticImage } from '@/utils'
const router = useRouter()
const playing = ref(false)
const showStepNavigation = ref(false)
const currentVideoIndex = ref(0)
const navigationSteps = [
{
id: 'q1-shoot-material',
label: '中期拍摄',
content: '素材怎么拍',
type: 'shooting' as const
},
{
id: 'q1-required-shots',
label: '中期拍摄',
content: '必拍镜头有哪些',
type: 'shooting' as const
},
{
id: 'q2-moving-shot',
label: '中期拍摄',
content: '移动镜头的作用',
type: 'shooting' as const
},
{
id: 'q2-close-up',
label: '中期拍摄',
content: '特写镜头的作用',
type: 'shooting' as const
}
]
const videoList = [
getPublicPath('video/on-site/dygmk_mmps_batch.mp4'),
getPublicPath('video/on-site/dygmk_hgydjt_batch.mp4'),
getPublicPath('video/on-site/dygmk_jzxjj_batch.mp4'),
getPublicPath('video/on-site/dygmk_czsjx_batch.mp4')
]
const currentVideo = computed(() => videoList[currentVideoIndex.value])
const back = () => {
router.back()
}
const handleStepClick = (index: number) => {
currentVideoIndex.value = index
// showStepNavigation.value = false
nextTick(() => {
playing.value = true
})
}
const handleStepNavigationClose = () => {
showStepNavigation.value = false
}
const handleEnded = () => {
if (currentVideoIndex.value >= videoList.length - 1) {
playing.value = false
return
}
currentVideoIndex.value++
nextTick(() => {
playing.value = true
})
}
</script>
<template>
<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"
@ended="handleEnded"
>
<template #overlay>
<ImgButton
class="absolute right-0 top-0 z-20 pointer-events-auto"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<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>
<StepNavigation
:visible="showStepNavigation"
:background-image="getStaticImage('work/vlog背景全屏待定')"
:steps="navigationSteps"
:current-step-index="currentVideoIndex"
@step-click="handleStepClick"
@close="handleStepNavigationClose"
/>
</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 { useInterviewStore } from '@/stores'
const router = useRouter()
const start = ref(false)
@@ -90,7 +89,6 @@ const captureScene = async () => {
*/
const handleComplete = () => {
showGuide.value = false
useInterviewStore().setInterviewUnlockProgress(3)
router.push({
name: 'Interview'
})
@@ -252,7 +250,7 @@ const handleComplete = () => {
<!-- 引导步骤 -->
<GuideSteps v-if="showGuide" positionOffsetY="80%" @complete="handleComplete">
<GuideStepsItem> 实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~ </GuideStepsItem>
<GuideStepsItem>实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~</GuideStepsItem>
</GuideSteps>
</div>
</template>
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { useRouter } from 'vue-router'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
const router = useRouter()
const back = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden">
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<img class="absolute inset-0" :src="getStaticImage('img/背景.png')" alt="" />
<img class="absolute top-5 z-10" :src="getStaticImage('img/资源筹备-标题.png')" alt="" />
<div class="relative h-full pt-28 overflow-y-scroll">
<img :src="getStaticImage('interview/img/资源筹备.png')" alt="" />
</div>
<GuideSteps :visible="true" position-offset-y="85%">
<GuideStepsItem>这份长图文干货满满,滚动鼠标查看完整内容吧</GuideStepsItem>
</GuideSteps>
</div>
</template>
Whitespace-only changes.
+20 -64
View File
@@ -1,88 +1,44 @@
<script lang="ts" setup>
import { IMAGE_PATHS } from '@/utils'
import { IMAGE_PATHS, getPublicPath, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { ref } from 'vue'
import sample from './sample.vue'
import writing from './writing.vue'
import { useRouter } from 'vue-router'
import { getStaticImage } from '@/utils'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
import { Video } from '@/components'
const router = useRouter()
const writingtext = ref('撰写格式')
const hiddenbar = ref(false)
const formatClick = () => {
writingtext.value = '撰写格式'
}
const sampleanClick = () => {
writingtext.value = '样例展示'
// router.push({ name: 'sample' })
}
const hiddenbarclick = () => {
hiddenbar.value = !hiddenbar.value
}
const back = () => {
// 跳转设备准备
router.back()
}
</script>
<template>
<div class="relative w-full h-full">
<!-- 返回按钮 -->
<div class="relative w-full h-full overflow-hidden">
<ImgButton
class="absolute right-0 top-0 z-10"
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<!-- 侧边栏 -->
<div
:class="[
'absolute h-full left-0 top-0 transition-transform duration-300 z-10',
hiddenbar ? 'translate-x-[-400px]' : 'translate-x-[0px]'
]"
>
<img class="absolute inset-0 -z-10" src="@/assets/images/equipment/侧边导航栏.png" alt="" />
<img class="absolute inset-0" :src="getStaticImage('img/背景.png')" alt="" />
<!-- 脚本撰写 -->
<img class="" :src="getStaticImage(IMAGE_PATHS.VLOG.SCRIPTINGTITLE)" />
<img class="absolute top-5 z-10" :src="getStaticImage('img/脚本编写-标题.png')" alt="" />
<img
class="absolute bottom-4 left-6"
src="@/assets/images/vlog/制作变焦镜/左边导航栏/ai分镜工具.png"
alt=""
/>
<div class="relative h-full pt-28 overflow-y-scroll">
<img :src="getStaticImage('interview/img/脚本编写.png')" alt="" />
<div class="absolute left-0 top-36">
<ImgButton
:image="
writingtext == '撰写格式' ? IMAGE_PATHS.VLOG.FORMATLIANG : IMAGE_PATHS.VLOG.FORMATAN
"
@click="formatClick"
/>
<ImgButton
:image="
writingtext == '样例展示' ? IMAGE_PATHS.VLOG.SAMPLELANG : IMAGE_PATHS.VLOG.SAMPLEAN
"
@click="sampleanClick"
<div class="absolute top-[250px] left-[8%] w-[960px]">
<Video
:src="getPublicPath('video/editing/color_jianji_batch.mp4')"
playsinline
controls
:muted="false"
:volume="1"
/>
</div>
</div>
<div
:class="[
'absolute transition-transform duration-200 top-1/2 -translate-y-1/2 z-20',
hiddenbar ? ' translate-x-[-38px]' : 'absolute translate-x-[270px]'
]"
@click="hiddenbarclick()"
>
<ImgButton :image="hiddenbar ? 'equipment/按钮-展开' : 'equipment/按钮-收缩'" />
</div>
<div class="absolute w-full h-full left-0 top-0">
<writing v-if="writingtext == '撰写格式'"></writing>
<sample v-else-if="writingtext == '样例展示'"></sample>
</div>
<GuideSteps :visible="true" position-offset-y="85%">
<GuideStepsItem>这份长图文干货满满,滚动鼠标查看完整内容吧</GuideStepsItem>
</GuideSteps>
</div>
</template>
@@ -1,175 +0,0 @@
<script lang="tsx" setup>
import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils'
import { GuideSteps, GuideStepsItem, type GuideStep } from '@/components/Guide'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ImagePreview } from '@/components'
import { useScale } from '@/composables/useScale'
import { useInterviewStore } from '@/stores'
const router = useRouter()
const showDoubao = ref(false)
const interview = useInterviewStore()
const steps = [
{ id: 'step1', content: '先认识 Vlog:点击右侧「1.vlog初识」继续' },
{ id: 'step2', content: '这是主要内容区域的边框' }
]
const stepIndex = ref(1)
const back = () => {
router.back()
}
const handleStepChange = (_step: GuideStep | null, index: number) => {
stepIndex.value = index
}
const currentHighlight = computed(() =>
stepIndex.value === 0 ? '.img-switch-item--vlog' : '.work-border'
)
const clickdoubao = () => {
interview.setInterviewUnlockProgress(2)
router.push({
name: 'Interview'
})
}
function openImages(urls: string[], startIndex = 0) {
ImagePreview({
images: urls,
startIndex,
maskClosable: true,
getScaleMetrics: () => ({ scale: scale.value, offsetX: offsetX.value, offsetY: offsetY.value }),
onClose: () => {
showDoubao.value = true
}
})
}
const { scale, offsetX, offsetY } = useScale()
onMounted(() => {
openImages([getStaticImage('vlog/脚本切图/我的志愿者手记拍摄脚本.png')])
})
</script>
<template>
<div class="writing-index">
<!-- 背景图片 -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat writing-bg"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.VLOG.BIAOGEBG) }"
>
<div>
<div class="writing-title">
<img src="@/assets/images/vlog/标题栏-中间.png" alt="" class="titledibu" />
<img
src="@/assets/images/vlog/制作变焦镜/文字标题/《我的志愿者手记》分镜头脚本.png"
alt=""
class="titletext"
/>
</div>
<div class="doubao" @click="clickdoubao">
<img
src="@/assets/images/vlog/制作变焦镜/ai脚本视频占位图.png"
alt=""
class="doubao-img"
/>
</div>
<div class="shooting-return">
<img src="@/assets/images/shooting/返回.png" alt="" @click="back" />
</div>
</div>
<!-- <div v-else>
<div class="writing-title">
<img src="@/assets/images/vlog/标题栏-中间.png" alt="" class="titledibu" />
<img
src="@/assets/images/vlog/制作变焦镜/文字标题/《我的志愿者手记》分镜头脚本.png"
alt=""
class="titletext"
/>
</div>
<div class="biaogehome" @click="deletebiaoge()">
<img
src="@/assets/images/vlog/脚本切图/我的志愿者手记拍摄脚本.png"
alt=""
class="biaoge-img"
/>
<img src="@/assets/images/midterm/叉.png" alt="" class="biaoge-x" />
</div>
<div class="shooting-return">
<img src="@/assets/images/shooting/返回.png" alt="" @click="back" />
</div>
</div> -->
<!-- 引导步骤 -->
<GuideSteps
v-if="showDoubao"
:steps="steps"
:highlight-selector="currentHighlight"
:highlight-padding="12"
:highlight-radius="20"
@step-change="handleStepChange"
position-offset-y="85%"
>
<GuideStepsItem>
<img src="@/assets/images/components/引导/ai脚本工具引导.png" alt="" />
</GuideStepsItem>
</GuideSteps>
</div>
</div>
</template>
<style lang="less" scoped>
.writing-index {
width: 100%;
height: 100%;
}
.shooting-return {
position: absolute;
right: -90%;
top: 0;
width: 50%;
height: 50%;
}
.doubao .doubao-img {
width: 100%;
height: 100%;
}
.biaoge-x {
position: absolute;
top: -20px;
right: 0px;
width: 70px;
cursor: pointer;
z-index: 9999999;
}
.writing-title {
.titledibu {
position: absolute;
top: 17px;
left: 30%;
}
.titletext {
position: absolute;
top: 30px;
left: 40%;
}
}
.biaogehome {
position: absolute;
top: 165px;
left: 5%;
width: 94%;
}
.biaogehome .biaoge-img {
height: 896px !important;
width: 1800px;
display: block;
}
</style>
@@ -1,61 +0,0 @@
<template>
<div class="writing-index">
<!-- 背景图片 -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat writing-bg"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.VLOG.SCRIPTING) }"
>
<div class="geshi-title">
<img src="@/assets/images/vlog/标题栏-中间.png" alt="" class="titledibu" />
<img
src="@/assets/images/vlog/制作变焦镜/文字标题/脚本的概念和基本要素.png"
alt=""
class="titletext"
/>
</div>
<video :src="fourth" controls></video>
<!-- <div class="shooting-return">
<img src="@/assets/images/shooting/返回.png" alt="" @click="back" />
</div> -->
<!-- 引导步骤 -->
<!-- <GuideSteps :steps="steps" :highlight-selector="currentHighlight" :highlight-padding="12"
:highlight-radius="20" @step-change="handleStepChange" position-offset-y="85%" hidden:false>
<GuideStepsItem>
<img src="@/assets/images/equipment/设备准备引导1.png" alt="" />
</GuideStepsItem>
</GuideSteps> -->
</div>
</div>
</template>
<script lang="tsx" setup>
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
import fourth from '/video/vlog_renwuqujing_jiaobencehua.mp4'
</script>
<style lang="less" scoped>
.writing-index {
width: 100%;
height: 100%;
}
.geshi-title {
.titledibu {
position: absolute;
top: 17px;
left: 30%;
}
.titletext {
position: absolute;
top: 30px;
left: 45%;
}
}
.shooting-return {
position: absolute;
right: -90%;
top: 0;
width: 100%;
height: 100%;
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import { useRouter } from 'vue-router'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
const router = useRouter()
const back = () => {
router.back()
}
</script>
<template>
<div class="relative w-full h-full overflow-hidden">
<ImgButton
class="absolute right-0 top-0 z-20"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@click="back"
/>
<img class="absolute inset-0" :src="getStaticImage('img/背景.png')" alt="" />
<img class="absolute top-5 z-10" :src="getStaticImage('img/分镜制作-标题.png')" alt="" />
<div class="relative h-full pt-28 overflow-y-scroll">
<img :src="getStaticImage('interview/img/分镜制作.png')" alt="" />
</div>
<GuideSteps :visible="true" position-offset-y="85%">
<GuideStepsItem>这份长图文干货满满,滚动鼠标查看完整内容吧</GuideStepsItem>
</GuideSteps>
</div>
</template>
+8 -11
View File
@@ -99,6 +99,13 @@ const handleVideoChange = () => {
}
const handleClose = () => {
if (key.value === 'idea') {
interview.setInterviewUnlockProgress(1)
router.push({
name: 'Interview'
})
return
}
router.back()
}
@@ -119,11 +126,6 @@ const handleComplete = () => {
router.push({
name: 'InterviewWork'
})
} else {
if (interview.InterviewUnlockProgress == 0) interview.setInterviewUnlockProgress(1)
router.push({
name: 'Interview'
})
}
}
@@ -150,7 +152,6 @@ onMounted(() => {
<template>
<div class="w-full h-full relative bg-white/10" @click="handleClick">
<template v-if="key !== 'case' && key !== 'profession'">
<ImgButton class="absolute left-4 top-4 z-10" :image="image"></ImgButton>
<ImgButton
class="absolute right-0 top-0 z-10"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@@ -189,10 +190,6 @@ onMounted(() => {
@ended="onEnded"
>
<template v-slot:overlay>
<ImgButton
class="absolute left-4 top-4 z-10 pointer-events-auto"
:image="image"
></ImgButton>
<ImgButton
class="absolute right-0 top-0 z-10 pointer-events-auto"
:image="IMAGE_PATHS.COMPONENTS.BACK"
@@ -201,7 +198,7 @@ onMounted(() => {
</template>
</Video>
<div v-else-if="key === 'idea'" :src="image">
<img :src="getStaticImage('work/思路提点全屏')" alt="" />
<img :src="getStaticImage('interview/work/思路提点全屏')" alt="" />
</div>
</div>
+28 -28
View File
@@ -4,7 +4,7 @@ import { ref, computed } from 'vue'
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
import { ImgButton } from '@/components/Button'
import WorkSideSwitcher from './components/WorkSideSwitcher.vue'
import { GuideSteps, type GuideStep } from '@/components/Guide'
import { GuideSteps } from '@/components/Guide'
import { useRouter } from 'vue-router'
import { useScale } from '@/composables/useScale'
import { ImagePreview } from '@/components'
@@ -17,7 +17,6 @@ const interview = useInterviewStore()
type WorkTab = 'vlog' | 'case' | 'profession' | 'idea'
const selected = ref<WorkTab>('case')
const stepIndex = ref(0)
const workSideSwitcherRef = ref<InstanceType<typeof WorkSideSwitcher>>()
const isGuideInterviewTask = computed(() => interview.isGuideInterviewTask)
@@ -28,32 +27,37 @@ const steps = [
{
id: 'step1',
content:
'现在我们要一起走进 Vlog 的世界。简单说,Vlog 就是用视频记录生活的一种方式 —— 它像一本会动的日记,能把我们身边的小事、瞬间的心情,通过镜头变得生动又具体。'
'现在我们要一起走进 Vlog 的世界。简单说,Vlog 就是用视频记录生活的一种方式 —— 它像一本会动的日记,能把我们身边的小事、瞬间的心情,通过镜头变得生动又具体。',
highlightSelector: '.img-switch-item--case'
},
{ id: 'step2', content: '接下来,我们就通过一个具体的案例,看看 Vlog 是怎么“捕捉美好” 的吧。' }
{
id: 'step2',
content: '接下来,我们就通过一个具体的案例,看看 Vlog 是怎么“捕捉美好” 的吧。',
highlightSelector: '.work-border'
}
]
//切换图片
const sideSwitcherItems = ref([
{
key: 'vlog',
description: '记录身边的美好',
image: 'work/vlog背景全屏待定'
description: '我的职业蓝图',
image: 'interview/work/案件赏析小屏'
},
{
key: 'case',
description: '案例赏析-桂花酒酿小圆子',
image: 'work/案例背景待定'
description: '我的职业蓝图',
image: 'interview/work/案件赏析小屏'
},
{
key: 'profession',
description: '专业剖析-桂花酒酿小圆子',
image: 'work/专业剖析小屏'
description: '我的职业蓝图',
image: 'interview/work/案件赏析小屏'
},
{
key: 'idea',
description: '思路提点-桂花酒酿小圆子',
image: 'work/思路提点小屏'
description: '我的职业蓝图',
image: 'interview/work/思路提点小屏'
}
])
@@ -62,21 +66,13 @@ const currentItem = computed(() => {
return sideSwitcherItems.value.find(item => item.key === selected.value)
})
const currentHighlight = computed(() =>
stepIndex.value === 0 ? '.img-switch-item--case' : '.work-border'
)
const handleStepChange = (_step: GuideStep | null, index: number) => {
stepIndex.value = index
}
const handleSideSwitcherChange = (value: WorkTab) => {
if (selected.value === value) return
}
const handleImageClick = () => {
router.push({
name: 'FilmVideo',
name: 'InterviewVideo',
query: {
image:
workSideSwitcherRef.value?.items.find(item => item.key === selected.value)?.activeImage ||
@@ -87,7 +83,7 @@ const handleImageClick = () => {
}
const handleClose = () => {
router.push({ name: 'Film' })
router.push({ name: 'Interview' })
}
const handleWorkClick = () => {
@@ -99,7 +95,6 @@ const handleGuideComplete = () => {
}
const handleOpenGuide = () => {
stepIndex.value = 0
interview.setIsGuideInterviewTask(true)
}
@@ -120,7 +115,7 @@ function openImages(urls: string[], startIndex = 0) {
<!-- 背景图片-center -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: getBackgroundImage(IMAGE_PATHS.WORK.BG) }"
:style="{ backgroundImage: getBackgroundImage('interview/work/任务取景框背景') }"
></div>
<!-- 关闭 -->
@@ -135,6 +130,13 @@ function openImages(urls: string[], startIndex = 0) {
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="absolute left-[46%] top-[40%] -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
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) }"
@@ -142,8 +144,8 @@ function openImages(urls: string[], startIndex = 0) {
<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
>
{{ currentItem?.description }}
</span>
</div>
<img
@click="handleImageClick"
@@ -174,10 +176,8 @@ function openImages(urls: string[], startIndex = 0) {
<GuideSteps
v-if="isGuideInterviewTask"
:steps="steps"
:highlight-selector="currentHighlight"
:highlight-padding="12"
:highlight-radius="20"
@step-change="handleStepChange"
@complete="handleGuideComplete"
@skip="handleGuideComplete"
position-offset-y="85%"