checkOffline.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <template>
  2. <view class="page" :style="{'min-height':(h)+'px','padding-top':mt+'px'}">
  3. <c-nav-bar title="核销" @goBack="back()"></c-nav-bar>
  4. <view class="types">
  5. <view class="item bg2" @tap="scanCode(0)">
  6. <view class="n">拍照登记</view>
  7. <u-icon name="arrow-right" color="#fff" size="26" label="点击登记" label-color="#fff" label-size="32" cu
  8. label-pos="left"></u-icon>
  9. </view>
  10. <view class="item bg1" @tap="scanCode(1)">
  11. <view class="n">身份证登记</view>
  12. <u-icon name="arrow-right" color="#fff" size="26" label="点击登记" label-color="#fff" label-size="32"
  13. label-pos="left"></u-icon>
  14. </view>
  15. </view>
  16. <!-- <view class="orderInfo">
  17. <view class="tit">
  18. 订单详情
  19. </view>
  20. <view class="time">{{orderInfo.playDate}} {{orderInfo.playTime}}出发</view>
  21. <view class="bottom">
  22. <text>{{orderInfo.num}}人</text>
  23. <text>出游·{{orderInfo.playLength||0}}小时</text>
  24. <text>¥{{orderInfo.realPrice}}</text>
  25. </view>
  26. </view> -->
  27. <view class="list">
  28. <view class="tit">共({{peoList.length}})人</view>
  29. <view class="item" v-for="(i,index) in peoList" :key="index">
  30. <!-- <text class="type" :class="i.remark=='book'?'t1':'t2'">{{i.remark=='book'?'预订单':'现场单'}}</text> -->
  31. <view class="personInfo" >
  32. <view class="name" style="display: flex;align-items: center;width: 90%;"><span style="width: 120rpx;">姓名:</span><u--input
  33. v-model="i.touristName"></u--input></view>
  34. <view class="code" style="display: flex;align-items: center;width: 90%;">
  35. <span style="width: 120rpx;">身份证:</span><u--input v-model="i.touristCode"></u--input>
  36. </view>
  37. <view style="display: flex;justify-content: flex-start;padding: 8rpx 0;margin-top: 12rpx;">
  38. <view v-if="i.touristJpg.length!=0">已补充证件照片
  39. <span @click="view(i.touristJpg)"
  40. style="background-color: #007A69;color: #007A69;color: #fff;display: inline-block;border-radius: 12rpx;padding: 4rpx 12rpx;margin-left: 8rpx;">查看</span>
  41. </view>
  42. <!-- <view v-if="i.touristType==1" @click="idCardUpload(i,index)" style="background-color: #007A69;color: #007A69;color: #fff;display: inline-block;border-radius: 12rpx;padding: 4rpx 12rpx;">
  43. {{i.touristJpg.length!=0?'重新上传':'补充证件照片'}}
  44. </view> -->
  45. </view>
  46. </view>
  47. <u-icon name="minus-circle" size="36" color="#FEA400" @tap="jian(index)"></u-icon>
  48. </view>
  49. </view>
  50. <view class="list">
  51. <view class="tit">选择渔船</view>
  52. <view class="typeItem">
  53. <view class="txt" v-for="(t,i) in types" :key="i" @click="typeIndex=i" :class="typeIndex==i?'on':''">
  54. {{t.boatNo}}
  55. <image v-if="typeIndex==i"
  56. src="https://fsy.shengsi.gov.cn/file/20240320/b458b03f8f654a51a921656b8aa955de.png"></image>
  57. </view>
  58. </view>
  59. </view>
  60. <view class="btn">
  61. <text @click="hexiao">开始发船</text>
  62. </view>
  63. </view>
  64. </template>
  65. <script>
  66. import {
  67. encrypt,
  68. decrypt,
  69. tuomin
  70. } from '../../utils/aes.js'
  71. var that;
  72. const lcReader = uni.requireNativePlugin('LcReader');
  73. const modal = uni.requireNativePlugin('modal');
  74. var globalEvent = uni.requireNativePlugin('globalEvent');
  75. // 依赖引入
  76. const Intent = plus.android.importClass('android.content.Intent')
  77. const IntentFilter = plus.android.importClass('android.content.IntentFilter')
  78. const main = plus.android.runtimeMainActivity()
  79. // pda广播名称
  80. const SCANACTION = "scan.rcv.message"//扫描名称
  81. export default {
  82. data() {
  83. return {
  84. typeIndex: null,
  85. types: [],
  86. date: new Date().Format('yyyy-MM-dd'),
  87. show: false,
  88. data: [],
  89. merchantId: uni.getStorageSync('merchantId'),
  90. playTime: '',
  91. boatNo: '',
  92. type: '',
  93. orderInfo: {},
  94. keyName: '',
  95. playDate: '',
  96. peoList: []
  97. }
  98. },
  99. onLoad(opt) {
  100. that = this;
  101. this.playTime = opt.playTime || '';
  102. this.boatNo = opt.boatNo || '';
  103. this.playDate = opt.playDate || '';
  104. this.getTypes();
  105. this.initSDK();
  106. globalEvent.addEventListener('readInfo', function(e) {
  107. if (e.Id && e.Name) that.handleScanResult(e.Id, 1, e.Name);
  108. });
  109. },
  110. filters: {
  111. handle(val, type) {
  112. return tuomin(val, type);
  113. }
  114. },
  115. destroyed() {
  116. this.keyName = this.orderInfo.playDate + this.orderInfo.playTime + this.boatNo;
  117. uni.setStorageSync(this.keyName, JSON.stringify(this.data));
  118. },
  119. methods: {
  120. view(item) {
  121. uni.previewImage({
  122. urls: [item]
  123. })
  124. },
  125. idCardUpload(item, index) {
  126. console.log(item)
  127. uni.chooseImage({
  128. sourceType: ['camera'], //从相册选择
  129. success: chooseImageRes => {
  130. const tempFilePaths = chooseImageRes.tempFilePaths;
  131. uni.uploadFile({
  132. url: 'https://fsy.shengsi.gov.cn/island-cloud-server/oss/file/aliyunUpload', // 仅为示例,非真实的接口地址
  133. filePath: tempFilePaths[0],
  134. name: 'file',
  135. header: {
  136. token: wx.getStorageSync('access_token')
  137. },
  138. success: res => {
  139. // uploadFile上传成功后,根据和后台的约定msgCode判断接口调用状态
  140. let data = JSON.parse(res.data);
  141. this.data[index].touristJpg = data.data.url
  142. }
  143. });
  144. },
  145. fail: err => {
  146. this.myToast('图片上传失败', 'none');
  147. }
  148. });
  149. },
  150. initSDK() {
  151. // 调用异步方法
  152. lcReader.initSDK({
  153. 'name': 'unimp',
  154. 'age': 1
  155. });
  156. },
  157. getTypes() {
  158. this.$api.get('/scenic/api/boat/page?sailFlag=0&merchantId=' + this.merchantId, {
  159. page: -1
  160. }).then(res => {
  161. if (res.data.code == 0) {
  162. this.types = res.data.data.list;
  163. }
  164. })
  165. },
  166. getOrderInfo() {
  167. this.$api.post('/scenic/api/order/queryStartOrderInfo', {
  168. merchantId: this.merchantId,
  169. playDate: this.playDate,
  170. boatNo: this.boatNo
  171. }).then(res => {
  172. if (res.data.code == 0) {
  173. this.orderInfo = res.data.data;
  174. this.keyName = this.orderInfo.playDate + this.orderInfo.playTime + this.boatNo;
  175. if (uni.getStorageSync(this.keyName)) {
  176. this.data = JSON.parse(uni.getStorageSync(this.keyName)) || [];
  177. }
  178. }
  179. })
  180. },
  181. scanCode(type) {
  182. if (type == 1) {
  183. this.$showToast('请放置身份证进行读取');
  184. lcReader.cycleReadCard(); //循环读卡
  185. }
  186. if (type == 0) this.scanCodeByEwm();
  187. },
  188. async OCR(imageUrl) {
  189. try {
  190. const res = await this.$api.get('/api/register/ocr', {
  191. 'imageUrl': imageUrl,
  192. type: 1
  193. });
  194. if (res.data.code === 0) {
  195. const result = res.data.data.words_result.map(item => item.words).join('');
  196. console.log('OCR识别结果:', result);
  197. const extractedInfo = this.extractInfo(result);
  198. console.log('提取的信息:', extractedInfo);
  199. if (extractedInfo.touristCode == '-' || extractedInfo.touristName == '-') {
  200. this.$showToast("OCR识别失败")
  201. }
  202. return extractedInfo;
  203. } else {
  204. console.error('OCR识别失败,错误码:', res.data.code);
  205. throw new Error(`OCR识别失败: ${res.data.msg || '未知错误'}`);
  206. }
  207. } catch (error) {
  208. console.error('OCR处理过程中发生错误:', error);
  209. throw error; // 可以选择抛出错误或返回null
  210. // 或者返回空对象: return { name: null, idCard: null };
  211. }
  212. },
  213. extractInfo(str) {
  214. // 提取姓名 - 优先匹配"姓名"和"性别"之间的内容
  215. const nameRegex = /姓名([\u4e00-\u9fa5]{2,4})(?=性别|民族|出生|住址|$)/;
  216. const nameMatch = str.match(nameRegex);
  217. const name = nameMatch ? nameMatch[1] : "-";
  218. // 提取身份证号正则(匹配18位数字或17位数字+X)
  219. const idReg = /[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]/;
  220. const idMatch = str.match(idReg);
  221. const idCard = idMatch ? idMatch[0] : '-';
  222. return {
  223. touristName: name,
  224. touristCode: idCard,
  225. touristType: 1
  226. };
  227. },
  228. async scanCodeByEwm(type) {
  229. try {
  230. // 1. 选择图片
  231. const chooseImageRes = await new Promise((resolve, reject) => {
  232. uni.chooseImage({
  233. sourceType: ['camera'],
  234. success: resolve,
  235. fail: reject
  236. });
  237. });
  238. const tempFilePaths = chooseImageRes.tempFilePaths;
  239. // 2. 上传文件
  240. const uploadRes = await new Promise((resolve, reject) => {
  241. uni.uploadFile({
  242. url: 'https://fsy.shengsi.gov.cn/island-cloud-server/oss/file/aliyunUpload',
  243. filePath: tempFilePaths[0],
  244. name: 'file',
  245. header: {
  246. token: wx.getStorageSync('access_token')
  247. },
  248. success: resolve,
  249. fail: reject
  250. });
  251. });
  252. // 3. 解析上传结果
  253. const data = JSON.parse(uploadRes.data);
  254. // 4. 调用OCR识别
  255. const ocrResult = await this.OCR(data.data.url);
  256. console.log(ocrResult)
  257. // 5. 将结果添加到列表
  258. ocrResult.touristJpg = data.data.url
  259. this.peoList.push(ocrResult);
  260. return ocrResult; // 可选:返回OCR结果供后续使用
  261. } catch (error) {
  262. console.error('处理流程出错:', error);
  263. throw error; // 可以选择抛出错误或处理错误
  264. }
  265. },
  266. //扫二维码或条形码
  267. // scanCodeByEwm(){
  268. // var filter = new IntentFilter();
  269. // filter.addAction(SCANACTION);
  270. // // 接收
  271. // let that = this;
  272. // var receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
  273. // onReceive: (context, intent)=>{
  274. // if (intent.getAction() === SCANACTION) {
  275. // // 扫描信息处理
  276. // let scanCode = intent.getStringExtra("barcodeData");
  277. // // 接收信息处理
  278. // if(scanCode) that.handleScanResult(scanCode,0,'');
  279. // }
  280. // }
  281. // })
  282. // main.registerReceiver(receiver, filter);
  283. // },
  284. // 处理扫码结果的函数,可以根据实际需求进行扩展
  285. handleScanResult(result, type, name) {
  286. // 例如:导航到某个页面,或者处理其他业务逻辑
  287. let sendData = {};
  288. sendData.playDate = this.orderInfo.playDate;
  289. sendData.playTime = this.orderInfo.playTime;
  290. sendData.playLength = this.orderInfo.playLength || 1;
  291. sendData.merchantId = this.orderInfo.merchantId;
  292. sendData.orderType = this.orderInfo.orderType;
  293. sendData.type = type;
  294. sendData.boatNo = this.boatNo;
  295. if (type == 0) sendData.writeOffCode = result;
  296. else if (type == 1) {
  297. sendData.touristCode = encrypt(result);
  298. sendData.touristName = encrypt(name);
  299. }
  300. this.$api.post('/scenic/api/order/scanCode', sendData).then(res => {
  301. if (res.data.code == 0) {
  302. if (this.peoList.findIndex((item) => {
  303. return item.touristCode == res.data.data.touristCode
  304. }) > -1) {
  305. let mg = '该核验码已扫';
  306. if (type == 1) mg = '该身份证已扫';
  307. this.$showToast(mg);
  308. } else {
  309. if (type == 1) {
  310. res.data.data.touristType = 0
  311. res.data.data.touristJpg = ''
  312. this.peoList.push(res.data.data);
  313. } else {
  314. res.data.data.touristType = 1
  315. res.data.data.touristJpg = ''
  316. this.peoList.push(res.data.data);
  317. }
  318. // if(res.data.data.remark=='book'){
  319. // if(type==1){
  320. // res.data.data.touristType = 0
  321. // }else{
  322. // res.data.data.touristType = 1
  323. // }
  324. // this.data.push(res.data.data);
  325. // }else{
  326. // this.$showToast("非订单内游客身份证,无法核销");
  327. // }
  328. }
  329. if (type == 1) lcReader.cancelCycle(); //关闭循环读卡
  330. } else {
  331. this.$showToast(res.data.msg)
  332. if (type == 1) lcReader.cancelCycle(); //关闭循环读卡
  333. }
  334. })
  335. },
  336. check() {
  337. uni.navigateTo({
  338. url: '/pagesHouse/Verification/check'
  339. })
  340. },
  341. hexiao() {
  342. let that = this
  343. if (this.peoList.length == 0) {
  344. return this.$showToast('请选择核销人')
  345. }
  346. if (this.typeIndex == null) {
  347. return this.$showToast('请选择渔船')
  348. }
  349. let boatCode = this.types[this.typeIndex].boatNo;
  350. let newData = JSON.parse(JSON.stringify(this.peoList));
  351. newData.forEach(d => {
  352. d.touristCode = encrypt(d.touristCode);
  353. d.touristName = encrypt(d.touristName);
  354. })
  355. console.log(newData)
  356. uni.showModal({
  357. title: '提示',
  358. content: '确定该操作?',
  359. success: function(res) {
  360. if (res.confirm) {
  361. that.$api.post('/scenic/order/sailWriteOff/offline', {
  362. playDate: that.orderInfo.playDate,
  363. playTime: that.orderInfo.playTime,
  364. playLength: that.orderInfo.playLength,
  365. orderType: that.orderInfo.orderType,
  366. orderCode: that.orderInfo.orderCode,
  367. merchantId: that.merchantId,
  368. touristList: newData,
  369. boatCode: boatCode
  370. }).then(res => {
  371. if (res.data.code == 0) {
  372. uni.navigateTo({
  373. url: "/pagesHouse/Verification/success?boatNo=" + that
  374. .boatNo
  375. })
  376. } else that.$showToast(res.data.msg)
  377. })
  378. }
  379. }
  380. })
  381. uni.removeStorageSync(that.keyName);
  382. },
  383. jian(index) {
  384. if (this.peoList.length > index) this.peoList.splice(index, 1);
  385. }
  386. }
  387. }
  388. </script>
  389. <style lang="less" scoped>
  390. /deep/.u-icon {
  391. justify-content: flex-end;
  392. }
  393. .btn {
  394. position: fixed;
  395. bottom: 0;
  396. left: 0;
  397. z-index: 1;
  398. padding: 16rpx 30rpx 36rpx 30rpx;
  399. width: 100%;
  400. box-sizing: border-box;
  401. background: #fff;
  402. text {
  403. display: block;
  404. background-color: #007A69;
  405. color: #007A69;
  406. color: #fff;
  407. height: 88rpx;
  408. line-height: 88rpx;
  409. border-radius: 44rpx;
  410. text-align: center;
  411. font-size: 32rpx;
  412. font-weight: bold;
  413. box-sizing: border-box;
  414. }
  415. }
  416. .page {
  417. background: #F5F8FA;
  418. padding: 20rpx 24rpx 160rpx;
  419. box-sizing: border-box;
  420. .types {
  421. display: flex;
  422. gap: 36rpx;
  423. justify-content: space-between;
  424. .n {
  425. margin-bottom: 20rpx;
  426. color: #fff;
  427. }
  428. .item {
  429. width: 330rpx;
  430. height: 156rpx;
  431. padding: 34rpx 24rpx;
  432. font-size: 38rpx;
  433. font-weight: bold;
  434. box-sizing: border-box;
  435. }
  436. .bg2 {
  437. background: url('https://fsy.shengsi.gov.cn/file/20240316/3fc9f6a78557492e98caa50392411e3a.png') no-repeat center;
  438. background-size: 100%;
  439. }
  440. .bg1 {
  441. background: url('https://fsy.shengsi.gov.cn/file/20240316/8a927d5d4da84728b6bb0896541baccf.png') no-repeat center;
  442. background-size: 100%;
  443. }
  444. }
  445. .orderInfo {
  446. background-color: #fff;
  447. color: #333;
  448. padding: 0 24rpx 36rpx;
  449. margin: 20rpx 0 0;
  450. background: #fff;
  451. border-radius: 16rpx;
  452. .tit {
  453. padding: 32rpx 0 24rpx;
  454. font-size: 32rpx;
  455. }
  456. .time {
  457. font-size: 36rpx;
  458. font-weight: bold;
  459. margin-bottom: 32rpx;
  460. }
  461. .bottom {
  462. display: flex;
  463. align-items: center;
  464. text {
  465. &:first-child {
  466. width: 88rpx;
  467. height: 54rpx;
  468. background-color: #007A69;
  469. color: #fff;
  470. font-size: 34rpx;
  471. font-weight: bold;
  472. margin-right: 10rpx;
  473. text-align: center;
  474. border-radius: 12rpx;
  475. }
  476. &:nth-child(2) {
  477. width: 200rpx;
  478. flex: 1;
  479. }
  480. &:last-child {
  481. font-size: 30rpx;
  482. font-weight: bold;
  483. }
  484. }
  485. }
  486. }
  487. }
  488. .list {
  489. box-sizing: border-box;
  490. padding: 0 24rpx 0;
  491. border-radius: 16rpx;
  492. background-color: #fff;
  493. margin-top: 20rpx;
  494. .tit {
  495. font-size: 36rpx;
  496. font-weight: bold;
  497. color: #111;
  498. padding: 36rpx 0 17rpx;
  499. }
  500. .typeItem {
  501. display: flex;
  502. justify-content: space-between;
  503. flex-wrap: wrap;
  504. gap: 20rpx 0;
  505. padding-bottom: 50rpx;
  506. .txt {
  507. display: inline-block;
  508. width: 316rpx;
  509. height: 80rpx;
  510. background: #fff;
  511. border-radius: 16rpx;
  512. text-align: center;
  513. line-height: 80rpx;
  514. border: 2rpx solid #EFEFEF;
  515. color: #333;
  516. position: relative;
  517. margin-bottom: 20rpx;
  518. font-size: 32rpx;
  519. &.on {
  520. color: #007A69;
  521. background: rgba(0, 122, 105, 0.06);
  522. border: 2rpx solid #007A69;
  523. image {
  524. position: absolute;
  525. bottom: 0;
  526. right: 0;
  527. width: 34rpx;
  528. height: 26rpx;
  529. z-index: 11;
  530. }
  531. }
  532. }
  533. }
  534. .item {
  535. display: flex;
  536. justify-content: space-between;
  537. padding: 24rpx 0;
  538. align-items: center;
  539. .type {
  540. width: 110rpx;
  541. height: 48rpx;
  542. text-align: center;
  543. display: inline-block;
  544. line-height: 48rpx;
  545. border-radius: 11rpx;
  546. font-size: 26rpx;
  547. font-weight: bold;
  548. color: #111;
  549. margin-right: 16rpx;
  550. }
  551. .t1 {
  552. background-color: #F0F8F6;
  553. color: #007A69;
  554. }
  555. .t2 {
  556. background-color: #E8EFFD;
  557. color: #326EE0;
  558. }
  559. .personInfo {
  560. flex: 1;
  561. .name {
  562. font-size: 30rpx;
  563. color: #111;
  564. margin-bottom: 16rpx;
  565. }
  566. .code {
  567. color: #666;
  568. font-size: 28rpx;
  569. }
  570. }
  571. }
  572. }
  573. </style>