Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

viewfinder.vue 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template>
  2. <view style="width: 100%;height: 100vh;position: fixed;left: 0;top:0;z-index: 111111;">
  3. <view ref="targetView" class="container viewfinder">
  4. <view v-if="phoneType == 1"
  5. style="display: flex;align-items: center;justify-content: flex-end;width: 100%;height: 100%;">
  6. <image src="/static/image/etc_bd_ocr_id_card_locator_front.png"
  7. style="height: 220rpx;margin-right: 30rpx;" mode="heightFix"></image>
  8. </view>
  9. <view v-if="phoneType == 2" style="margin: 30rpx 0 0 30rpx;">
  10. <image src="/static/image/etc_bd_ocr_id_card_locator_back.png" style="height: 160rpx;" mode="heightFix">
  11. </image>
  12. </view>
  13. <view v-if="phoneType == 3"
  14. style="margin: -30rpx 0 0 30rpx;display: flex;height: 100%;align-self: flex-end;flex-direction: column;justify-content: flex-end;">
  15. <view style="border: 1px solid #fff;height: 160rpx;width: 160rpx;">
  16. </view>
  17. </view>
  18. <view v-if="phoneType == 4"
  19. style="margin: -30rpx 30rpx 0 0;display: flex;height: 100%;justify-content: flex-end;align-items: flex-end;">
  20. <view style="border: 1px solid #fff;height: 60rpx;width: 300rpx;">
  21. </view>
  22. </view>
  23. <canvas canvas-id="canvasbg" id="canvasbg" style="width: 100%;height: 100%;"></canvas>
  24. </view>
  25. <!-- camera -->
  26. <camera v-if="startPhoto" id="camera" style="height: 70vh;width: 100vh;background-color: black;width: 100%;"
  27. mode="normal" :device-position="cameraPosition" :flash="flash" @stop="cameraStop" @error="cameraError" />
  28. <view v-if="!startPhoto" style="height: 70vh;background-color: black;">
  29. <!-- <image style="position: absolute;top: 20%;left: 10%;touch-action: none;" :src="srcImg"
  30. @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd"
  31. :style="{ transform: `translate(${offsetX}px, ${offsetY}px) rotate(${rotateDegree}deg) scale(${scale})`, transition: 'transform ' + transitionDuration + 's' }">
  32. </image> -->
  33. <canvas canvas-id="canvasId" id="canvasId" style="height: 98vh;width: 100vh;" @touchstart="handleTouchStart"
  34. @touchmove="handleTouchMove" @touchend="handleTouchEnd"
  35. :style="{ transform: `translate(${offsetX}px, ${offsetY}px) rotate(${rotateDegree}deg) scale(${scale})`, transition: 'transform ' + transitionDuration + 's' }"></canvas>
  36. </view>
  37. <view style="background-color: #A5A5A5;top: 60%;width: 100%;height: 40%;position: absolute;">
  38. <view style="display: flex;flex-direction: row;justify-content: center;align-items: center;height: 100%;">
  39. <view style="flex: 1;"></view>
  40. <image v-if="false" @click="camera" src="/static/image/etc_bd_ocr_cancel.png"
  41. style="width: 50rpx;height: 50rpx;"></image>
  42. <view style="flex: 1;"></view>
  43. <image @click="takePhoto"
  44. :src="startPhoto ? '/static/image/etc_bd_ocr_take_photo_normal.png': '/static/etc_bd_ocr_rotate.png'"
  45. :style="startPhoto ? 'width: 140rpx;height: 140rpx;' : 'width: 80rpx;height: 80rpx;'"></image>
  46. <view style="flex: 1;"></view>
  47. <image v-if="false" @click="success" src="/static/image/etc_bd_ocr_confirm.png"
  48. style="width: 50rpx;height: 50rpx;"></image>
  49. <view style="flex: 1;"></view>
  50. </view>
  51. </view>
  52. </view>
  53. </template>
  54. <script setup lang="ts">
  55. import {
  56. ref,
  57. getCurrentInstance,
  58. onMounted,
  59. nextTick
  60. } from 'vue'
  61. let prop = defineProps({
  62. phoneType: {
  63. type: Number,
  64. default: function () {
  65. return 3 //1 身份证正面 2 身份证反面 3 行驶证正面 4 行驶证反面
  66. }
  67. },
  68. showStartPhoto: {
  69. type: Boolean,
  70. default: function () {
  71. return true
  72. }
  73. },
  74. images: {
  75. type: String
  76. }
  77. });
  78. console.log('输出内容', prop.phoneType)
  79. const emit = defineEmits<{
  80. (e : "confirmReturn", content : any) : void;
  81. (e : "failReturn", content : any) : void;
  82. (e : "camera") : void;
  83. }>();
  84. let cameraPosition = 'back' // 'back'为后置摄像头,'front'为前置摄像头
  85. let flash = 'off' // 'off'为关闭闪光灯,'on'为打开闪光灯
  86. let scaleStep = 0.5 //每次缩放的最大增量
  87. let srcImg = ref(prop.images)
  88. let rotateDegree = ref(0)
  89. let touchStartDistance = ref(0)
  90. let touchMoveDistance = ref(0)
  91. let scale = ref(1)
  92. let transitionDuration = ref(0)
  93. let startPhoto = ref(prop.showStartPhoto)
  94. const startX = ref(0);
  95. const startY = ref(0);
  96. const offsetX = ref(0);
  97. const offsetY = ref(0);
  98. const dragging = ref(false);
  99. const canvasId = 'canvasId';
  100. const targetView = ref();
  101. const currentInstance = ref();
  102. onMounted(() => {
  103. currentInstance.value = getCurrentInstance()
  104. if (!prop.showStartPhoto) {
  105. emit('confirmReturn', { tempImagePath: prop.images })
  106. }
  107. })
  108. const handleTouchStart = (event : TouchEvent) => {
  109. console.log('手柄触摸启动', '==1')
  110. if (event.touches.length === 1) {
  111. startX.value = event.changedTouches[0].pageX - offsetX.value;
  112. startY.value = event.changedTouches[0].pageY - offsetY.value;
  113. dragging.value = true;
  114. transitionDuration.value = 0.15
  115. } else if (event.touches.length === 2) {
  116. touchStartDistance.value = getTouchDistance(event.touches);
  117. transitionDuration.value = 0.5
  118. }
  119. };
  120. const handleTouchMove = (event : TouchEvent) => {
  121. console.log('手柄触摸启动', '==2')
  122. if (event.touches.length === 2) {
  123. touchMoveDistance.value = getTouchDistance(event.touches);
  124. updateScale();
  125. } else {
  126. if (dragging.value) {
  127. offsetX.value = event.changedTouches[0].pageX - startX.value;
  128. offsetY.value = event.changedTouches[0].pageY - startY.value;
  129. }
  130. }
  131. };
  132. function getTouchDistance(touches) {
  133. const x = touches[0].clientX - touches[1].clientX;
  134. const y = touches[0].clientY - touches[1].clientY;
  135. return Math.sqrt(x * x + y * y);
  136. }
  137. const handleTouchEnd = () => {
  138. console.log('手柄触摸启动', '==3')
  139. touchStartDistance.value = 0;
  140. touchMoveDistance.value = 0;
  141. dragging.value = false;
  142. };
  143. function updateScale() {
  144. // 计算缩放的增量
  145. const scales = (touchMoveDistance.value - touchStartDistance.value) * scaleStep;
  146. scale.value = Math.max(0.5, Math.min(scale.value + scales, 1)); // 设置缩放的范围,这里设置最小为1,最大为3
  147. }
  148. function takePhoto() {
  149. console.log('输出内容', rotateDegree.value)
  150. if (prop.showStartPhoto) {
  151. let cameraContext = null
  152. // #ifdef MP-WEIXIN
  153. cameraContext = uni.createCameraContext();
  154. // #endif
  155. // #ifdef MP-ALIPAY
  156. cameraContext = uni.createCameraContext('camera');
  157. // #endif
  158. // 调用拍照方法
  159. cameraContext.takePhoto({
  160. quality: 'normal',
  161. success: (res : any) => {
  162. // 在这里处理拍照成功后的逻辑,res.tempImagePath 为拍照的图片路径
  163. // srcImg.value = res.tempImagePath
  164. startPhoto.value = false
  165. cameraContext.stopRecord();
  166. getViewPosition(res.tempImagePath)
  167. console.log('拍照成功:', res.tempImagePath);
  168. },
  169. fail: (error) => {
  170. console.error('拍照失败:', error);
  171. emit('failReturn', error);
  172. },
  173. });
  174. } else {
  175. rotateDegree.value += 90
  176. }
  177. // console.log('输出内容', '===123')
  178. }
  179. const getViewPosition = (src : any) => {
  180. const context = uni.createCanvasContext(canvasId, currentInstance.value)
  181. const query = uni.createSelectorQuery().in(currentInstance.value)
  182. // query.in(this)
  183. query.select('.container').boundingClientRect((rect : any) => {
  184. if (rect) {
  185. // 获取图片信息
  186. uni.getImageInfo({
  187. src: src,
  188. success: (imgS) => {
  189. console.log('输出内容', imgS.width, imgS.height)
  190. // canvas.drawImage(img, 起始 x 坐标, 起始 y 坐标, 宽度, 高度, 绘制到 Canvas 的起始 x 坐标, 绘制到 Canvas 的起始 y 坐标, 裁剪后的宽度, 裁剪后的高度);
  191. context.drawImage(src, rect.left + 30, rect.top + 140, rect.width * 1.8, rect.height * 1.8, rect.left, rect.top, rect.width, rect.height);
  192. context.draw(false, () => {
  193. uni.canvasToTempFilePath({
  194. canvasId: canvasId,
  195. x: rect.left,
  196. y: rect.top,
  197. width: rect.width,
  198. height: rect.height,
  199. destWidth: rect.width,
  200. destHeight: rect.height,
  201. success(res : any) {
  202. // res.tempFilePath 是转换后的图片路径
  203. console.log(res.tempFilePath);
  204. res.tempImagePath = res.tempFilePath
  205. emit('confirmReturn', res)
  206. },
  207. fail(err) {
  208. console.error('canvasToTempFilePath failed', err);
  209. }
  210. }, currentInstance.value)
  211. });
  212. },
  213. fail: (error) => {
  214. console.error('获取图片信息失败', error);
  215. },
  216. });
  217. //把当前画布指定区域的内容导出生成指定大小的图片,并返回文件路径。在自定义组件下,第二个参数传入自定义组件实例,以操作组件内 <canvas> 组件。
  218. console.log('视图的位置信息:', rect);
  219. console.log('视图的 x 坐标:', rect.left);
  220. console.log('视图的 y 坐标:', rect.top);
  221. console.log('视图的宽度:', rect.width);
  222. console.log('视图的高度:', rect.height);
  223. }
  224. }).exec();
  225. };
  226. //成功
  227. function success() { }
  228. //获取节点信息
  229. function boundingClientRect(fun : any) {
  230. const query = uni.createSelectorQuery().in(currentInstance.value)
  231. query.select('.container').boundingClientRect((rect : any) => {
  232. if (rect) {
  233. //把当前画布指定区域的内容导出生成指定大小的图片,并返回文件路径。在自定义组件下,第二个参数传入自定义组件实例,以操作组件内 <canvas> 组件。
  234. console.log('视图的位置信息:', rect);
  235. console.log('视图的 x 坐标:', rect.left);
  236. console.log('视图的 y 坐标:', rect.top);
  237. console.log('视图的宽度:', rect.width);
  238. console.log('视图的高度:', rect.height);
  239. fun(rect)
  240. }
  241. }).exec();
  242. }
  243. //重新拍摄
  244. function camera() {
  245. emit('camera')
  246. }
  247. function cameraStop() {
  248. // 相机停止时的回调
  249. console.log('相机已停止');
  250. }
  251. function cameraError(e) {
  252. // 相机出错时的回调
  253. console.error('相机错误:', e.detail.errMsg);
  254. }
  255. </script>
  256. <style>
  257. .viewfinder {
  258. pointer-events: none;
  259. position: absolute;
  260. top: 180px;
  261. left: 40px;
  262. width: 80%;
  263. height: 400rpx;
  264. border: 2px solid #fff;
  265. box-sizing: border-box;
  266. border-radius: 16rpx;
  267. z-index: 2;
  268. }
  269. /* .content {
  270. position: absolute;
  271. top: 50%;
  272. left: 50%;
  273. transform: translate(-50%, -50%);
  274. }
  275. */
  276. </style>