Przeglądaj źródła

Merge branch '2025北京诚祥-家政版' of https://gogs.mtavip.com/wangguoyu/uniProject into 2025北京诚祥-家政版

tanxue 3 tygodni temu
rodzic
commit
4de31d1666

+ 357 - 0
components/zhuapaiConfirm/zhuapai - 副本.vue

@@ -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>

+ 153 - 25
components/zhuapaiConfirm/zhuapai.vue

@@ -5,8 +5,6 @@
 			<camera class="camera" device-position="front" flash="off" style="width: 200px;height: 200px" :class="myClass" @initdone="onVideoSuccess" @error="handleError" @stop="handleStop"></camera>
 			<camera class="camera" device-position="front" flash="off" style="width: 200px;height: 200px" :class="myClass" @initdone="onVideoSuccess" @error="handleError" @stop="handleStop"></camera>
 			<!-- 用于抓拍切出去传递固定img-->
 			<!-- 用于抓拍切出去传递固定img-->
 			<img :src="imgUrl" alt="" v-show="false">
 			<img :src="imgUrl" alt="" v-show="false">
-			<!-- 测试抓拍使用 -->
-			<!-- <button @click="handleZhua">抓拍</button> -->
 		</view>
 		</view>
 		<view v-show="showVideo" @click="noShowVideoBtn" class="shiti-video-hidden-btn">
 		<view v-show="showVideo" @click="noShowVideoBtn" class="shiti-video-hidden-btn">
 			<icon :style="{ backgroundImage: 'url(' + iconsArr.videoCloseIcon + ')' }"></icon>
 			<icon :style="{ backgroundImage: 'url(' + iconsArr.videoCloseIcon + ')' }"></icon>
@@ -14,11 +12,16 @@
 		<view v-show="!showVideo" @click="showVideoBtn" class="shiti-video-show-btn">
 		<view v-show="!showVideo" @click="showVideoBtn" class="shiti-video-show-btn">
 			<icon :style="{ backgroundImage: 'url(' + iconsArr.videoPlayIcon + ')' }"></icon>
 			<icon :style="{ backgroundImage: 'url(' + iconsArr.videoPlayIcon + ')' }"></icon>
 		</view>
 		</view>
+    <canvas
+        canvas-id="frameCanvas"
+        id="frameCanvas"
+        style="position: absolute;top:9999px;left: 0px;width: 100vw; height: 100vh;z-index:-2"
+    ></canvas>
 	</view>
 	</view>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-	import {ref,onUnmounted,nextTick,computed,onMounted,reactive} from "vue";
+	import {ref,onUnmounted,nextTick,computed,onMounted,reactive,getCurrentInstance} from "vue";
 	import cacheManager from '@/utils/cacheManager.js';
 	import cacheManager from '@/utils/cacheManager.js';
 	import * as ksApi from "@/api/kaoshi.js"
 	import * as ksApi from "@/api/kaoshi.js"
 	import {
 	import {
@@ -34,6 +37,13 @@
 
 
 	const cameraContext = ref(null);
 	const cameraContext = ref(null);
 
 
+  // 响应式数据
+  const frameListener = ref(null)
+  const isCapturing = ref(false)
+  const capturedImage = ref('')
+  const currentFrame = ref(null) // 存储当前帧数据
+  const myInstanceR = getCurrentInstance()
+
 	const DropRef = ref(null);
 	const DropRef = ref(null);
 	const DropContainerRef = ref(null);
 	const DropContainerRef = ref(null);
 	const zhuapai = ref(0); // 单位分
 	const zhuapai = ref(0); // 单位分
@@ -63,6 +73,8 @@
 
 
 	const emits = defineEmits(['init', 'success', 'error', 'cancel', 'progress'])
 	const emits = defineEmits(['init', 'success', 'error', 'cancel', 'progress'])
 
 
+  let timer1 = null;
+
 	function noShowVideoBtn() {
 	function noShowVideoBtn() {
 		showVideo.value = false
 		showVideo.value = false
 	}
 	}
@@ -125,7 +137,10 @@
 			nextTick(() => {
 			nextTick(() => {
 				startCamera()
 				startCamera()
 				// 设定计时器
 				// 设定计时器
-				setInterval(() => handleZhua(), zhuapai.value * 60 * 1000)
+        console.log('抓拍设定', zhuapai.value)
+				setInterval(() => {
+          handleZhua()
+        }, zhuapai.value * 60 * 1000)
 			})
 			})
 
 
 		}
 		}
@@ -135,8 +150,9 @@
 	function startCamera() {
 	function startCamera() {
 		// 请求摄像头权限并获取流
 		// 请求摄像头权限并获取流
 		cameraContext.value = uni.createCameraContext();
 		cameraContext.value = uni.createCameraContext();
-	}
-	
+    startFrameListener()
+  }
+
 	function urlToBase64(url) {
 	function urlToBase64(url) {
 		return new Promise((resolve, reject) => {
 		return new Promise((resolve, reject) => {
 			// #ifdef MP-WEIXIN
 			// #ifdef MP-WEIXIN
@@ -156,9 +172,9 @@
 			// #endif
 			// #endif
 		});
 		});
 	}
 	}
-	
-	
+
 	function getSnapShotImage(data) {
 	function getSnapShotImage(data) {
+    console.log('转转图片成base64')
 		urlToBase64(data).then((d1) => {
 		urlToBase64(data).then((d1) => {
 			const imgData = d1.split(';base64,');
 			const imgData = d1.split(';base64,');
 			
 			
@@ -172,7 +188,7 @@
 				suffix: 'png',
 				suffix: 'png',
 				
 				
 			};
 			};
-				
+      console.log('上传图片')
 			getFileUpload(opt).then(res => {
 			getFileUpload(opt).then(res => {
 				const dOption = {
 				const dOption = {
 					operId:operId.value,
 					operId:operId.value,
@@ -181,6 +197,8 @@
 				ksApi.getClientZhuaPaiUpdate(dOption)
 				ksApi.getClientZhuaPaiUpdate(dOption)
 					.then(res => {
 					.then(res => {
 						console.log('【源 : 获取抓拍数据】');
 						console.log('【源 : 获取抓拍数据】');
+            // 抓拍成功 继续监听
+            frameListener.value.start()
 					})
 					})
 					.catch(err => {
 					.catch(err => {
 						console.error('源 :抓拍接口异常', err);
 						console.error('源 :抓拍接口异常', err);
@@ -205,7 +223,6 @@
 		
 		
 	}
 	}
 	
 	
-	
 	function doZhuaiPai(ImageFile) {
 	function doZhuaiPai(ImageFile) {
 		try {
 		try {
 			if (zhuapaiStore.status == 0) {
 			if (zhuapaiStore.status == 0) {
@@ -220,22 +237,15 @@
 	}
 	}
 
 
 	function handleZhua() {
 	function handleZhua() {
-		cameraContext.value.takePhoto({
-			quality: 'high',
-			success: (res) => {
-			  doZhuaiPai(res.tempImagePath)
-			},
-			fail: (err) => {
-			  console.error('拍照失败:', err);
-			}
-		});
+    console.log('抓拍')
+    captureSilently();
 	}
 	}
 
 
 	function onVideoSuccess() {
 	function onVideoSuccess() {
-		// setTimeout(() => {
-		// 	// 首次运行进行抓拍一次
-		// 	handleZhua();
-		// }, 3000);
+/*		setTimeout(() => {
+			// 首次运行进行抓拍一次
+			handleZhua();
+		}, 3000);*/
 	}
 	}
 
 
 	function onVideoError() {
 	function onVideoError() {
@@ -272,17 +282,135 @@
 		isBuffer.value = true;
 		isBuffer.value = true;
 		console.log('onProgress')
 		console.log('onProgress')
 		// buffer时间增大到3秒 过滤掉后续的onTimeupdate
 		// buffer时间增大到3秒 过滤掉后续的onTimeupdate
-		setTimeout(() => {isBuffer.value = false}, 3000)
+    timer1 = setTimeout(() => {isBuffer.value = false}, 3000)
 		// 视频中途暂停被占用
 		// 视频中途暂停被占用
 		stopTimer.value = setTimeout(() => {
 		stopTimer.value = setTimeout(() => {
 			emits('progress', false);
 			emits('progress', false);
 			console.log('结束')
 			console.log('结束')
 		}, 10 * 1000)
 		}, 10 * 1000)
 	}
 	}
-	onMounted(() => {
+
+  /******************************************************/
+  // 开始监听相机帧数据
+  function startFrameListener(){
+    if (!cameraContext.value) {
+      uni.showToast({ title: '相机未初始化', icon: 'none' })
+      return
+    }
+
+    frameListener.value = cameraContext.value.onCameraFrame((frame) => {
+      // 存储当前帧数据,用于后续抓拍
+      currentFrame.value = frame
+      // console.log('接收帧数据:', frame.width, frame.height)
+    })
+
+    frameListener.value.start()
+    isCapturing.value = true
+    // uni.showToast({ title: '开始监控帧数据', icon: 'success' })
+  }
+
+  // 停止监听帧数据
+  function stopFrameListener(){
+    if (frameListener.value) {
+      frameListener.value.stop()
+      frameListener.value = null
+    }
+    isCapturing.value = false
+    // uni.showToast({ title: '已停止监控', icon: 'success' })
+  }
+  // 静音抓拍核心函数
+  async function captureSilently (){
+
+    if (!isCapturing.value || !currentFrame.value) {
+      uni.showToast({ title: '请先开始监控帧数据', icon: 'none' })
+      return
+    }
+    try {
+      console.log('停止前')
+      // 临时停止监听以避免数据冲突
+      stopFrameListener()
+      console.log('开始绘制',)
+      // 将帧数据绘制到Canvas并导出为图片
+      await drawFrameToCanvas(currentFrame.value).then(url => {
+        console.log('获取地址')
+        getSnapShotImage(url)
+      })
+      // 重新开始监听
+      startFrameListener()
+    } catch (error) {
+      console.log('errrrr',error)
+      handleError();
+    }
+  }
+
+  // 将帧数据绘制到Canvas
+  function drawFrameToCanvas(frame) {
+    return new Promise((resolve, reject) => {
+      try {
+        // 确保有有效的帧数据
+        if (!frame || !frame.data || !frame.width || !frame.height) {
+          reject(new Error('无效的帧数据'))
+          return
+        }
+        // 将ArrayBuffer转换为Uint8ClampedArray[4](@ref)
+        const dataArray = new Uint8Array(frame.data)
+        const clampedArray = new Uint8ClampedArray(dataArray)
+        console.log('clampedArray', clampedArray.length)
+        // 使用wx.canvasPutImageData将帧数据绘制到Canvas[4](@ref)
+        wx.canvasPutImageData({
+          canvasId: 'frameCanvas',
+          data: clampedArray,
+          x: 0,
+          y: 0,
+          width: frame.width,
+          height: frame.height,
+          success: () => {
+            // 绘制成功后,将Canvas内容转换为临时图片文件
+            wx.canvasToTempFilePath({
+              canvasId: 'frameCanvas',
+              destWidth: frame.width,
+              destHeight: frame.height,
+              fileType: 'png',
+              quality: 0.7,
+              success: (res) => {
+                capturedImage.value = res.tempFilePath
+                resolve(res.tempFilePath)
+              },
+              fail: (err) => {
+                console.log('5555',err)
+                reject(new Error(`生成图片失败: ${err.errMsg}`))
+              }
+            }, myInstanceR.proxy)
+          },
+          fail: (err) => {
+            console.log('6666',err)
+            reject(new Error(`绘制到Canvas失败: ${err.errMsg}`))
+          }
+        }, myInstanceR.proxy)
+
+      } catch (err) {
+
+        console.log('ccccc',err)
+      }
+    })
+  }
+
+  /******************************************************/
+
+  onMounted(() => {
 		iconsArr.videoCloseIcon = cacheManager.get('projectImg').video_close_icon;
 		iconsArr.videoCloseIcon = cacheManager.get('projectImg').video_close_icon;
 		iconsArr.videoPlayIcon = cacheManager.get('projectImg').video_play_icon;
 		iconsArr.videoPlayIcon = cacheManager.get('projectImg').video_play_icon;
 	});
 	});
+
+  onUnmounted(() => {
+    stopFrameListener()
+
+    if (timer1) {
+      clearTimeout(timer1);
+      timer1 = null;
+    }
+  })
+
 	defineExpose({
 	defineExpose({
 		init,
 		init,
 		showVideoBtn
 		showVideoBtn

+ 25 - 3
pages/client/Kaoshi/exam.vue

@@ -80,7 +80,7 @@
 		<template v-if="data.zhuapai && data.zhuapai > 0">
 		<template v-if="data.zhuapai && data.zhuapai > 0">
 			<!-- 抓拍 -->
 			<!-- 抓拍 -->
 			<zhuapaiVue ref="zhuapaiRef" @error="zpError" @success="zpSuccess" key="2" @progress="onProgress">
 			<zhuapaiVue ref="zhuapaiRef" @error="zpError" @success="zpSuccess" key="2" @progress="onProgress">
-			</zhuapaiVue>
+      </zhuapaiVue>
 		</template>
 		</template>
 
 
 		<!-- 交卷确认 -->
 		<!-- 交卷确认 -->
@@ -145,6 +145,26 @@
 		init: qiepingInit
 		init: qiepingInit
 	} = useQiePing();
 	} = useQiePing();
 
 
+  function debounce(func, wait) {
+    let timeout;
+    return function (...args) {
+      const context = this;
+      clearTimeout(timeout);
+      timeout = setTimeout(() => {
+        func.apply(context, args);
+      }, wait);
+    };
+  }
+  const handleBack = debounce(() => {
+    console.log('back')
+    const pages = getCurrentPages();
+    if (pages.length > 1) {
+      uni.navigateBack()
+    } else {
+      history.back();
+    }
+  },1000)
+
 	onLoad((option) => {
 	onLoad((option) => {
 		data.ksId = option.ksId;
 		data.ksId = option.ksId;
 		data.zhuapai = option.zhuapai;
 		data.zhuapai = option.zhuapai;
@@ -507,14 +527,16 @@
 
 
 	}
 	}
 
 
-  function handleBack () {
+
+
+  /*function handleBack () {
     const pages = getCurrentPages();
     const pages = getCurrentPages();
     if (pages.length > 1) {
     if (pages.length > 1) {
       uni.navigateBack()
       uni.navigateBack()
     } else {
     } else {
       history.back();
       history.back();
     }
     }
-  }
+  }*/
 
 
 	function showAnswerCard() {
 	function showAnswerCard() {
 		popupRef.value.open('top')
 		popupRef.value.open('top')