scan-frame.vue 8.6 KB

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