identity - 副本 (2).vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <view class="auth-container">
  3. <!-- 身份证上传区域 -->
  4. <view class="upload-section">
  5. <text class="upload-title">身份证照片</text>
  6. <view class="idcard-wrapper">
  7. <!--<view class="upload-item" @click="uploadIDCard('front')">
  8. <image v-if="idFront" :src="idFront" class="preview-image" mode="aspectFill" />
  9. <text v-else class="placeholder">待上传</text>
  10. </view>
  11. <view class="upload-item" @click="uploadIDCard('back')">
  12. <image v-if="idBack" :src="idBack" class="preview-image" mode="aspectFill" />
  13. <text v-else class="placeholder">待上传</text>
  14. </view>-->
  15. <view class="upload-item" v-for="type in ['front', 'back']" :key="type" @click="uploadIDCard(type)">
  16. <image v-if="type === 'front' ? idFront : idBack"
  17. :src="type === 'front' ? idFront : idBack"
  18. class="preview-image" />
  19. <text v-if="!(type === 'front' ? idFront : idBack)"
  20. class="placeholder">点击上传{{ type === 'front' ? '正面' : '背面' }}</text>
  21. <!-- 上传状态层 -->
  22. <view v-if="uploadStatus[type].loading" class="upload-mask">
  23. <text>识别中...</text>
  24. </view>
  25. <view v-if="uploadStatus[type].result" class="upload-mask">
  26. <text v-if="uploadStatus[type].result === 'success'">识别成功</text>
  27. <text v-if="uploadStatus[type].result === 'fail'">{{uploadStatus[type].info}}</text>
  28. </view>
  29. <!-- 识别结果 -->
  30. <!--<view v-if="uploadStatus[type].result"
  31. class="result-tag"
  32. :class="uploadStatus[type].result">
  33. {{ uploadStatus[type].result === 'success' ? '✓' : '✕' }}
  34. </view>-->
  35. </view>
  36. </view>
  37. </view>
  38. <!--&lt;!&ndash; 表单区域 &ndash;&gt;
  39. <view class="form-section">
  40. <view class="form-item">
  41. <text class="label">姓名</text>
  42. <input class="input" v-model="formData.realName" placeholder="请输入姓名" />
  43. </view>
  44. <view class="form-item">
  45. <text class="label">身份证号</text>
  46. <input class="input" v-model="formData.idCard" placeholder="请输入身份证号" />
  47. </view>
  48. </view>-->
  49. <view class="form-section">
  50. <view class="form-item" v-for="(item, key) in filteredFields" :key="key">
  51. <text class="label">{{ item.label }}</text>
  52. <input
  53. class="input"
  54. v-model="item.value"
  55. :placeholder="'请输入'+item.label"
  56. />
  57. </view>
  58. </view>
  59. <!-- 操作按钮 -->
  60. <view class="action-buttons">
  61. <button class="btn secondary" @click="handleBack">返回</button>
  62. <button class="btn primary"
  63. :disabled="!canSubmit || loading"
  64. @click="handleSubmit">
  65. {{ loading ? '提交中...' : '去认证' }}
  66. </button>
  67. <!--<button class="btn primary" @click="handleSubmit">去认证</button>-->
  68. </view>
  69. </view>
  70. </template>
  71. <script>
  72. import { authApi } from '@/api/auth.js';
  73. import UUpload from "@/uview-ui/components/u-upload/u-upload.vue";
  74. export default {
  75. components: {UUpload},
  76. data() {
  77. return {
  78. isAuthed: false, // 是否已认证通过
  79. loading: false,
  80. accessToken:'',
  81. uploadStatus: {
  82. front: { loading: false, result: null,info:null },
  83. back: { loading: false, result: null,info:null }
  84. },
  85. idFront: '', // 身份证正面
  86. idBack: '', // 身份证反面
  87. idFrontFile: {}, // 身份证正面
  88. idBackFile: {}, // 身份证反面
  89. fieldLabels: {
  90. // orderid: "流水号",
  91. realname: "姓名",
  92. sex: "性别",
  93. nation: "民族",
  94. born: "生日",
  95. address: "籍贯",
  96. idcard: "证件号",
  97. begin: "生效日期",
  98. end: "截止日期",
  99. department: "发放机关"
  100. },
  101. formData: {
  102. // orderid: null,
  103. realname: null,
  104. sex: null,
  105. nation: null,
  106. born: null,
  107. address: null,
  108. idcard: null,
  109. begin: null,
  110. end: null,
  111. department: null,
  112. // realName: '梁妙杏',
  113. // idCard: '460034199204034721'
  114. },
  115. }
  116. },
  117. computed: {
  118. // 提交按钮是否可用
  119. canSubmit() {
  120. return (
  121. // 必须填写表单
  122. // this.formData.realName &&
  123. // this.formData.idCard &&
  124. // 必须上传成功或已有认证数据
  125. (this.idFront || this.isAuthed) &&
  126. (this.idBack || this.isAuthed)
  127. );
  128. },
  129. // 过滤处理后的字段数据
  130. filteredFields() {
  131. return Object.entries(this.formData)
  132. .filter(([key, value]) =>
  133. value !== null &&
  134. this.fieldLabels.hasOwnProperty(key)
  135. )
  136. .reduce((acc, [key, value]) => {
  137. acc[key] = {
  138. label: this.fieldLabels[key],
  139. value: value
  140. };
  141. return acc;
  142. }, {});
  143. }
  144. },
  145. onLoad(options) {
  146. this.initData(options);
  147. },
  148. methods: {
  149. initData(options) {
  150. try {
  151. if (options.payload) {
  152. const payload = JSON.parse(decodeURIComponent(options.payload));
  153. this.originalData = payload.threeAuthInfo || {};
  154. // 初始化表单数据
  155. this.formData = { ...this.formData, ...this.originalData };
  156. // 初始化身份证图片
  157. if (this.originalData.front) {
  158. this.idFront = this.originalData.front;
  159. this.idBack = this.originalData.back;
  160. }
  161. // 设置编辑模式
  162. this.isEditMode = !!this.originalData.idcard;
  163. }
  164. } catch (e) {
  165. console.error('参数解析失败:', e);
  166. }
  167. },
  168. // 上传身份证照片
  169. async uploadIDCard(type) {
  170. try {
  171. // 重置状态
  172. this.$set(this.uploadStatus[type], 'loading', false);
  173. this.$set(this.uploadStatus[type], 'result', null);
  174. const res = await uni.chooseImage({ count: 1 });
  175. const tempPath = res.tempFilePaths[0];
  176. // 显示临时图片预览
  177. if (type === 'front') this.idFront = tempPath;
  178. else this.idBack = tempPath;
  179. // 上传到服务器
  180. this.$set(this.uploadStatus[type], 'loading', true);
  181. this.$set(this.uploadStatus[type], 'result', null);
  182. const uploadRes = await this.uploadFile(type, tempPath);
  183. // 更新状态
  184. this.$set(this.uploadStatus[type], 'loading', false);
  185. this.$set(this.uploadStatus[type], 'result', 'success');
  186. this.$set(this.uploadStatus[type], 'info', '识别成功');
  187. // 保存服务器返回的URL
  188. if (type === 'front') {
  189. this.idFront = uploadRes.data['fileUrl'];
  190. }else {
  191. this.idBack = uploadRes.data['fileUrl'];
  192. }
  193. const filteredData = Object.fromEntries(
  194. Object.entries(uploadRes.data).filter(([_, v]) => v !== null)
  195. )
  196. this.formData = { ...this.formData, ...filteredData }
  197. } catch (error) {
  198. console.error('上传失败:', error);
  199. if (type === 'front') this.idFront = null;
  200. else this.idBack = null;
  201. this.$set(this.uploadStatus[type], 'loading', false);
  202. this.$set(this.uploadStatus[type], 'result', 'fail');
  203. this.$set(this.uploadStatus[type], 'info', error.msg);
  204. // uni.showToast({ title: '上传失败,请重试', icon: 'none' });
  205. }
  206. },
  207. // 文件上传方法
  208. async uploadFile(type, filePath) {
  209. return new Promise((resolve, reject) => {
  210. uni.uploadFile({
  211. url: authApi.ocrApi(),
  212. filePath,
  213. name: 'file',
  214. formData: { type },
  215. header: {
  216. Authorization: `Bearer ${this.accessToken}`,
  217. // 'Content-Type': 'multipart/form-data'//注意请求类型
  218. },
  219. success: (res) => {
  220. const data = JSON.parse(res.data);
  221. if (data.code === 0) {
  222. resolve(data);
  223. } else {
  224. reject(data);
  225. }
  226. },
  227. fail: reject
  228. });
  229. });
  230. },
  231. // 返回操作
  232. handleBack() {
  233. uni.navigateBack()
  234. },
  235. // 提交认证
  236. async handleSubmit() {
  237. if (!this.canSubmit) return;
  238. try {
  239. const postData = {};
  240. /*Object.keys(this.filteredFields).forEach((key) => {
  241. const item = this.filteredFields[key];
  242. if (item && item.value !== null) {
  243. postData[item.label] = item.value; // 使用label作为key
  244. }
  245. });*/
  246. Object.keys(this.filteredFields).forEach(originKey => {
  247. const fieldConfig = this.fieldLabels[originKey];
  248. const value = this.filteredFields[originKey]?.value;
  249. if (fieldConfig.required && !value) {
  250. throw new Error(`${fieldConfig.label}不能为空`);
  251. }
  252. postData[originKey] = value;
  253. });
  254. postData['front'] = this.idFront
  255. postData['back'] = this.idBack
  256. this.loading = true;
  257. const res = await authApi.threeElementApi(postData);
  258. if (res.code === 0) {
  259. //核验无误,需要输入验证码
  260. //显示弹窗验证码弹窗
  261. uni.showToast({ title: '认证成功' });
  262. this.isAuthed = true;
  263. // 更新本地数据
  264. // await this.loadAuthData();
  265. //活体
  266. const res2 = await authApi.personFaceApi({
  267. ...this.formData,
  268. front: this.idFront,
  269. back: this.idBack
  270. });
  271. console.log('res2res2res2',res2.data);
  272. if (res2.code === 0) {
  273. uni.navigateTo({
  274. url: `/pages/face-auth/face-auth?faceUrl=${encodeURIComponent(res2.data.faceUrl)}`
  275. });
  276. // 监听认证结果
  277. uni.$on('FACE_AUTH_SUCCESS', this.handleAuthSuccess);
  278. }
  279. }
  280. } finally {
  281. this.loading = false;
  282. }
  283. }
  284. },
  285. // 表单验证
  286. validateForm() {
  287. if (!this.idFront || !this.idBack) {
  288. uni.showToast({ title: '请上传身份证正反面照片', icon: 'none' })
  289. return false
  290. }
  291. if (!this.formData.realName.trim()) {
  292. uni.showToast({ title: '请输入姓名', icon: 'none' })
  293. return false
  294. }
  295. if (!/^\d{17}[\dXx]$/.test(this.formData.idCard)) {
  296. uni.showToast({ title: '请输入有效身份证号码', icon: 'none' })
  297. return false
  298. }
  299. return true
  300. },
  301. // 新增结果处理方法
  302. handleAuthSuccess(data) {
  303. console.log('检测活体检测 ',data)
  304. /*uni.showToast({ title: '活体认证成功' });
  305. // 处理后续业务逻辑...
  306. // 例如更新认证状态、跳转回个人中心等
  307. setTimeout(() => {
  308. uni.navigateBack({ delta: 2 }); // 返回两级页面
  309. }, 1500);*/
  310. },
  311. }
  312. </script>
  313. <style lang="scss" scoped>
  314. .auth-container {
  315. background: #fff;
  316. min-height: 100vh;
  317. padding: 40rpx;
  318. }
  319. .upload-section {
  320. margin-bottom: 40rpx;
  321. .upload-title {
  322. font-size: 32rpx;
  323. color: #333;
  324. display: block;
  325. margin-bottom: 30rpx;
  326. }
  327. .idcard-wrapper {
  328. display: flex;
  329. gap: 20rpx;
  330. }
  331. .upload-item {
  332. flex: 1;
  333. height: 240rpx;
  334. background: #f5f5f5;
  335. border-radius: 12rpx;
  336. display: flex;
  337. align-items: center;
  338. justify-content: center;
  339. .preview-image {
  340. width: 100%;
  341. height: 100%;
  342. border-radius: 12rpx;
  343. }
  344. .placeholder {
  345. color: #999;
  346. font-size: 28rpx;
  347. }
  348. }
  349. }
  350. .form-item {
  351. display: flex;
  352. align-items: center;
  353. //margin-bottom: 40rpx;
  354. //padding: 20rpx 0;
  355. min-height: 96rpx;
  356. border-bottom: 1rpx solid #eee;
  357. .label {
  358. width: 160rpx;
  359. font-size: 28rpx;
  360. color: #666;
  361. flex-shrink: 0;
  362. }
  363. .input {
  364. flex: 1;
  365. height: 60rpx;
  366. font-size: 28rpx;
  367. color: #333;
  368. padding-left: 20rpx;
  369. &::placeholder {
  370. color: #999;
  371. }
  372. }
  373. }
  374. .action-buttons {
  375. position: fixed;
  376. bottom: 40rpx;
  377. left: 40rpx;
  378. right: 40rpx;
  379. display: flex;
  380. gap: 20rpx;
  381. .btn {
  382. flex: 1;
  383. height: 88rpx;
  384. line-height: 88rpx;
  385. border-radius: 44rpx;
  386. font-size: 32rpx;
  387. &.secondary {
  388. background: #f5f5f5;
  389. color: #666;
  390. }
  391. &.primary {
  392. background: #007aff;
  393. color: #fff;
  394. }
  395. }
  396. }
  397. .upload-item {
  398. position: relative;
  399. }
  400. .upload-mask {
  401. position: absolute;
  402. top: 0;
  403. left: 0;
  404. right: 0;
  405. bottom: 0;
  406. background: rgba(0,0,0,0.5);
  407. display: flex;
  408. align-items: center;
  409. justify-content: center;
  410. color: white;
  411. }
  412. .result-tag {
  413. position: absolute;
  414. right: 8px;
  415. bottom: 8px;
  416. width: 24px;
  417. height: 24px;
  418. border-radius: 50%;
  419. display: flex;
  420. align-items: center;
  421. justify-content: center;
  422. font-size: 16px;
  423. }
  424. .result-tag.success {
  425. background: #67C23A;
  426. color: white;
  427. }
  428. .result-tag.fail {
  429. background: #F56C6C;
  430. color: white;
  431. }
  432. </style>