You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

viewfinder.vue 11KB

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