af-mobile-client-vue3 1.6.39 → 1.6.40
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 +1 -1
- package/src/components/core/ImageUploader/index.vue +160 -12
- 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 +94 -0
- package/src/views/SafeInspection/SecurityCertificate/userInfo/index.vue +8 -2
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
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
|
-
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
7
|
+
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
8
|
+
import {
|
|
9
|
+
|
|
10
|
+
openNativeFilePicker,
|
|
11
|
+
shouldUseDocumentPickerPanel,
|
|
12
|
+
triggerBrowserFileInput,
|
|
13
|
+
} from '@af-mobile-client-vue3/utils/nativeFilePicker'
|
|
7
14
|
import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
|
|
8
15
|
import { formatNow } from '@af-mobile-client-vue3/utils/timeUtil'
|
|
9
16
|
import {
|
|
@@ -44,21 +51,23 @@ const imageList = ref<Array<any>>(props.imageList ?? [])
|
|
|
44
51
|
const parentData: any = inject('provideParent')
|
|
45
52
|
const imageInputRef = ref<HTMLInputElement | undefined>()
|
|
46
53
|
const documentInputRef = ref<HTMLInputElement | undefined>()
|
|
54
|
+
const documentPanelInputRef = ref<HTMLInputElement | undefined>()
|
|
55
|
+
const showDocumentPickerPopup = ref(false)
|
|
47
56
|
|
|
48
57
|
const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
|
|
49
58
|
|
|
50
59
|
function openImageFilePicker() {
|
|
51
|
-
|
|
52
|
-
imageInputRef.value.value = ''
|
|
53
|
-
imageInputRef.value.click()
|
|
54
|
-
}
|
|
60
|
+
triggerBrowserFileInput(imageInputRef.value)
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
function openDocumentFilePicker() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
triggerBrowserFileInput(documentInputRef.value)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onDocumentPanelInputChange(e: Event) {
|
|
68
|
+
const input = e.target as HTMLInputElement
|
|
69
|
+
handleBrowserDocumentFiles(input.files)
|
|
70
|
+
showDocumentPickerPopup.value = false
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
function emitUpdatesAfterChange() {
|
|
@@ -215,6 +224,74 @@ function handleBrowserDocumentFiles(files: FileList | null) {
|
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
|
|
227
|
+
function handleNativeDocumentUpload(fileData: NativePickedFile) {
|
|
228
|
+
const filePath = fileData.filePath || fileData.path || ''
|
|
229
|
+
const fileName = fileData.name || filePath.split('/').pop() || 'file'
|
|
230
|
+
const tempFile = createTempUploadingItem(fileName, '', false)
|
|
231
|
+
|
|
232
|
+
const param = {
|
|
233
|
+
resUploadMode: props.uploadMode,
|
|
234
|
+
pathKey: 'Default',
|
|
235
|
+
formType: 'file' as const,
|
|
236
|
+
useType: props.attr?.useType || 'Default',
|
|
237
|
+
resUploadStock: '1',
|
|
238
|
+
filename: fileName,
|
|
239
|
+
filesize: fileData.size,
|
|
240
|
+
f_operator: 'server',
|
|
241
|
+
imgPath: filePath,
|
|
242
|
+
urlPath: `/api/${props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
|
|
243
|
+
commonId: parentData?.commonId?.value ?? '',
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
mobileUtil.execute({
|
|
247
|
+
funcName: 'uploadResource',
|
|
248
|
+
param,
|
|
249
|
+
callbackFunc: (result: any) => {
|
|
250
|
+
const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
|
|
251
|
+
if (result.status === 'success') {
|
|
252
|
+
if (index !== -1) {
|
|
253
|
+
imageList.value[index].uid = result.data.id
|
|
254
|
+
imageList.value[index].id = result.data.id
|
|
255
|
+
imageList.value[index].f_downloadpath = result.data.f_downloadpath
|
|
256
|
+
imageList.value[index].photo_name = result.data.f_filename
|
|
257
|
+
imageList.value[index].isDocument = true
|
|
258
|
+
imageList.value[index].isImage = true
|
|
259
|
+
imageList.value[index].url = DOC_TILE_PLACEHOLDER
|
|
260
|
+
delete imageList.value[index].message
|
|
261
|
+
imageList.value[index].status = 'done'
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (index !== -1) {
|
|
265
|
+
imageList.value[index].status = 'failed'
|
|
266
|
+
imageList.value[index].message = '上传失败'
|
|
267
|
+
}
|
|
268
|
+
emitUpdatesAfterChange()
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function triggerDocumentPicker() {
|
|
274
|
+
const startedNative = openNativeFilePicker({
|
|
275
|
+
accept: documentAccept.value,
|
|
276
|
+
multiple: true,
|
|
277
|
+
onSuccess: files => files.forEach(handleNativeDocumentUpload),
|
|
278
|
+
onError: (msg) => {
|
|
279
|
+
if (msg)
|
|
280
|
+
showFailToast(msg)
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
if (startedNative)
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
// App WebView 内 hidden input 的 programmatic click 无效,改用用户直接点击的弹层
|
|
287
|
+
if (shouldUseDocumentPickerPanel()) {
|
|
288
|
+
showDocumentPickerPopup.value = true
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
openDocumentFilePicker()
|
|
293
|
+
}
|
|
294
|
+
|
|
218
295
|
// 触发拍照
|
|
219
296
|
function triggerCamera() {
|
|
220
297
|
mobileUtil.execute({
|
|
@@ -512,6 +589,11 @@ function handleUploadAreaClick() {
|
|
|
512
589
|
}
|
|
513
590
|
|
|
514
591
|
function handleActionSelect(option: any) {
|
|
592
|
+
if (option.key === 'document') {
|
|
593
|
+
triggerDocumentPicker()
|
|
594
|
+
showActionSheet.value = false
|
|
595
|
+
return
|
|
596
|
+
}
|
|
515
597
|
showActionSheet.value = false
|
|
516
598
|
if (option.key === 'camera') {
|
|
517
599
|
triggerCamera()
|
|
@@ -532,9 +614,6 @@ function handleActionSelect(option: any) {
|
|
|
532
614
|
},
|
|
533
615
|
})
|
|
534
616
|
}
|
|
535
|
-
else if (option.key === 'document') {
|
|
536
|
-
openDocumentFilePicker()
|
|
537
|
-
}
|
|
538
617
|
}
|
|
539
618
|
|
|
540
619
|
function getUploadStatus() {
|
|
@@ -617,6 +696,35 @@ defineExpose({
|
|
|
617
696
|
cancel-text="取消"
|
|
618
697
|
@select="handleActionSelect"
|
|
619
698
|
/>
|
|
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>
|
|
620
728
|
</div>
|
|
621
729
|
</template>
|
|
622
730
|
|
|
@@ -701,4 +809,44 @@ defineExpose({
|
|
|
701
809
|
overflow: hidden;
|
|
702
810
|
}
|
|
703
811
|
}
|
|
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
|
+
}
|
|
704
852
|
</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>
|