whiteness="config.white" :aspect="config.ratio" :device-position="config.camera" @statechange="statechange">
</live-pusher>
<!-- </view> -->
<view class="overlay">
<!-- 顶部遮罩 -->
<view class="mask top" :style="{
position: 'absolute',
top: '0px',
left: '0px',
height: highlightTop + 'px',
width: systemInfo.screenWidth + 'px',
backgroundColor: 'rgba(0,0,0,0.7)'
}">
</view>
<!-- 底部遮罩 -->
<view class="mask bottom" :style="{
position: 'absolute',
top: highlightTop +highlightHeight+ 'px',
left: '0px',
height: bottomMaskHeight + 'px',
width: systemInfo.screenWidth + 'px',
backgroundColor: 'rgba(0,0,0,0.7)'
}">
</view>
<!-- 左侧遮罩 -->
<view class="mask left" :style="{
position: 'absolute',
top: leftMaskTop + 'px',
left: '0px',
width: leftMaskWidth + 'px',
height: highlightHeight + 'px',
backgroundColor: 'rgba(0,0,0,0.7)'
}">
</view>
<!-- 右侧遮罩 -->
<view class="mask right" :style="{
position: 'absolute',
top: leftMaskTop + 'px',
left: (highlightLeft + highlightWidth) + 'px',
width: leftMaskWidth + 'px',
height: highlightHeight + 'px',
backgroundColor: 'rgba(0,0,0,0.7)'
}">
</view>
<!-- 高亮边框 -->
<view class="highlight-frame" :style="{
position: 'absolute',
top: highlightTop + 'px',
left: highlightLeft + 'px',
width: highlightWidth + 'px',
height: highlightHeight + 'px',
border: '2px solid #008CFF'
}">
<view class="scan-line" :style="{
position: 'absolute',
top: scanTop + 'px',
left: '0px',
right: '0px',
height: '2px',
backgroundColor: '#008CFF'
}">
</view>
<view class="corner tl"></view>
<view class="corner tr"></view>
<view class="corner bl"></view>
<view class="corner br"></view>
</view>
<text class="tip-text" :style="{top: (highlightTop + highlightHeight + 10)+'px'}">{{tipText}}</text>
</view>
</template>
<script setup>
import {
onLoad,
onUnload,
onHide
} from "@dcloudio/uni-app";
import {
ref,
getCurrentInstance,
nextTick,
onMounted
} from "vue";
import {
useDetectionStore
} from '../../store/modules/detection'
const userDetectionStore = useDetectionStore()
const systemInfo = uni.getSystemInfoSync();
const pusherComponentSize = ref({ width: 0, height: 0 });
// 获取当前设备宽、高
const screen = {
width: 375,
height: 667,
}
// 动态计算遮罩层的宽高和位置
const bottomMaskHeight = ref(0);
const leftMaskWidth = ref(0);
const highlightWidth = ref(0);
const highlightHeight = ref(0);
const highlightTop = ref(0);
const highlightLeft = ref(0);
const leftMaskTop = ref(0);
// livePusher相关申明
const livePusherId = ref(null);
const livePusher = ref(null);
const livePusherContext = ref(null);
// livePusher 配置
const config = ref({
url: '',
quality: 'SD',
ratio: '9:16',
isMute: false,
autoFocus: true,
beauty: 0,
white: 0,
camera: 'back'
})
// 添加扫描线位置变量
const scanTop = ref(0);
// 扫描线定时器
let scanTimer = null;
// 定时拍摄
let shotTimer = null;
// 文本提示
const tipText = ref('');
// 切换预览状态
const togglePreview = (target) => {
if (!livePusherContext.value) {
return;
}
if (target === 'stop') {
// 停止预览
livePusherContext.value.stopPreview({
success: () => {
},
fail: (err) => {
uni.showModal({
title: '停止预览失败',
content: err.message || err.errMsg,
showCancel: false
});
}
});
}
if (target === 'start') {
// 开始预览
livePusherContext.value.startPreview({
success: () => {
startScanAnimation();
},
fail: (err) => {
uni.showModal({
title: '预览开启失败',
content: err.message || err.errMsg,
showCancel: false
});
}
});
}
}
// 扫描线动画
const startScanAnimation = () => {
stopScanAnimation();
let top = 0;
const frameHeight = systemInfo.windowHeight * 0.5; // 高亮框高度为窗口的50%
scanTimer = setInterval(() => {
top += 2;
if (top > frameHeight) {
top = 0;
}
scanTop.value = top;
}, 30);
}
// 将快照发送给服务器
const takeScreenshot = async () => {
return new Promise((resolve, reject) => {
livePusherContext.value.snapshot({
success: async (res) => {
// 1. 获取快照图片的真实尺寸 (图片坐标系)
const imageInfo = await uni.getImageInfo({ src: res.message.tempImagePath });
const { width: imgWidth, height: imgHeight } = imageInfo;
console.log('--- [Step 1] 快照图片尺寸 (px):', { imgWidth, imgHeight });
// 2. 获取屏幕/组件的尺寸 (屏幕坐标系)
const { screenWidth, windowHeight } = systemInfo;
console.log('--- [Step 2] 预览窗口尺寸 (px):', { screenWidth, windowHeight });
// 3. 【核心】基于“宽度填满,上下裁剪”的模型进行计算
// a. 计算视频流在屏幕上的缩放比例
const scale = screenWidth / imgWidth;
console.log('--- [Step 3a] 缩放比例 (screen/img):', scale);
// b. 计算视频流按此比例缩放后,在屏幕上的总高度
const scaledImgHeight = imgHeight * scale;
console.log('--- [Step 3b] 缩放后的视频高度 (px):', scaledImgHeight);
// c. 计算视频流顶部被裁剪掉的高度 (换算回图片坐标系)
// 这就是“图片偏下”的根本原因
const offsetY_img = (scaledImgHeight - windowHeight) / 2 / scale;
console.log('--- [Step 3c] 图片顶部被裁剪的高度 (px):', offsetY_img);
// 4. 【核心】坐标转换
const boxX_screen = highlightLeft.value;
const boxY_screen = highlightTop.value;
const boxW_screen = highlightWidth.value;
const boxH_screen = highlightHeight.value;
console.log('--- [Step 4] 高亮框屏幕坐标 (px):', { boxX_screen, boxY_screen, boxW_screen, boxH_screen });
const cropData = {
// X坐标没有偏移,直接按比例缩放
crop_x: Math.round(boxX_screen / scale),
// Y坐标需要加上被裁剪掉的顶部偏移量
crop_y: Math.round(boxY_screen / scale + offsetY_img),
crop_width: Math.round(boxW_screen / scale),
crop_height: Math.round(boxH_screen / scale),
};
console.log('--- [Step 5] 最终发送给后端的裁剪数据:', cropData);
const resData = await userDetectionStore.uploadDetecionImg(res.message
.tempImagePath);
if (resData?.code === 200) {
uni.redirectTo({
url: '/pages/data/detailsData'
});
} else {
tipText.value = resData?.msg;
}
},
fail: (err) => {
reject(err.message || err.errMsg);
}
});
});
};
const autoCaptureProcess = async () => {
const imgPath = await takeScreenshot();
};
// 快照
const takeSnapshot = () => {
shotTimer = setInterval(takeScreenshot, 1000);
}
// livePusher状态改变
const statechange = (e) => {
const errCode = e.detail.code || e.detail.errCode;
const errMsg = e.detail.message || e.detail.errMsg;
if (errCode == 1007) {
takeSnapshot();
}
}
// 初始化
const createLivePusher = () => {
livePusherId.value = `livePusher_${Date.now()}`;
if (livePusherContext.value) {
livePusherContext.value = null;
};
// 动态计算遮罩层的宽高和位置
const screenWidth = systemInfo.screenWidth;
const screenHeight = systemInfo.screenHeight;
console.log(" screenWidth " + screenWidth);
console.log(" screenHeight " + screenHeight);
// 高亮框的宽高和位置
highlightWidth.value = screenWidth * 0.3; // 占屏幕宽度的45%
highlightHeight.value = screenHeight * 0.4; // 占屏幕高度的50%
highlightTop.value = screenHeight * 0.3; // 距离顶部25%
highlightLeft.value = screenWidth * 0.35; // 距离左侧30%
// console.log(" screenWidth " + screenWidth);
// console.log(" screenHeight " + screenHeight);
// console.log(" highlightWidth " + highlightWidth.value);
// console.log(" highlightHeight " + highlightHeight.value);
// console.log(" highlightTop " + highlightTop.value);
// console.log(" highlightLeft " + highlightLeft.value);
// 底部遮罩:高度 = 屏幕总高度 - 高亮区域顶部 - 高亮区域高度
bottomMaskHeight.value = screenHeight - highlightTop.value - highlightHeight.value;
// 左侧遮罩:宽度 = 高亮区域左侧位置
leftMaskWidth.value = highlightLeft.value;
// 左侧遮罩层的顶部位置与高亮区域顶部对齐
leftMaskTop.value = highlightTop.value;
nextTick(() => {
const currentPage = getCurrentPages()[getCurrentPages().length - 1];
livePusherContext.value = uni.createLivePusherContext(livePusherId.value, currentPage.$vm);
console.log('Live-pusher 组件尺寸:', JSON.stringify(livePusherContext.value))
// const query = uni.createSelectorQuery().in(currentPage);
// query.select('.live-pusher').boundingClientRect(data => {
// if (data) {
// pusherComponentSize.value = { width: data.width, height: data.height };
// console.log('Live-pusher 组件尺寸:', pusherComponentSize.value);
// }
// }).exec();
setTimeout(togglePreview.bind(null, 'start'), 100)
})
}
// 停止扫描线动画
const stopScanAnimation = () => {
if (scanTimer) {
clearInterval(scanTimer);
}
scanTimer = null;
}
// 停止定时快照
const stopAutoCapture = () => {
if (shotTimer) {
clearInterval(shotTimer)
}
shotTimer = null
}
onUnload(() => {
stopScanAnimation();
stopAutoCapture();
});
onHide(() => {
stopScanAnimation();
stopAutoCapture();
});
onLoad(() => {
createLivePusher();
});
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
background: rgba(0, 0, 0, 0.700);
}
/* 推流容器:占满根容器(确保画面全屏) */
.live-pusher {
display: flex;
flex-direction: column;
flex: 1;
background: rgba(0, 0, 0, 0.700);
}
/* 截图预览:保持原有 */
.snapshot-preview {
position: fixed;
right: 20px;
top: 100px;
z-index: 1000;
}
.snapshot-preview-image {
width: 100%;
height: auto;
}
.overlay {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 9999;
flex: 0
}
/* 半透明遮罩 */
.mask {
position: absolute;
background: rgba(0, 0, 0, 0.700);
}
/* 高亮边框 */
.highlight-frame {
position: absolute;
border: 2px solid #008CFF;
overflow: hidden;
position: relative;
box-shadow: 0 0 15px rgba(39, 89, 255, 0.5);
}
/* 四角装饰 */
.corner {
position: absolute;
width: 20px;
height: 20px;
border-style: solid;
border-color: #008CFF;
z-index: 2;
}
.tl {
/* 左上角 */
top: -2px;
left: -2px;
border-width: 2px 0 0 2px;
}
.tr {
/* 右上角 */
top: -2px;
right: -2px;
border-width: 2px 2px 0 0;
}
.bl {
/* 左下角 */
bottom: -2px;
left: -2px;
border-width: 0 0 2px 2px;
}
.br {
/* 右下角 */
bottom: -2px;
right: -2px;
border-width: 0 2px 2px 0;
}
.tip-text {
color: #EBEFFA;
font-size: 14px;
text-align: center;
position: absolute;
align-items: center;
word-wrap: break-word;
lines: 2;
width: 750rpx;
left: 0;
}
</style>
0 个回复