scan-frame.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <template>
  2. <view class="cameraBg">
  3. <!-- position: absolute;left: 100000px; 隐藏canvas画布 -->
  4. <canvas style="width: 100%; height: 100vh;position: absolute;left: 100000px;" canvas-id="canvas" id="canvas"></canvas>
  5. <!-- 相机组件 -->
  6. <camera device-position="back" @initdone="cameraInit" flash="auto" style="width: 100%; height: 100vh">
  7. <cover-image :src="userInfo.sysUserFlag == '0'?'/static/scan-frame/cameraBg1.png':'/static/scan-frame/cameraBg.png'" class="cover-img"> </cover-image>
  8. <cover-view class="noAuth" v-if="showAuth" @click="openAuth">
  9. 您未开启摄像机权限,<cover-view class="btns">请点击开启权限</cover-view>
  10. </cover-view>
  11. <!-- <cover-view class="tips" v-if="userInfo.sysUserFlag == '0'">个人用户限10次/天</cover-view> -->
  12. <cover-view class="scanBtn" v-if="scanShow">
  13. <cover-view class="beat" @click="scan">
  14. <cover-image class="beatImg" src="@/static/scan-frame/album.png"></cover-image>
  15. <cover-view> 相册 </cover-view>
  16. </cover-view>
  17. <cover-view class="album" @click="takePhoto">
  18. <cover-image class="albumImg" src="@/static/scan-frame/beat.png"></cover-image>
  19. <cover-view> 拍照 </cover-view>
  20. </cover-view>
  21. <cover-view class="inputs" @click="inputs">
  22. <cover-image class="beatImg" src="@/static/scan-frame/inputs.png"></cover-image>
  23. <cover-view> 手动输入 </cover-view>
  24. </cover-view>
  25. </cover-view>
  26. </camera>
  27. </view>
  28. </template>
  29. <script>
  30. import {
  31. mapState,
  32. mapMutations,
  33. } from 'vuex'
  34. import { vinCode } from '@/api/car.js'
  35. export default {
  36. data() {
  37. return {
  38. scanShow: true, // 显示操作按钮
  39. sysinfo: null, // 设备信息
  40. imageInfo: null ,// 拍照图片信息
  41. showAuth: false,
  42. pageType: 'VIN', // 用途,VIN 码识别 或 UUID 唯一码识别
  43. }
  44. },
  45. onLoad(opts) {
  46. if(opts && opts.pageType){
  47. this.pageType = opts.pageType
  48. }
  49. },
  50. onReady() {
  51. const sysinfo = uni.getSystemInfoSync()
  52. this.sysinfo = sysinfo
  53. },
  54. onShow() {
  55. const _this = this
  56. uni.getSetting({
  57. success(res) {
  58. _this.showAuth = !res.authSetting['scope.camera']
  59. }
  60. })
  61. },
  62. computed: {
  63. ...mapState(['hasLogin','vuex_vinScanNums','vuex_scanMaxNums']),
  64. userInfo(){
  65. return this.$store.state.vuex_userInfo
  66. }
  67. },
  68. methods: {
  69. cameraInit(e){
  70. this.showAuth = false
  71. },
  72. openAuth(){
  73. uni.openSetting({
  74. success(res) {
  75. if(res.authSetting['scope.camera']){
  76. uni.navigateBack()
  77. }
  78. }
  79. })
  80. },
  81. // 手动输入
  82. inputs(){
  83. if(this.pageType == 'VIN'){
  84. uni.redirectTo({
  85. url: "/pages/vinInput/confirmVin?verifyCode=" + "&type=input"
  86. })
  87. }
  88. if(this.pageType == 'UUID'){
  89. this.$store.state.vuex_tempData = 'inputCode'
  90. uni.navigateBack()
  91. }
  92. },
  93. // 相册
  94. scan() {
  95. // 选择图片
  96. uni.chooseImage({
  97. count: 1,
  98. sizeType: ['original', 'compressed'],
  99. sourceType: ['album'],
  100. success: (res) => {
  101. this.compress(res.tempFilePaths[0],false)
  102. }
  103. })
  104. },
  105. delImgs(filePath){
  106. // #ifdef MP-WEIXIN
  107. const fs = wx.getFileSystemManager();
  108. try {
  109. const res = fs.unlinkSync(filePath)
  110. console.log(res)
  111. } catch(e) {
  112. console.error(e)
  113. }
  114. // #endif
  115. },
  116. // 裁剪图片
  117. cutImg(fileUrl){
  118. const vm = this
  119. const ctx = uni.createCanvasContext('canvas')
  120. // 将拍照的图片绘制到canvas,宽高等于窗口宽高和手机窗口保持一致
  121. ctx.drawImage(fileUrl,0,0,this.sysinfo.windowWidth,this.sysinfo.windowHeight)
  122. ctx.draw(true,()=>{
  123. const cutW = 650 // 裁剪框宽度
  124. const cutH = 160 // 裁剪框高度
  125. const cutT = 400 // 裁剪框距离顶部距离
  126. const cutL = 50 // 裁剪框距离左侧距离
  127. // 以上裁剪框宽高及坐标是相对于实际蒙板图片的位置
  128. const canvasW=cutW/750*this.sysinfo.windowWidth; // canvas 画布实际宽度
  129. const canvasH=cutH/1624*this.sysinfo.windowHeight; // canvas 画布实际高度
  130. const canvasL =cutL/750*this.sysinfo.windowWidth; // canvas 画布开始坐标x
  131. const canvasT= cutT/1624*this.sysinfo.windowHeight; // canvas 画布开始坐标y
  132. // 开始裁剪
  133. uni.canvasToTempFilePath({
  134. x: canvasL,
  135. y: canvasT,
  136. width: canvasW,
  137. height: canvasH,
  138. canvasId: 'canvas',
  139. success: function(res) {
  140. // 在H5平台下,tempFilePath 为 base64
  141. vm.parseImgs(res.tempFilePath)
  142. // 删除临时文件
  143. // vm.delImgs(fileUrl)
  144. }
  145. })
  146. })
  147. },
  148. // 启动图片压缩
  149. compress(tempFilePaths,isCut) {
  150. const vm = this
  151. vm.scanShow = false
  152. uni.showLoading({
  153. title: '智能识别中...'
  154. })
  155. uni.compressImage({
  156. src: tempFilePaths,
  157. quality: 100,
  158. success: (imageRes) => {
  159. // 获取类型
  160. uni.getImageInfo({
  161. src: imageRes.tempFilePath,
  162. success(imageInfo) {
  163. console.log(imageInfo)
  164. vm.imageInfo = imageInfo
  165. if(isCut){
  166. // 裁剪图片
  167. vm.cutImg(imageRes.tempFilePath)
  168. }else{
  169. // 相册选择的图片直接解析,这里如果需要裁剪可以跳转到裁剪图片页面在继续下一步
  170. vm.parseImgs(imageRes.tempFilePath)
  171. }
  172. }
  173. })
  174. }
  175. })
  176. },
  177. // 拿到图片转base64后在开始进行识别
  178. parseImgs(filePath) {
  179. // 图片转base64格式
  180. uni.getFileSystemManager().readFile({
  181. filePath: filePath,
  182. encoding: 'base64',
  183. success: (base) => {
  184. // 拿到base64,不需要base64 就把上层的转换去掉
  185. this.scanShow = true
  186. // 此处为后端接口 传base64图片 进行ocr识别
  187. if(this.pageType == 'VIN' && this.userInfo.sysUserFlag == '0'){
  188. if(this.vuex_vinScanNums < this.vuex_scanMaxNums){
  189. this.parseVinNo(base.data)
  190. }else{
  191. uni.showModal({
  192. title: '提示',
  193. content: '个人用户扫描VIN仅限10次,您的次数已用完!',
  194. confirmText: '好的',
  195. showCancel: false,
  196. success(res) {}
  197. })
  198. }
  199. }else{
  200. this.parseVinNo(base.data)
  201. }
  202. },
  203. fail: (err) => {
  204. uni.hideLoading()
  205. console.log(err)
  206. }
  207. })
  208. },
  209. // 点击开始拍照
  210. takePhoto() {
  211. // 获取相机上下文对象
  212. const ctx = uni.createCameraContext()
  213. ctx.takePhoto({
  214. quality: 'high',
  215. success: (res) => {
  216. this.compress(res.tempImagePath,true)
  217. }
  218. })
  219. },
  220. // 识别vin
  221. parseVinNo(data) {
  222. const _this = this
  223. const base64Str = 'data:image/' + this.imageInfo.type + ';base64,' + data
  224. vinCode({
  225. imageByte: data
  226. }).then(res => {
  227. console.log(res, '识别VIN')
  228. if(res.status == 200){
  229. if(this.pageType == 'VIN'){
  230. uni.redirectTo({
  231. url: "/pages/vinInput/confirmVin?filePath="+base64Str + "&verifyCode="+res.data + "&type=carmen"
  232. })
  233. }else{
  234. this.$store.state.vuex_tempData = {filePath:base64Str,retCode:res.data}
  235. uni.navigateBack()
  236. }
  237. }else{
  238. uni.showToast({
  239. icon: 'none',
  240. title: res.message
  241. })
  242. }
  243. uni.hideLoading()
  244. }).catch(err => {
  245. uni.hideLoading()
  246. uni.showToast({
  247. icon: 'none',
  248. title: '系统错误或超时,请重试'
  249. })
  250. })
  251. },
  252. error(e) {
  253. console.log(e.detail)
  254. }
  255. }
  256. }
  257. </script>
  258. <style lang="scss" scoped>
  259. .cameraBg {
  260. width: 100%;
  261. height: 100vh;
  262. position: fixed;
  263. .cover-img {
  264. width: 100%;
  265. height: 100vh;
  266. z-index: 1;
  267. }
  268. .noAuth,.tips{
  269. width: 100%;
  270. z-index: 999998;
  271. position: fixed;
  272. bottom: 55vh;
  273. text-align: center;
  274. color: #999;
  275. .btns{
  276. color: dodgerblue;
  277. }
  278. }
  279. .scanBtn {
  280. width: 100%;
  281. z-index: 99999;
  282. position: fixed;
  283. bottom: 100rpx;
  284. display: flex;
  285. flex-direction: column;
  286. justify-content: center;
  287. align-items: center;
  288. .beat {
  289. position: absolute;
  290. bottom: 0rpx;
  291. left: 100rpx;
  292. display: flex;
  293. flex-direction: column;
  294. justify-content: center;
  295. align-items: center;
  296. font-size: 24rpx;
  297. font-weight: 400;
  298. color: #ffffff;
  299. .beatImg {
  300. width: 88rpx;
  301. height: 88rpx;
  302. margin-bottom: 30rpx;
  303. }
  304. }
  305. .inputs{
  306. position: absolute;
  307. bottom: 0rpx;
  308. right: 100rpx;
  309. display: flex;
  310. flex-direction: column;
  311. justify-content: center;
  312. align-items: center;
  313. font-size: 24rpx;
  314. font-weight: 400;
  315. color: #ffffff;
  316. .beatImg {
  317. width: 88rpx;
  318. height: 88rpx;
  319. margin-bottom: 30rpx;
  320. }
  321. }
  322. .album {
  323. display: flex;
  324. flex-direction: column;
  325. justify-content: center;
  326. align-items: center;
  327. font-size: 24rpx;
  328. font-weight: 400;
  329. color: #ffffff;
  330. .albumImg {
  331. width: 120rpx;
  332. height: 120rpx;
  333. margin-bottom: 30rpx;
  334. }
  335. }
  336. }
  337. }
  338. </style>