Files
guoju0808/ry-h5/composables/useCamera.ts
T

93 lines
2.0 KiB
TypeScript

/**
* 共享相机 composable (H5 only)
*
* 用法: const { captured, errorMsg, startCamera, flipCamera, takePhoto, retake, confirm } = useCamera('my-video-id')
*
* - 自动在 onMounted 启动摄像头
* - 自动在 onBeforeUnmount 停止摄像头
* - 返回 ref 和方法,业务页只需负责 UI 布局
*/
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { openCamera, stopCamera, captureFrame, describeCameraError, type Facing } from '@/utils/camera'
export function useCamera(videoId: string) {
const facing = ref<Facing>('environment')
const captured = ref<string>('')
const errorMsg = ref<string>('')
function getVideoEl(): HTMLVideoElement | null {
return document.getElementById(videoId) as HTMLVideoElement | null
}
async function startCamera() {
errorMsg.value = ''
const video = getVideoEl()
if (!video) {
errorMsg.value = '视频元素未找到'
return
}
try {
await openCamera(video, facing.value)
} catch (err) {
errorMsg.value = describeCameraError(err)
console.error('[camera]', err)
}
}
function flipCamera() {
facing.value = facing.value === 'environment' ? 'user' : 'environment'
startCamera()
}
async function takePhoto() {
const video = getVideoEl()
if (!video) return
try {
captured.value = captureFrame(video)
} catch (err) {
uni.showToast({
title: (err as Error).message || '截图失败',
icon: 'none',
})
}
}
function retake() {
captured.value = ''
}
async function confirm() {
// POC: 假上传 + loading 给用户完整仪式感
uni.showLoading({ title: '上传中...' })
await new Promise<void>((resolve) => setTimeout(resolve, 800))
uni.hideLoading()
uni.showToast({
title: '已保存 (POC 未上传)',
icon: 'none',
duration: 1500,
})
captured.value = ''
}
onMounted(() => {
startCamera()
})
onBeforeUnmount(() => {
const video = getVideoEl()
if (video) {
stopCamera(video)
}
})
return {
facing,
captured,
errorMsg,
startCamera,
flipCamera,
takePhoto,
retake,
confirm,
}
}