af-mobile-client-vue3 1.6.39 → 1.6.41

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.6.39",
4
+ "version": "1.6.41",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -1,9 +1,14 @@
1
1
  <script setup lang="ts">
2
+ import type { NativePickedFile } from '@af-mobile-client-vue3/utils/nativeFilePicker'
2
3
  // import cameraIcon from '@af-mobile-client-vue3/assets/img/component/camera.png'
3
4
  import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
4
5
  import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
5
6
  import { buildInputAccept, getFileTypeMeta, getUploadFormType, isImageFileName } from '@af-mobile-client-vue3/utils/fileType'
6
7
  import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
8
+ import {
9
+ openNativeFilePicker,
10
+ triggerBrowserFileInput,
11
+ } from '@af-mobile-client-vue3/utils/nativeFilePicker'
7
12
  import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
8
13
  import { formatNow } from '@af-mobile-client-vue3/utils/timeUtil'
9
14
  import {
@@ -48,17 +53,11 @@ const documentInputRef = ref<HTMLInputElement | undefined>()
48
53
  const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
49
54
 
50
55
  function openImageFilePicker() {
51
- if (imageInputRef.value) {
52
- imageInputRef.value.value = ''
53
- imageInputRef.value.click()
54
- }
56
+ triggerBrowserFileInput(imageInputRef.value)
55
57
  }
56
58
 
57
59
  function openDocumentFilePicker() {
58
- if (documentInputRef.value) {
59
- documentInputRef.value.value = ''
60
- documentInputRef.value.click()
61
- }
60
+ triggerBrowserFileInput(documentInputRef.value)
62
61
  }
63
62
 
64
63
  function emitUpdatesAfterChange() {
@@ -215,6 +214,69 @@ function handleBrowserDocumentFiles(files: FileList | null) {
215
214
  }
216
215
  }
217
216
 
217
+ function handleNativeDocumentUpload(fileData: NativePickedFile) {
218
+ const filePath = fileData.filePath || fileData.path || ''
219
+ const fileName = fileData.name || filePath.split('/').pop() || 'file'
220
+ const tempFile = createTempUploadingItem(fileName, '', false)
221
+
222
+ const param = {
223
+ resUploadMode: props.uploadMode,
224
+ pathKey: 'Default',
225
+ formType: 'file' as const,
226
+ useType: props.attr?.useType || 'Default',
227
+ resUploadStock: '1',
228
+ filename: fileName,
229
+ filesize: fileData.size,
230
+ f_operator: 'server',
231
+ imgPath: filePath,
232
+ urlPath: `/api/${props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
233
+ commonId: parentData?.commonId?.value ?? '',
234
+ }
235
+
236
+ mobileUtil.execute({
237
+ funcName: 'uploadResource',
238
+ param,
239
+ callbackFunc: (result: any) => {
240
+ const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
241
+ if (result.status === 'success') {
242
+ if (index !== -1) {
243
+ imageList.value[index].uid = result.data.id
244
+ imageList.value[index].id = result.data.id
245
+ imageList.value[index].f_downloadpath = result.data.f_downloadpath
246
+ imageList.value[index].photo_name = result.data.f_filename
247
+ imageList.value[index].isDocument = true
248
+ imageList.value[index].isImage = true
249
+ imageList.value[index].url = DOC_TILE_PLACEHOLDER
250
+ delete imageList.value[index].message
251
+ imageList.value[index].status = 'done'
252
+ }
253
+ }
254
+ else if (index !== -1) {
255
+ imageList.value[index].status = 'failed'
256
+ imageList.value[index].message = '上传失败'
257
+ }
258
+ emitUpdatesAfterChange()
259
+ },
260
+ })
261
+ }
262
+
263
+ function triggerDocumentPicker() {
264
+ const startedNative = openNativeFilePicker({
265
+ accept: documentAccept.value,
266
+ multiple: true,
267
+ onSuccess: files => files.forEach(handleNativeDocumentUpload),
268
+ onError: (msg) => {
269
+ if (msg)
270
+ showFailToast(msg)
271
+ },
272
+ })
273
+ if (startedNative)
274
+ return
275
+
276
+ // 与「相册中选择」一致:ActionSheet 点击后立即调起系统文件选择器
277
+ openDocumentFilePicker()
278
+ }
279
+
218
280
  // 触发拍照
219
281
  function triggerCamera() {
220
282
  mobileUtil.execute({
@@ -512,6 +574,11 @@ function handleUploadAreaClick() {
512
574
  }
513
575
 
514
576
  function handleActionSelect(option: any) {
577
+ if (option.key === 'document') {
578
+ triggerDocumentPicker()
579
+ showActionSheet.value = false
580
+ return
581
+ }
515
582
  showActionSheet.value = false
516
583
  if (option.key === 'camera') {
517
584
  triggerCamera()
@@ -532,9 +599,6 @@ function handleActionSelect(option: any) {
532
599
  },
533
600
  })
534
601
  }
535
- else if (option.key === 'document') {
536
- openDocumentFilePicker()
537
- }
538
602
  }
539
603
 
540
604
  function getUploadStatus() {
@@ -565,7 +629,7 @@ defineExpose({
565
629
  type="file"
566
630
  multiple
567
631
  accept="image/*"
568
- style="display:none"
632
+ class="hidden-file-input"
569
633
  @change="(e:any) => handleBrowserImageFiles(e.target.files)"
570
634
  >
571
635
  <!-- 通用文件选择(PDF / Word 等) -->
@@ -574,7 +638,7 @@ defineExpose({
574
638
  type="file"
575
639
  multiple
576
640
  :accept="documentAccept"
577
- style="display:none"
641
+ class="hidden-file-input"
578
642
  @change="(e:any) => handleBrowserDocumentFiles(e.target.files)"
579
643
  >
580
644
  <van-uploader
@@ -630,6 +694,19 @@ defineExpose({
630
694
  // 该属性会影响表单布局
631
695
  // gap: 16px;
632
696
  }
697
+
698
+ /* WebView 内 display:none 会导致 file input 无法被程序化触发,改用视觉隐藏 */
699
+ .hidden-file-input {
700
+ position: fixed;
701
+ top: 0;
702
+ left: 0;
703
+ width: 1px;
704
+ height: 1px;
705
+ opacity: 0;
706
+ overflow: hidden;
707
+ pointer-events: none;
708
+ z-index: -1;
709
+ }
633
710
  /* 让自定义上传按钮可点击:关闭 Vant 覆盖在上方的透明 input 的事件捕获 */
634
711
  :deep(.custom-trigger-uploader .van-uploader__input) {
635
712
  pointer-events: none;
@@ -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>