CRM_26-08-05#story#9126,车辆管理、服务商管理系统需求
This commit is contained in:
@@ -33,7 +33,7 @@
|
|||||||
<div class="guideFrameBorder"></div>
|
<div class="guideFrameBorder"></div>
|
||||||
<div class="guideHole">
|
<div class="guideHole">
|
||||||
<!-- 相机画面仅在取景框内显示 -->
|
<!-- 相机画面仅在取景框内显示 -->
|
||||||
<video ref="video" class="cameraVideo" autoplay playsinline muted></video>
|
<video ref="video" class="cameraVideo" autoplay playsinline webkit-playsinline muted></video>
|
||||||
<!-- 主页模板:标题 + 印章 -->
|
<!-- 主页模板:标题 + 印章 -->
|
||||||
<div v-if="type==='front'" class="licenseTemplate ltFrontPage">
|
<div v-if="type==='front'" class="licenseTemplate ltFrontPage">
|
||||||
<div class="ltInner">
|
<div class="ltInner">
|
||||||
@@ -134,6 +134,11 @@ export default {
|
|||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
// 父组件前置探测时已拿到的相机流,传入则直接复用(iOS 上避免二次 getUserMedia 黑屏)
|
||||||
|
stream: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -144,7 +149,7 @@ export default {
|
|||||||
'medium', 'tall', 'short wide', 'medium', 'tall', 'short', 'medium wide', 'tall']
|
'medium', 'tall', 'short wide', 'medium', 'tall', 'short', 'medium wide', 'tall']
|
||||||
return {
|
return {
|
||||||
step: 'guide', // guide 拍摄引导 | preview 预览确认
|
step: 'guide', // guide 拍摄引导 | preview 预览确认
|
||||||
stream: null,
|
activeStream: null,
|
||||||
previewBlob: null,
|
previewBlob: null,
|
||||||
previewDataURL: '',
|
previewDataURL: '',
|
||||||
barcodeBars: heights
|
barcodeBars: heights
|
||||||
@@ -176,7 +181,8 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async startCamera() {
|
async startCamera() {
|
||||||
try {
|
try {
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
// 优先复用父组件探测时拿到的流,未传入才自行申请
|
||||||
|
const stream = this.stream || await navigator.mediaDevices.getUserMedia({
|
||||||
video: { facingMode: 'environment' },
|
video: { facingMode: 'environment' },
|
||||||
audio: false
|
audio: false
|
||||||
})
|
})
|
||||||
@@ -185,12 +191,23 @@ export default {
|
|||||||
stream.getTracks().forEach(t => t.stop())
|
stream.getTracks().forEach(t => t.stop())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.stream = stream
|
this.activeStream = stream
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
const video = this.$refs.video
|
const video = this.$refs.video
|
||||||
if (video) {
|
if (video) {
|
||||||
|
// iOS 自动播放加固:muted/playsinline 属性与特性同时设置,元数据就绪后再 play
|
||||||
|
video.muted = true
|
||||||
|
video.setAttribute('playsinline', 'true')
|
||||||
|
video.setAttribute('webkit-playsinline', 'true')
|
||||||
video.srcObject = stream
|
video.srcObject = stream
|
||||||
video.play().catch(() => {})
|
const playVideo = () => {
|
||||||
|
video.play().catch(e => console.warn('video play error:', e && (e.name || e.message || e)))
|
||||||
|
}
|
||||||
|
if (video.readyState >= 1) {
|
||||||
|
playVideo()
|
||||||
|
} else {
|
||||||
|
video.onloadedmetadata = playVideo
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 权限拒绝或相机不可用 → 通知父组件降级为系统选择器
|
// 权限拒绝或相机不可用 → 通知父组件降级为系统选择器
|
||||||
@@ -199,9 +216,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
stopCamera() {
|
stopCamera() {
|
||||||
if (this.stream) {
|
if (this.activeStream) {
|
||||||
this.stream.getTracks().forEach(t => t.stop())
|
this.activeStream.getTracks().forEach(t => t.stop())
|
||||||
this.stream = null
|
this.activeStream = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
takePhoto() {
|
takePhoto() {
|
||||||
@@ -373,7 +390,8 @@ export default {
|
|||||||
max-width: 340px;
|
max-width: 340px;
|
||||||
padding-top: 69.93%; /* 宽高比约 1.43:1 */
|
padding-top: 69.93%; /* 宽高比约 1.43:1 */
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
filter: drop-shadow(0 4px 16px rgba(20, 30, 50, .12));
|
/* 不用 filter: drop-shadow,iOS 上父级 filter 会导致 video 合成层不绘制(黑屏) */
|
||||||
|
box-shadow: 0 4px 16px rgba(20, 30, 50, .12);
|
||||||
}
|
}
|
||||||
.corner {
|
.corner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -457,6 +457,7 @@
|
|||||||
<camera-guide
|
<camera-guide
|
||||||
:visible="cameraGuideVisible"
|
:visible="cameraGuideVisible"
|
||||||
:type="cameraGuideType"
|
:type="cameraGuideType"
|
||||||
|
:stream="cameraStream"
|
||||||
@confirm="onCameraConfirm"
|
@confirm="onCameraConfirm"
|
||||||
@cancel="onCameraCancel"
|
@cancel="onCameraCancel"
|
||||||
@fallback="onCameraFallback"
|
@fallback="onCameraFallback"
|
||||||
@@ -512,6 +513,7 @@ export default {
|
|||||||
imageUrl: require('@/assets/arr_right.png'),
|
imageUrl: require('@/assets/arr_right.png'),
|
||||||
cameraGuideVisible: false, // 拍摄引导弹层显示
|
cameraGuideVisible: false, // 拍摄引导弹层显示
|
||||||
cameraGuideType: 'front', // 当前拍摄槽位 front/back/car
|
cameraGuideType: 'front', // 当前拍摄槽位 front/back/car
|
||||||
|
cameraStream: null, // 探测成功的相机流,交给 CameraGuide 复用
|
||||||
vehicleLicenseFrontList: [],
|
vehicleLicenseFrontList: [],
|
||||||
vehicleLicenseBackList: [],
|
vehicleLicenseBackList: [],
|
||||||
vehicleLicenseCarPhotoList: [],
|
vehicleLicenseCarPhotoList: [],
|
||||||
@@ -954,9 +956,10 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// 前置真实探测相机可用性,避免进入拍摄引导页后黑屏;拿到流立即释放
|
// 前置真实探测相机可用性;探测成功的流直接交给 CameraGuide 复用,
|
||||||
|
// 避免 iOS 上 stop 后立即二次 getUserMedia 拿到不渲染的“死流”(黑屏)
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false })
|
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false })
|
||||||
stream.getTracks().forEach(t => t.stop())
|
this.cameraStream = stream
|
||||||
this.cameraGuideVisible = true
|
this.cameraGuideVisible = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('probe getUserMedia error:', e && (e.name || e.message || e))
|
console.warn('probe getUserMedia error:', e && (e.name || e.message || e))
|
||||||
@@ -968,6 +971,7 @@ export default {
|
|||||||
},
|
},
|
||||||
handleCameraFail(e) { // 相机不可用统一处理:权限类弹确认框,其他异常提示后由用户点击触发相册(保证点击手势有效)
|
handleCameraFail(e) { // 相机不可用统一处理:权限类弹确认框,其他异常提示后由用户点击触发相册(保证点击手势有效)
|
||||||
this.cameraGuideVisible = false
|
this.cameraGuideVisible = false
|
||||||
|
this.cameraStream = null
|
||||||
if (this.isCameraPermissionError(e)) {
|
if (this.isCameraPermissionError(e)) {
|
||||||
Dialog.confirm({
|
Dialog.confirm({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
@@ -1002,6 +1006,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onCameraConfirm({ blob, dataURL }) { // 拍摄确认:构造 {file, content} 走现有压缩→上传→OCR 链路
|
onCameraConfirm({ blob, dataURL }) { // 拍摄确认:构造 {file, content} 走现有压缩→上传→OCR 链路
|
||||||
this.cameraGuideVisible = false
|
this.cameraGuideVisible = false
|
||||||
|
this.cameraStream = null
|
||||||
const type = this.cameraGuideType
|
const type = this.cameraGuideType
|
||||||
const file = new File([blob], `vehicle_license_${type}_${Date.now()}.jpg`, { type: 'image/jpeg' })
|
const file = new File([blob], `vehicle_license_${type}_${Date.now()}.jpg`, { type: 'image/jpeg' })
|
||||||
const fileItem = { file, content: dataURL }
|
const fileItem = { file, content: dataURL }
|
||||||
@@ -1021,6 +1026,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onCameraCancel() { // 返回放弃
|
onCameraCancel() { // 返回放弃
|
||||||
this.cameraGuideVisible = false
|
this.cameraGuideVisible = false
|
||||||
|
this.cameraStream = null
|
||||||
},
|
},
|
||||||
onCameraFallback(e) { // CameraGuide 内 getUserMedia 失败(前置探测已通过,概率极低):按异常类型统一处理
|
onCameraFallback(e) { // CameraGuide 内 getUserMedia 失败(前置探测已通过,概率极低):按异常类型统一处理
|
||||||
this.handleCameraFail(e)
|
this.handleCameraFail(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user