Files
super_video/src/views/interview/midterm-shooting/shooting/filmingLocation-b/index.vue
T
2026-05-09 09:53:06 +08:00

302 lines
9.1 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="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'
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
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('interview/shooting/教学楼-清晰.png')
: getBackgroundImage('interview/shooting/教学楼-模糊.png'),
}"
>
<!-- 标题 -->
<ImgButton
image="interview/shooting/教学楼-侧边标题.png"
class="title-button absolute left-0"
/>
<!-- 返回按钮 -->
<ImgButton
image="shooting/返回.png"
class="back-button absolute right-0 z-50"
@click="back"
/>
<StartOverlay
scene="b"
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/interview/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>