uni-OCRUploadPopup.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. <template>
  2. <view class='uni-page'>
  3. <u-popup :show="OCRUploadPopup" @close="close" closeable round="20rpx">
  4. <view class="dis dis header a-end j-c">
  5. <u-tabs :list="list" @click="click" :activeStyle="{color:'#333',fontWeight:'bold'}"
  6. :inactiveStyle="{color:'#999'}" :current="current" :scrollable="true" lineColor="#FDE935"
  7. lineWidth="100rpx"></u-tabs>
  8. </view>
  9. <view class="content">
  10. <!-- 车辆类型显示内容 -->
  11. <template v-if="checkType=='carInfo'">
  12. <view class="img" v-if="current==0">
  13. <!-- H5:拖拽上传区域。拖拽/松手事件不绑在模板上(uni-app 事件包装层取不到 dataTransfer),
  14. 而是走 mounted 里 document 级原生监听(见 onDocumentDragOver/onDocumentDrop);
  15. click 为点击选图兜底,data-kind 标记本区域对应的影像场景 -->
  16. <!-- #ifdef H5 -->
  17. <view class="drop-wrap" :class="{'drop-active': dragOver}" @click="chooseFile('carfront')"
  18. data-kind="carfront">
  19. <image :src="carfrontImg?carfrontImg:'/static/image/carbackimg.png'" mode=""></image>
  20. </view>
  21. <!-- #endif -->
  22. <!-- #ifdef APP-PLUS || MP -->
  23. <image :src="carfrontImg?carfrontImg:'/static/image/carbackimg.png'" mode=""
  24. @click="carfrontChange"></image>
  25. <!-- #endif -->
  26. </view>
  27. <view class="img" v-else>
  28. <!-- #ifdef H5 -->
  29. <view class="drop-wrap" :class="{'drop-active': dragOver}" @click="chooseFile('carback')"
  30. data-kind="carback">
  31. <image :src="carbackImg?carbackImg:'/static/image/carbackimg.png'" mode=""></image>
  32. </view>
  33. <!-- #endif -->
  34. <!-- #ifdef APP-PLUS || MP -->
  35. <image :src="carbackImg?carbackImg:'/static/image/carbackimg.png'" mode=""
  36. @click="carbackChange"></image>
  37. <!-- #endif -->
  38. </view>
  39. </template>
  40. <template v-else>
  41. <!-- 人员信息显示内容 -->
  42. <template v-if="current==0">
  43. <!-- 正反面区分按钮 -->
  44. <view class="user-Subsection dis a-c">
  45. <view class="item dis a-c j-c " :class="[toggleModeIndex==index?'active':'']"
  46. v-for="(item,index) in toggleMode" :key="index" @click="Subsectionclick(index)">
  47. {{item.name}}
  48. </view>
  49. </view>
  50. <view class="img" v-if="toggleModeIndex==0">
  51. <!-- #ifdef H5 -->
  52. <view class="drop-wrap" :class="{'drop-active': dragOver}" @click="chooseFile('userfront')"
  53. data-kind="userfront">
  54. <image :src="userfrontImg?userfrontImg:'/static/image/userfrontImg.png'" mode="">
  55. </image>
  56. </view>
  57. <!-- #endif -->
  58. <!-- #ifdef APP-PLUS || MP -->
  59. <image :src="userfrontImg?userfrontImg:'/static/image/userfrontImg.png'" mode=""
  60. @click="userfrontChange"></image>
  61. <!-- #endif -->
  62. </view>
  63. <view class="img" v-else>
  64. <!-- #ifdef H5 -->
  65. <view class="drop-wrap" :class="{'drop-active': dragOver}" @click="chooseFile('userback')"
  66. data-kind="userback">
  67. <image :src="userbackImg?userbackImg:'/static/image/userbackImg.png'" mode=""></image>
  68. </view>
  69. <!-- #endif -->
  70. <!-- #ifdef APP-PLUS || MP -->
  71. <image :src="userbackImg?userbackImg:'/static/image/userbackImg.png'" mode=""
  72. @click="userbackChange"></image>
  73. <!-- #endif -->
  74. </view>
  75. </template>
  76. <template v-else>
  77. <view class="img">
  78. <!-- #ifdef H5 -->
  79. <view class="drop-wrap" :class="{'drop-active': dragOver}"
  80. @click="chooseFile('businessLicense')" data-kind="businessLicense">
  81. <image
  82. :src="businessLicenseImg?businessLicenseImg:'/static/image/businessLicenseimg.png'"
  83. mode=""></image>
  84. </view>
  85. <!-- #endif -->
  86. <!-- #ifdef APP-PLUS || MP -->
  87. <image :src="businessLicenseImg?businessLicenseImg:'/static/image/businessLicenseimg.png'"
  88. mode="" @click="businessLicenseChange"></image>
  89. <!-- #endif -->
  90. </view>
  91. </template>
  92. </template>
  93. <view class="footer dis a-c j-s mt-2">
  94. <u-button class="btn" type="primary" text="关闭" shape="circle" plain color="#FDE935"
  95. :hairline="false" style="margin-right: 20px;" @click="close"></u-button>
  96. <u-button class="btn" type="primary" text="确定" shape="circle" color="#FDE935"
  97. v-if="checkType=='carInfo'" :disabled="!carfrontImg && !carbackImg" :hairline="false"
  98. @click="ocrInt"></u-button>
  99. <u-button class="btn" type="primary" text="确定" shape="circle" color="#FDE935"
  100. v-if="checkType=='ownerInfo'" :disabled="!userfrontImg && !userbackImg && !businessLicenseImg"
  101. :hairline="false" @click="ownerInfoInt"></u-button>
  102. <u-button class="btn" type="primary" text="确定" shape="circle" color="#FDE935"
  103. v-if="checkType=='policyHolderInfo'"
  104. :disabled="!userfrontImg && !userbackImg && !businessLicenseImg" :hairline="false"
  105. @click="policyHolderInfoInt"></u-button>
  106. <u-button class="btn" type="primary" text="确定" shape="circle" color="#FDE935"
  107. v-if="checkType=='insuredPersonInfo'"
  108. :disabled="!userfrontImg && !userbackImg && !businessLicenseImg" :hairline="false"
  109. @click="insuredPersonInfoInt"></u-button>
  110. </view>
  111. </view>
  112. <view class="loading" style="" v-if="loadingShow">
  113. <view class="dis f-c a-c j-c " style="height: 100%;">
  114. <u-loading-icon></u-loading-icon>
  115. <text style="color: #909399;" class="mt-1">{{ loadingText }}</text>
  116. </view>
  117. </view>
  118. </u-popup>
  119. </view>
  120. </template>
  121. <script>
  122. import store from '@/store';
  123. // 各影像类型的 OCR 识别结果解析(字段结构各不相同,不可互用,按 kind 隔离)
  124. const OCR_PARSE = {
  125. // 行驶证正面:车主/车牌/车架号/日期等
  126. carfront(data) {
  127. let rdate = '';
  128. if (!!data.carInfo.registerDate) {
  129. rdate = data.carInfo.registerDate.substr(0, 4) +
  130. '-' + data.carInfo.registerDate.substr(4, 2) + '-' + data.carInfo
  131. .registerDate.substr(6, 2)
  132. }
  133. let isdate = '';
  134. if (!!data.carInfo.issueDate) {
  135. isdate = data.carInfo.issueDate.substr(0, 4) +
  136. '-' + data.carInfo.issueDate.substr(4, 2) + '-' + data.carInfo
  137. .issueDate.substr(6, 2)
  138. }
  139. return {
  140. carOwner: data.carInfo.carOwner, //车主
  141. licenseNo: data.carInfo.plateNo, //车牌号
  142. brandName: data.carInfo.backOcrID, //品牌型号
  143. frameNo: data.carInfo.vin, //车架号
  144. engineNo: data.carInfo.engine, //发动机号
  145. vinNo: data.carInfo.vin, //车架号
  146. issueDate: isdate, //发证日期
  147. registerDate: rdate, //注册日期
  148. carKindCode: data.carInfo.plateType, //车辆种类
  149. carTypeCode: data.carInfo.category, //车辆类型
  150. carnature: data.carInfo.useNature,
  151. vehicleUseCode: data.carInfo.vehicleUse, //车辆用途
  152. }
  153. },
  154. // 行驶证反面:总质量/整备质量/核定载客数/核定载质量
  155. carback(data) {
  156. return {
  157. vehicleweight: data.carInfo.grossMass, //总质量
  158. completeKerbMass: data.carInfo.unladenMass, //整备质量
  159. seatCount: data.carInfo.approvedPassengersCapacity, //核定载客数
  160. limitLoad: data.carInfo.limitLoad, //核定载质量
  161. }
  162. },
  163. // 身份证人像面
  164. userfront(data) {
  165. return {
  166. name: data.customerInfo.name,
  167. identifyNumber: data.customerInfo.identifyNumber,
  168. addr: data.customerInfo.addr,
  169. }
  170. },
  171. // 身份证国徽面
  172. userback(data) {
  173. let identifyValidDate = '';
  174. if (!!data.customerInfo.identifyValidDate) {
  175. identifyValidDate = data.customerInfo.identifyValidDate.substr(0, 4) +
  176. '-' + data.customerInfo.identifyValidDate.substr(4, 2) + '-' +
  177. data.customerInfo.identifyValidDate.substr(6, 2)
  178. }
  179. let identifyValidEndDate = '';
  180. if (!!data.customerInfo.identifyValidEndDate) {
  181. identifyValidEndDate = data.customerInfo.identifyValidEndDate.substr(0, 4) +
  182. '-' + data.customerInfo.identifyValidEndDate.substr(4, 2) + '-' +
  183. data.customerInfo.identifyValidEndDate.substr(6, 2)
  184. }
  185. return {
  186. identifyValidDate: identifyValidDate, //起期
  187. identifyValidEndDate: identifyValidEndDate == '长期--' ? '9999-12-31' : identifyValidEndDate, //止期
  188. }
  189. },
  190. // 营业执照
  191. businessLicense(data) {
  192. return {
  193. name: data.company,
  194. organizationCode: data.socialCreditCode, //社会信用代码
  195. addr: data.address,
  196. identifyType: "10",
  197. scope: data.businessScope,
  198. legalPerson: data.legalRepresentative,
  199. registeredCapital: data.registeredCapital
  200. }
  201. }
  202. };
  203. export default {
  204. props: {
  205. show: {
  206. type: Boolean,
  207. default: false
  208. },
  209. list: {
  210. type: Array,
  211. default: () => []
  212. },
  213. checkType: {
  214. type: String,
  215. default: 'carInfo'
  216. }
  217. },
  218. data() {
  219. return {
  220. loadingShow: false,
  221. loadingText: '加载中',
  222. OCRUploadPopup: this.show,
  223. current: 0,
  224. carfrontImg: null, //行驶证正面
  225. carbackImg: null, //行驶证反面
  226. carImageListId: [],
  227. carfront: {},
  228. carback: {},
  229. userfront: {},
  230. userback: {},
  231. userfrontImg: null, //人像面
  232. userbackImg: null, //国徽面
  233. businessLicenseImg: null, //营业执照
  234. businessLicenseInfo: {}, //营业执照识别内容
  235. ownerImageListId: [],
  236. policyHolderImageListId: [],
  237. insuredPersonImageListId: [],
  238. toggleMode: [{
  239. name: "人像面"
  240. }, {
  241. name: "国徽面",
  242. }],
  243. toggleModeIndex: 0,
  244. dragOver: false, //H5 拖拽悬停高亮
  245. }
  246. },
  247. watch: {
  248. show(newVal) {
  249. this.OCRUploadPopup = newVal
  250. }
  251. },
  252. onShow() {},
  253. onLoad() {
  254. },
  255. // #ifdef H5
  256. /**
  257. * H5 拖拽上传:拖拽监听必须绑在 document 原生事件上,不能绑在模板(@dragover/@drop)上。
  258. * 实测踩坑:uni-app H5 的模板事件经过其事件包装层,handler 收到的是包装后的标准事件对象
  259. * {type, detail, target, currentTarget},其中不含原生 DragEvent 的 dataTransfer,
  260. * 导致无法取出拖入的文件(现象:悬停高亮正常、松手无反应、无报错)。
  261. * document.addEventListener 绑定的是原生 DOM 监听,e.dataTransfer 一定可用;
  262. * drop 时用 closest('.drop-wrap') 判定拖放目标是否落在某个上传区域内,data-kind 标记场景。
  263. */
  264. mounted() {
  265. document.addEventListener('dragover', this.onDocumentDragOver);
  266. document.addEventListener('drop', this.onDocumentDrop);
  267. },
  268. // 组件销毁时移除监听并释放所有 blob 预览 URL(仅 H5 存在 blob: 链接)
  269. beforeDestroy() {
  270. document.removeEventListener('dragover', this.onDocumentDragOver);
  271. document.removeEventListener('drop', this.onDocumentDrop);
  272. ['carfrontImg', 'carbackImg', 'userfrontImg', 'userbackImg', 'businessLicenseImg'].forEach(
  273. key => {
  274. if (this[key] && String(this[key]).indexOf('blob:') === 0) {
  275. URL.revokeObjectURL(this[key]);
  276. }
  277. });
  278. },
  279. // #endif
  280. methods: {
  281. /**
  282. * 各 kind 的上传识别配置(配置表驱动,按 kind 隔离,互不复用):
  283. * - carfront/carback:行驶证正面/反面,imageCode 固定 C01/D01,影像列表固定 carImageListId,
  284. * OCR 接口 drivingPermit,表单名 image1/image2
  285. * - userfront/userback/businessLicense:身份证人像面/国徽面/营业执照,imageCode 前缀
  286. * C/D/Y + 序号随 checkType 推导(ownerInfo=02、policyHolderInfo=03、insuredPersonInfo=04),
  287. * 影像列表也随 checkType 切换(owner/policyHolder/insuredPersonImageListId)
  288. * 注意:不同影像类型的 OCR 识别内容与字段结构不同,target(回填字段)与 parse(解析器)
  289. * 必须按 kind 一一对应,绝不能混用——这是抽公共方法时最容易踩的坑。
  290. */
  291. getOcrConfig(kind) {
  292. let suffix = '02';
  293. let imageList = 'ownerImageListId';
  294. switch (this.checkType) {
  295. case 'policyHolderInfo':
  296. suffix = '03';
  297. imageList = 'policyHolderImageListId';
  298. break;
  299. case 'insuredPersonInfo':
  300. suffix = '04';
  301. imageList = 'insuredPersonImageListId';
  302. break;
  303. default:
  304. break;
  305. }
  306. const config = {
  307. carfront: {
  308. imageCode: 'C01',
  309. imageList: 'carImageListId',
  310. ocrUrl: '/order/identify/drivingPermit',
  311. ocrName: 'image1',
  312. target: 'carfront',
  313. parse: OCR_PARSE.carfront,
  314. },
  315. carback: {
  316. imageCode: 'D01',
  317. imageList: 'carImageListId',
  318. ocrUrl: '/order/identify/drivingPermit',
  319. ocrName: 'image2',
  320. target: 'carback',
  321. parse: OCR_PARSE.carback,
  322. },
  323. userfront: {
  324. imageCode: 'C' + suffix,
  325. imageList: imageList,
  326. ocrUrl: '/order/identify/idCard',
  327. ocrName: 'image1',
  328. target: 'userfront',
  329. parse: OCR_PARSE.userfront,
  330. },
  331. userback: {
  332. imageCode: 'D' + suffix,
  333. imageList: imageList,
  334. ocrUrl: '/order/identify/idCard',
  335. ocrName: 'image2',
  336. target: 'userback',
  337. parse: OCR_PARSE.userback,
  338. },
  339. businessLicense: {
  340. imageCode: 'Y' + suffix,
  341. imageList: imageList,
  342. ocrUrl: '/order/identify/businessLicense',
  343. ocrName: 'image1',
  344. target: 'businessLicenseInfo',
  345. parse: OCR_PARSE.businessLicense,
  346. },
  347. };
  348. return config[kind];
  349. },
  350. /**
  351. * 通用文件上传(两个接口共用:/file/upload 传原图、/order/identify/* 传 OCR 识别):
  352. * - H5:用 XMLHttpRequest + FormData,source 直接是 File 对象(拖拽/原生 input 拿到),
  353. * 不走 uni.uploadFile(其在 H5 对 File/blob URL 的兼容性不可靠)
  354. * - APP-PLUS / 小程序:用 uni.uploadFile,source 是 chooseImage 返回的临时路径
  355. * 成功 resolve 服务端返回的字符串(JSON),非 2xx 状态 reject。
  356. */
  357. uploadFileGeneric(url, source, name, formData) {
  358. // #ifdef H5
  359. return new Promise((resolve, reject) => {
  360. const xhr = new XMLHttpRequest();
  361. xhr.open('POST', url);
  362. xhr.setRequestHeader('Authorization', store.state.token);
  363. xhr.setRequestHeader('system_code', store.state.system_code ? store.state
  364. .system_code : '');
  365. const fd = new FormData();
  366. if (formData) {
  367. Object.keys(formData).forEach(key => {
  368. fd.append(key, formData[key]);
  369. });
  370. }
  371. fd.append(name, source);
  372. xhr.onload = () => {
  373. if (xhr.status >= 200 && xhr.status < 300) {
  374. resolve(xhr.responseText);
  375. } else {
  376. reject({
  377. errMsg: '上传失败: ' + xhr.status,
  378. statusCode: xhr.status
  379. });
  380. }
  381. };
  382. xhr.onerror = () => {
  383. reject({
  384. errMsg: '网络错误',
  385. statusCode: 0
  386. });
  387. };
  388. xhr.send(fd);
  389. });
  390. // #endif
  391. // #ifdef APP-PLUS || MP
  392. return new Promise((resolve, reject) => {
  393. uni.uploadFile({
  394. url: url,
  395. filePath: source,
  396. name: name,
  397. formData: formData,
  398. header: {
  399. Authorization: store.state.token,
  400. system_code: store.state.system_code ? store.state.system_code : ''
  401. },
  402. success: (res) => {
  403. if (res.statusCode >= 200 && res.statusCode < 300) {
  404. resolve(res.data);
  405. } else {
  406. reject({
  407. errMsg: '上传失败: ' + res.statusCode,
  408. statusCode: res.statusCode
  409. });
  410. }
  411. },
  412. fail: (err) => {
  413. reject(err);
  414. }
  415. });
  416. });
  417. // #endif
  418. },
  419. // 维护影像列表:按 imageCode 查重更新或追加(与原逻辑等价)
  420. updateImageList(cfg, imageId) {
  421. const list = this[cfg.imageList];
  422. if (list.some(v => v.imageCode == cfg.imageCode)) {
  423. list.map(val => {
  424. if (val.imageCode == cfg.imageCode) {
  425. val.imageBase64List[0].imageId = imageId;
  426. }
  427. })
  428. } else {
  429. list.push({
  430. imageBase64List: [{
  431. imageId: imageId
  432. }],
  433. imageCode: cfg.imageCode,
  434. })
  435. }
  436. },
  437. /**
  438. * 公共上传识别(H5 拖拽/原生选图与非 H5 chooseImage 的最终汇聚点):
  439. * 1. /file/upload 上传原图 -> 拿服务端图片 id,按 imageCode 维护对应影像列表(updateImageList)
  440. * 2. /order/identify/{ocrUrl} 再传同一文件做 OCR 识别 -> 按 kind 的 parse 解析字段
  441. * 回填到 this[cfg.target](如 carfront / userfront / businessLicenseInfo)
  442. * 串行 await 与原嵌套回调逻辑等价;任一步失败统一被 catch 捕获打日志,不阻断页面。
  443. */
  444. async uploadAndIdentify(source, kind) {
  445. this.loadingShow = true;
  446. const cfg = this.getOcrConfig(kind);
  447. try {
  448. const imgRes = await this.uploadFileGeneric(this.$base.baseUrl + '/file/upload',
  449. source, 'file', {
  450. 'type': 'image'
  451. });
  452. const data = JSON.parse(imgRes);
  453. if (data.code == '200') {
  454. this.updateImageList(cfg, data.data.id);
  455. const ocrRes = await this.uploadFileGeneric(this.$base.baseUrl + cfg.ocrUrl,
  456. source, cfg.ocrName);
  457. const ocrData = JSON.parse(ocrRes).data;
  458. this[cfg.target] = cfg.parse(ocrData);
  459. }
  460. this.loadingShow = false;
  461. } catch (e) {
  462. console.log('uploadAndIdentify error', kind, e);
  463. }
  464. },
  465. // 图片大小超限提示(原 uToast 引用在模板中不存在,改为 uni.showToast)
  466. showSizeToast() {
  467. uni.showToast({
  468. title: '上传图片大小不能超过 5MB!',
  469. icon: 'none',
  470. duration: 1500
  471. });
  472. },
  473. // #ifdef H5
  474. /**
  475. * 全局 dragover(document 原生监听):必须 preventDefault,否则浏览器默认行为会在松手时
  476. * 直接打开拖入的图片;同时判断拖放目标是否在 .drop-wrap 内,决定是否显示悬停高亮。
  477. */
  478. onDocumentDragOver(e) {
  479. e.preventDefault();
  480. const wrap = e.target && e.target.closest ? e.target.closest('.drop-wrap') : null;
  481. this.dragOver = !!wrap;
  482. },
  483. /**
  484. * 全局 drop(document 原生监听):只有落在 .drop-wrap 区域内才处理。
  485. * 取原生事件 e.dataTransfer.files[0](模板事件拿不到 dataTransfer,见 mounted 注释)
  486. * 与元素 data-kind 标记的场景,交给 handleFile 走统一的预览+上传识别流程。
  487. */
  488. onDocumentDrop(e) {
  489. e.preventDefault();
  490. this.dragOver = false;
  491. const wrap = e.target && e.target.closest ? e.target.closest('.drop-wrap') : null;
  492. if (!wrap) {
  493. return;
  494. }
  495. const kind = wrap.dataset && wrap.dataset.kind;
  496. const file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
  497. console.log('[OCRUpload] onDocumentDrop', kind, file && file.name);
  498. if (file && kind) {
  499. this.handleFile(file, kind);
  500. }
  501. },
  502. /**
  503. * H5 点击选图兜底(保留点击上传能力):动态创建原生 input[type=file] 触发文件选择,
  504. * 选中后与拖拽走同一条 handleFile 流程。uni-app 的 <input type="file"> 在 H5 端不生效,
  505. * 必须用 document.createElement 原生创建。
  506. */
  507. chooseFile(kind) {
  508. const input = document.createElement('input');
  509. input.type = 'file';
  510. input.accept = 'image/*';
  511. input.onchange = (ev) => {
  512. const file = ev.target.files && ev.target.files[0];
  513. if (file) {
  514. this.handleFile(file, kind);
  515. }
  516. };
  517. input.click();
  518. },
  519. /**
  520. * H5 文件统一入口(拖拽 drop 与点击选图共用):
  521. * 1. 5MB 大小校验(与 chooseImage 路径一致)
  522. * 2. URL.createObjectURL 生成 blob 链接做即时预览(拖入的 File 没有路径,模板 :src 直接复用)
  523. * 换图前 revoke 旧链接防内存泄漏(组件销毁时的统一释放见 beforeDestroy)
  524. * 3. 调 uploadAndIdentify 走公共上传识别链路
  525. */
  526. handleFile(file, kind) {
  527. let size = file.size / 1024 / 1024 < 5;
  528. if (!size) {
  529. this.showSizeToast();
  530. return false
  531. }
  532. const imgKey = kind + 'Img';
  533. // 释放上一次的 blob URL,避免换图时内存泄漏
  534. if (this[imgKey] && String(this[imgKey]).indexOf('blob:') === 0) {
  535. URL.revokeObjectURL(this[imgKey]);
  536. }
  537. this[imgKey] = URL.createObjectURL(file);
  538. this.uploadAndIdentify(file, kind);
  539. },
  540. // #endif
  541. Subsectionclick(item) {
  542. this.toggleModeIndex = item;
  543. },
  544. ocrInt() {
  545. let Info = Object.assign({}, this.carfront, this.carback)
  546. if (this.isEmptyObject(Info)) {
  547. uni.showToast({
  548. icon: 'none',
  549. title: '未检测到图片',
  550. duration: 1500
  551. });
  552. } else {
  553. let ImgList = this.carImageListId;
  554. let frontImg = this.carfrontImg;
  555. let backImg = this.carbackImg;
  556. this.$emit('carCallback', {
  557. Info,
  558. ImgList,
  559. frontImg,
  560. backImg,
  561. });
  562. this.userfrontImg = '';
  563. this.userbackImg = '';
  564. this.carfrontImg = '';
  565. this.carbackImg = '';
  566. this.current = 0;
  567. }
  568. },
  569. ownerInfoInt() {
  570. if (this.current == 0) {
  571. let Info = Object.assign({}, this.userfront, this.userback)
  572. if (this.isEmptyObject(Info)) {
  573. uni.showToast({
  574. icon: 'none',
  575. title: '未检测到图片',
  576. duration: 1500
  577. });
  578. } else {
  579. let ImgList = this.ownerImageListId;
  580. let frontImg = this.userfrontImg;
  581. let backImg = this.userbackImg;
  582. this.$emit('ownerInfoCallback', {
  583. Info,
  584. ImgList,
  585. frontImg,
  586. backImg,
  587. });
  588. this.userfrontImg = '';
  589. this.userbackImg = '';
  590. this.carfrontImg = '';
  591. this.carbackImg = '';
  592. this.businessLicenseImg = '';
  593. this.current = 0;
  594. }
  595. } else {
  596. let Info = Object.assign({}, this.businessLicenseInfo)
  597. if (this.isEmptyObject(Info)) {
  598. uni.showToast({
  599. icon: 'none',
  600. title: '未检测到图片',
  601. duration: 1500
  602. });
  603. } else {
  604. let ImgList = this.ownerImageListId;
  605. let businessLicenseImg = this.businessLicenseImg;
  606. this.$emit('ownerInfoCallback', {
  607. Info,
  608. ImgList,
  609. businessLicenseImg,
  610. });
  611. this.userfrontImg = '';
  612. this.userbackImg = '';
  613. this.carfrontImg = '';
  614. this.carbackImg = '';
  615. this.businessLicenseImg = '';
  616. this.current = 0;
  617. }
  618. }
  619. },
  620. policyHolderInfoInt() {
  621. if (this.current == 0) {
  622. let Info = Object.assign({}, this.userfront, this.userback)
  623. if (this.isEmptyObject(Info)) {
  624. uni.showToast({
  625. icon: 'none',
  626. title: '未检测到图片',
  627. duration: 1500
  628. });
  629. } else {
  630. let ImgList = this.policyHolderImageListId;
  631. let frontImg = this.userfrontImg;
  632. let backImg = this.userbackImg;
  633. this.$emit('applicantInfoCallback', {
  634. Info,
  635. ImgList,
  636. frontImg,
  637. backImg,
  638. });
  639. this.userfrontImg = '';
  640. this.userbackImg = '';
  641. this.carfrontImg = '';
  642. this.carbackImg = '';
  643. this.current = 0;
  644. }
  645. } else {
  646. let Info = Object.assign({}, this.businessLicenseInfo)
  647. if (this.isEmptyObject(Info)) {
  648. uni.showToast({
  649. icon: 'none',
  650. title: '未检测到图片',
  651. duration: 1500
  652. });
  653. } else {
  654. let ImgList = this.policyHolderImageListId;
  655. let businessLicenseImg = this.businessLicenseImg;
  656. this.$emit('applicantInfoCallback', {
  657. Info,
  658. ImgList,
  659. businessLicenseImg,
  660. });
  661. this.userfrontImg = '';
  662. this.userbackImg = '';
  663. this.carfrontImg = '';
  664. this.carbackImg = '';
  665. this.businessLicenseImg = '';
  666. this.current = 0;
  667. }
  668. }
  669. },
  670. insuredPersonInfoInt() {
  671. if (this.current == 0) {
  672. let Info = Object.assign({}, this.userfront, this.userback)
  673. if (this.isEmptyObject(Info)) {
  674. uni.showToast({
  675. icon: 'none',
  676. title: '未检测到图片',
  677. duration: 1500
  678. });
  679. } else {
  680. let ImgList = this.insuredPersonImageListId;
  681. let frontImg = this.userfrontImg;
  682. let backImg = this.userbackImg;
  683. this.$emit('InsuredInfoCallback', {
  684. Info,
  685. ImgList,
  686. frontImg,
  687. backImg,
  688. });
  689. this.userfrontImg = '';
  690. this.userbackImg = '';
  691. this.carfrontImg = '';
  692. this.carbackImg = '';
  693. this.businessLicenseImg = '';
  694. this.current = 0;
  695. }
  696. } else {
  697. let Info = Object.assign({}, this.businessLicenseInfo)
  698. if (this.isEmptyObject(Info)) {
  699. uni.showToast({
  700. icon: 'none',
  701. title: '未检测到图片',
  702. duration: 1500
  703. });
  704. } else {
  705. let ImgList = this.insuredPersonImageListId;
  706. let businessLicenseImg = this.businessLicenseImg;
  707. this.$emit('InsuredInfoCallback', {
  708. Info,
  709. ImgList,
  710. businessLicenseImg,
  711. });
  712. this.userfrontImg = '';
  713. this.userbackImg = '';
  714. this.carfrontImg = '';
  715. this.carbackImg = '';
  716. this.businessLicenseImg = '';
  717. this.current = 0;
  718. }
  719. }
  720. },
  721. click(item) {
  722. this.current = item.index;
  723. },
  724. close() {
  725. this.current = 0;
  726. this.$emit('close')
  727. },
  728. isEmptyObject(obj) {
  729. return JSON.stringify(obj) === '{}';
  730. },
  731. //car正面
  732. async carfrontChange() {
  733. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  734. count: 1,
  735. sizeType: ['compressed']
  736. });
  737. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  738. if (!size) {
  739. this.showSizeToast();
  740. return false
  741. }
  742. if (chooseImageRes) {
  743. this.carfrontImg = chooseImageRes.tempFilePaths[0];
  744. this.uploadAndIdentify(chooseImageRes.tempFilePaths[0], 'carfront');
  745. } else {}
  746. },
  747. async carbackChange() {
  748. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  749. count: 1,
  750. sizeType: ['compressed']
  751. });
  752. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  753. if (!size) {
  754. this.showSizeToast();
  755. return false
  756. }
  757. if (chooseImageRes) {
  758. this.carbackImg = chooseImageRes.tempFilePaths[0];
  759. this.uploadAndIdentify(chooseImageRes.tempFilePaths[0], 'carback');
  760. } else {}
  761. },
  762. async userfrontChange() {
  763. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  764. count: 1,
  765. sizeType: ['compressed']
  766. });
  767. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  768. if (!size) {
  769. this.showSizeToast();
  770. return false
  771. }
  772. if (chooseImageRes) {
  773. this.userfrontImg = chooseImageRes.tempFilePaths[0];
  774. this.uploadAndIdentify(chooseImageRes.tempFilePaths[0], 'userfront');
  775. } else {}
  776. },
  777. //身份证背面
  778. async userbackChange() {
  779. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  780. count: 1,
  781. sizeType: ['compressed']
  782. });
  783. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  784. if (!size) {
  785. this.showSizeToast();
  786. return false
  787. }
  788. if (chooseImageRes) {
  789. this.userbackImg = chooseImageRes.tempFilePaths[0];
  790. this.uploadAndIdentify(chooseImageRes.tempFilePaths[0], 'userback');
  791. } else {}
  792. },
  793. //营业执照识别
  794. async businessLicenseChange() {
  795. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  796. count: 1,
  797. sizeType: ['compressed']
  798. });
  799. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  800. if (!size) {
  801. this.showSizeToast();
  802. return false
  803. }
  804. if (chooseImageRes) {
  805. this.businessLicenseImg = chooseImageRes.tempFilePaths[0];
  806. this.uploadAndIdentify(chooseImageRes.tempFilePaths[0], 'businessLicense');
  807. } else {}
  808. }
  809. }
  810. }
  811. </script>
  812. <style lang="scss" scoped>
  813. .header {
  814. padding: 4rpx 27rpx;
  815. }
  816. .content {
  817. padding: 40rpx 60rpx;
  818. .img {
  819. background: #fff;
  820. image {
  821. width: 100%;
  822. height: 360rpx;
  823. }
  824. // H5 拖拽悬停高亮(只在 H5 端被触发)
  825. .drop-wrap.drop-active {
  826. border: 2rpx dashed #FDE935;
  827. background: rgba(253, 233, 53, 0.15);
  828. box-sizing: border-box;
  829. }
  830. }
  831. .footer {
  832. .btn {
  833. color: #1F1F1F !important;
  834. }
  835. }
  836. .user-Subsection {
  837. width: fit-content;
  838. margin: 0 auto 40rpx;
  839. border-radius: 4rpx;
  840. .item {
  841. padding: 9rpx 24rpx;
  842. box-sizing: border-box;
  843. font-size: 30rpx;
  844. color: #666;
  845. border: 1rpx solid #eee;
  846. }
  847. .item:first-child {
  848. border-right-color: #FDE935;
  849. }
  850. .item:last-child {
  851. border-left: none;
  852. }
  853. .active {
  854. background: rgba(253, 233, 53, 0.2);
  855. font-size: 30rpx;
  856. color: #333;
  857. border-color: #FDE935;
  858. }
  859. }
  860. }
  861. .loading {
  862. position: absolute;
  863. width: 100%;
  864. height: 100%;
  865. background-color: rgba(255, 255, 255, 0.6);
  866. z-index: 99999;
  867. border-radius: 20rpx;
  868. }
  869. </style>