fstarter 2.10.56 → 2.10.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.babelrc +6 -6
  2. package/.editorconfig +9 -9
  3. package/index.html +22 -22
  4. package/index.js +222 -222
  5. package/package.json +104 -104
  6. package/src/App.vue +38 -38
  7. package/src/i18n/en-US.js +35 -35
  8. package/src/i18n/zh-CN.js +35 -35
  9. package/src/main.js +95 -95
  10. package/src/plugins/assets/ak.js +948 -948
  11. package/src/plugins/assets/callNative.js +490 -490
  12. package/src/plugins/assets/compressImg.js +75 -75
  13. package/src/plugins/assets/config.js +106 -106
  14. package/src/plugins/assets/fileServer.js +469 -469
  15. package/src/plugins/assets/http.js +344 -344
  16. package/src/plugins/assets/ua.js +27 -27
  17. package/src/plugins/components/BSButton.vue +61 -61
  18. package/src/plugins/components/BSCascader.vue +465 -465
  19. package/src/plugins/components/BSCell.vue +48 -48
  20. package/src/plugins/components/BSDatePicker.vue +167 -167
  21. package/src/plugins/components/BSImage.vue +42 -42
  22. package/src/plugins/components/BSInput.vue +140 -140
  23. package/src/plugins/components/BSList.vue +81 -81
  24. package/src/plugins/components/BSPicCode.vue +96 -96
  25. package/src/plugins/components/BSPopup.vue +43 -43
  26. package/src/plugins/components/BSRadio.vue +97 -97
  27. package/src/plugins/components/BSSearch.vue +109 -109
  28. package/src/plugins/components/BSSelect.vue +144 -144
  29. package/src/plugins/components/BSSign.vue +454 -454
  30. package/src/plugins/components/BSStepper.vue +115 -115
  31. package/src/plugins/components/BSUpload.vue +92 -92
  32. package/src/plugins/components/BSUpload2.vue +397 -397
  33. package/src/plugins/components/BSVerCode.vue +128 -128
  34. package/src/plugins/components/BSViewer.vue +92 -92
  35. package/src/plugins/components/base.js +496 -496
  36. package/src/plugins/components/base2.js +489 -489
  37. package/src/plugins/lib/weixin.js +20 -20
  38. package/src/plugins/platform/index.js +7 -7
  39. package/src/plugins/platform/isp_phone.js +310 -310
  40. package/src/plugins/plugins/README.md +560 -0
  41. package/src/plugins/plugins/browserUtils.js +925 -0
  42. package/src/plugins/plugins/components/imageCropper.vue +299 -0
  43. package/src/plugins/plugins/components/images/bz.png +0 -0
  44. package/src/plugins/plugins/components/images/correct.png +0 -0
  45. package/src/plugins/plugins/components/images/error.png +0 -0
  46. package/src/plugins/plugins/components/images/mohu.png +0 -0
  47. package/src/plugins/plugins/components/images/qs.png +0 -0
  48. package/src/plugins/plugins/components/images/zd.png +0 -0
  49. package/src/plugins/plugins/components/pdfPreview.vue +688 -0
  50. package/src/plugins/plugins/demo.vue +832 -0
  51. package/src/plugins/plugins/native-js-sdk.js +360 -0
  52. package/src/plugins/plugins/nativeUtils.js +1444 -0
  53. package/src/plugins/route/index.js +140 -140
  54. package/src/plugins/selector/index.js +342 -342
  55. package/src/plugins/service/index.js +81 -81
  56. package/src/plugins/services/callCamera.js +53 -53
  57. package/src/plugins/services/exit.js +37 -36
  58. package/src/plugins/services/face.js +69 -69
  59. package/src/plugins/services/faceH5.js +54 -54
  60. package/src/plugins/services/faceInApp.js +31 -31
  61. package/src/plugins/services/faceTx.js +61 -61
  62. package/src/plugins/services/getFaceResult.js +104 -104
  63. package/src/plugins/services/getH5FaceResult.js +62 -62
  64. package/src/plugins/services/getMenus.js +40 -40
  65. package/src/plugins/services/getSystemData.js +144 -128
  66. package/src/plugins/services/getToken.js +93 -79
  67. package/src/plugins/services/getTxFaceResult.js +83 -83
  68. package/src/plugins/services/getUserInfo.js +47 -47
  69. package/src/plugins/services/goSetPage.js +40 -40
  70. package/src/plugins/services/hideFhoneTitle.js +36 -36
  71. package/src/plugins/services/index.js +45 -45
  72. package/src/plugins/services/init.js +35 -35
  73. package/src/plugins/services/jumpView.js +42 -40
  74. package/src/plugins/services/logout.js +44 -43
  75. package/src/plugins/services/share.js +113 -113
  76. package/src/plugins/services/statusBarHeight.js +39 -39
  77. package/src/plugins/session/index.js +32 -32
  78. package/src/services/getAuthInfo.js +22 -22
  79. package/src/services/index.js +9 -9
  80. package/src/services/sendVerCode.js +23 -23
  81. package/src/views/auth.vue +367 -367
  82. package/src/views/auth2.vue +90 -90
  83. package/src/views/auth3.vue +157 -157
  84. package/src/views/auth4.vue +8979 -8979
  85. package/src/views/auth5.vue +50 -50
  86. package/src/views/authh5.vue +369 -369
  87. package/src/views/components/BankSelect.vue +55 -55
  88. package/src/views/foot.vue +140 -140
  89. package/src/views/page.vue +222 -222
  90. package/src/views/shellFunc.vue +41 -41
  91. package/themes/basic.css +1 -1
  92. package/webpack.config.js +144 -144
  93. package/fstarter.iml +0 -9
@@ -0,0 +1,1444 @@
1
+ import native from './native-js-sdk.js'
2
+ import ImageCropper from './components/imageCropper.vue';
3
+ import { Toast } from "vant"
4
+ import axios from 'axios'
5
+ import { JSEncrypt } from 'jsencrypt'
6
+ const publicKey = `-----BEGIN PUBLIC KEY-----
7
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCSd6fXGTr+aQRQCFt3Vz7Pi3zW
8
+ rqUtVJt/U8lGVU8XX1aQ9mvqNTpeKwyHFNJSKBDbM3OrnHo2FvX7c8/ymhGKMBM3
9
+ NQfTlKqP3qXn3hvDppIrImpNAuYLNgfbiyHw37ggCpA1ynwJWqdHjOhllnuNsv67
10
+ 1TpLHC4iCdA2vizuBwIDAQAB
11
+ -----END PUBLIC KEY-----`
12
+ import Vue from "vue";
13
+ // 获取当前环境是否为生产环境
14
+ function isProductionEnvironment(){
15
+ const url = window.location.href;
16
+ // 将URL转换为小写以便不区分大小写比较
17
+ const lowerUrl = url.toLowerCase();
18
+ console.log(lowerUrl, 'lowerUrl');
19
+
20
+ // 修复正则表达式 - 正确的IP地址匹配模式
21
+ const ipPattern = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
22
+
23
+ // 其他测试环境关键词
24
+ const testKeywords = ['teststatic', 'testwx'];
25
+
26
+ // 如果URL包含IP地址格式或任一测试关键词,则返回false(测试环境)
27
+ // 否则返回true(生产环境)
28
+ return !(ipPattern.test(lowerUrl) || testKeywords.some(keyword => lowerUrl.includes(keyword)));
29
+ }
30
+ // 根据环境设置请求url key
31
+ let auth = 0 // 0: 不需要token 1: 需要token
32
+ let upLoadUrl = ''
33
+ let creatFaceUrl = ''
34
+ let faceResultUrl = ''
35
+ let appid = ''
36
+ let tokenKey = ''
37
+ let loginUrl = ''
38
+ // 是否登录
39
+ let loginPromise = null
40
+
41
+ if(isProductionEnvironment()){
42
+ console.log('生产环境')
43
+ auth = 1
44
+ upLoadUrl = 'https://file.ihasl.com/file/v1/hasl/fileAdmin/fileAdmin/upload'
45
+ creatFaceUrl = 'https://bcs.ihasl.com/api/v1/bcs/face/h5CreateOrder'
46
+ faceResultUrl = 'https://bcs.ihasl.com/api/v1/bcs/face/faceResultQuery'
47
+ appid = 'wx751d925fbededc73' // 回调安全域名 wx.ihasl
48
+ tokenKey = '6297391dca6d4090ae60959acb73cda1'
49
+ loginUrl = 'https://www.ihasl.com/pages/salessupport/app/#/login'
50
+ } else {
51
+ console.log('测试环境')
52
+ auth = 0
53
+ upLoadUrl = 'https://testfile.ihasl.com/file/v1/hasl/fileAdmin/fileAdmin/upload'
54
+ creatFaceUrl = 'https://testbcs.ihasl.com/api/v1/bcs/face/h5CreateOrder'
55
+ faceResultUrl = 'https://testbcs.ihasl.com/api/v1/bcs/face/faceResultQuery'
56
+ appid = 'wx38c646900b5b7ec0' // 回调安全域名 teststatic.com
57
+ tokenKey = '814a0b7eb0f82e02246da5e098b6e2b5'
58
+ loginUrl = 'https://teststatic.ihasl.com/pages/salessupport/app/#/login'
59
+ // 本地的
60
+ // loginUrl = 'http://172.20.10.2:8080/#/login'
61
+ }
62
+ console.log(appid, 'appid')
63
+
64
+ //
65
+ const nativeUtils = {
66
+ /**
67
+ * 判断当前环境是否为生产环境
68
+ */
69
+ isProductionEnvironment(){
70
+ const url = window.location.href;
71
+ // 将URL转换为小写以便不区分大小写比较
72
+ const lowerUrl = url.toLowerCase();
73
+
74
+ // 检查URL中是否包含测试环境的关键词
75
+ const testKeywords = ['ip', 'teststatic', 'testwx'];
76
+
77
+ // 如果URL包含任一测试关键词,则返回false(测试环境)
78
+ // 否则返回true(生产环境)
79
+ return !testKeywords.some(keyword => lowerUrl.includes(keyword));
80
+ },
81
+ /**
82
+ * 设置用户信息
83
+ */
84
+ setUserInfo(parmas) {
85
+ return new Promise((resolve, reject) => {
86
+ native.auth.setUserInfo(parmas).then(res => {
87
+ resolve(res);
88
+ }).catch(error => {
89
+ Toast.fail(`设置用户信息失败: ${error.message}`)
90
+ reject(error);
91
+ });
92
+ })
93
+ },
94
+ /**
95
+ * 获取用户信息
96
+ */
97
+ getUserInfo() {
98
+ return new Promise((resolve, reject) => {
99
+ native.auth.getUserInfo().then(res => {
100
+ if(!!res){
101
+ resolve(res);
102
+ } else {
103
+ // 拿不到信息跳转登录
104
+ nativeUtils.toLogin()
105
+ }
106
+ }).catch(error => {
107
+ Toast.fail(`获取用户信息: ${error.message}`)
108
+ reject(error);
109
+ });
110
+ })
111
+ },
112
+ /**
113
+ * 清除用户信息
114
+ */
115
+ clearUserInfo() {
116
+ return new Promise((resolve, reject) => {
117
+ native.auth.clearUserInfo().then(res => {
118
+ resolve(res);
119
+ }).catch(error => {
120
+ Toast.fail(`清除用户信息失败: ${error.message}`)
121
+ reject(error);
122
+ });
123
+ })
124
+ },
125
+ /**
126
+ * 微信分享
127
+ */
128
+ share(parmas) {
129
+ return new Promise((resolve, reject) => {
130
+ native.thirdParty.wechat.share(parmas).then(res => {
131
+ resolve(res);
132
+ }).catch(error => {
133
+ Toast.fail(`分享失败: ${error.message}`)
134
+ reject(error);
135
+ });
136
+ })
137
+ },
138
+ /**
139
+ * 微信登录
140
+ */
141
+ wechatLogin() {
142
+ return new Promise((resolve, reject) => {
143
+ native.thirdParty.wechat.login({
144
+ state: 'custom_state'
145
+ }).then(res => {
146
+ resolve(res);
147
+ }).catch(error => {
148
+ Toast.fail(`微信登录: ${error.message}`)
149
+ reject(error);
150
+ });
151
+ })
152
+ },
153
+ /**
154
+ * 打开系统浏览器
155
+ */
156
+ openSystemBrowser(url) {
157
+ return new Promise((resolve, reject) => {
158
+ native.utils.openSystemBrowser({
159
+ url: url
160
+ }).then(res => {
161
+ resolve(res);
162
+ }).catch(error => {
163
+ Toast.fail(`微信登录: ${error.message}`)
164
+ reject(error);
165
+ });
166
+ })
167
+ },
168
+ /**
169
+ * 截屏
170
+ */
171
+ screenshot(maxSize = 1024) {
172
+ return new Promise((resolve, reject) => {
173
+ native.utils.screenshot({
174
+ maxSize: maxSize // 截图压缩大小,单位为KB,默认200KB
175
+ }).then(res => {
176
+ resolve(res);
177
+ }).catch(error => {
178
+ Toast.fail(`截屏: ${error.message}`)
179
+ reject(error);
180
+ });
181
+ })
182
+ },
183
+ /**
184
+ * 横竖屏切换
185
+ */
186
+ setScreenOrientation(screenOrientation) {
187
+ return new Promise((resolve, reject) => {
188
+ native.device.setScreenOrientation({
189
+ orientation: screenOrientation, // 可选值:'portrait', 'landscape' 或 'auto'
190
+ }).then(res => {
191
+ resolve(res);
192
+ }).catch(error => {
193
+ Toast.fail(`横竖屏切换: ${error.message}`)
194
+ reject(error);
195
+ });
196
+ })
197
+ },
198
+ /**
199
+ * 唤起其他app
200
+ */
201
+ launchApp(params) {
202
+ return new Promise((resolve, reject) => {
203
+ native.utils.launchApp(params).then(res => {
204
+ resolve(res);
205
+ }).catch(error => {
206
+ Toast.fail(`唤起其他app: ${error.message}`)
207
+ reject(error);
208
+ });
209
+ })
210
+ },
211
+ /**
212
+ * 返回拦截
213
+ */
214
+ setBackInterceptor(enable) {
215
+ return new Promise((resolve, reject) => {
216
+ native.navigator.setBackInterceptor({
217
+ enable: enable
218
+ }).then(res => {
219
+ resolve(res);
220
+ }).catch(error => {
221
+ Toast.fail(`设置返回拦截失败: ${error.message}`)
222
+ reject(error);
223
+ });
224
+ })
225
+ },
226
+ /**
227
+ * 页面重定向
228
+ */
229
+ redirectTo(url) {
230
+ return new Promise((resolve, reject) => {
231
+ native.navigator.redirectTo({
232
+ name: 'h5',
233
+ params: {
234
+ url: url
235
+ }
236
+ }).then(res => {
237
+ resolve(res);
238
+ }).catch(error => {
239
+ Toast.fail(`页面重定向失败: ${error.message}`)
240
+ reject(error);
241
+ });
242
+ })
243
+ },
244
+ /**
245
+ * 去人脸
246
+ */
247
+ goCheckFace(params) {
248
+ return new Promise((resolve, reject) => {
249
+ axios({
250
+ method: 'post',
251
+ url: creatFaceUrl,
252
+ data: params,
253
+ timeout: 30 * 1000, // 30秒超时
254
+ headers: {
255
+ 'Content-Type': 'application/json',
256
+ 'Accept-Attr4': 'NULL'
257
+ }
258
+ }).then(response => {
259
+ if (response.data.resultType === 'SUCCESS') {
260
+ resolve(response.data)
261
+ } else {
262
+ reject(new Error('接口调用失败'))
263
+ }
264
+ }).catch(error => {
265
+ if (error) {
266
+ reject(new Error('接口调用失败'))
267
+ }
268
+ })
269
+ });
270
+ },
271
+
272
+ /**
273
+ * 检查当前页面URL中是否同时存在resultType和orderNo参数
274
+ * @returns {boolean} 如果两个参数都存在返回true,否则返回false
275
+ */
276
+ hasResultTypeAndOrderNo() {
277
+ const hash = window.location.hash;
278
+ let search = '';
279
+
280
+ // 如果有hash并且包含查询参数
281
+ if (hash.includes('?')) {
282
+ search = hash.split('?')[1];
283
+ } else {
284
+ // 否则使用标准的location.search
285
+ search = window.location.search.substring(1);
286
+ }
287
+
288
+ // 如果没有查询参数,直接返回false
289
+ if (!search) {
290
+ return false;
291
+ }
292
+
293
+ const urlParams = new URLSearchParams(search);
294
+ return urlParams.has('resultType') && urlParams.has('orderNo');
295
+ },
296
+
297
+ /**
298
+ * 检测人脸识别
299
+ */
300
+ checkFace() {
301
+ return new Promise((resolve, reject) => {
302
+ if (nativeUtils.hasResultTypeAndOrderNo()) {
303
+ Toast.loading({
304
+ mask: true,
305
+ message: '人脸结果加载中...',
306
+ duration: 0, // 持续展示 toast
307
+ });
308
+ // 处理Vue Router hash模式下的查询参数
309
+ const hash = window.location.hash;
310
+ let search = '';
311
+
312
+ // 如果有hash并且包含查询参数
313
+ if (hash.includes('?')) {
314
+ search = hash.split('?')[1];
315
+ } else {
316
+ // 否则使用标准的location.search
317
+ search = window.location.search.substring(1);
318
+ }
319
+
320
+ const urlParams = new URLSearchParams(search);
321
+
322
+ let params = {
323
+ orderNo: urlParams.get('orderNo'), // 从URL中获取orderNo参数
324
+ requestSource: 'WX', // 请求来源
325
+ meta: { // 元数据
326
+ tenantCode: 'WX', // 租户编码
327
+ businessCode: 'WX-EDOR', // 业务编码
328
+ serviceType: 'FACE' // 业务类型
329
+ }
330
+ }
331
+ axios({
332
+ method: 'post',
333
+ url: faceResultUrl,
334
+ data: params,
335
+ timeout: 30 * 1000, // 30秒超时
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ 'Accept-Attr4': 'NULL'
339
+ }
340
+ }).then(response => {
341
+ if (response.data.resultType === 'SUCCESS') {
342
+ // console.log('接口调用成功', response)
343
+ // if(response.facePhotoUrl && response.facePhotoUrl!=='null' && response.similarity && response.similarity!=='null'){
344
+ // resolve(response.data)
345
+ // } else {
346
+ // Toast('人脸对比未返回图片和相似度,请重试');
347
+ // }
348
+ Toast.clear()
349
+ resolve({
350
+ similarity: "99",
351
+ liveRate: "99",
352
+ facePhotoUrl: "http://testfile.ihasl.com/file/v1/hasl/fileAdmin/fileAdmin/get/z254g0pc55tj11apjge0dun8hvxjdxxs.jpg",
353
+ })
354
+ } else {
355
+ reject(new Error('接口调用失败'))
356
+ }
357
+ }).catch(error => {
358
+ if (error) {
359
+ reject(new Error('接口调用失败'))
360
+ }
361
+ })
362
+ } else {
363
+ reject(new Error('缺少必要的参数'))
364
+ }
365
+ })
366
+ },
367
+ /**
368
+ * 扫描二维码
369
+ */
370
+ scanQRCode() {
371
+ return new Promise((resolve, reject) => {
372
+ native.utils.scanQRCode().then(res => {
373
+ resolve(res);
374
+ }).catch(error => {
375
+ Toast.fail(`扫描二维码失败: ${error.message}`)
376
+ reject(error);
377
+ });
378
+ });
379
+ },
380
+ /**
381
+ * 阅读pdf
382
+ */
383
+ pdfRead(pdfs, options = {}) {
384
+ return new Promise((resolve, reject) => {
385
+ try {
386
+ // 确保pdfs是数组格式
387
+ let pdfList = Array.isArray(pdfs) ? pdfs : [pdfs];
388
+
389
+ // 格式化PDF数据
390
+ const formattedPdfs = pdfList.map(pdf => typeof pdf === 'string' ? {
391
+ url: pdf
392
+ } : pdf);
393
+
394
+ // 创建挂载点
395
+ const mountElement = document.createElement('div');
396
+ mountElement.id = 'pdf-preview-mount-' + Date.now();
397
+ document.body.appendChild(mountElement);
398
+
399
+ // 动态导入组件
400
+ import('./components/pdfPreview.vue').then(componentModule => {
401
+ const PdfPreviewComponent = componentModule.default;
402
+
403
+ const instance = new Vue({
404
+ el: mountElement,
405
+ render: h => h(PdfPreviewComponent, {
406
+ ref: 'pdfPreview'
407
+ }),
408
+ mounted() {
409
+ this.$nextTick(() => {
410
+ const pdfPreview = this.$refs.pdfPreview;
411
+ if (pdfPreview) {
412
+ pdfPreview.open(formattedPdfs, options);
413
+
414
+ // 监听关闭事件
415
+ pdfPreview.$on('close', () => {
416
+ try {
417
+ if (mountElement && mountElement.parentNode) {
418
+ mountElement.parentNode.removeChild(mountElement);
419
+ }
420
+ } catch (e) {
421
+ console.warn('Error removing PDF preview element:', e);
422
+ }
423
+ resolve({
424
+ success: true
425
+ });
426
+ });
427
+ } else {
428
+ document.body.removeChild(mountElement);
429
+ reject(new Error('PdfPreview引用未找到'));
430
+ }
431
+ });
432
+ }
433
+ });
434
+ }).catch(err => {
435
+ console.error('Failed to load PDF preview component:', err);
436
+ document.body.removeChild(mountElement);
437
+ reject(new Error('无法加载PDF预览组件'));
438
+ });
439
+ } catch (error) {
440
+ console.error('Error in pdfRead method:', error);
441
+ reject(error);
442
+ }
443
+ });
444
+ },
445
+ Toast(message) {
446
+ Toast(message)
447
+ },
448
+ /**
449
+ * 添加日历事件
450
+ */
451
+ addCalendarEvent(params) {
452
+ return new Promise((resolve, reject) => {
453
+ native.device.addCalendarEvent(params).then(res => {
454
+ resolve(res);
455
+ }).catch(error => {
456
+ Toast.fail(`添加日历事件失败: ${error.message}`)
457
+ reject(error);
458
+ });
459
+ });
460
+ },
461
+ /**
462
+ * 持久化存储
463
+ */
464
+ setItem(key, value) {
465
+ return new Promise((resolve, reject) => {
466
+ native.storage.setItem(key, value).then(res => {
467
+ resolve(res);
468
+ }).catch(error => {
469
+ Toast.fail(`持久化存储失败: ${error.message}`)
470
+ reject(error);
471
+ });
472
+ });
473
+ },
474
+
475
+ /**
476
+ * 持久化获取
477
+ */
478
+ getItem(key) {
479
+ return new Promise((resolve, reject) => {
480
+ native.storage.getItem(key).then(res => {
481
+ resolve(res);
482
+ }).catch(error => {
483
+ console.log(error, 'error')
484
+ Toast.fail(`持久化获取失败: ${error.message}`)
485
+ reject(error);
486
+ });
487
+ });
488
+ },
489
+
490
+ /**
491
+ * 持久化删除
492
+ */
493
+ removeItem(key) {
494
+ return new Promise((resolve, reject) => {
495
+ native.storage.removeItem(key).then(res => {
496
+ resolve(res);
497
+ }).catch(error => {
498
+ Toast.fail(`持久化删除失败: ${error.message}`)
499
+ reject(error);
500
+ });
501
+ });
502
+ },
503
+
504
+ /**
505
+ * 获取定位信息
506
+ */
507
+ getLocation() {
508
+ return new Promise((resolve, reject) => {
509
+ native.thirdParty.geo.getLocation().then(res => {
510
+ resolve(res)
511
+ }).catch(error => {
512
+ Toast.fail(`获取定位信息失败: ${error.message}`)
513
+ reject(error);
514
+ });
515
+ });
516
+ },
517
+
518
+ /**
519
+ * 获取设备信息
520
+ */
521
+ getDeviceInfo() {
522
+ return new Promise((resolve, reject) => {
523
+ native.device.getDeviceInfo({
524
+ fields: ['osName', 'model', 'brand', 'osVersion', 'screen', 'deviceId', 'networkType', 'ip', 'timezone', 'StartTime', 'packageInfo', 'isp', 'appVersion'],
525
+ sceneDesc: '获取设备信息'
526
+ }).then(res => {
527
+ resolve(res);
528
+ }).catch(error => {
529
+ Toast.fail(`获取设备信息失败: ${error.message}`)
530
+ reject(error);
531
+ });
532
+ });
533
+ },
534
+
535
+ /**
536
+ * 获取缓存大小
537
+ */
538
+ getCacheSize() {
539
+ return new Promise((resolve, reject) => {
540
+ native.utils.getCacheSize().then(res => {
541
+ resolve(res);
542
+ }).catch(error => {
543
+ Toast.fail(`获取缓存大小失败: ${error.message}`)
544
+ reject(error);
545
+ });
546
+ });
547
+ },
548
+
549
+ /**
550
+ * 清除缓存
551
+ */
552
+ clearCache() {
553
+ return new Promise((resolve, reject) => {
554
+ native.utils.clearCache().then(res => {
555
+ resolve(res)
556
+ }).catch(error => {
557
+ Toast.fail(`清除缓存失败: ${error.message}`)
558
+ reject(error);
559
+ });
560
+ });
561
+ },
562
+
563
+ // 跳转页面
564
+ openPage(jumpUrl, visible = false, title = '', safeArea = true) {
565
+ return new Promise((resolve, reject) => {
566
+ // 兼容测试、生产、环境
567
+ let result = jumpUrl
568
+ let url = ''
569
+ const replaceRules = [
570
+ { // 测试环境-e行
571
+ pattern: "https://teststatic.ihasl.com/pages/eas/#/",
572
+ replacement: "https://teststatic.ihasl.com/pages/uat2/eas/#/"
573
+ },
574
+ { // 生产环境-e行
575
+ pattern: "https://www.ihasl.com/pages/eas/#/",
576
+ replacement: "https://www.ihasl.com/pages/master2/eas/#/"
577
+ },
578
+ { // 测试环境-app-hasl
579
+ pattern: "https://teststatic.ihasl.com/pages/salessupport/app/#/",
580
+ replacement: "https://teststatic.ihasl.com/pages/uat2/salessupport/app/#/"
581
+ },
582
+ { // 生产环境-app-hasl
583
+ pattern: "https://www.ihasl.com/pages/salessupport/app/#/",
584
+ replacement: "https://www.ihasl.com/pages/master2/salessupport/app/#/"
585
+ },
586
+ { //测试环境 app2-hasl
587
+ pattern: "https://teststatic.ihasl.com/pages/app2-hasl/#/",
588
+ replacement: "https://teststatic.ihasl.com/pages/uat2/app2-hasl/#/"
589
+ },
590
+ { //生产环境 app2-hasl
591
+ pattern: "https://www.ihasl.com/pages/app2-hasl/#/",
592
+ replacement: "https://www.ihasl.com/pages/master2/app2-hasl/#/"
593
+ },
594
+ { // 测试环境 招募recruit
595
+ pattern: "https://teststatic.ihasl.com/pages/salessupport/recruit/#/",
596
+ replacement: "https://teststatic.ihasl.com/pages/uat2/salessupport/recruit/#/"
597
+ },
598
+ { // 生产环境 招募recruit
599
+ pattern: "https://www.ihasl.com/pages/salessupport/recruit/#/",
600
+ replacement: "https://www.ihasl.com/pages/master2/salessupport/recruit/#/"
601
+ }
602
+ ]
603
+
604
+ // 遍历所有规则,找到匹配的进行替换
605
+ for (let rule of replaceRules) {
606
+ if (result.includes(rule.pattern)) {
607
+ url = result.replace(rule.pattern, rule.replacement);
608
+ break; // 找到匹配后跳出循环
609
+ }
610
+ }
611
+ native.navigator.openPage({
612
+ name: 'h5',
613
+ params: {
614
+ url: url,
615
+ navigationBarVisible: visible, // false:不显示 true:显示
616
+ title: title,
617
+ safeArea: safeArea, // true处理安全区 false不处理 默认false不处理
618
+ }
619
+ }).then(res => {
620
+ resolve(res);
621
+ }).catch(error => {
622
+ Toast.fail(`打开页面失败: ${error.message}`)
623
+ reject(error);
624
+ });
625
+ });
626
+ },
627
+ // 跳转页面带分享
628
+ openPageWithShare(params) {
629
+ return new Promise((resolve, reject) => {
630
+ native.navigator.openPage({
631
+ name: 'externalH5',
632
+ params: {
633
+ url: params.url,
634
+ actionType: 'share',
635
+ title: params.title,
636
+ shareInfo: {
637
+ type: params.shareInfo.type, // 分享类型 webpage|text|image|miniProgram
638
+ // scene: 'session', // 可选值: session|timeline
639
+ content: {
640
+ title: params.shareInfo.content.title ? params.shareInfo.content.title : '恒安标准人寿',
641
+ description: params.shareInfo.content.description ? params.shareInfo.content.description : '恒安标准人寿',
642
+ webpageUrl: params.shareInfo.content.webpageUrl, // 替换为实际的分享链接
643
+ thumb: params.shareInfo.content.thumb ? params.shareInfo.content.thumb : 'https://www.ihasl.com/pages/salessupport/app/images/logo.png',
644
+ }
645
+ }
646
+ }
647
+ }).then(res => {
648
+ resolve(res);
649
+ }).catch(error => {
650
+ Toast.fail(`打开页面失败: ${error.message}`)
651
+ reject(error);
652
+ });
653
+ });
654
+ },
655
+
656
+ /**
657
+ * 关闭当前H5页面
658
+ */
659
+ closePage() {
660
+ return new Promise((resolve, reject) => {
661
+ native.navigator.closePage().then(res => {
662
+ resolve(res);
663
+ }).catch(error => {
664
+ Toast.fail(`关闭当前H5页面失败: ${error.message}`)
665
+ reject(error);
666
+ });
667
+ });
668
+ },
669
+
670
+ // 隐藏原生标题
671
+ setNavigationBarVisible(visible) {
672
+ return new Promise((resolve, reject) => {
673
+ native.navigator.setNavigationBarVisible({
674
+ visible: visible
675
+ }).then(res => {
676
+ resolve(res);
677
+ }).catch(error => {
678
+ Toast.fail(`隐藏原生标题失败: ${error.message}`)
679
+ reject(error);
680
+ });
681
+ });
682
+ },
683
+
684
+ /**
685
+ * 拨打电话
686
+ * @param {*} phoneNo 手机号
687
+ */
688
+ callphone(phoneNo) {
689
+ return new Promise((resolve, reject) => {
690
+ native.device.callPhone({
691
+ phoneNo: phoneNo,
692
+ sceneDesc: '拨打电话'
693
+ }).then(res => {
694
+ resolve(res);
695
+ }).catch(error => {
696
+ if (error.code === 2000) {
697
+ Toast.fail(`拨打电话失败: ${error.message}`)
698
+ }
699
+ reject(error);
700
+ });
701
+ });
702
+ },
703
+
704
+ // 短信
705
+ sendSMS(phone, content) {
706
+ return new Promise((resolve, reject) => {
707
+ native.device.sendSMS({
708
+ phoneNo: phone,
709
+ message: content,
710
+ sceneDesc: '发送短信',
711
+ }).then(() => {
712
+ resolve()
713
+ }).catch(error => {
714
+ Toast.fail(`发送短信失败: ${error.message}`)
715
+ reject(error);
716
+ });
717
+ });
718
+ },
719
+ segmentedEncrypt(data) {
720
+ try {
721
+ // 创建JSEncrypt对象
722
+ const encrypt = new JSEncrypt()
723
+ // 设置公钥
724
+ encrypt.setPublicKey(publicKey)
725
+
726
+ // RSA 1024位密钥的最大加密长度为117字节
727
+ const maxChunkSize = 117
728
+ const bytes = nativeUtils.stringToBytes(data)
729
+ const chunks = []
730
+
731
+ // 分段处理
732
+ for (let i = 0; i < bytes.length; i += maxChunkSize) {
733
+ const chunk = bytes.slice(i, i + maxChunkSize)
734
+ const chunkStr = nativeUtils.bytesToString(chunk)
735
+ const encryptedChunk = encrypt.encrypt(chunkStr)
736
+
737
+ if (!encryptedChunk) {
738
+ throw new Error(`加密第${i/maxChunkSize + 1}段数据时失败`)
739
+ }
740
+
741
+ chunks.push(encryptedChunk)
742
+ }
743
+ console.log('chunks', chunks)
744
+ // 将所有加密段用分隔符连接
745
+ return chunks.join('|SEGMENT|')
746
+ } catch (e) {
747
+ console.error('分段加密失败:', e)
748
+ return null
749
+ }
750
+ },
751
+ /**
752
+ * 字符串转字节数组
753
+ */
754
+ stringToBytes(str) {
755
+ const bytes = []
756
+ for (let i = 0; i < str.length; i++) {
757
+ const code = str.charCodeAt(i)
758
+ bytes.push(code)
759
+ }
760
+ return bytes
761
+ },
762
+
763
+ /**
764
+ * 字节数组转字符串
765
+ */
766
+ bytesToString(bytes) {
767
+ return String.fromCharCode.apply(null, bytes)
768
+ },
769
+
770
+ /**
771
+ * 加密 先加密-再转base64
772
+ */
773
+ encrypt(content) {
774
+ return new Promise((resolve, reject) => {
775
+ // 创建JSEncrypt对象
776
+ // const encrypt = new JSEncrypt()
777
+ // // 设置公钥
778
+ // encrypt.setPublicKey(publicKey)
779
+ // // 执行加密
780
+ // const encrypted = encrypt.encrypt(content)
781
+ const encrypted = nativeUtils.segmentedEncrypt(content)
782
+ if (encrypted) {
783
+ // resolve(btoa(encrypted))
784
+ resolve(encrypted)
785
+ } else {
786
+ reject(new Error('加密失败'))
787
+ }
788
+ });
789
+ },
790
+ /**
791
+ * 解密 仅为示例,先base64解码-再解密
792
+ */
793
+ decrypt(content) {
794
+ return new Promise((resolve, reject) => {
795
+ // 1.创建JSEncrypt对象用于解密
796
+ const decrypt = new JSEncrypt();
797
+ // 2.设置私钥
798
+ decrypt.setPrivateKey(`-----BEGIN RSA PRIVATE KEY-----
799
+ MIIBOQIBAAJAe87bvdKkch7/tNDl1RvsO+av72yMXdbiryuhS88WjoldxzPZpzAR
800
+ UR74cEHk0svxtWtM6rIBmk4w//x9z0s4tQIDAQABAkA8DrFbmIW68jyaSsdipEPp
801
+ HtNzchV5I9ccoC6DJrbLdzjhGuTT51aa2qC4CfamW6zEYCjSbgeMqT4CzfZOKGaB
802
+ AiEA2iFY/H4+0s1CO1p6VoHOMDP3W+Ahy1Lu74G8fRQkzuECIQCRTWxVpPuxPzQ3
803
+ pfKZe5QrVRucb5vEhbV6Q3lu3YuIVQIhAKnQBEKs1aOuf71NNqhZ7XbBPfScjDKJ
804
+ odF7Io4NPIqBAiBBy3k+3tJJ1IDkofRUo6zYYlV6ZN94AcPHdT5LgW5pcQIgF2by
805
+ nFqBB1zM586NpO+4hua/1Ol5OiqoQJ4iQinyDiA=
806
+ -----END RSA PRIVATE KEY-----`);
807
+ // 3.执行解码
808
+ const decodedData = atob(content)
809
+ // 4.再进行RSA解密
810
+ const decrypted = decrypt.decrypt(decodedData);
811
+ if (decrypted) {
812
+ resolve(decrypted)
813
+ } else {
814
+ reject(new Error('解密失败'))
815
+ }
816
+ });
817
+ },
818
+
819
+ /**
820
+ * 唤起通讯录,并且返回name phone
821
+ */
822
+ readContacts() {
823
+ return new Promise((resolve, reject) => {
824
+ native.device.readContacts({
825
+ fields: ['name', 'phone'],
826
+ sceneDesc: '获取通讯录',
827
+ }).then((data) => {
828
+ resolve(data)
829
+ }).catch(error => {
830
+ Toast.fail(`获取通讯录失败: ${error.message}`)
831
+ reject(error);
832
+ });
833
+ });
834
+ },
835
+ /**
836
+ * 拍照上传
837
+ * @param {Object} params 参数对象
838
+ * @param {string} params.actions 图片类型:身份证正面ocr_id0、反面ocr_id1、银行卡ocr_bc、默认为空
839
+ * @param {number} params.maxSize 图片最大大小,默认1024
840
+ * @param {string} params.domain 域名,默认INDUCTION
841
+ * @param {string} params.buz 业务类型,默认PICTURE
842
+ * @param {string} params.buzId 业务ID,默认111
843
+ * @param {boolean} params.isCopper 是否需要裁剪,默认false
844
+ * @param {boolean} params.isUpload 是否需要上传,默认true
845
+ */
846
+ takePhoto(params) {
847
+ console.log(params, '拍照上传参数');
848
+ return new Promise((resolve, reject) => {
849
+ // 参数默认值处理
850
+ const {
851
+ maxSize = 1024,
852
+ actions = '',
853
+ domain = 'INDUCTION',
854
+ buz = 'PICTURE',
855
+ buzId = '111',
856
+ isCopper = false
857
+ } = params;
858
+
859
+ // isUpload特殊处理:未传值时默认为true,传值时使用传入值
860
+ const isUpload = (params.isUpload !== undefined) ? params.isUpload : true;
861
+
862
+ console.log(isCopper, '-------是否需要裁剪-------');
863
+ console.log(isUpload, '-------是否需要上传-------');
864
+
865
+ native.media.takePhoto({
866
+ maxSize: maxSize
867
+ }).then(async (data) => {
868
+ nativeUtils.closeDialog();
869
+ try {
870
+ // 调接口上传base64至文件服务器
871
+ const base64Files = data;
872
+
873
+ // 身份证银行卡必须裁剪,或者明确指定需要裁剪
874
+ const needCropper = actions === 'ocr_id0' || actions === 'ocr_id1' || actions === 'ocr_bc' || isCopper;
875
+ if (needCropper) {
876
+ base64Files.imageBase64 = await nativeUtils.cropper(base64Files.imageBase64, actions);
877
+ }
878
+
879
+ // 如果不需要上传,直接返回base64数据
880
+ if (!isUpload) {
881
+ resolve({
882
+ imageBase64: base64Files.imageBase64
883
+ });
884
+ return;
885
+ }
886
+
887
+ // 需要上传:显示上传提示
888
+ await Toast.loading({
889
+ forbidClick: true,
890
+ message: '上传中...',
891
+ duration: 0, // 持续展示 toast
892
+ });
893
+
894
+ // 将base64转换为file对象
895
+ const file = await nativeUtils.base64ToFile(base64Files.imageBase64, 'image.png');
896
+
897
+ // 上传文件到服务器
898
+ const result = await nativeUtils.uploadFile(base64Files, file, actions, domain, buz, buzId);
899
+
900
+ resolve(result);
901
+ Toast.clear();
902
+ } catch (error) {
903
+ Toast.fail(`文件上传失败: ${error.message}`);
904
+ reject(error);
905
+ }
906
+ }).catch(error => {
907
+ Toast.fail(`拍照失败: ${error.message}`);
908
+ reject(error);
909
+ });
910
+ });
911
+ },
912
+
913
+ /**
914
+ * 从相册选择图片
915
+ * @param {Object} params 参数对象
916
+ * @param {number} params.maxCount 最大选择数量,超过9张时自动设为9
917
+ * @param {string} params.actions 图片类型
918
+ * @param {string} params.domain 域名
919
+ * @param {string} params.buz 业务类型
920
+ * @param {string} params.buzId 业务ID
921
+ * @param {boolean} params.isCopper 是否需要裁剪
922
+ * @param {boolean} params.isCropper 是否启用裁剪(单张图片时)
923
+ * @param {boolean} params.isUpload 是否需要上传,默认true
924
+ */
925
+ chooseImage(params) {
926
+ // 参数预处理
927
+ params.isCopper = params.isCopper ? params.isCopper : false;
928
+ params.isUpload = (params.isUpload !== undefined) ? params.isUpload : true;
929
+
930
+ // 限制最大选择数量为9张
931
+ if (params.maxCount > 9) {
932
+ params.maxCount = 9;
933
+ }
934
+
935
+ console.log(params, '相册上传参数');
936
+
937
+ return new Promise((resolve, reject) => {
938
+ nativeUtils.closeDialog();
939
+
940
+ native.media.chooseImage(params).then(async (data) => {
941
+ const base64Files = data;
942
+ const uploadResults = []; // 存储所有处理结果
943
+ let successCount = 0; // 成功计数
944
+ let failCount = 0; // 失败计数
945
+
946
+ console.log(base64Files.length, '--------------选择的图片数量---------');
947
+
948
+ // 判断是否需要裁剪:身份证、银行卡或明确指定需要裁剪,且只选择了一张图片时才进行裁剪
949
+ let isCropper = false;
950
+ const isSpecialAction = params.actions === 'ocr_id0' || params.actions === 'ocr_id1' || params.actions === 'ocr_bc';
951
+ if ((isSpecialAction || params.isCropper) && base64Files.length === 1) {
952
+ isCropper = true;
953
+ }
954
+
955
+ // 逐个处理图片
956
+ for (let i = 0; i < base64Files.length; i++) {
957
+ const element = base64Files[i];
958
+
959
+ // 需要上传时显示进度提示
960
+ if (params.isUpload) {
961
+ Toast.loading({
962
+ forbidClick: true,
963
+ message: `选择了${base64Files.length}张图片,正在上传第${i+1}张`,
964
+ duration: 0, // 持续展示 toast
965
+ });
966
+ }
967
+
968
+ try {
969
+ console.log(isCropper, '--------------是否需要裁剪---------');
970
+
971
+ // 1. 裁剪处理:只有在需要裁剪且只有一张图片时才进行裁剪
972
+ if (isCropper && base64Files.length === 1) {
973
+ element.imageBase64 = await nativeUtils.cropper(element.imageBase64, params.actions);
974
+ }
975
+
976
+ // 2. 不需要上传:直接返回base64数据
977
+ if (!params.isUpload) {
978
+ uploadResults.push({
979
+ imageBase64: element.imageBase64
980
+ });
981
+
982
+ // 如果是最后一张图片,返回所有base64数据
983
+ if (i === base64Files.length - 1) {
984
+ // 单张返回对象,多张返回数组
985
+ const result = uploadResults.length === 1 ? uploadResults[0] : uploadResults;
986
+ resolve(result);
987
+ return;
988
+ }
989
+ continue; // 继续处理下一张图片
990
+ }
991
+
992
+ // 3. 需要上传:转换为文件并上传
993
+ const file = await nativeUtils.base64ToFile(element.imageBase64, 'image.png');
994
+ const result = await nativeUtils.uploadFile(
995
+ element.imageBase64,
996
+ file,
997
+ params.actions,
998
+ params.domain,
999
+ params.buz,
1000
+ params.buzId
1001
+ );
1002
+
1003
+ // 保存上传结果
1004
+ uploadResults.push(result);
1005
+ successCount++;
1006
+
1007
+ } catch (error) {
1008
+ // 单张图片失败不影响其他图片上传
1009
+ failCount++;
1010
+ console.error(`第${i+1}张图片上传失败:`, error);
1011
+ }
1012
+ }
1013
+
1014
+ // 所有图片处理完成后的结果处理
1015
+ if (successCount > 0) {
1016
+ // 有成功上传的图片:单张返回对象,多张返回数组
1017
+ const result = uploadResults.length === 1 ? uploadResults[0] : uploadResults;
1018
+ resolve(result);
1019
+ Toast.clear();
1020
+
1021
+ // 有失败图片时提示用户
1022
+ if (failCount > 0) {
1023
+ Toast.fail(`有${failCount}张图片上传失败`);
1024
+ }
1025
+ } else {
1026
+ // 所有图片都上传失败
1027
+ reject(new Error('所有图片上传都失败了'));
1028
+ }
1029
+ }).catch(error => {
1030
+ Toast.fail(`选择图片失败: ${error.message}`);
1031
+ reject(error);
1032
+ });
1033
+ });
1034
+ },
1035
+
1036
+ /**
1037
+ * 拍照+相册 h5自己封装
1038
+ */
1039
+ takePicture(params) {
1040
+ return new Promise((resolve, reject) => {
1041
+ nativeUtils._takePictureResolve = resolve;
1042
+ nativeUtils._takePictureReject = reject;
1043
+ nativeUtils.openCameraDialog(params)
1044
+ });
1045
+ },
1046
+
1047
+ /**
1048
+ * 唤起相机相册选择弹窗
1049
+ */
1050
+ openCameraDialog(params) {
1051
+ // 移除已存在的弹窗(如果存在)
1052
+ const existingDialog = document.getElementById('cameraDialog');
1053
+ if (existingDialog) {
1054
+ document.body.removeChild(existingDialog);
1055
+ }
1056
+
1057
+ // 创建弹窗容器
1058
+ const dialog = document.createElement('div');
1059
+ dialog.id = 'cameraDialog';
1060
+
1061
+ // 添加样式
1062
+ const styles = `
1063
+ .camera-dialog {
1064
+ position: fixed;
1065
+ top: 0;
1066
+ left: 0;
1067
+ width: 100%;
1068
+ height: 100%;
1069
+ background: rgba(0, 0, 0, 0.3);
1070
+ display: flex;
1071
+ justify-content: center;
1072
+ align-items: center;
1073
+ z-index: 1000;
1074
+ opacity: 0;
1075
+ animation: fadeIn 0.3s forwards;
1076
+ }
1077
+
1078
+ @keyframes fadeIn {
1079
+ to { opacity: 1; }
1080
+ }
1081
+
1082
+ .dialog-content {
1083
+ width: 100%;
1084
+ max-width: 400px;
1085
+ border-radius: 6px;
1086
+ overflow: hidden;
1087
+ transform: translateY(20px);
1088
+ animation: slideUp 0.3s forwards;
1089
+ position: fixed;
1090
+ bottom: 0;
1091
+ z-index: 1001;
1092
+ }
1093
+
1094
+ @keyframes slideUp {
1095
+ to { transform: translateY(0); }
1096
+ }
1097
+
1098
+ .dialog-body {
1099
+ padding: 20px 0px;
1100
+ margin: 0 20px;
1101
+ }
1102
+ .options {
1103
+ border-radius: 10px;
1104
+ background: #fff;
1105
+ overflow: hidden;
1106
+ }
1107
+ .option-btn {
1108
+ display: flex;
1109
+ align-items: center;
1110
+ justify-content: center;
1111
+ width: 100%;
1112
+ padding: 17px;
1113
+ background: #fff;
1114
+ border-bottom: 1px solid #e0e0e0;
1115
+ font-size: 16px;
1116
+ cursor: pointer;
1117
+ transition: all 0.2s ease;
1118
+ color: #007EEC;
1119
+ font-weight: 500;
1120
+ }
1121
+
1122
+ .cancel-btn {
1123
+ margin-top: 10px;
1124
+ background: #fff;
1125
+ color: #007EEC;
1126
+ border: none;
1127
+ margin-bottom: 20px;
1128
+ padding: 17px;
1129
+ width: 100%;
1130
+ font-size: 16px;
1131
+ cursor: pointer;
1132
+ transition: all 0.2s ease;
1133
+ border-radius: 8px;
1134
+ font-weight: bold;
1135
+ }
1136
+ `;
1137
+
1138
+ // 创建样式元素
1139
+ const styleEl = document.createElement('style');
1140
+ styleEl.textContent = styles;
1141
+ document.head.appendChild(styleEl);
1142
+
1143
+ // 弹窗内容
1144
+ dialog.innerHTML = `
1145
+ <div class="camera-dialog">
1146
+ <div class="dialog-content">
1147
+ <div class="dialog-body">
1148
+ <div class="options">
1149
+ <button class="option-btn" id="cameraBtn">
1150
+ 拍照
1151
+ </button>
1152
+ <button class="option-btn" id="albumBtn">
1153
+ 从相册选择
1154
+ </button>
1155
+ </div>
1156
+ <button class="cancel-btn" id="cancelBtn">取消</button>
1157
+ </div>
1158
+ </div>
1159
+ </div>
1160
+ `;
1161
+ setTimeout(() => {
1162
+ document.getElementById('cameraBtn').addEventListener('click', () => {
1163
+ nativeUtils.handleCamera(nativeUtils._takePictureResolve, nativeUtils._takePictureReject, params);
1164
+ });
1165
+ document.getElementById('albumBtn').addEventListener('click', () => {
1166
+ nativeUtils.handleAlbum(nativeUtils._takePictureResolve, nativeUtils._takePictureReject, params);
1167
+ });
1168
+ document.getElementById('cancelBtn').addEventListener('click', () => {
1169
+ nativeUtils.closeDialog();
1170
+ nativeUtils._takePictureReject(new Error('用户取消了操作'));
1171
+ });
1172
+ }, 0);
1173
+
1174
+ // 添加到文档
1175
+ document.body.appendChild(dialog);
1176
+
1177
+ // 添加事件监听器,点击背景关闭弹窗
1178
+ dialog.addEventListener('click', function (e) {
1179
+ if (e.target.classList.contains('camera-dialog')) {
1180
+ this.closeDialog();
1181
+ }
1182
+ });
1183
+ },
1184
+ // 关闭弹窗
1185
+ closeDialog() {
1186
+ const dialog = document.getElementById('cameraDialog');
1187
+ if (dialog) {
1188
+ dialog.querySelector('.camera-dialog').style.animation = 'fadeOut 0.3s forwards';
1189
+ // 添加淡出动画
1190
+ const styles = document.querySelector('style');
1191
+ if (styles && !styles.textContent.includes('fadeOut')) {
1192
+ styles.textContent += `
1193
+ @keyframes fadeOut {
1194
+ from { opacity: 1; }
1195
+ to { opacity: 0; }
1196
+ }
1197
+ `;
1198
+ }
1199
+
1200
+ setTimeout(() => {
1201
+ document.body.removeChild(dialog);
1202
+ }, 300);
1203
+ }
1204
+ delete nativeUtils._takePictureResolve;
1205
+ delete nativeUtils._takePictureReject;
1206
+ },
1207
+
1208
+ // 处理拍照
1209
+ async handleCamera(resolve, reject, params) {
1210
+ try {
1211
+ const res = await nativeUtils.takePhoto(params);
1212
+ resolve(res); // 返回数组以保持与多图一致
1213
+ nativeUtils.closeDialog();
1214
+ } catch (error) {
1215
+ reject(error);
1216
+ nativeUtils.closeDialog();
1217
+ }
1218
+ },
1219
+
1220
+ // 处理相册选择
1221
+ async handleAlbum(resolve, reject, params) {
1222
+ try {
1223
+ const res = await nativeUtils.chooseImage(params);
1224
+ resolve(res);
1225
+ nativeUtils.closeDialog();
1226
+ } catch (error) {
1227
+ reject(error);
1228
+ nativeUtils.closeDialog();
1229
+ }
1230
+ },
1231
+ /**
1232
+ * 裁剪图片
1233
+ * @param {string} base64String - 原始图片的base64数据
1234
+ * @returns {Promise<string>} 裁剪后图片的base64数据
1235
+ */
1236
+ cropper(base64String, actions) {
1237
+ return new Promise((resolve, reject) => {
1238
+ // 创建裁剪容器
1239
+ const cropperContainer = document.createElement('div');
1240
+ cropperContainer.id = 'image-cropper-container';
1241
+ document.body.appendChild(cropperContainer);
1242
+
1243
+ // 创建Vue实例
1244
+ const cropperInstance = new Vue({
1245
+ data() {
1246
+ return {
1247
+ imgSrc: 'data:image/jpeg;base64,' + base64String,
1248
+ };
1249
+ },
1250
+ methods: {
1251
+ handleConfirm(croppedData) {
1252
+ var divToDelete = document.querySelector('.image-cropper-container');
1253
+ divToDelete.remove()
1254
+ resolve(croppedData);
1255
+ },
1256
+ handleCancel() {
1257
+ var divToDelete = document.querySelector('.image-cropper-container');
1258
+ divToDelete.remove()
1259
+ reject(new Error('用户取消裁剪'));
1260
+ }
1261
+ },
1262
+ render(h) {
1263
+ return h(ImageCropper, {
1264
+ props: {
1265
+ imgSrc: this.imgSrc,
1266
+ actions: actions
1267
+ },
1268
+ on: {
1269
+ confirm: this.handleConfirm,
1270
+ cancel: this.handleCancel
1271
+ }
1272
+ });
1273
+ }
1274
+ }).$mount(cropperContainer);
1275
+ });
1276
+ },
1277
+ /**
1278
+ * 将base64字符串转换为File对象
1279
+ * @param {string} base64String - base64编码的字符串
1280
+ * @param {string} filename - 文件名
1281
+ * @param {string} mimeType - MIME类型,默认为'image/jpeg'
1282
+ * @returns {File} File对象
1283
+ */
1284
+ base64ToFile(base64String, filename = 'file.png') {
1285
+ return new Promise((resolve, reject) => {
1286
+ // 检查 base64String 是否包含数据 URI 前缀
1287
+ if (base64String.includes(',')) {
1288
+ const arr = base64String.split(',');
1289
+ const mimeMatch = arr[0].match(/:(.*?);/);
1290
+ const mime = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
1291
+ const bstr = atob(arr[1]);
1292
+ let n = bstr.length;
1293
+ const u8arr = new Uint8Array(n);
1294
+
1295
+ while (n--) {
1296
+ u8arr[n] = bstr.charCodeAt(n);
1297
+ }
1298
+
1299
+ const file = new File([u8arr], filename, {
1300
+ type: mime
1301
+ });
1302
+ resolve(file);
1303
+ } else {
1304
+ // 如果不包含逗号,可能是纯 base64 字符串
1305
+ const bstr = atob(base64String);
1306
+ let n = bstr.length;
1307
+ const u8arr = new Uint8Array(n);
1308
+
1309
+ while (n--) {
1310
+ u8arr[n] = bstr.charCodeAt(n);
1311
+ }
1312
+
1313
+ const file = new File([u8arr], filename, {
1314
+ type: 'application/octet-stream'
1315
+ });
1316
+ resolve(file);
1317
+ }
1318
+ });
1319
+ },
1320
+ /**
1321
+ * 上传文件到服务器
1322
+ */
1323
+ async uploadFile(imageBase64, file, actions = '', domain, buz, buzId) {
1324
+ const params = new FormData();
1325
+ params.append('file', file);
1326
+ params.append('domain', domain); //业务系统代码
1327
+ params.append('buz', buz); //文件种类代码
1328
+ params.append('actions', actions);
1329
+ params.append('buzId', buzId); // 业务编码
1330
+ params.append('expired', '-1'); // 失效时间
1331
+ params.append('auth', auth); // 是否需要token
1332
+ console.log(params, '-----壳方法上传的参数----nativeUtils.js------');
1333
+ // token 自己生成一个吧
1334
+ try {
1335
+ const response = await axios({
1336
+ method: 'post',
1337
+ url: upLoadUrl,
1338
+ data: params,
1339
+ timeout: 60 * 1000,
1340
+ headers: {
1341
+ 'Content-Type': 'multipart/form-data',
1342
+ 'Authorization': 'Bearer ' + SSOToken.getToken('iscl', tokenKey, 60 * 10),
1343
+ 'Accept-Device': 'Null',
1344
+ 'Accept-DeviceOS': 'NULL',
1345
+ 'Accept-Location': 'NULL',
1346
+ 'Accept-ISP': 'NULL',
1347
+ 'Accept-StartTime': 'NULL',
1348
+ 'Accept-TimeZone': 28800000,
1349
+ 'Accept-Attr1': 'NULL',
1350
+ 'Accept-Attr2': 'PC',
1351
+ 'Accept-Attr3': 'en_CN',
1352
+ 'Real-Ip': 'NULL',
1353
+ 'Accept-Attr4': 'NULL',
1354
+ 'Accept-Attr6': '623023198301252893'
1355
+ }
1356
+ });
1357
+
1358
+ // 检查响应是否有效
1359
+ if (!response || !response.data) {
1360
+ throw new Error('无效的服务器响应');
1361
+ }
1362
+
1363
+ // 检查返回数据结构
1364
+ if (!response.data.data) {
1365
+ throw new Error('服务器返回数据结构不完整');
1366
+ }
1367
+
1368
+ // 安全地提取返回数据
1369
+ const fileId = response.data.data.fileId;
1370
+ const ext = response.data.data.ext;
1371
+ const returnedActions = response.data.data.actions;
1372
+
1373
+ // 验证必要字段
1374
+ if (!fileId) {
1375
+ throw new Error('服务器未返回fileId');
1376
+ }
1377
+
1378
+ // 返回结果
1379
+ return {
1380
+ fileId: fileId,
1381
+ ext: ext,
1382
+ actions: returnedActions || ''
1383
+ };
1384
+ } catch (error) {
1385
+ // 详细错误处理
1386
+ if (error.response) {
1387
+ // 服务器响应了错误状态码
1388
+ throw new Error(`上传失败: ${error.response.status} - ${error.response.statusText}`);
1389
+ } else if (error.request) {
1390
+ // 请求已发出但没有收到响应
1391
+ throw new Error('网络错误: 无法连接到服务器');
1392
+ } else {
1393
+ // 其他错误
1394
+ throw new Error(`上传过程中发生错误: ${error.message}`);
1395
+ }
1396
+ }
1397
+ },
1398
+ /**
1399
+ * 跳转到登录页面
1400
+ */
1401
+ /**
1402
+ * 跳转到登录页面
1403
+ */
1404
+ async toLogin() {
1405
+ // 如果已经有登录中的 Promise,直接返回它
1406
+ if (loginPromise) {
1407
+ console.log('登录操作正在进行中')
1408
+ return loginPromise
1409
+ }
1410
+
1411
+ loginPromise = (async () => {
1412
+ try {
1413
+ console.log('----clearCache----nativeUtils.js------')
1414
+ await nativeUtils.clearCache()
1415
+ await nativeUtils.clearUserInfo()
1416
+ console.log('----toLogin----nativeUtils.js------', loginUrl)
1417
+ await nativeUtils.openPage(loginUrl)
1418
+ return false
1419
+ } finally {
1420
+ loginPromise = null
1421
+ }
1422
+ })()
1423
+
1424
+ return loginPromise
1425
+ },
1426
+ /**
1427
+ * 跳转到首页
1428
+ */
1429
+ toHome(refresh = false, path = '') {
1430
+ return new Promise((resolve, reject) => {
1431
+ native.navigator.toHome({
1432
+ refresh: refresh,
1433
+ path: path // 可选参数,指定打开的页面路径
1434
+ }).then(res => {
1435
+ resolve(res);
1436
+ }).catch(error => {
1437
+ Toast.fail(`返回首页: ${error.message}`)
1438
+ reject(error);
1439
+ });
1440
+ })
1441
+ }
1442
+ };
1443
+
1444
+ export default nativeUtils;