af-mobile-client-vue3 1.6.42 → 1.6.43

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-safecheck
8
+ VITE_APP_SYSTEM_NAME=af-system
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.42",
4
+ "version": "1.6.43",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -6,6 +6,7 @@ import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
6
6
  import { buildInputAccept, getFileTypeMeta, getUploadFormType, isImageFileName } from '@af-mobile-client-vue3/utils/fileType'
7
7
  import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
8
8
  import {
9
+ hasNativeFilePicker,
9
10
  openNativeFilePicker,
10
11
  triggerBrowserFileInput,
11
12
  } from '@af-mobile-client-vue3/utils/nativeFilePicker'
@@ -49,6 +50,7 @@ const imageList = ref<Array<any>>(props.imageList ?? [])
49
50
  const parentData: any = inject('provideParent')
50
51
  const imageInputRef = ref<HTMLInputElement | undefined>()
51
52
  const documentInputRef = ref<HTMLInputElement | undefined>()
53
+ const showActionSheet = ref(false)
52
54
 
53
55
  const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
54
56
 
@@ -56,9 +58,7 @@ function openImageFilePicker() {
56
58
  triggerBrowserFileInput(imageInputRef.value)
57
59
  }
58
60
 
59
- function openDocumentFilePicker() {
60
- triggerBrowserFileInput(documentInputRef.value)
61
- }
61
+ const useNativeDocumentPicker = computed(() => hasNativeFilePicker())
62
62
 
63
63
  function emitUpdatesAfterChange() {
64
64
  // 新增:对外抛出完整与ID列表
@@ -260,8 +260,16 @@ function handleNativeDocumentUpload(fileData: NativePickedFile) {
260
260
  })
261
261
  }
262
262
 
263
- function triggerDocumentPicker() {
264
- const startedNative = openNativeFilePicker({
263
+ function onDocumentInputChange(e: Event) {
264
+ const input = e.target as HTMLInputElement
265
+ handleBrowserDocumentFiles(input.files)
266
+ showActionSheet.value = false
267
+ input.value = ''
268
+ }
269
+
270
+ function onPickDocumentNative() {
271
+ showActionSheet.value = false
272
+ openNativeFilePicker({
265
273
  accept: documentAccept.value,
266
274
  multiple: true,
267
275
  onSuccess: files => files.forEach(handleNativeDocumentUpload),
@@ -270,11 +278,6 @@ function triggerDocumentPicker() {
270
278
  showFailToast(msg)
271
279
  },
272
280
  })
273
- if (startedNative)
274
- return
275
-
276
- // 与「相册中选择」一致:ActionSheet 点击后立即调起系统文件选择器
277
- openDocumentFilePicker()
278
281
  }
279
282
 
280
283
  // 触发拍照
@@ -562,47 +565,35 @@ function deleteFileFunction(file: any) {
562
565
  return false // 阻止van-uploader二次处理
563
566
  }
564
567
 
565
- const showActionSheet = ref(false)
566
568
  const uploaderRef = ref()
567
569
 
568
- const actionOptions = [
569
- { name: '拍摄', key: 'camera' },
570
- { name: '相册中选择', key: 'album' },
571
- { name: '文件上传', key: 'document' },
572
- ]
573
-
574
570
  function handleUploadAreaClick() {
575
571
  if (props.readonly)
576
572
  return
577
573
  showActionSheet.value = true
578
574
  }
579
575
 
580
- function handleActionSelect(option: any) {
581
- if (option.key === 'document') {
582
- triggerDocumentPicker()
583
- showActionSheet.value = false
584
- return
585
- }
576
+ function onPickCamera() {
586
577
  showActionSheet.value = false
587
- if (option.key === 'camera') {
588
- triggerCamera()
589
- }
590
- else if (option.key === 'album') {
591
- mobileUtil.execute({
592
- funcName: 'photoAlbum',
593
- param: {},
594
- callbackFunc: (result: any) => {
595
- if (result.status === 'success') {
596
- result.data?.photos.forEach((photo: any) => {
597
- rePhotoCheck(photo)
598
- })
599
- }
600
- else {
601
- openImageFilePicker()
602
- }
603
- },
604
- })
605
- }
578
+ triggerCamera()
579
+ }
580
+
581
+ function onPickAlbum() {
582
+ showActionSheet.value = false
583
+ mobileUtil.execute({
584
+ funcName: 'photoAlbum',
585
+ param: {},
586
+ callbackFunc: (result: any) => {
587
+ if (result.status === 'success') {
588
+ result.data?.photos.forEach((photo: any) => {
589
+ rePhotoCheck(photo)
590
+ })
591
+ }
592
+ else {
593
+ openImageFilePicker()
594
+ }
595
+ },
596
+ })
606
597
  }
607
598
 
608
599
  function getUploadStatus() {
@@ -636,15 +627,6 @@ defineExpose({
636
627
  class="hidden-file-input"
637
628
  @change="(e:any) => handleBrowserImageFiles(e.target.files)"
638
629
  >
639
- <!-- 通用文件选择(PDF / Word 等) -->
640
- <input
641
- ref="documentInputRef"
642
- type="file"
643
- multiple
644
- :accept="documentAccept"
645
- class="hidden-file-input"
646
- @change="(e:any) => handleBrowserDocumentFiles(e.target.files)"
647
- >
648
630
  <van-uploader
649
631
  ref="uploaderRef"
650
632
  v-model="imageList"
@@ -680,11 +662,37 @@ defineExpose({
680
662
  </van-uploader>
681
663
  <ActionSheet
682
664
  v-model:show="showActionSheet"
683
- :actions="actionOptions"
684
665
  teleport="body"
685
666
  cancel-text="取消"
686
- @select="handleActionSelect"
687
- />
667
+ >
668
+ <div class="upload-source-sheet">
669
+ <button type="button" class="upload-source-sheet__item" @click="onPickCamera">
670
+ 拍摄
671
+ </button>
672
+ <button type="button" class="upload-source-sheet__item" @click="onPickAlbum">
673
+ 相册中选择
674
+ </button>
675
+ <button
676
+ v-if="useNativeDocumentPicker"
677
+ type="button"
678
+ class="upload-source-sheet__item"
679
+ @click="onPickDocumentNative"
680
+ >
681
+ 文件上传
682
+ </button>
683
+ <label v-else class="upload-source-sheet__item upload-source-sheet__item--file">
684
+ 文件上传
685
+ <input
686
+ ref="documentInputRef"
687
+ type="file"
688
+ multiple
689
+ :accept="documentAccept"
690
+ class="upload-source-sheet__file-input"
691
+ @change="onDocumentInputChange"
692
+ >
693
+ </label>
694
+ </div>
695
+ </ActionSheet>
688
696
  </div>
689
697
  </template>
690
698
 
@@ -749,6 +757,42 @@ defineExpose({
749
757
  }
750
758
  }
751
759
 
760
+ .upload-source-sheet {
761
+ &__item {
762
+ display: block;
763
+ width: 100%;
764
+ padding: 14px 16px;
765
+ font-size: 16px;
766
+ line-height: 22px;
767
+ text-align: center;
768
+ background: #fff;
769
+ border: none;
770
+ color: #323233;
771
+ cursor: pointer;
772
+ position: relative;
773
+ box-sizing: border-box;
774
+
775
+ & + & {
776
+ border-top: 1px solid #ebedf0;
777
+ }
778
+ }
779
+
780
+ &__item--file {
781
+ margin: 0;
782
+ }
783
+
784
+ /* 用户手指直接点 input,WebView 才允许调起系统文件选择器(程序化 click 在 APK 内会被拦截) */
785
+ &__file-input {
786
+ position: absolute;
787
+ inset: 0;
788
+ width: 100%;
789
+ height: 100%;
790
+ opacity: 0;
791
+ cursor: pointer;
792
+ font-size: 0;
793
+ }
794
+ }
795
+
752
796
  .file-type-tile {
753
797
  position: absolute;
754
798
  inset: 0;
@@ -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>