Files
super_video/src/views/vlog/midterm-shooting/shooting/filmingLocation-b/index.vue
T

288 lines
9.6 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 showInstruction = ref<boolean>(false)
type ResizeDirection = 'nw' | 'ne' | 'sw' | 'se'
// 基础逻辑
const back = (): void => router.back()
const hideInstruction = (): void => {
showInstruction.value = false
}
const handleStart = (): void => {
start.value = true
showInstruction.value = true
}
// 截图功能
const { isLoading, showModal, capturedImageUrl, capture } = useCanvasCapture()
const handleModalClose = () => {
showModal.value = false
}
// 自由取景框模式
const { viewfinderContainer, viewfinderBox, viewfinder, startDrag, startResize } = useViewfinderB()
const handleStartDrag = (event: MouseEvent | TouchEvent): void => {
hideInstruction()
startDrag(event)
}
const handleStartResize = (direction: ResizeDirection, event: MouseEvent | TouchEvent): void => {
hideInstruction()
startResize(direction, event)
}
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 (): Promise<void> => {
if (isLoading.value) return
hideInstruction()
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 = (): void => {
showGuide.value = false
router.push({
name: 'Vlog'
})
}
</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"
/>
<p
v-if="start && showInstruction"
class="absolute left-1/2 -translate-x-1/2 top-[220px] z-30 text-red-500 text-4xl leading-normal font-bold"
>
操作说明:拖动取景框四角调整裁剪范围,点击“取景”键就完成啦!
</p>
<StartOverlay v-if="!start" @start="handleStart" />
<!-- 取景 -->
<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="handleStartDrag"
@touchstart="handleStartDrag"
>
<!-- 九宫格辅助线 -->
<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="handleStartResize('nw', $event)"
@touchstart.stop="handleStartResize('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="handleStartResize('ne', $event)"
@touchstart.stop="handleStartResize('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="handleStartResize('sw', $event)"
@touchstart.stop="handleStartResize('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="handleStartResize('se', $event)"
@touchstart.stop="handleStartResize('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>