af-mobile-client-vue3 1.6.43 → 1.6.45

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +4 -1
  3. package/src/components/common/otherCharge/GridFileUploader.vue +4 -2
  4. package/src/components/core/ImageUploader/index.vue +94 -311
  5. package/src/components/core/Signature/AdaptiveSignatureField.vue +172 -172
  6. package/src/components/core/Uploader/index.vue +6 -2
  7. package/src/components/core/XDatePicker/index.vue +8 -2
  8. package/src/components/core/XMultiSelect/index.vue +6 -2
  9. package/src/components/core/XSelect/index.vue +40 -15
  10. package/src/components/data/AttachmentListItem/index.vue +174 -174
  11. package/src/components/data/XCellList/index.vue +14 -9
  12. package/src/components/data/XCellListFilter/index.vue +25 -3
  13. package/src/components/data/XForm/index.vue +10 -5
  14. package/src/components/data/XFormItem/index.vue +57 -73
  15. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  16. package/src/components/data/XReportGrid/print.js +184 -184
  17. package/src/stores/modules/safecheckStore.ts +47 -47
  18. package/src/utils/PhotoClean.ts +21 -21
  19. package/src/utils/appBackManager.ts +23 -23
  20. package/src/utils/safeBack.ts +38 -38
  21. package/src/utils/timeUtil.ts +27 -27
  22. package/src/views/SafeInspection/SecurityCertificate/AddDevice/index.vue +9 -20
  23. package/src/views/SafeInspection/SecurityCertificate/OverallHiddenDangers/index.vue +21 -8
  24. package/src/views/SafeInspection/SecurityCertificate/contractSign/index.vue +0 -1
  25. package/src/views/SafeInspection/SecurityCertificate/index.vue +1 -2
  26. package/src/views/SafeInspection/SecurityCertificate/photoAiRecognition/components/PhotoCaptureCard.vue +21 -5
  27. package/src/views/SafeInspection/SecurityCertificate/photoSignature/SignatureComponent/SignatureComponent.vue +13 -3
  28. package/src/views/SafeInspection/SecurityCertificate/photoSignature/index.vue +9 -5
  29. package/src/views/SafeInspection/SecurityCertificate/userInfo/index.vue +11 -15
  30. package/src/views/SafeInspection/SecurityCertificate/userInfo/upaddress.vue +5 -4
  31. package/src/views/SafeInspection/SecurityFormItem/CameraView/index.vue +2 -8
  32. package/src/views/SafeInspection/SecurityFormItem/FormItem.vue +23 -15
  33. package/src/views/SafeInspection/SecurityFormItem/XMultiSelect/index.vue +8 -10
  34. package/src/views/SafeInspection/SecurityFormItem/index.vue +16 -10
  35. package/src/views/component/XFormView/index.vue +2 -2
  36. package/vite.config.ts +1 -1
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.43",
4
+ "version": "1.6.45",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -6,7 +6,7 @@ import {
6
6
  Picker as VanPicker,
7
7
  Popup as VanPopup,
8
8
  } from 'vant'
9
- import { ref, watch } from 'vue'
9
+ import { onBeforeUnmount, ref, watch } from 'vue'
10
10
 
11
11
  const props = defineProps<{
12
12
  modelValue: string
@@ -51,6 +51,9 @@ watch(() => props.modelValue, (newValue) => {
51
51
  watch(() => props.remarks, (newValue) => {
52
52
  remarks.value = newValue
53
53
  })
54
+ onBeforeUnmount(() => {
55
+ showPicker.value = false
56
+ })
54
57
  </script>
55
58
 
56
59
  <template>
@@ -10,7 +10,7 @@ import {
10
10
  Icon as VanIcon,
11
11
  ImagePreview as VanImagePreview,
12
12
  } from 'vant'
13
- import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
13
+ import { computed, onBeforeUnmount, onMounted, onUnmounted, reactive, ref } from 'vue'
14
14
 
15
15
  export interface FileTypeConfig {
16
16
  userType: string
@@ -371,7 +371,9 @@ function validateAll(): boolean {
371
371
  const minLength = computed(() => {
372
372
  return props.fileTypes.reduce((acc, config) => acc + config.picMinNum, 0)
373
373
  })
374
-
374
+ onBeforeUnmount(() => {
375
+ showPreview.value = false
376
+ })
375
377
  // 暴露验证方法给父组件
376
378
  defineExpose({
377
379
  validateAll,
@@ -1,16 +1,8 @@
1
1
  <script setup lang="ts">
2
- import type { NativePickedFile } from '@af-mobile-client-vue3/utils/nativeFilePicker'
3
2
  // import cameraIcon from '@af-mobile-client-vue3/assets/img/component/camera.png'
4
3
  import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
5
4
  import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
6
- import { buildInputAccept, getFileTypeMeta, getUploadFormType, isImageFileName } from '@af-mobile-client-vue3/utils/fileType'
7
5
  import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
8
- import {
9
- hasNativeFilePicker,
10
- openNativeFilePicker,
11
- triggerBrowserFileInput,
12
- } from '@af-mobile-client-vue3/utils/nativeFilePicker'
13
- import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
14
6
  import { formatNow } from '@af-mobile-client-vue3/utils/timeUtil'
15
7
  import {
16
8
  ActionSheet,
@@ -19,7 +11,7 @@ import {
19
11
  Icon as VanIcon,
20
12
  Uploader as vanUploader,
21
13
  } from 'vant'
22
- import { computed, inject, ref } from 'vue'
14
+ import { inject, onBeforeUnmount, ref } from 'vue'
23
15
 
24
16
  interface WatermarkConfig {
25
17
  fontSize?: number | string
@@ -36,7 +28,7 @@ const props = defineProps({
36
28
  outerIndex: { default: undefined },
37
29
  authority: { default: 'user' },
38
30
  uploadMode: { default: 'server' },
39
- attr: { type: Object as () => { type?: string, addOrEdit?: string, accept?: string[] | string, acceptCount?: number, minAcceptCount?: number, uploadImage?: boolean, watermark?: WatermarkConfig, useType?: string }, default: () => ({}) },
31
+ attr: { type: Object as () => { addOrEdit?: string, acceptCount?: number, minAcceptCount?: number, uploadImage?: boolean, watermark?: WatermarkConfig, useType?: string }, default: () => ({}) },
40
32
  mode: { default: '新增' }, // 预览
41
33
  serviceName: { type: String, default: '' },
42
34
  // 整体只读:只允许预览,禁止拍照/上传/删除
@@ -47,19 +39,30 @@ const emit = defineEmits(['updateFileList', 'updateAllFileList'])
47
39
 
48
40
  const imageList = ref<Array<any>>(props.imageList ?? [])
49
41
 
50
- const parentData: any = inject('provideParent')
51
- const imageInputRef = ref<HTMLInputElement | undefined>()
52
- const documentInputRef = ref<HTMLInputElement | undefined>()
53
- const showActionSheet = ref(false)
42
+ // 从父组件获取 commonId,仅读取一次而非持有引用
43
+ const parentCommonId = (() => {
44
+ try {
45
+ const parent = inject<any>('provideParent', undefined)
46
+ return parent?.commonId?.value ?? ''
47
+ }
48
+ catch {
49
+ return ''
50
+ }
51
+ })()
54
52
 
55
- const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
53
+ // 浏览器 / 微信内 H5:统一用隐藏 file input(与单独 camera input 行为等价,均为系统选图)
54
+ // const fileInputRef = ref<HTMLInputElement | undefined>()
55
+ const parentData: any = inject('provideParent')
56
+ // 浏览器 / 微信内 H5:统一用隐藏 file input(与单独 camera input 行为等价,均为系统选图)
57
+ const fileInputRef = ref<HTMLInputElement | undefined>()
56
58
 
57
- function openImageFilePicker() {
58
- triggerBrowserFileInput(imageInputRef.value)
59
+ function openBrowserFilePicker() {
60
+ if (fileInputRef.value) {
61
+ fileInputRef.value.value = ''
62
+ fileInputRef.value.click()
63
+ }
59
64
  }
60
65
 
61
- const useNativeDocumentPicker = computed(() => hasNativeFilePicker())
62
-
63
66
  function emitUpdatesAfterChange() {
64
67
  // 新增:对外抛出完整与ID列表
65
68
  (emit as any)('updateAllFileList', imageList.value.filter(item => item.status === 'done').map(item => item))
@@ -74,31 +77,13 @@ function emitUpdatesAfterChange() {
74
77
  }
75
78
  }
76
79
 
77
- const DOC_TILE_PLACEHOLDER = `data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="82" height="82"><rect width="82" height="82" rx="8" fill="#f0f1f5"/></svg>')}`
78
-
79
- function isFileItemImage(item: any): boolean {
80
- if (item?.isDocument)
81
- return false
82
- if (item?.isImage === false)
83
- return false
84
- if (item?.isImage === true && !item?.isDocument)
85
- return true
86
- return isImageFileName(item?.name || item?.photo_name || item?.f_downloadpath || '')
87
- }
88
-
89
- function getItemFileMeta(item: any) {
90
- return getFileTypeMeta(item?.name || item?.photo_name || item?.f_downloadpath || '')
91
- }
92
-
93
- function createTempUploadingItem(name: string, previewUrl: string, isImage = true) {
80
+ function createTempUploadingItem(name: string, previewUrl: string) {
94
81
  const temp = {
95
82
  uid: Date.now() + Math.random().toString(36).substr(2, 5),
96
83
  name,
97
84
  status: 'uploading',
98
85
  message: '上传中...',
99
- url: isImage ? (previewUrl || undefined) : DOC_TILE_PLACEHOLDER,
100
- isImage: true,
101
- isDocument: !isImage,
86
+ url: previewUrl,
102
87
  }
103
88
  if (!imageList.value)
104
89
  imageList.value = [temp]
@@ -106,12 +91,12 @@ function createTempUploadingItem(name: string, previewUrl: string, isImage = tru
106
91
  return temp
107
92
  }
108
93
 
109
- function uploadFileViaHttp(file: File, previewUrl: string, temp: any, formType: 'image' | 'file' = 'image') {
94
+ function uploadFileViaHttp(file: File, previewUrl: string, temp: any) {
110
95
  const formData = new FormData()
111
96
  formData.append('avatar', file)
112
97
  formData.append('resUploadMode', props.uploadMode)
113
98
  formData.append('pathKey', 'Default')
114
- formData.append('formType', formType)
99
+ formData.append('formType', 'image')
115
100
  formData.append('useType', 'Default')
116
101
  formData.append('resUploadStock', '1')
117
102
  formData.append('filename', file.name)
@@ -130,15 +115,9 @@ function uploadFileViaHttp(file: File, previewUrl: string, temp: any, formType:
130
115
  if (index !== -1) {
131
116
  imageList.value[index].uid = res.data.id
132
117
  imageList.value[index].id = res.data.id
133
- imageList.value[index].f_downloadpath = res.data.f_downloadpath
134
- imageList.value[index].photo_name = res.data.f_filename
135
- imageList.value[index].isDocument = formType === 'file'
136
- imageList.value[index].isImage = true
137
- if (formType === 'file')
138
- imageList.value[index].url = DOC_TILE_PLACEHOLDER
139
118
  delete imageList.value[index].message
140
119
  imageList.value[index].status = 'done'
141
- // 与签名一致:保留本地 base64 预览,不因替换为服务器相对路径导致部分 APK 展示损坏
120
+ imageList.value[index].url = `${import.meta.env.VITE_APP_RESOURCE_PATH || ''}${res.data.f_downloadpath}`
142
121
  }
143
122
  }
144
123
  else {
@@ -165,7 +144,7 @@ function buildWatermarkText() {
165
144
  return lines.join('\n')
166
145
  }
167
146
 
168
- function handleBrowserImageFiles(files: FileList | null) {
147
+ function handleBrowserFiles(files: FileList | null) {
169
148
  if (!files || files.length === 0)
170
149
  return
171
150
  const max = props.attr?.acceptCount ?? Number.POSITIVE_INFINITY
@@ -173,120 +152,23 @@ function handleBrowserImageFiles(files: FileList | null) {
173
152
  if (imageList.value.length >= max)
174
153
  break
175
154
  const file = files[i]
176
- const formType = getUploadFormType(file)
177
- if (formType === 'file') {
178
- const temp = createTempUploadingItem(file.name, '', false)
179
- uploadFileViaHttp(file, '', temp, 'file')
180
- continue
181
- }
182
155
  const reader = new FileReader()
183
156
  reader.onload = (e: any) => {
184
157
  const previewUrl = e.target?.result as string
185
- const temp = createTempUploadingItem(file.name, previewUrl, true)
186
- uploadFileViaHttp(file, previewUrl, temp, 'image')
158
+ const temp = createTempUploadingItem(file.name, previewUrl)
159
+ uploadFileViaHttp(file, previewUrl, temp)
187
160
  }
188
161
  reader.readAsDataURL(file)
189
162
  }
190
163
  }
191
164
 
192
- function handleBrowserDocumentFiles(files: FileList | null) {
193
- if (!files || files.length === 0)
194
- return
195
- const max = props.attr?.acceptCount ?? Number.POSITIVE_INFINITY
196
- for (let i = 0; i < files.length; i++) {
197
- if (imageList.value.length >= max)
198
- break
199
- const file = files[i]
200
- const formType = getUploadFormType(file)
201
- if (formType === 'image') {
202
- const reader = new FileReader()
203
- reader.onload = (e: any) => {
204
- const previewUrl = e.target?.result as string
205
- const temp = createTempUploadingItem(file.name, previewUrl, true)
206
- uploadFileViaHttp(file, previewUrl, temp, 'image')
207
- }
208
- reader.readAsDataURL(file)
209
- }
210
- else {
211
- const temp = createTempUploadingItem(file.name, '', false)
212
- uploadFileViaHttp(file, '', temp, 'file')
213
- }
214
- }
215
- }
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 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({
273
- accept: documentAccept.value,
274
- multiple: true,
275
- onSuccess: files => files.forEach(handleNativeDocumentUpload),
276
- onError: (msg) => {
277
- if (msg)
278
- showFailToast(msg)
279
- },
280
- })
281
- }
282
-
283
165
  // 触发拍照
284
166
  function triggerCamera() {
285
167
  mobileUtil.execute({
286
168
  funcName: 'takePicture',
287
169
  param: (() => {
288
170
  const param: any = {}
289
- const watermark = (props.attr as any)?.watermark
171
+ const watermark = { ...(props.attr as any)?.watermark }
290
172
  if (watermark) {
291
173
  const txt = buildWatermarkText()
292
174
  if (txt)
@@ -305,9 +187,6 @@ function triggerCamera() {
305
187
  rePhotoCheck(result.data)
306
188
  }
307
189
  else if (result.status === 'error') {
308
- if (result.message && result.message.startsWith('图片质量检测失败')) {
309
- parentData?.onPhotoQualityFailed?.()
310
- }
311
190
  const env = detectEnvironment()
312
191
  // App 环境下弹窗提示错误
313
192
  if (env.isApp) {
@@ -317,8 +196,8 @@ function triggerCamera() {
317
196
  })
318
197
  return
319
198
  }
320
- // 浏览器模式:打开图片文件选择
321
- openImageFilePicker()
199
+ // 浏览器模式:打开文件选择
200
+ openBrowserFilePicker()
322
201
  }
323
202
  },
324
203
  })
@@ -351,7 +230,6 @@ function rePhotoCheck(photoData: any) {
351
230
  if (result && result.status === 'success') {
352
231
  const checkRePhotoResult = result.data
353
232
  if (checkRePhotoResult.is_rephoto === true || checkRePhotoResult.is_rephoto === 'true') {
354
- parentData?.onPhotoRephotodetected?.()
355
233
  console.warn(`检测到当前图片属于翻拍照片\n请上传合法图片!`)
356
234
  showFailToast({
357
235
  message: `当前图片属于翻拍照片\n请上传合法图片!`,
@@ -396,14 +274,13 @@ function handlePhotoUpload(photoData: any) {
396
274
  console.log('图片上传前的参数:', JSON.stringify(photoData))
397
275
  // 添加临时预览
398
276
  const mimeType = getImageMimeType(photoData.filePath)
399
- const previewUrl = toDataUrl(photoData.content, mimeType)
400
277
  const tempFile = {
401
278
  uid: Date.now() + Math.random().toString(36).substr(2, 5),
402
279
  name: photoData.filePath.split('/').pop(),
403
280
  status: 'uploading',
404
281
  message: '上传中...',
405
- url: previewUrl,
406
- content: previewUrl,
282
+ url: `data:${mimeType};base64,${photoData.content}`,
283
+ content: `data:image/png;base64,${photoData.content}`,
407
284
  isImage: true,
408
285
  type: mimeType,
409
286
  }
@@ -418,7 +295,7 @@ function handlePhotoUpload(photoData: any) {
418
295
  const param = {
419
296
  resUploadMode: props.uploadMode,
420
297
  pathKey: 'Default',
421
- formType: 'image' as const,
298
+ formType: 'image',
422
299
  useType: props.attr?.useType || 'Default',
423
300
  resUploadStock: '1',
424
301
  filename: photoData.name,
@@ -426,7 +303,7 @@ function handlePhotoUpload(photoData: any) {
426
303
  f_operator: 'server',
427
304
  imgPath: photoData.filePath,
428
305
  urlPath: `/api/${props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
429
- commonId: parentData?.commonId?.value ?? '',
306
+ commonId: parentCommonId,
430
307
  }
431
308
  if (props.isAsyncUpload) {
432
309
  // 添加上传队列
@@ -477,12 +354,9 @@ function handlePhotoUpload(photoData: any) {
477
354
  if (index !== -1) {
478
355
  imageList.value[index].uid = result.data.id
479
356
  imageList.value[index].id = result.data.id
480
- imageList.value[index].f_downloadpath = result.data.f_downloadpath
481
- imageList.value[index].photo_name = result.data.f_filename
482
- imageList.value[index].isImage = true
483
357
  delete imageList.value[index].message
484
358
  imageList.value[index].status = 'done'
485
- // 保留拍照后的 base64 预览,与 SignatureComponent 行为一致
359
+ imageList.value[index].url = `${import.meta.env.VITE_APP_RESOURCE_PATH || ''}${result.data.f_downloadpath}`
486
360
  }
487
361
  }
488
362
  else {
@@ -565,35 +439,54 @@ function deleteFileFunction(file: any) {
565
439
  return false // 阻止van-uploader二次处理
566
440
  }
567
441
 
442
+ const showActionSheet = ref(false)
568
443
  const uploaderRef = ref()
569
444
 
445
+ const actionOptions = [
446
+ { name: '拍照', key: 'camera' },
447
+ { name: '上传', key: 'file' },
448
+ ]
449
+
570
450
  function handleUploadAreaClick() {
571
451
  if (props.readonly)
572
452
  return
573
- showActionSheet.value = true
574
- }
575
-
576
- function onPickCamera() {
577
- showActionSheet.value = false
578
- triggerCamera()
453
+ const env = detectEnvironment()
454
+ // 微信内:直接走浏览器式文件选择,不弹出拍照/上传菜单
455
+ if (env.isWechat || env.isMiniprogram) {
456
+ openBrowserFilePicker()
457
+ return
458
+ }
459
+ if (props.attr?.uploadImage) {
460
+ showActionSheet.value = true
461
+ }
462
+ else {
463
+ triggerCamera()
464
+ }
579
465
  }
580
466
 
581
- function onPickAlbum() {
467
+ function handleActionSelect(option: any) {
582
468
  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
- })
469
+ if (option.key === 'camera') {
470
+ triggerCamera()
471
+ }
472
+ else if (option.key === 'file') {
473
+ // 手机文件上传
474
+ mobileUtil.execute({
475
+ funcName: 'photoAlbum',
476
+ param: {},
477
+ callbackFunc: (result: any) => {
478
+ if (result.status === 'success') {
479
+ result.data?.photos.forEach((photo: any) => {
480
+ rePhotoCheck(photo)
481
+ })
482
+ }
483
+ else {
484
+ // 手机文件选择失败,则走浏览器式文件选择
485
+ openBrowserFilePicker()
486
+ }
487
+ },
488
+ })
489
+ }
597
490
  }
598
491
 
599
492
  function getUploadStatus() {
@@ -614,18 +507,27 @@ function getUploadStatus() {
614
507
  defineExpose({
615
508
  getUploadStatus,
616
509
  })
510
+
511
+ /**
512
+ * 组件卸载时清理:关闭 ActionSheet,清空图片列表以释放闭包引用
513
+ */
514
+ onBeforeUnmount(() => {
515
+ showActionSheet.value = false
516
+ // 清空图片列表,释放闭包中对 props.attr 等对象的引用,防止内存泄漏
517
+ imageList.value = []
518
+ })
617
519
  </script>
618
520
 
619
521
  <template>
620
522
  <div class="uploader-container">
621
- <!-- 图片选择(拍摄 / 相册) -->
523
+ <!-- 浏览器 / 微信 H5:统一隐藏文件选择(多选图片) -->
622
524
  <input
623
- ref="imageInputRef"
525
+ ref="fileInputRef"
624
526
  type="file"
625
527
  multiple
626
528
  accept="image/*"
627
- class="hidden-file-input"
628
- @change="(e:any) => handleBrowserImageFiles(e.target.files)"
529
+ style="display:none"
530
+ @change="(e:any) => handleBrowserFiles(e.target.files)"
629
531
  >
630
532
  <van-uploader
631
533
  ref="uploaderRef"
@@ -637,16 +539,6 @@ defineExpose({
637
539
  :preview-image="true"
638
540
  :before-delete="!props.readonly && props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin' ? deleteFileFunction : undefined"
639
541
  >
640
- <template #preview-cover="{ index }">
641
- <div
642
- v-if="imageList[index] && !isFileItemImage(imageList[index])"
643
- class="file-type-tile"
644
- :style="{ background: getItemFileMeta(imageList[index]).color }"
645
- >
646
- <span class="file-type-tile__ext">{{ getItemFileMeta(imageList[index]).ext }}</span>
647
- <span class="file-type-tile__label">{{ getItemFileMeta(imageList[index]).label }}</span>
648
- </div>
649
- </template>
650
542
  <template #default>
651
543
  <div
652
544
  v-if="!props.readonly && props.mode !== '预览' && (imageList.length < props.attr?.acceptCount && props.attr?.addOrEdit !== 'readonly')"
@@ -662,37 +554,11 @@ defineExpose({
662
554
  </van-uploader>
663
555
  <ActionSheet
664
556
  v-model:show="showActionSheet"
557
+ :actions="actionOptions"
665
558
  teleport="body"
666
559
  cancel-text="取消"
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>
560
+ @select="handleActionSelect"
561
+ />
696
562
  </div>
697
563
  </template>
698
564
 
@@ -706,19 +572,6 @@ defineExpose({
706
572
  // 该属性会影响表单布局
707
573
  // gap: 16px;
708
574
  }
709
-
710
- /* WebView 内 display:none 会导致 file input 无法被程序化触发,改用视觉隐藏 */
711
- .hidden-file-input {
712
- position: fixed;
713
- top: 0;
714
- left: 0;
715
- width: 1px;
716
- height: 1px;
717
- opacity: 0;
718
- overflow: hidden;
719
- pointer-events: none;
720
- z-index: -1;
721
- }
722
575
  /* 让自定义上传按钮可点击:关闭 Vant 覆盖在上方的透明 input 的事件捕获 */
723
576
  :deep(.custom-trigger-uploader .van-uploader__input) {
724
577
  pointer-events: none;
@@ -756,74 +609,4 @@ defineExpose({
756
609
  margin-top: 0;
757
610
  }
758
611
  }
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
-
796
- .file-type-tile {
797
- position: absolute;
798
- inset: 0;
799
- display: flex;
800
- flex-direction: column;
801
- align-items: center;
802
- justify-content: center;
803
- color: #fff;
804
- border-radius: 8px;
805
- padding: 4px;
806
- box-sizing: border-box;
807
- text-align: center;
808
- pointer-events: none;
809
-
810
- &__ext {
811
- font-size: 16px;
812
- font-weight: 700;
813
- line-height: 1.2;
814
- }
815
-
816
- &__label {
817
- font-size: 10px;
818
- margin-top: 2px;
819
- opacity: 0.95;
820
- }
821
- }
822
-
823
- :deep(.custom-trigger-uploader) {
824
- .van-uploader__preview {
825
- position: relative;
826
- overflow: hidden;
827
- }
828
- }
829
612
  </style>