base.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <template>
  2. <view>
  3. <uni-card>
  4. <uni-section title="基本信息" type="line">
  5. <uni-forms ref="baseForm" :modelValue="baseFormData" :rules="rules"
  6. style="margin-top: 3vh;margin-left: 3vw;">
  7. <uni-forms-item label="证件照" required name="lrXm">
  8. <lzcOCR class="lzcOCR" :src="zjcaijianSrc" @end="idcardEnd"></lzcOCR>
  9. </uni-forms-item>
  10. <uni-forms-item label="姓名" required name="lrXm">
  11. <uni-easyinput ref="xm" type="text" v-model="baseFormData.lrXm" placeholder="请输入姓名"
  12. :inputBorder="false"></uni-easyinput>
  13. </uni-forms-item>
  14. <uni-forms-item label="手机号" required name="lrCydh">
  15. <uni-easyinput type="text" v-model="baseFormData.lrCydh" placeholder="请输入手机号"
  16. :inputBorder="false"></uni-easyinput>
  17. </uni-forms-item>
  18. <uni-forms-item label="住址" required name="lrHjbcxx">
  19. <uni-easyinput type="text" v-model="baseFormData.lrHjbcxx" placeholder="请输入住址"
  20. :inputBorder="false"></uni-easyinput>
  21. </uni-forms-item>
  22. <!-- <uni-forms-item label="分数">
  23. <uni-easyinput type="text" v-model="score"
  24. :inputBorder="false"></uni-easyinput>
  25. </uni-forms-item> -->
  26. <uni-forms-item label="人像" required>
  27. <image :src="src" @click="takePhoto" mode="widthFix" style="width: 150px;"></image>
  28. </uni-forms-item>
  29. <view v-if="show">
  30. <uni-forms-item label="直接保存">
  31. <uni-data-checkbox v-model="radio" :localdata="radioData"></uni-data-checkbox>
  32. </uni-forms-item>
  33. <text>注意:由于人脸比对不成功可以直接保存人工审核!</text>
  34. </view>
  35. <view>
  36. <button cursor-spacing="22px" class="buttonClass" @click="save()">保存</button>
  37. </view>
  38. </uni-forms>
  39. </uni-section>
  40. </uni-card>
  41. </view>
  42. </template>
  43. <script>
  44. import lzcOCR from '@/components/lzc-OCR/lzc-OCR.vue';
  45. import {
  46. decryptRowData_ECB,
  47. decryptData_ECB
  48. } from '@/common/sm4.js'
  49. import {
  50. showConfirm,
  51. getDictInfo,
  52. toast,
  53. toBase64
  54. } from '@/common/common.js'
  55. import {
  56. UploadOne
  57. } from '@/api/upload.js'
  58. import {
  59. updateKhjbxx,
  60. infoKhjbxx,
  61. GetLrByZjhm,
  62. SaveKhjbxx
  63. } from '@/api/kh.js'
  64. import {
  65. getUser
  66. } from '@/common/auth.js'
  67. import {
  68. setToken,
  69. setOpenid,
  70. getOpenid,
  71. setUser
  72. } from '@/common/auth.js'
  73. import CryptoJS from 'crypto-js';
  74. import config from '@/config.js';
  75. import idCardNoUtil from '@/common/idcard.js'
  76. import {
  77. translate,
  78. base64ToUrl
  79. } from '@/common/image.js'
  80. import {
  81. Register
  82. } from '@/api/login.js'
  83. export default {
  84. components: {
  85. lzcOCR
  86. },
  87. data() {
  88. return {
  89. show: false,
  90. radio: 0,
  91. radioData: [{
  92. text: '否',
  93. value: 0
  94. }, {
  95. text: '是',
  96. value: 1
  97. }],
  98. // 字典
  99. dicts: {
  100. // 民族
  101. C0009: [],
  102. // 性别
  103. C0007: [],
  104. },
  105. // 百度云access_token
  106. token: '',
  107. client_id: config.face_client_id,
  108. client_secret: config.face_client_secret,
  109. // 人像地址
  110. src: "/static/images/head.png",
  111. // 身份拍摄地址
  112. zjSrc: "",
  113. // 身份证裁剪地址
  114. zjcaijianSrc: "/static/images/sfsb.png",
  115. showSrc: "",
  116. // 人员信息
  117. userInfo: {},
  118. // 组
  119. groupId: '',
  120. // 身份证号加密
  121. idcardMD5: '',
  122. // 人像base64
  123. face: '',
  124. // 身份证base64
  125. idcardFace: '',
  126. idcardFaceUrl: '',
  127. // 对比份数
  128. score: 0,
  129. // ocrXm
  130. ocrXm: '',
  131. isSearch: false,
  132. updateFlag: false,
  133. baseFormData: {
  134. lrXm: '',
  135. lrZjhm: '',
  136. lrHjbcxx: '',
  137. lrCydh: '',
  138. },
  139. rules: {
  140. lrHjbcxx: {
  141. rules: [{
  142. required: true,
  143. errorMessage: '住址不能为空'
  144. }]
  145. },
  146. lrXm: {
  147. rules: [{
  148. required: true,
  149. errorMessage: '姓名不能为空'
  150. }]
  151. },
  152. photo: {
  153. rules: [{
  154. required: true,
  155. errorMessage: '请采集人像'
  156. }]
  157. },
  158. lrCydh: {
  159. rules: [{
  160. required: true,
  161. errorMessage: '手机号不能为空'
  162. },
  163. {
  164. format: 'number',
  165. errorMessage: '请输入正确的手机号',
  166. },
  167. {
  168. pattern: '^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1}))+\\d{8})$',
  169. errorMessage: '请输入正确的手机号',
  170. }
  171. ]
  172. }
  173. },
  174. }
  175. },
  176. created() {},
  177. onLoad(o) {
  178. this.getDictList(Object.keys(this.dicts), this.dicts)
  179. this.userInfo = getUser()
  180. this.baseFormData.lrHjdz = this.userInfo.dept.locationCode
  181. this.groupId = this.userInfo.dept.locationCode.substring(0, 6)
  182. if (this.userInfo.userType == '07') {
  183. this.updateFlag = true
  184. let info = JSON.parse(o.info)
  185. this.baseFormData.lrHjdz = info.lrHjdz
  186. this.groupId = info.lrHjdz.substring(0, 6)
  187. this.baseFormData = info
  188. this.src = config.baseUrl + info.lrTx
  189. }
  190. },
  191. methods: {
  192. getAccessToken() {
  193. uni.request({
  194. url: '/baiduApi/oauth/2.0/token',
  195. data: {
  196. grant_type: 'client_credentials',
  197. client_id: this.client_id,
  198. client_secret: this.client_secret
  199. },
  200. method: 'POST',
  201. header: {
  202. 'Content-Type': 'application/x-www-form-urlencoded'
  203. },
  204. success: (res) => {
  205. if (res.statusCode == 200) {
  206. this.token = res.data.access_token
  207. this.match()
  208. }
  209. },
  210. error: (err) => {
  211. uni.hideLoading()
  212. }
  213. })
  214. },
  215. // 人脸对比
  216. match() {
  217. let face = this.face
  218. let idcardFace = this.idcardFace
  219. let data = [{
  220. image: face,
  221. image_type: 'BASE64',
  222. liveness_control: 'NORMAL',
  223. }, {
  224. image: idcardFace,
  225. image_type: 'BASE64'
  226. }]
  227. console.log("人脸对比:", data)
  228. uni.request({
  229. url: '/baiduApi/rest/2.0/face/v3/match?access_token=' + this.token,
  230. data: data,
  231. method: 'POST',
  232. header: {
  233. 'Content-Type': 'application/json'
  234. },
  235. success: (res) => {
  236. console.log('对比', res);
  237. if (res.data.error_msg == 'SUCCESS') {
  238. this.score = res.data.result.score;
  239. this.baseFormData.lrTxdb = JSON.stringify({
  240. "lrJmzjhm": this.idcardMD5,
  241. "xsfs": this.score
  242. })
  243. if (this.score >= config.score) {
  244. this.show = false
  245. this.baseFormData.lzzt = 3
  246. this.baseFormData.lrSpyj = '同意'
  247. this.baseFormData.prelrZjz = this.idcardFaceUrl
  248. this.faceSearch()
  249. } else {
  250. // 低于80选项是否人工审核,是的话进记录表
  251. // 身份证头像保存后端
  252. // 人脸库注册人脸、身份证md5
  253. // 修改的时候去人脸库搜索,搜索不到不允许修改
  254. // 修改成功替换原始人脸库照片
  255. // 首次修改搜索身份证
  256. // 后端没入库,人脸库相应删除
  257. // ocr失败身份证原版入库,ocr成功人脸入库
  258. // showConfirm('人像与身份证不符,请重新上传')
  259. this.baseFormData.lzzt = 1
  260. this.baseFormData.lrSpyj = ''
  261. this.show = true
  262. this.baseFormData.prelrZjz = this.zjcaijianSrc
  263. this.faceSearch()
  264. }
  265. } else {
  266. showConfirm(res.data.error_msg)
  267. uni.hideLoading()
  268. }
  269. },
  270. error: (err) => {
  271. console.log("对比失败,", err);
  272. uni.hideLoading()
  273. }
  274. })
  275. },
  276. createGroup() {
  277. uni.request({
  278. url: '/baiduApi/rest/2.0/face/v3/faceset/group/add?access_token=' + this.token,
  279. data: {
  280. group_id: this.groupId,
  281. },
  282. method: 'POST',
  283. header: {
  284. 'Content-Type': 'application/x-www-form-urlencoded'
  285. },
  286. success: (res) => {
  287. if (res.statusCode == 200) {
  288. this.faceAdd()
  289. }
  290. },
  291. error: (err) => {
  292. uni.hideLoading()
  293. }
  294. })
  295. },
  296. // 人脸注册
  297. faceAdd() {
  298. // https://cloud.baidu.com/doc/FACE/s/Gk37c1uzc#%E4%BA%BA%E8%84%B8%E6%B3%A8%E5%86%8C
  299. let face = this.face
  300. let data = {
  301. image: face,
  302. image_type: 'BASE64',
  303. group_id: this.groupId,
  304. user_id: this.idcardMD5,
  305. action_type: 'REPLACE', // 操作方式 APPEND: 当user_id在库中已经存在时,对此user_id重复注册时,新注册的图片默认会追加到该user_id下 REPLACE : 当对此user_id重复注册时,则会用新图替换库中该user_id下所有图片 默认使用APPEND
  306. }
  307. console.log("人脸注册:", data)
  308. uni.request({
  309. url: '/baiduApi/rest/2.0/face/v3/faceset/user/add?access_token=' + this.token,
  310. data: data,
  311. method: 'POST',
  312. header: {
  313. 'Content-Type': 'application/json'
  314. },
  315. success: (res) => {
  316. console.log("人脸注册:", res);
  317. uni.hideLoading()
  318. if (res.data.error_msg != 'SUCCESS') {
  319. showConfirm(res.data.error_msg)
  320. }
  321. },
  322. error: (err) => {
  323. uni.hideLoading()
  324. }
  325. })
  326. },
  327. // 人脸搜索
  328. faceSearch() {
  329. let face = this.face
  330. let data = {
  331. image: face,
  332. image_type: 'BASE64',
  333. group_id_list: this.groupId,
  334. match_threshold: config.score,
  335. max_user_num: 50
  336. }
  337. console.log("人脸搜索:", data)
  338. uni.request({
  339. url: '/baiduApi/rest/2.0/face/v3/search?access_token=' + this.token,
  340. data: data,
  341. method: 'POST',
  342. header: {
  343. 'Content-Type': 'application/json'
  344. },
  345. success: (res) => {
  346. console.log("人脸搜索:", res)
  347. if (res.data.error_msg == 'SUCCESS') {
  348. // this.isSearch = true
  349. if (res.data.result.user_list.length < 1) {
  350. console.log("人脸不存在");
  351. this.createGroup()
  352. } else {
  353. if (res.data.result.user_list[0].score >= config.score) {
  354. let xs = []
  355. res.data.result.user_list.forEach(e => {
  356. if (e.user_id != this.idcardMD5) {
  357. xs.push({
  358. "lrJmzjhm": e.user_id,
  359. "xsfs": e.score
  360. })
  361. this.baseFormData.lzzt = 2
  362. this.baseFormData.lrSpyj = ''
  363. }
  364. })
  365. this.baseFormData.xslrZjhm = JSON.stringify(xs)
  366. }
  367. // this.faceAdd()
  368. uni.hideLoading()
  369. }
  370. } else {
  371. // this.isSearch = false
  372. // uni.hideLoading()
  373. this.faceAdd()
  374. }
  375. },
  376. error: (err) => {
  377. uni.hideLoading()
  378. }
  379. })
  380. },
  381. save() {
  382. if (this.src == '/static/images/head.png') {
  383. showConfirm('请采集人像')
  384. return
  385. } else {
  386. if (!this.ocrXm) {
  387. showConfirm('修改信息请重新识别身份证人像面')
  388. return
  389. }
  390. if (this.ocrXm != this.baseFormData.lrXm) {
  391. showConfirm('姓名与真实姓名不符,请重新识别身份证人像面')
  392. return
  393. }
  394. // if (!this.isSearch) {
  395. // showConfirm('当前区县暂未开放,请联系相关人员')
  396. // return
  397. // }
  398. if ((this.score >= config.score) || (this.score < config.score && this.radio == '1')) {
  399. uni.showLoading({
  400. title: '正在保存中...'
  401. })
  402. this.$refs['baseForm'].validate().then(res => {
  403. UploadOne(this.baseFormData.prelrZjz, {}).then(re => {
  404. console.log("re", re)
  405. this.baseFormData.lrZjz = re.data.url
  406. UploadOne(this.src, {}).then(re => {
  407. console.log("re", re)
  408. this.baseFormData.lrTx = re.data.url
  409. if (this.userInfo.userType == '07') this.baseFormData.id = this
  410. .userInfo.userId
  411. if (this.updateFlag) {
  412. updateKhjbxx(this.baseFormData).then(r => {
  413. console.log("r:", r)
  414. uni.hideLoading()
  415. if (r.code == 200) {
  416. toast('保存成功')
  417. setTimeout(function() {
  418. uni.switchTab({
  419. url: '/pages/index/index'
  420. })
  421. }, 1000)
  422. }
  423. })
  424. } else {
  425. SaveKhjbxx(this.baseFormData).then(r => {
  426. console.log("r:", r)
  427. uni.hideLoading()
  428. if (r.code == 200) {
  429. toast('保存成功')
  430. // setOpenid(r.data.openId)
  431. // setUser(r.data.sysUser)
  432. // setToken(r.data.token.access_token)
  433. setTimeout(function() {
  434. uni.switchTab({
  435. url: '/pages/index/index'
  436. })
  437. }, 1000)
  438. }
  439. })
  440. }
  441. })
  442. })
  443. }).catch(err => {
  444. uni.hideLoading()
  445. console.log(err);
  446. })
  447. } else {
  448. showConfirm('请重新采集人像')
  449. return
  450. }
  451. }
  452. },
  453. takePhoto() {
  454. if (!this.zjSrc) {
  455. showConfirm('请先识别身份证人像面')
  456. return
  457. }
  458. uni.chooseImage({
  459. count: 1,
  460. mediaType: ['image'],
  461. sizeType: ['compressed'],
  462. sourceType: ['camera'],
  463. success: (res) => {
  464. uni.showLoading({
  465. title: '正在识别中...'
  466. })
  467. let size = res.tempFiles[0].size
  468. let scale = 1
  469. if (size / 1024 / 1024 > 0.9) scale = 0.6
  470. translate(res.tempFilePaths[0], scale, 'blob', this.setSrc)
  471. }
  472. })
  473. },
  474. setSrc(e, blobUrl) {
  475. this.src = blobUrl
  476. uni.getFileInfo({
  477. filePath: blobUrl,
  478. success: (res) => {
  479. console.log(res);
  480. let size = res.size
  481. let scale = 1
  482. if (size / 1024 / 1024 > 0.9) {
  483. scale = 0.6
  484. translate(this.src, scale, 'blob', this.setSrc)
  485. } else {
  486. e = e.replace('data:image/jpeg;base64,', "");
  487. this.face = e
  488. this.getAccessToken()
  489. }
  490. },
  491. fail: (err) => {
  492. console.log("err:", err);
  493. }
  494. })
  495. },
  496. // 身份证识别
  497. idcardEnd(words, src) {
  498. if (words.image_status == "other_type_card") {
  499. showConfirm('请识别正确的身份证人像面')
  500. return
  501. }
  502. // 身份证号校验 性别 出生日期
  503. // 修改之后的姓名和ocr返回校验,重新ocr
  504. let id = words.words_result['公民身份号码'].words
  505. let csrq = words.words_result['出生'].words
  506. let sex = words.words_result['性别'].words
  507. let info = idCardNoUtil.getIdCardInfo(id)
  508. if (!idCardNoUtil.checkIdCardNo(id)) {
  509. showConfirm('身份证号识别有误,请重新识别')
  510. return
  511. }
  512. if (this.userType == '07' && this.baseFormData.lrZjhm != id) {
  513. showConfirm('请使用本人身份证重新识别')
  514. return
  515. }
  516. if (info.birthday != csrq) {
  517. showConfirm('身份证出生日期识别有误,请重新识别')
  518. return
  519. }
  520. if (info.gender != sex) {
  521. showConfirm('身份证性别识别有误,请重新识别')
  522. return
  523. }
  524. if (words.image_status == "reversed_side") {
  525. showConfirm('请识别身份证人像面')
  526. return
  527. }
  528. if (words.risk_type != "normal") {
  529. // normal-正常身份证;copy-复印件;temporary-临时身份证;screen-翻拍;unknown-其他未知情况
  530. showConfirm('请识别正确的身份证人像面')
  531. return
  532. }
  533. this.baseFormData.lrXb = getDictInfo(this.dicts.C0007, words.words_result['性别'].words)[0].value
  534. this.baseFormData.lrMz = getDictInfo(this.dicts.C0009, words.words_result['民族'].words)[0].value
  535. this.idcardFace = words.photo
  536. this.idcardFaceUrl = base64ToUrl(words.photo);
  537. this.baseFormData.lrZjhm = id
  538. this.baseFormData.lrCsrq = csrq
  539. this.idcardMD5 = CryptoJS.MD5(this.baseFormData.lrZjhm).toString()
  540. this.baseFormData.lrXm = words.words_result['姓名'].words
  541. this.ocrXm = words.words_result['姓名'].words
  542. this.zjSrc = src
  543. this.zjcaijianSrc = base64ToUrl(words.card_image);
  544. if (this.userInfo.userType != '07') {
  545. GetLrByZjhm({
  546. lrJmzjhm: this.idcardMD5
  547. }).then(res => {
  548. console.log(res);
  549. if (res.data) this.baseFormData = decryptData_ECB(res.data, ["lrZjhm", "lrXm", "lrHjbcxx",
  550. "lrXjdzBcxx",
  551. "lrCydh", "lrPoxm", "lrPoZjmh", "jhrXm", "jhrSjhm", "zlrXm", "zlrDh", "cjzh",
  552. "yhzh", "khmc"
  553. ], [2, 1, 4, 4, 3, 1, 2, 1, 2, 1, 3, 5, 5, 5]);
  554. if (res.data && res.data.lrHjdz) this.baseFormData.lrHjdz = res.data
  555. .lrHjdz
  556. if (this.baseFormData.lrTx) this.src = config.baseUrl + this.baseFormData.lrTx
  557. if (res.data) this.updateFlag = true
  558. })
  559. }
  560. },
  561. }
  562. }
  563. </script>
  564. <style scoped>
  565. .lzcOCR {
  566. /* width: 77%;
  567. display: flex;
  568. justify-content: center; */
  569. }
  570. .buttonClass {
  571. margin-top: 4vh;
  572. margin-left: 4vw;
  573. width: 280px;
  574. /* height: 5vh; */
  575. border-radius: 5.8vw;
  576. /* border: 1px solid rgba(176, 179, 199, 1); */
  577. background: #28d87d;
  578. color: white;
  579. height: 5vh;
  580. display: flex;
  581. align-items: center;
  582. justify-content: center;
  583. box-shadow:0rpx 4rpx 10rpx 1rpx rgba(40, 216, 125,0.4);
  584. }
  585. </style>