af-mobile-client-vue3 1.6.40 → 1.6.42

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.
package/.env CHANGED
@@ -5,7 +5,7 @@ VITE_APP_OUT_DIR=dist_af-library-mobile-web
5
5
  VITE_APP_VCONSOLE=false
6
6
  # 自定义参数
7
7
  VITE_APP_WEB_CONFIG_KEY=admin.webconfig
8
- VITE_APP_SYSTEM_NAME=af-system
8
+ VITE_APP_SYSTEM_NAME=af-safecheck
9
9
  # 最低兼容性 V4(最新产品)V3(V3产品) OA(公司OA)
10
10
  VITE_APP_COMPATIBLE=V4
11
11
  VITE_RSA_PRIVATE_KEY=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIRrmftLDHCQqREEJ132Onu+W3vmFbdF7QD751SrcDDGDTfzuz1zBuElvkHhuDBb7KZkXrCIe+MhvX2IvxcLObl3faX+evYlnfj2HRbF0hIpQLuIq22tL06ZcV5w7wqLxUZRpFElIFm8gZTkUvfKXVuHw89e4daDVhU5hK3GHNGTAgMBAAECgYABiINrFaE1E8pkBYx1JJA5yuhL73aUktfd2TeCU00vFg6kyrWCI85Sa2RKu/6CJNZWeOFgdubEUv7a22tRrNIZb3yUMaqtTwSso78mspIOJqjWXTkTH9WPElfTcdpdIse/lgZtPz6egxkuhadSvwrM9Y6NgusiW/5+x95Ct08iOQJBAN5aK+7uISURvGQj2EaRtgGEd8+d4oHl+BYvvTeG3qSgUikHQW3j0sp4gXPw2kxw6sjVgLFOc4FB6LGqwzOTzokCQQCYdYG8ty3Uo/ebUlNzeJFxHXjy/KvBSytAUzAXkRu3nZrkEaPQsi3dgOkZgk+F1fMDzfQ4EbDIU6xvqOoZXHg7AkATCW9XfoXR8anKfRMoP5Nwn9HOMbtR2cmaxK2TknV/bMZ8AsYETYwfj5+tuIJIJybC2RyykX/sIiN1CqS5xr7ZAkArj19rMRdaKyMi8MnBM1Cy9g3Jt2HHj5ejAGG8SgyWUOShh1y70z0BjcSMMkxQXAncK2s83ekZw7aADM4eQupjAkARRgTwwMOnn3IoKmQusKhZk0uxilZ4Zc2LH6Z4GiWnvteM0W8Zw4Z1lJUcjgQq3dGqL2RdmzeQZ+HgPIOXrZVK
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.6.40",
4
+ "version": "1.6.42",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -4,11 +4,9 @@ import type { NativePickedFile } from '@af-mobile-client-vue3/utils/nativeFilePi
4
4
  import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
5
5
  import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
6
6
  import { buildInputAccept, getFileTypeMeta, getUploadFormType, isImageFileName } from '@af-mobile-client-vue3/utils/fileType'
7
- import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
7
+ import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
8
8
  import {
9
-
10
9
  openNativeFilePicker,
11
- shouldUseDocumentPickerPanel,
12
10
  triggerBrowserFileInput,
13
11
  } from '@af-mobile-client-vue3/utils/nativeFilePicker'
14
12
  import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
@@ -51,8 +49,6 @@ const imageList = ref<Array<any>>(props.imageList ?? [])
51
49
  const parentData: any = inject('provideParent')
52
50
  const imageInputRef = ref<HTMLInputElement | undefined>()
53
51
  const documentInputRef = ref<HTMLInputElement | undefined>()
54
- const documentPanelInputRef = ref<HTMLInputElement | undefined>()
55
- const showDocumentPickerPopup = ref(false)
56
52
 
57
53
  const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
58
54
 
@@ -64,12 +60,6 @@ function openDocumentFilePicker() {
64
60
  triggerBrowserFileInput(documentInputRef.value)
65
61
  }
66
62
 
67
- function onDocumentPanelInputChange(e: Event) {
68
- const input = e.target as HTMLInputElement
69
- handleBrowserDocumentFiles(input.files)
70
- showDocumentPickerPopup.value = false
71
- }
72
-
73
63
  function emitUpdatesAfterChange() {
74
64
  // 新增:对外抛出完整与ID列表
75
65
  (emit as any)('updateAllFileList', imageList.value.filter(item => item.status === 'done').map(item => item))
@@ -283,12 +273,7 @@ function triggerDocumentPicker() {
283
273
  if (startedNative)
284
274
  return
285
275
 
286
- // App WebView 内 hidden input 的 programmatic click 无效,改用用户直接点击的弹层
287
- if (shouldUseDocumentPickerPanel()) {
288
- showDocumentPickerPopup.value = true
289
- return
290
- }
291
-
276
+ // 与「相册中选择」一致:ActionSheet 点击后立即调起系统文件选择器
292
277
  openDocumentFilePicker()
293
278
  }
294
279
 
@@ -317,6 +302,9 @@ function triggerCamera() {
317
302
  rePhotoCheck(result.data)
318
303
  }
319
304
  else if (result.status === 'error') {
305
+ if (result.message && result.message.startsWith('图片质量检测失败')) {
306
+ parentData?.onPhotoQualityFailed?.()
307
+ }
320
308
  const env = detectEnvironment()
321
309
  // App 环境下弹窗提示错误
322
310
  if (env.isApp) {
@@ -360,6 +348,7 @@ function rePhotoCheck(photoData: any) {
360
348
  if (result && result.status === 'success') {
361
349
  const checkRePhotoResult = result.data
362
350
  if (checkRePhotoResult.is_rephoto === true || checkRePhotoResult.is_rephoto === 'true') {
351
+ parentData?.onPhotoRephotodetected?.()
363
352
  console.warn(`检测到当前图片属于翻拍照片\n请上传合法图片!`)
364
353
  showFailToast({
365
354
  message: `当前图片属于翻拍照片\n请上传合法图片!`,
@@ -644,7 +633,7 @@ defineExpose({
644
633
  type="file"
645
634
  multiple
646
635
  accept="image/*"
647
- style="display:none"
636
+ class="hidden-file-input"
648
637
  @change="(e:any) => handleBrowserImageFiles(e.target.files)"
649
638
  >
650
639
  <!-- 通用文件选择(PDF / Word 等) -->
@@ -653,7 +642,7 @@ defineExpose({
653
642
  type="file"
654
643
  multiple
655
644
  :accept="documentAccept"
656
- style="display:none"
645
+ class="hidden-file-input"
657
646
  @change="(e:any) => handleBrowserDocumentFiles(e.target.files)"
658
647
  >
659
648
  <van-uploader
@@ -696,35 +685,6 @@ defineExpose({
696
685
  cancel-text="取消"
697
686
  @select="handleActionSelect"
698
687
  />
699
-
700
- <!-- App 内文件选择:用户直接点击 label 触发系统文件管理器 -->
701
- <van-popup
702
- v-model:show="showDocumentPickerPopup"
703
- position="bottom"
704
- round
705
- teleport="body"
706
- safe-area-inset-bottom
707
- >
708
- <div class="document-picker-panel">
709
- <div class="document-picker-title">
710
- 选择文件
711
- </div>
712
- <p class="document-picker-tip">
713
- 支持 PDF、Word、Excel 等格式
714
- </p>
715
- <label class="document-picker-btn">
716
- 点击选择文件
717
- <input
718
- ref="documentPanelInputRef"
719
- type="file"
720
- multiple
721
- :accept="documentAccept"
722
- class="document-picker-input"
723
- @change="onDocumentPanelInputChange"
724
- >
725
- </label>
726
- </div>
727
- </van-popup>
728
688
  </div>
729
689
  </template>
730
690
 
@@ -738,6 +698,19 @@ defineExpose({
738
698
  // 该属性会影响表单布局
739
699
  // gap: 16px;
740
700
  }
701
+
702
+ /* WebView 内 display:none 会导致 file input 无法被程序化触发,改用视觉隐藏 */
703
+ .hidden-file-input {
704
+ position: fixed;
705
+ top: 0;
706
+ left: 0;
707
+ width: 1px;
708
+ height: 1px;
709
+ opacity: 0;
710
+ overflow: hidden;
711
+ pointer-events: none;
712
+ z-index: -1;
713
+ }
741
714
  /* 让自定义上传按钮可点击:关闭 Vant 覆盖在上方的透明 input 的事件捕获 */
742
715
  :deep(.custom-trigger-uploader .van-uploader__input) {
743
716
  pointer-events: none;
@@ -809,44 +782,4 @@ defineExpose({
809
782
  overflow: hidden;
810
783
  }
811
784
  }
812
-
813
- .document-picker-panel {
814
- padding: 20px 16px 28px;
815
- text-align: center;
816
- }
817
-
818
- .document-picker-title {
819
- font-size: 16px;
820
- font-weight: 600;
821
- color: #323233;
822
- margin-bottom: 8px;
823
- }
824
-
825
- .document-picker-tip {
826
- font-size: 13px;
827
- color: #969799;
828
- margin: 0 0 20px;
829
- }
830
-
831
- .document-picker-btn {
832
- display: block;
833
- position: relative;
834
- width: 100%;
835
- padding: 12px 0;
836
- font-size: 15px;
837
- color: #fff;
838
- background: #1989fa;
839
- border-radius: 8px;
840
- cursor: pointer;
841
- overflow: hidden;
842
- }
843
-
844
- .document-picker-input {
845
- position: absolute;
846
- inset: 0;
847
- width: 100%;
848
- height: 100%;
849
- opacity: 0;
850
- cursor: pointer;
851
- }
852
785
  </style>
@@ -1,172 +1,172 @@
1
- <script setup lang="ts">
2
- import type { SignatureComponentExpose, SignatureComponentProps } from './signature'
3
- import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
4
- import { upload } from '@af-mobile-client-vue3/services/api/common'
5
- import { isNativeApp } from '@af-mobile-client-vue3/utils/environment'
6
- import { showFailToast, Field as VanField } from 'vant'
7
- import { onMounted, ref, watch } from 'vue'
8
- import SignatureComponent from './SignatureComponent.vue'
9
-
10
- const props = withDefaults(defineProps<SignatureComponentProps & {
11
- serviceName?: string
12
- }>(), {
13
- label: '用户签字',
14
- required: false,
15
- disabled: false,
16
- uploadMode: 'server',
17
- imageList: null,
18
- formReadonly: false,
19
- isAsyncUpload: false,
20
- serviceName: undefined,
21
- })
22
-
23
- const emit = defineEmits<{
24
- signatureComplete: [data: any]
25
- }>()
26
-
27
- // App / Flutter 壳走原生签字板;浏览器、微信等走 H5 签名
28
- const isApp = ref(isNativeApp())
29
- onMounted(() => {
30
- // 部分 WebView 桥接注入略晚于首屏,挂载后再判一次
31
- isApp.value = isNativeApp()
32
- })
33
-
34
- const nativeSignatureRef = ref<InstanceType<typeof SignatureComponent>>()
35
- const webSignatureImage = ref('')
36
- const webSignatureData = ref<any>(null)
37
- const webUploading = ref(false)
38
-
39
- const serviceName = props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME
40
-
41
- watch(
42
- () => props.imageList,
43
- (list) => {
44
- if (isApp.value || !Array.isArray(list) || !list[0]?.url)
45
- return
46
- webSignatureImage.value = list[0].url
47
- },
48
- { immediate: true, deep: true },
49
- )
50
-
51
- async function uploadWebSignature(base64Image: string) {
52
- const blob = await (await fetch(base64Image)).blob()
53
- const filename = `signature_${Date.now()}.png`
54
- const file = new File([blob], filename, { type: blob.type || 'image/png' })
55
- const formData = new FormData()
56
- formData.append('avatar', file)
57
- formData.append('resUploadMode', props.uploadMode)
58
- formData.append('pathKey', 'Default')
59
- formData.append('formType', 'image')
60
- formData.append('useType', 'Default')
61
- formData.append('resUploadStock', '1')
62
- formData.append('filename', filename)
63
- formData.append('filesize', (file.size / 1024 / 1024).toFixed(4))
64
- formData.append('f_operator', 'server')
65
-
66
- return upload(formData, serviceName, { 'Content-Type': 'multipart/form-data' })
67
- }
68
-
69
- function resetWebSignature() {
70
- webSignatureImage.value = ''
71
- webSignatureData.value = null
72
- }
73
-
74
- async function handleWebSignatureSave(base64Image: string) {
75
- if (props.disabled || props.formReadonly || webUploading.value)
76
- return
77
-
78
- webUploading.value = true
79
- try {
80
- const res: any = await uploadWebSignature(base64Image)
81
- if (!res?.data?.id) {
82
- showFailToast('签名上传失败')
83
- resetWebSignature()
84
- return
85
- }
86
- webSignatureData.value = res.data
87
- emit('signatureComplete', { status: 'success', data: res.data })
88
- }
89
- catch (error) {
90
- console.error('签名上传失败:', error)
91
- showFailToast('签名上传失败')
92
- resetWebSignature()
93
- }
94
- finally {
95
- webUploading.value = false
96
- }
97
- }
98
-
99
- function handleWebSignatureClear() {
100
- resetWebSignature()
101
- emit('signatureComplete', { base64: '', status: 'cleared' })
102
- }
103
-
104
- defineExpose<SignatureComponentExpose>({
105
- clearSignature() {
106
- if (isApp.value)
107
- nativeSignatureRef.value?.clearSignature()
108
- else
109
- handleWebSignatureClear()
110
- },
111
- hasSignature() {
112
- if (isApp.value)
113
- return nativeSignatureRef.value?.hasSignature() ?? false
114
- return !!webSignatureData.value?.id || !!webSignatureImage.value
115
- },
116
- getSignatureData() {
117
- if (isApp.value)
118
- return nativeSignatureRef.value?.getSignatureData() ?? ''
119
- return webSignatureImage.value.replace(/^data:image\/\w+;base64,/, '')
120
- },
121
- previewSignature() {
122
- nativeSignatureRef.value?.previewSignature()
123
- },
124
- getSignatureList() {
125
- if (isApp.value)
126
- return nativeSignatureRef.value?.getSignatureList() ?? []
127
- if (!webSignatureData.value)
128
- return []
129
- return [{
130
- photo_name: webSignatureData.value.f_filename,
131
- id: webSignatureData.value.id,
132
- f_downloadpath: webSignatureData.value.f_downloadpath,
133
- }]
134
- },
135
- })
136
- </script>
137
-
138
- <template>
139
- <!-- App:原生签字板 -->
140
- <SignatureComponent
141
- v-if="isApp"
142
- ref="nativeSignatureRef"
143
- :label="label"
144
- :required="required"
145
- :disabled="disabled"
146
- :upload-mode="uploadMode"
147
- :image-list="imageList"
148
- :form-readonly="formReadonly"
149
- :is-async-upload="isAsyncUpload"
150
- @signature-complete="emit('signatureComplete', $event)"
151
- />
152
- <!-- 非 App:H5 在线签名(浏览器、微信等) -->
153
- <VanField
154
- v-else
155
- center
156
- name="signature"
157
- :label="label"
158
- :required="required"
159
- >
160
- <template #input>
161
- <XSignature
162
- v-model="webSignatureImage"
163
- button-text="点击签字"
164
- :show-button-after-signed="true"
165
- :show-sign-button="!formReadonly"
166
- :show-preview="true"
167
- @save="handleWebSignatureSave"
168
- @clear="handleWebSignatureClear"
169
- />
170
- </template>
171
- </VanField>
172
- </template>
1
+ <script setup lang="ts">
2
+ import type { SignatureComponentExpose, SignatureComponentProps } from './signature'
3
+ import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
4
+ import { upload } from '@af-mobile-client-vue3/services/api/common'
5
+ import { isNativeApp } from '@af-mobile-client-vue3/utils/environment'
6
+ import { showFailToast, Field as VanField } from 'vant'
7
+ import { onMounted, ref, watch } from 'vue'
8
+ import SignatureComponent from './SignatureComponent.vue'
9
+
10
+ const props = withDefaults(defineProps<SignatureComponentProps & {
11
+ serviceName?: string
12
+ }>(), {
13
+ label: '用户签字',
14
+ required: false,
15
+ disabled: false,
16
+ uploadMode: 'server',
17
+ imageList: null,
18
+ formReadonly: false,
19
+ isAsyncUpload: false,
20
+ serviceName: undefined,
21
+ })
22
+
23
+ const emit = defineEmits<{
24
+ signatureComplete: [data: any]
25
+ }>()
26
+
27
+ // App / Flutter 壳走原生签字板;浏览器、微信等走 H5 签名
28
+ const isApp = ref(isNativeApp())
29
+ onMounted(() => {
30
+ // 部分 WebView 桥接注入略晚于首屏,挂载后再判一次
31
+ isApp.value = isNativeApp()
32
+ })
33
+
34
+ const nativeSignatureRef = ref<InstanceType<typeof SignatureComponent>>()
35
+ const webSignatureImage = ref('')
36
+ const webSignatureData = ref<any>(null)
37
+ const webUploading = ref(false)
38
+
39
+ const serviceName = props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME
40
+
41
+ watch(
42
+ () => props.imageList,
43
+ (list) => {
44
+ if (isApp.value || !Array.isArray(list) || !list[0]?.url)
45
+ return
46
+ webSignatureImage.value = list[0].url
47
+ },
48
+ { immediate: true, deep: true },
49
+ )
50
+
51
+ async function uploadWebSignature(base64Image: string) {
52
+ const blob = await (await fetch(base64Image)).blob()
53
+ const filename = `signature_${Date.now()}.png`
54
+ const file = new File([blob], filename, { type: blob.type || 'image/png' })
55
+ const formData = new FormData()
56
+ formData.append('avatar', file)
57
+ formData.append('resUploadMode', props.uploadMode)
58
+ formData.append('pathKey', 'Default')
59
+ formData.append('formType', 'image')
60
+ formData.append('useType', 'Default')
61
+ formData.append('resUploadStock', '1')
62
+ formData.append('filename', filename)
63
+ formData.append('filesize', (file.size / 1024 / 1024).toFixed(4))
64
+ formData.append('f_operator', 'server')
65
+
66
+ return upload(formData, serviceName, { 'Content-Type': 'multipart/form-data' })
67
+ }
68
+
69
+ function resetWebSignature() {
70
+ webSignatureImage.value = ''
71
+ webSignatureData.value = null
72
+ }
73
+
74
+ async function handleWebSignatureSave(base64Image: string) {
75
+ if (props.disabled || props.formReadonly || webUploading.value)
76
+ return
77
+
78
+ webUploading.value = true
79
+ try {
80
+ const res: any = await uploadWebSignature(base64Image)
81
+ if (!res?.data?.id) {
82
+ showFailToast('签名上传失败')
83
+ resetWebSignature()
84
+ return
85
+ }
86
+ webSignatureData.value = res.data
87
+ emit('signatureComplete', { status: 'success', data: res.data })
88
+ }
89
+ catch (error) {
90
+ console.error('签名上传失败:', error)
91
+ showFailToast('签名上传失败')
92
+ resetWebSignature()
93
+ }
94
+ finally {
95
+ webUploading.value = false
96
+ }
97
+ }
98
+
99
+ function handleWebSignatureClear() {
100
+ resetWebSignature()
101
+ emit('signatureComplete', { base64: '', status: 'cleared' })
102
+ }
103
+
104
+ defineExpose<SignatureComponentExpose>({
105
+ clearSignature() {
106
+ if (isApp.value)
107
+ nativeSignatureRef.value?.clearSignature()
108
+ else
109
+ handleWebSignatureClear()
110
+ },
111
+ hasSignature() {
112
+ if (isApp.value)
113
+ return nativeSignatureRef.value?.hasSignature() ?? false
114
+ return !!webSignatureData.value?.id || !!webSignatureImage.value
115
+ },
116
+ getSignatureData() {
117
+ if (isApp.value)
118
+ return nativeSignatureRef.value?.getSignatureData() ?? ''
119
+ return webSignatureImage.value.replace(/^data:image\/\w+;base64,/, '')
120
+ },
121
+ previewSignature() {
122
+ nativeSignatureRef.value?.previewSignature()
123
+ },
124
+ getSignatureList() {
125
+ if (isApp.value)
126
+ return nativeSignatureRef.value?.getSignatureList() ?? []
127
+ if (!webSignatureData.value)
128
+ return []
129
+ return [{
130
+ photo_name: webSignatureData.value.f_filename,
131
+ id: webSignatureData.value.id,
132
+ f_downloadpath: webSignatureData.value.f_downloadpath,
133
+ }]
134
+ },
135
+ })
136
+ </script>
137
+
138
+ <template>
139
+ <!-- App:原生签字板 -->
140
+ <SignatureComponent
141
+ v-if="isApp"
142
+ ref="nativeSignatureRef"
143
+ :label="label"
144
+ :required="required"
145
+ :disabled="disabled"
146
+ :upload-mode="uploadMode"
147
+ :image-list="imageList"
148
+ :form-readonly="formReadonly"
149
+ :is-async-upload="isAsyncUpload"
150
+ @signature-complete="emit('signatureComplete', $event)"
151
+ />
152
+ <!-- 非 App:H5 在线签名(浏览器、微信等) -->
153
+ <VanField
154
+ v-else
155
+ center
156
+ name="signature"
157
+ :label="label"
158
+ :required="required"
159
+ >
160
+ <template #input>
161
+ <XSignature
162
+ v-model="webSignatureImage"
163
+ button-text="点击签字"
164
+ :show-button-after-signed="true"
165
+ :show-sign-button="!formReadonly"
166
+ :show-preview="true"
167
+ @save="handleWebSignatureSave"
168
+ @clear="handleWebSignatureClear"
169
+ />
170
+ </template>
171
+ </VanField>
172
+ </template>