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 +1 -1
- package/package.json +1 -1
- package/src/components/core/ImageUploader/index.vue +98 -54
- package/src/components/core/Signature/AdaptiveSignatureField.vue +172 -172
- package/src/components/data/AttachmentListItem/index.vue +174 -174
- package/src/components/data/OtherCharge/OtherChargeGroupModal.vue +542 -542
- package/src/utils/nativeFilePicker.ts +10 -1
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-
|
|
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
|
@@ -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
|
-
|
|
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
|
|
264
|
-
const
|
|
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
|
|
581
|
-
if (option.key === 'document') {
|
|
582
|
-
triggerDocumentPicker()
|
|
583
|
-
showActionSheet.value = false
|
|
584
|
-
return
|
|
585
|
-
}
|
|
576
|
+
function onPickCamera() {
|
|
586
577
|
showActionSheet.value = false
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
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>
|