forked from zhongyidian/super_video
feat: 对接各个视频数据
This commit is contained in:
1 parent
6103453e32
commit
6ab98c4764
339 files changed
+17316
-1531
No files matched your search
@@ -0,0 +1,258 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { getBackgroundImage } from '@/utils'
|
||||
import { ImgButton } from '@/components/Button'
|
||||
import { Overlay } from '@/components/Overlay'
|
||||
import { useViewfinderB } from './useViewfinderB'
|
||||
import { useCanvasCapture } from '../composables/useCanvasCapture'
|
||||
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)
|
||||
|
||||
// 基础逻辑
|
||||
const back = () => router.back()
|
||||
|
||||
// 截图功能
|
||||
const { isLoading, showModal, capturedImageUrl, capture } = useCanvasCapture()
|
||||
|
||||
const handleModalClose = () => {
|
||||
showModal.value = false
|
||||
}
|
||||
|
||||
// 自由取景框模式
|
||||
const { viewfinderContainer, viewfinderBox, viewfinder, startDrag, startResize } = useViewfinderB()
|
||||
|
||||
const showGuide = ref(false)
|
||||
const backgroundImage = ref<HTMLImageElement | null>(null)
|
||||
|
||||
// Canvas 截图配置常量
|
||||
const UI_UPDATE_DELAY = 50 // UI 更新延迟(ms)
|
||||
const CANVAS_SCALE = 2 // Canvas 缩放比例(2倍高清)
|
||||
const GUIDE_SHOW_DELAY = 1000 // 引导显示延迟(ms)
|
||||
|
||||
/**
|
||||
* 截取场景
|
||||
* 使用 Canvas 手动绘制,确保移动端兼容性
|
||||
*/
|
||||
/**
|
||||
* 截取场景
|
||||
* 使用 Canvas 手动绘制,确保移动端兼容性
|
||||
*/
|
||||
const captureScene = async () => {
|
||||
if (isLoading.value) return
|
||||
|
||||
await capture({
|
||||
width: viewfinder.value.width,
|
||||
height: viewfinder.value.height,
|
||||
scale: CANVAS_SCALE,
|
||||
delay: UI_UPDATE_DELAY,
|
||||
onDraw: ctx => {
|
||||
if (!backgroundImage.value) {
|
||||
throw new Error('Background image not found')
|
||||
}
|
||||
|
||||
// 计算图片实际尺寸和设计稿尺寸的缩放比例
|
||||
// viewfinder 的坐标是基于设计稿尺寸(1920x1080)
|
||||
// 但 backgroundImage 的实际尺寸可能不同
|
||||
const imageScaleX = backgroundImage.value.naturalWidth / 1920
|
||||
const imageScaleY = backgroundImage.value.naturalHeight / 1080
|
||||
|
||||
// 绘制背景图片的裁剪区域
|
||||
// 将设计稿坐标转换为图片实际坐标
|
||||
ctx.drawImage(
|
||||
backgroundImage.value,
|
||||
viewfinder.value.x * imageScaleX, // 源图 x(转换为实际坐标)
|
||||
viewfinder.value.y * imageScaleY, // 源图 y(转换为实际坐标)
|
||||
viewfinder.value.width * imageScaleX, // 源图 width(转换为实际坐标)
|
||||
viewfinder.value.height * imageScaleY, // 源图 height(转换为实际坐标)
|
||||
0, // 目标 x
|
||||
0, // 目标 y
|
||||
viewfinder.value.width, // 目标 width
|
||||
viewfinder.value.height // 目标 height
|
||||
)
|
||||
},
|
||||
onComplete: () => {
|
||||
// 延迟显示引导
|
||||
setTimeout(() => {
|
||||
showGuide.value = true
|
||||
}, GUIDE_SHOW_DELAY)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成引导,跳转到 Vlog 页面
|
||||
*/
|
||||
const handleComplete = () => {
|
||||
showGuide.value = false
|
||||
useInterviewStore().setInterviewUnlockProgress(3)
|
||||
router.push({
|
||||
name: 'Interview'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full">
|
||||
<div
|
||||
class="relative w-full h-full bg-cover bg-center bg-no-repeat shooting-bg"
|
||||
:style="{
|
||||
backgroundImage: start
|
||||
? getBackgroundImage('shooting/取景大图.png')
|
||||
: getBackgroundImage('shooting/取景地模糊背景.png')
|
||||
}"
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<ImgButton image="shooting/标题-取景地b.png" class="title-button absolute left-0" />
|
||||
|
||||
<!-- 返回按钮 -->
|
||||
<ImgButton
|
||||
image="shooting/返回.png"
|
||||
class="back-button absolute right-0 z-50"
|
||||
@click="back"
|
||||
/>
|
||||
|
||||
<StartOverlay v-if="!start" @start="start = true" />
|
||||
|
||||
<!-- 取景 -->
|
||||
<div class="absolute bottom-0 w-full h-full" v-else>
|
||||
<!-- 隐藏的背景图片,用于 Canvas 绘制 -->
|
||||
<img
|
||||
ref="backgroundImage"
|
||||
class="absolute inset-0 w-full h-full object-cover pointer-events-none opacity-0"
|
||||
src="@/assets/images/shooting/取景大图.png"
|
||||
alt=""
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
|
||||
<!-- 取景框容器 -->
|
||||
<div ref="viewfinderContainer" class="relative w-full h-full">
|
||||
<!-- 四个方向的遮罩层 -->
|
||||
<!-- 上遮罩 -->
|
||||
<div
|
||||
class="mask-layer absolute left-0 right-0 bg-black/60 pointer-events-none z-10"
|
||||
:style="{
|
||||
top: 0,
|
||||
height: viewfinder.y + 'px'
|
||||
}"
|
||||
></div>
|
||||
<!-- 下遮罩 -->
|
||||
<div
|
||||
class="mask-layer absolute left-0 right-0 bg-black/60 pointer-events-none z-10"
|
||||
:style="{
|
||||
top: viewfinder.y + viewfinder.height + 'px',
|
||||
bottom: 0
|
||||
}"
|
||||
></div>
|
||||
<!-- 左遮罩 -->
|
||||
<div
|
||||
class="mask-layer absolute left-0 bg-black/60 pointer-events-none z-10"
|
||||
:style="{
|
||||
top: viewfinder.y + 'px',
|
||||
width: viewfinder.x + 'px',
|
||||
height: viewfinder.height + 'px'
|
||||
}"
|
||||
></div>
|
||||
<!-- 右遮罩 -->
|
||||
<div
|
||||
class="mask-layer absolute right-0 bg-black/60 pointer-events-none z-10"
|
||||
:style="{
|
||||
top: viewfinder.y + 'px',
|
||||
left: viewfinder.x + viewfinder.width + 'px',
|
||||
height: viewfinder.height + 'px'
|
||||
}"
|
||||
></div>
|
||||
|
||||
<!-- 可拖动调整的取景框 -->
|
||||
<div
|
||||
ref="viewfinderBox"
|
||||
class="viewfinder-frame absolute border-4 border-white cursor-move z-20"
|
||||
:style="{
|
||||
left: viewfinder.x + 'px',
|
||||
top: viewfinder.y + 'px',
|
||||
width: viewfinder.width + 'px',
|
||||
height: viewfinder.height + 'px'
|
||||
}"
|
||||
@mousedown="startDrag"
|
||||
@touchstart="startDrag"
|
||||
>
|
||||
<!-- 九宫格辅助线 -->
|
||||
<div class="grid-lines absolute inset-0 grid grid-cols-3 grid-rows-3">
|
||||
<div
|
||||
v-for="i in 9"
|
||||
:key="i"
|
||||
class="border border-white/40"
|
||||
:class="{
|
||||
'border-t-0': i <= 3,
|
||||
'border-b-0': i >= 7,
|
||||
'border-l-0': (i - 1) % 3 === 0,
|
||||
'border-r-0': i % 3 === 0
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 四个角的调整手柄 -->
|
||||
<div
|
||||
class="resize-handle absolute w-6 h-6 bg-white rounded-full -left-3 -top-3 cursor-nwse-resize"
|
||||
@mousedown.stop="startResize('nw', $event)"
|
||||
@touchstart.stop="startResize('nw', $event)"
|
||||
></div>
|
||||
<div
|
||||
class="resize-handle absolute w-6 h-6 bg-white rounded-full -right-3 -top-3 cursor-nesw-resize"
|
||||
@mousedown.stop="startResize('ne', $event)"
|
||||
@touchstart.stop="startResize('ne', $event)"
|
||||
></div>
|
||||
<div
|
||||
class="resize-handle absolute w-6 h-6 bg-white rounded-full -left-3 -bottom-3 cursor-nesw-resize"
|
||||
@mousedown.stop="startResize('sw', $event)"
|
||||
@touchstart.stop="startResize('sw', $event)"
|
||||
></div>
|
||||
<div
|
||||
class="resize-handle absolute w-6 h-6 bg-white rounded-full -right-3 -bottom-3 cursor-nwse-resize"
|
||||
@mousedown.stop="startResize('se', $event)"
|
||||
@touchstart.stop="startResize('se', $event)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部取景按钮 - 跟随取景框 -->
|
||||
<div
|
||||
class="capture-button-container absolute z-20 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{
|
||||
left: viewfinder.x + viewfinder.width / 2 + 'px',
|
||||
top: viewfinder.y + viewfinder.height + 'px'
|
||||
}"
|
||||
>
|
||||
<ImgButton class="w-36" image="shooting/按钮-取景" @click="captureScene" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果弹窗 -->
|
||||
<Overlay :visible="showModal" @close="handleModalClose">
|
||||
<div class="relative w-[1000px] h-[600px] flex items-center justify-center">
|
||||
<!-- 截图内容 -->
|
||||
<div v-if="capturedImageUrl" class="bg-white p-2 rounded-md -rotate-6">
|
||||
<img :src="capturedImageUrl" alt="拍摄结果" />
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<!-- <div class="absolute bottom-1/2 -right-1/3 translate-y-1/2">
|
||||
<ImgButton image="shooting/按钮-前往下一个取景地" @click="handleFinish" />
|
||||
</div> -->
|
||||
</div>
|
||||
</Overlay>
|
||||
</div>
|
||||
|
||||
<LoadingOverlay :visible="isLoading" text="正在出片中..." />
|
||||
|
||||
<!-- 引导步骤 -->
|
||||
<GuideSteps v-if="showGuide" positionOffsetY="80%" @complete="handleComplete">
|
||||
<GuideStepsItem> 实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~ </GuideStepsItem>
|
||||
</GuideSteps>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user