daShangDao_scanBook/pages/upload/camera_capture.vue

264 lines
10 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.

<template>
<view class="cc-page">
<!-- H5 环境WebRTC 摄像头预览 -->
<view class="cc-camera-wrap" v-if="useWebRTC">
<video id="ccVideo" ref="ccVideo" class="cc-video" autoplay playsinline muted></video>
<canvas id="ccCanvas" ref="ccCanvas" class="cc-canvas" style="display:none;"></canvas>
<!-- 连拍模式提示遮罩 -->
<view class="cc-burst-overlay" v-if="isBurstMode">
<text class="cc-burst-tip">连拍中⋯</text>
<text class="cc-burst-count">{{ capturedList.length }} 张</text>
<text class="cc-burst-dots">
<text class="cc-dot" v-for="n in 3" :key="n">.</text>
</text>
<text class="cc-burst-hint">点击红色按钮停止</text>
</view>
</view>
<!-- APP 环境:提示信息 -->
<view class="cc-camera-wrap" v-else>
<view class="cc-camera-hint">
<text class="cc-hint-icon">📷</text>
<text class="cc-hint-text">{{ isBurstMode ? '连拍中… 系统相机将自动打开' : '点击下方按钮打开相机拍照' }}</text>
<text class="cc-hint-sub">已拍 {{ capturedList.length }} 张</text>
</view>
<canvas id="ccCanvas" ref="ccCanvas" class="cc-canvas" style="display:none;"></canvas>
</view>
<!-- 已拍照九宫格 -->
<view class="cc-thumb-bar">
<view class="cc-thumb-count" v-if="capturedList.length > 0">已拍 {{ capturedList.length }} 张</view>
<view class="cc-thumb-list">
<view class="cc-thumb-item" v-for="(img, idx) in capturedList" :key="idx">
<image class="cc-thumb-img" :src="img" mode="aspectFill"></image>
<view class="cc-thumb-del" @click.stop="deletePhoto(idx)">
<text class="cc-del-icon"></text>
</view>
</view>
<view class="cc-thumb-item cc-thumb-placeholder" v-for="n in (9 - capturedList.length)" :key="'p'+n"></view>
</view>
</view>
<!-- 底部操作区 -->
<view class="cc-footer">
<!-- 连拍模式 - 停止按钮 -->
<view class="cc-stop-btn" @click="stopBurst" v-if="isBurstMode">
<view class="cc-stop-inner">■</view>
</view>
<!-- 普通模式 - 拍照按钮 -->
<view class="cc-capture-btn" @click="capturePhoto" v-else :class="{ disabled: capturedList.length >= 9 }">
<view class="cc-capture-inner"></view>
</view>
<view class="cc-right-actions">
<view class="cc-burst-toggle" @click="toggleBurst" v-if="!isBurstMode && capturedList.length < 9">
<text class="cc-burst-toggle-text">⚡连拍</text>
</view>
<view class="cc-confirm-btn" @click="confirmCapture" :class="{ disabled: capturedList.length === 0 }">
<text class="cc-confirm-text">确认 ({{ capturedList.length }})</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
capturedList: [],
mediaStream: null,
useWebRTC: false,
ctxReady: false,
isBurstMode: false,
burstTimer: null
}
},
onReady() {
this.checkEnvironment()
},
onUnload() {
this.stopCamera()
this.clearBurstTimer()
},
methods: {
// 检测环境
checkEnvironment() {
try {
if (typeof document !== 'undefined' && typeof navigator !== 'undefined' && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
this.useWebRTC = true
this.startWebRTC()
return
}
} catch (e) {}
this.useWebRTC = false
this.ctxReady = true
console.log('APP环境:使用系统相机拍照')
},
// H5: WebRTC 启动摄像头
startWebRTC() {
var that = this
try {
if (typeof document === 'undefined') { this.ctxReady = true; return }
var video = document.getElementById('ccVideo')
if (!video) { this.ctxReady = true; return }
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: 1280, height: 720 } })
.then(function(stream) {
that.mediaStream = stream
video.srcObject = stream
video.play()
that.ctxReady = true
})
.catch(function() { that.ctxReady = true })
} catch (e) { this.ctxReady = true }
},
stopCamera() {
this.clearBurstTimer()
if (this.mediaStream) {
var tracks = this.mediaStream.getTracks()
for (var i = 0; i < tracks.length; i++) { tracks[i].stop() }
this.mediaStream = null
}
},
// 连拍开关
toggleBurst() {
if (this.capturedList.length >= 9) {
uni.showToast({ title: '最多拍9张', icon: 'none' })
return
}
this.isBurstMode = true
this.doBurstCapture()
},
doBurstCapture() {
if (!this.isBurstMode || this.capturedList.length >= 9) {
this.isBurstMode = false
return
}
var that = this
this.captureOne(function() {
if (that.isBurstMode) {
that.burstTimer = setTimeout(function() { that.doBurstCapture() }, 1500)
}
})
},
stopBurst() {
this.isBurstMode = false
this.clearBurstTimer()
},
clearBurstTimer() {
if (this.burstTimer) { clearTimeout(this.burstTimer); this.burstTimer = null }
},
// 单拍
capturePhoto() {
if (this.capturedList.length >= 9) { uni.showToast({ title: '最多拍9张', icon: 'none' }); return }
this.captureOne()
},
// 底层拍照
captureOne(callback) {
if (this.useWebRTC && this.ctxReady && this.mediaStream) {
this.webRTCCapture(callback)
} else {
this.systemCapture(callback)
}
},
// H5: WebRTC canvas截图
webRTCCapture(callback) {
if (typeof document === 'undefined') { this.systemCapture(callback); return }
var canvas = document.getElementById('ccCanvas')
var video = document.getElementById('ccVideo')
if (!canvas || !video) { this.systemCapture(callback); return }
canvas.width = video.videoWidth || 1280
canvas.height = video.videoHeight || 720
var ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
this.capturedList.push(canvas.toDataURL('image/jpeg', 0.85))
if (callback) callback()
},
// APP: 系统相机拍照Android/iOS APP 都走这里)
systemCapture(callback) {
var that = this
uni.chooseImage({
count: 1,
sourceType: ['camera'],
sizeType: ['original'],
success: function(res) {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
that.capturedList.push(res.tempFilePaths[0])
}
if (callback) callback()
},
fail: function() {
// APP上取消拍照 → 连拍模式暂停
that.isBurstMode = false
that.clearBurstTimer()
if (callback) callback()
}
})
},
deletePhoto(idx) {
this.capturedList.splice(idx, 1)
},
confirmCapture() {
if (this.capturedList.length === 0) { uni.showToast({ title: '请先拍照', icon: 'none' }); return }
this.stopCamera()
var pages = getCurrentPages()
var prevPage = pages[pages.length - 2]
if (prevPage) { prevPage.$vm.capturedPhotoList = this.capturedList }
uni.navigateBack()
}
}
}
</script>
<style>
.cc-page { display:flex; flex-direction:column; height:100vh; background:#000; overflow:hidden; }
.cc-camera-wrap { flex:1; position:relative; overflow:hidden; background:#111; display:flex; align-items:center; justify-content:center; }
.cc-video { width:100%; height:100%; object-fit:cover; }
.cc-canvas { display:none; }
.cc-camera-hint { text-align:center; padding:40rpx; }
.cc-hint-icon { font-size:80rpx; display:block; margin-bottom:20rpx; }
.cc-hint-text { color:#999; font-size:28rpx; }
.cc-hint-sub { color:#666; font-size:24rpx; margin-top:10rpx; }
.cc-burst-overlay { position:absolute; top:0;left:0;right:0;bottom:0; background:rgba(0,0,0,0.25); display:flex; flex-direction:column; align-items:center; justify-content:center; z-index:5; pointer-events:none; }
.cc-burst-tip { color:#fff; font-size:40rpx; font-weight:bold; text-shadow:0 2rpx 8rpx rgba(0,0,0,0.6); }
.cc-burst-count { color:rgba(255,255,255,0.9); font-size:32rpx; margin-top:16rpx; text-shadow:0 2rpx 8rpx rgba(0,0,0,0.6); }
.cc-burst-dots { color:#409eff; font-size:60rpx; margin-top:10rpx; animation:burstBlink 0.8s ease-in-out infinite; }
@keyframes burstBlink { 0%,100%{opacity:0.3} 50%{opacity:1} }
.cc-burst-hint { color:rgba(255,255,255,0.6); font-size:24rpx; margin-top:20rpx; text-shadow:0 2rpx 8rpx rgba(0,0,0,0.6); }
.cc-dot { margin:0 4rpx; }
.cc-thumb-bar { background:#1a1a1a; padding:16rpx 20rpx; }
.cc-thumb-count { color:#999; font-size:22rpx; margin-bottom:12rpx; }
.cc-thumb-list { display:flex; flex-wrap:wrap; gap:10rpx; }
.cc-thumb-item { width:100rpx; height:100rpx; border-radius:10rpx; overflow:hidden; position:relative; background:#333; }
.cc-thumb-img { width:100%; height:100%; }
.cc-thumb-del { position:absolute; top:-6rpx;right:-6rpx; width:32rpx;height:32rpx; border-radius:50%; background:rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; }
.cc-del-icon { color:#fff; font-size:18rpx; }
.cc-thumb-placeholder { border:2rpx dashed #444; box-sizing:border-box; }
.cc-footer { display:flex; justify-content:space-between; align-items:center; padding:30rpx 40rpx; padding-bottom:calc(30rpx + constant(safe-area-inset-bottom)); padding-bottom:calc(30rpx + env(safe-area-inset-bottom)); background:#1a1a1a; }
.cc-capture-btn { width:80rpx;height:80rpx; border-radius:50%; border:6rpx solid #fff; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.cc-capture-inner { width:64rpx;height:64rpx; border-radius:50%; background:#fff; }
.cc-capture-btn:active .cc-capture-inner { background:#ccc; }
.cc-capture-btn.disabled { opacity:0.4; }
.cc-stop-btn { width:80rpx;height:80rpx; border-radius:50%; border:6rpx solid #ff4444; display:flex; align-items:center; justify-content:center; flex-shrink:0; background:rgba(255,68,68,0.15); }
.cc-stop-inner { color:#ff4444; font-size:32rpx; font-weight:bold; }
.cc-right-actions { display:flex; align-items:center; gap:20rpx; }
.cc-burst-toggle { padding:16rpx 24rpx; border-radius:32rpx; border:2rpx solid #409eff; background:transparent; }
.cc-burst-toggle-text { color:#409eff; font-size:24rpx; font-weight:bold; }
.cc-confirm-btn { padding:18rpx 40rpx; border-radius:40rpx; background:#409eff; }
.cc-confirm-btn.disabled { background:#555; }
.cc-confirm-text { color:#fff; font-size:28rpx; font-weight:bold; }
</style>