|
|
@@ -0,0 +1,357 @@
|
|
|
+<template>
|
|
|
+ <view class="zhuapai-drop-container" id="Drop" ref="DropRef" :style="style" @touchmove="touchmove($event)"
|
|
|
+ @touchstart="touchstart($event)">
|
|
|
+ <view class="phone-camera-box-zhuapai">
|
|
|
+ <camera class="camera" device-position="front" flash="off" style="width: 200px;height: 200px" :class="myClass" @initdone="onVideoSuccess" @error="handleError" @stop="handleStop"></camera>
|
|
|
+ <!-- 用于抓拍切出去传递固定img-->
|
|
|
+ <img :src="imgUrl" alt="" v-show="false">
|
|
|
+ <!-- 测试抓拍使用 -->
|
|
|
+ <!-- <button @click="handleZhua">抓拍</button> -->
|
|
|
+ </view>
|
|
|
+ <view v-show="showVideo" @click="noShowVideoBtn" class="shiti-video-hidden-btn">
|
|
|
+ <icon :style="{ backgroundImage: 'url(' + iconsArr.videoCloseIcon + ')' }"></icon>
|
|
|
+ </view>
|
|
|
+ <view v-show="!showVideo" @click="showVideoBtn" class="shiti-video-show-btn">
|
|
|
+ <icon :style="{ backgroundImage: 'url(' + iconsArr.videoPlayIcon + ')' }"></icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {ref,onUnmounted,nextTick,computed,onMounted,reactive} from "vue";
|
|
|
+import cacheManager from '@/utils/cacheManager.js';
|
|
|
+import * as ksApi from "@/api/kaoshi.js"
|
|
|
+import {
|
|
|
+ getStaticUrl
|
|
|
+} from "@/utils/common.js"
|
|
|
+
|
|
|
+import {useZhuapaiStore} from "@/store/zhuapai.js"
|
|
|
+import {getFileUpload} from "@/api/kaoshi.js"
|
|
|
+
|
|
|
+const zhuapaiStore = useZhuapaiStore();
|
|
|
+
|
|
|
+const imgUrl = '';
|
|
|
+
|
|
|
+const cameraContext = ref(null);
|
|
|
+
|
|
|
+const DropRef = ref(null);
|
|
|
+const DropContainerRef = ref(null);
|
|
|
+const zhuapai = ref(0); // 单位分
|
|
|
+const operId = ref(null); // 单位分
|
|
|
+const disX = ref(0); // 移动x
|
|
|
+const disY = ref(0); // 移动y
|
|
|
+const showVideo = ref(true);
|
|
|
+const isBuffer = ref(false);
|
|
|
+
|
|
|
+const stopTimer = ref(null);
|
|
|
+const style = ref({
|
|
|
+ top: "17vh",
|
|
|
+ right: "0",
|
|
|
+});
|
|
|
+
|
|
|
+const iconsArr = reactive({
|
|
|
+ noKaoshiImg: '',
|
|
|
+ videoCloseIcon: '',
|
|
|
+ videoPlayIcon: '',
|
|
|
+})
|
|
|
+const myClass = computed(() => {
|
|
|
+ return {
|
|
|
+ 'show-video': showVideo.value,
|
|
|
+ 'hidden-video': !showVideo.value
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emits = defineEmits(['init', 'success', 'error', 'cancel', 'progress'])
|
|
|
+
|
|
|
+function noShowVideoBtn() {
|
|
|
+ showVideo.value = false
|
|
|
+}
|
|
|
+
|
|
|
+function showVideoBtn() {
|
|
|
+ showVideo.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function touchmove(event) {
|
|
|
+ // 2,获取手指移动的实时位置 需要减去位置差
|
|
|
+ let moveX = event.touches[0].pageX - disX.value;
|
|
|
+ let moveY = event.touches[0].pageY - disY.value;
|
|
|
+
|
|
|
+ const systemInfo = uni.getAppBaseInfo();
|
|
|
+ const windowHeight = systemInfo.windowHeight; // 可视区域高度 :ml-citation{ref="1,3" data="citationList"}
|
|
|
+ const windowWidth = systemInfo.windowWidth; // 可视区域高度 :ml-citation{ref="1,3" data="citationList"}
|
|
|
+
|
|
|
+
|
|
|
+ // 3,获取容器的宽高和拖动元素的宽高 每一次移动都会获取一次 ,建议放在外面进行获取
|
|
|
+ // let dragHeight = DropRef.value.$el.offsetHeight;
|
|
|
+ // let dragWidth = DropRef.value.$el.offsetWidth;
|
|
|
+ let dragHeight = 0;
|
|
|
+ let dragWidth = 0;
|
|
|
+
|
|
|
+ // 4,控制范围:在元素 被拖拽的过程中 判断 元素的定位值 是否到达边界 如果到了 就不能在走了
|
|
|
+ if (moveX <= 0) {
|
|
|
+ moveX = 0;
|
|
|
+ }
|
|
|
+ // 上边界
|
|
|
+ if (moveY <= 0) {
|
|
|
+ moveY = 0;
|
|
|
+ }
|
|
|
+ //下边界 容器高度 - 拖动元素高度
|
|
|
+ if (moveY >= windowHeight - dragHeight - 150) {
|
|
|
+ moveY = windowHeight - dragHeight - 150;
|
|
|
+ }
|
|
|
+ //右边界 容器宽度 - 拖动元素宽度
|
|
|
+ if (moveX >= windowWidth - dragWidth) {
|
|
|
+ moveX = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5,开始移动
|
|
|
+ style.value.top = moveY + "px";
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function touchstart(event) {
|
|
|
+ // disX.value = event.touches[0].pageX - DropRef.value.$el.offsetLeft;
|
|
|
+ // disY.value = event.touches[0].pageY - DropRef.value.$el.offsetTop;
|
|
|
+ disX.value = 0;
|
|
|
+ disY.value = 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function init(options) {
|
|
|
+ zhuapai.value = options.zhuapai;
|
|
|
+ operId.value = options.operId;
|
|
|
+ if (zhuapai.value > 0) {
|
|
|
+ // 启动摄像头
|
|
|
+ nextTick(() => {
|
|
|
+ startCamera()
|
|
|
+ // 设定计时器
|
|
|
+ setInterval(() => handleZhua(), zhuapai.value * 60 * 1000)
|
|
|
+ })
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function startCamera() {
|
|
|
+ // 请求摄像头权限并获取流
|
|
|
+ cameraContext.value = uni.createCameraContext();
|
|
|
+}
|
|
|
+
|
|
|
+function urlToBase64(url) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ // #ifdef MP-WEIXIN
|
|
|
+ // 微信小程序平台
|
|
|
+ uni.getFileSystemManager().readFile({
|
|
|
+ filePath: url, // 图片临时路径
|
|
|
+ encoding: 'base64', // 指定编码格式
|
|
|
+ success: (res) => {
|
|
|
+ // 拼接Data URL前缀,注意图片类型可能需要根据实际情况调整(如png)
|
|
|
+ let base64 = 'data:image/jpeg;base64,' + res.data;
|
|
|
+ resolve(base64);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // #endif
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function getSnapShotImage(data) {
|
|
|
+ urlToBase64(data).then((d1) => {
|
|
|
+ const imgData = d1.split(';base64,');
|
|
|
+
|
|
|
+ if (!imgData.length) {
|
|
|
+ console.error('【源 :拍照数据异常,未找到图片二进制数据分割节点: `;base64,`】');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const opt = {
|
|
|
+ data: imgData[1],
|
|
|
+ prefix: 'kaoshi/zhuapai',
|
|
|
+ suffix: 'png',
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ getFileUpload(opt).then(res => {
|
|
|
+ const dOption = {
|
|
|
+ operId:operId.value,
|
|
|
+ url: res.data
|
|
|
+ }
|
|
|
+ ksApi.getClientZhuaPaiUpdate(dOption)
|
|
|
+ .then(res => {
|
|
|
+ console.log('【源 : 获取抓拍数据】');
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ console.error('源 :抓拍接口异常', err);
|
|
|
+ uni.showToast({
|
|
|
+ icon: 'none',
|
|
|
+ title: '抓拍图片异常!'
|
|
|
+ })
|
|
|
+ uni.redirectTo({
|
|
|
+ url: "/pages/client/Kaoshi/list"
|
|
|
+ })
|
|
|
+ });
|
|
|
+ }).catch(err => {
|
|
|
+ uni.showToast({
|
|
|
+ icon: 'none',
|
|
|
+ title: '当前网络可能存在异常,请稍后重试,如持续异常,请联系管理员。注:若异常未联系管理员,可能会影响考试结果。'
|
|
|
+ })
|
|
|
+ uni.redirectTo({
|
|
|
+ url: "/pages/client/Kaoshi/list"
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function doZhuaiPai(ImageFile) {
|
|
|
+ try {
|
|
|
+ if (zhuapaiStore.status == 0) {
|
|
|
+ getSnapShotImage(ImageFile);
|
|
|
+ } else {
|
|
|
+ const ImageFile1 = imgUrl.value;
|
|
|
+ getSnapShotImage(ImageFile1);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('源 :绘图失败', err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleZhua() {
|
|
|
+ cameraContext.value.takePhoto({
|
|
|
+ quality: 'high',
|
|
|
+ success: (res) => {
|
|
|
+ doZhuaiPai(res.tempImagePath)
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('拍照失败:', err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function onVideoSuccess() {
|
|
|
+ // setTimeout(() => {
|
|
|
+ // // 首次运行进行抓拍一次
|
|
|
+ // handleZhua();
|
|
|
+ // }, 3000);
|
|
|
+}
|
|
|
+
|
|
|
+function onVideoError() {
|
|
|
+ emits('error')
|
|
|
+}
|
|
|
+
|
|
|
+function handleError() {
|
|
|
+ emits('error')
|
|
|
+}
|
|
|
+
|
|
|
+function handleStop() {
|
|
|
+ emits('error')
|
|
|
+}
|
|
|
+
|
|
|
+// 针对视频通话的监听处理
|
|
|
+
|
|
|
+function onTimeupdate() {
|
|
|
+ if (isBuffer.value) {
|
|
|
+ console.log('buffer')
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!stopTimer.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log('onTimeupdate')
|
|
|
+ clearTimeout(stopTimer.value);
|
|
|
+ stopTimer.value = null;
|
|
|
+}
|
|
|
+
|
|
|
+function onProgress() {
|
|
|
+ if (stopTimer.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ isBuffer.value = true;
|
|
|
+ console.log('onProgress')
|
|
|
+ // buffer时间增大到3秒 过滤掉后续的onTimeupdate
|
|
|
+ setTimeout(() => {isBuffer.value = false}, 3000)
|
|
|
+ // 视频中途暂停被占用
|
|
|
+ stopTimer.value = setTimeout(() => {
|
|
|
+ emits('progress', false);
|
|
|
+ console.log('结束')
|
|
|
+ }, 10 * 1000)
|
|
|
+}
|
|
|
+onMounted(() => {
|
|
|
+ iconsArr.videoCloseIcon = cacheManager.get('projectImg').video_close_icon;
|
|
|
+ iconsArr.videoPlayIcon = cacheManager.get('projectImg').video_play_icon;
|
|
|
+});
|
|
|
+defineExpose({
|
|
|
+ init,
|
|
|
+ showVideoBtn
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.zhuapai-drop-container {
|
|
|
+ width: 180rpx;
|
|
|
+ height: 400rpx;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ z-index: 10;
|
|
|
+ position: absolute;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .phone-camera-box-zhuapai {
|
|
|
+ width: 100%;
|
|
|
+ height: 240rpx;
|
|
|
+ position: absolute;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .uni-video-container {
|
|
|
+ background-color: transparent;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .canvas-view-box,
|
|
|
+ .hidden-video {
|
|
|
+ transform: translateY(10000rpx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-view-box {
|
|
|
+ width: 100%;
|
|
|
+ height: 240rpx;
|
|
|
+ position: absolute;
|
|
|
+ }
|
|
|
+
|
|
|
+ .shiti-video-hidden-btn,
|
|
|
+ .shiti-video-show-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+
|
|
|
+ icon{
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ display: block;
|
|
|
+ background-size: cover;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .shiti-video-hidden-btn {
|
|
|
+ width: 60rpx;
|
|
|
+ height: 60rpx;
|
|
|
+ left: 0;
|
|
|
+
|
|
|
+ icon {
|
|
|
+ margin: 6rpx auto 6rpx 6rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .shiti-video-show-btn {
|
|
|
+ background-color: #dcfbf1;
|
|
|
+ padding: 20rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ right: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|