af-mobile-client-vue3 1.6.40 → 1.6.42
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 +21 -88
- 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 +89 -94
- package/src/views/SafeInspection/SecurityCertificate/index.vue +20 -0
- package/src/views/SafeInspection/SecurityCertificate/photoAiRecognition/components/PhotoCaptureCard.vue +42 -2
- package/src/views/SafeInspection/SecurityFormItem/CameraView/index.vue +4 -0
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-safecheck
|
|
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
|
@@ -4,11 +4,9 @@ import type { NativePickedFile } from '@af-mobile-client-vue3/utils/nativeFilePi
|
|
|
4
4
|
import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
|
|
5
5
|
import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
|
|
6
6
|
import { buildInputAccept, getFileTypeMeta, getUploadFormType, isImageFileName } from '@af-mobile-client-vue3/utils/fileType'
|
|
7
|
-
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
7
|
+
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
9
|
openNativeFilePicker,
|
|
11
|
-
shouldUseDocumentPickerPanel,
|
|
12
10
|
triggerBrowserFileInput,
|
|
13
11
|
} from '@af-mobile-client-vue3/utils/nativeFilePicker'
|
|
14
12
|
import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
|
|
@@ -51,8 +49,6 @@ const imageList = ref<Array<any>>(props.imageList ?? [])
|
|
|
51
49
|
const parentData: any = inject('provideParent')
|
|
52
50
|
const imageInputRef = ref<HTMLInputElement | undefined>()
|
|
53
51
|
const documentInputRef = ref<HTMLInputElement | undefined>()
|
|
54
|
-
const documentPanelInputRef = ref<HTMLInputElement | undefined>()
|
|
55
|
-
const showDocumentPickerPopup = ref(false)
|
|
56
52
|
|
|
57
53
|
const documentAccept = computed(() => buildInputAccept(props.attr?.accept))
|
|
58
54
|
|
|
@@ -64,12 +60,6 @@ function openDocumentFilePicker() {
|
|
|
64
60
|
triggerBrowserFileInput(documentInputRef.value)
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
function onDocumentPanelInputChange(e: Event) {
|
|
68
|
-
const input = e.target as HTMLInputElement
|
|
69
|
-
handleBrowserDocumentFiles(input.files)
|
|
70
|
-
showDocumentPickerPopup.value = false
|
|
71
|
-
}
|
|
72
|
-
|
|
73
63
|
function emitUpdatesAfterChange() {
|
|
74
64
|
// 新增:对外抛出完整与ID列表
|
|
75
65
|
(emit as any)('updateAllFileList', imageList.value.filter(item => item.status === 'done').map(item => item))
|
|
@@ -283,12 +273,7 @@ function triggerDocumentPicker() {
|
|
|
283
273
|
if (startedNative)
|
|
284
274
|
return
|
|
285
275
|
|
|
286
|
-
//
|
|
287
|
-
if (shouldUseDocumentPickerPanel()) {
|
|
288
|
-
showDocumentPickerPopup.value = true
|
|
289
|
-
return
|
|
290
|
-
}
|
|
291
|
-
|
|
276
|
+
// 与「相册中选择」一致:ActionSheet 点击后立即调起系统文件选择器
|
|
292
277
|
openDocumentFilePicker()
|
|
293
278
|
}
|
|
294
279
|
|
|
@@ -317,6 +302,9 @@ function triggerCamera() {
|
|
|
317
302
|
rePhotoCheck(result.data)
|
|
318
303
|
}
|
|
319
304
|
else if (result.status === 'error') {
|
|
305
|
+
if (result.message && result.message.startsWith('图片质量检测失败')) {
|
|
306
|
+
parentData?.onPhotoQualityFailed?.()
|
|
307
|
+
}
|
|
320
308
|
const env = detectEnvironment()
|
|
321
309
|
// App 环境下弹窗提示错误
|
|
322
310
|
if (env.isApp) {
|
|
@@ -360,6 +348,7 @@ function rePhotoCheck(photoData: any) {
|
|
|
360
348
|
if (result && result.status === 'success') {
|
|
361
349
|
const checkRePhotoResult = result.data
|
|
362
350
|
if (checkRePhotoResult.is_rephoto === true || checkRePhotoResult.is_rephoto === 'true') {
|
|
351
|
+
parentData?.onPhotoRephotodetected?.()
|
|
363
352
|
console.warn(`检测到当前图片属于翻拍照片\n请上传合法图片!`)
|
|
364
353
|
showFailToast({
|
|
365
354
|
message: `当前图片属于翻拍照片\n请上传合法图片!`,
|
|
@@ -644,7 +633,7 @@ defineExpose({
|
|
|
644
633
|
type="file"
|
|
645
634
|
multiple
|
|
646
635
|
accept="image/*"
|
|
647
|
-
|
|
636
|
+
class="hidden-file-input"
|
|
648
637
|
@change="(e:any) => handleBrowserImageFiles(e.target.files)"
|
|
649
638
|
>
|
|
650
639
|
<!-- 通用文件选择(PDF / Word 等) -->
|
|
@@ -653,7 +642,7 @@ defineExpose({
|
|
|
653
642
|
type="file"
|
|
654
643
|
multiple
|
|
655
644
|
:accept="documentAccept"
|
|
656
|
-
|
|
645
|
+
class="hidden-file-input"
|
|
657
646
|
@change="(e:any) => handleBrowserDocumentFiles(e.target.files)"
|
|
658
647
|
>
|
|
659
648
|
<van-uploader
|
|
@@ -696,35 +685,6 @@ defineExpose({
|
|
|
696
685
|
cancel-text="取消"
|
|
697
686
|
@select="handleActionSelect"
|
|
698
687
|
/>
|
|
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>
|
|
728
688
|
</div>
|
|
729
689
|
</template>
|
|
730
690
|
|
|
@@ -738,6 +698,19 @@ defineExpose({
|
|
|
738
698
|
// 该属性会影响表单布局
|
|
739
699
|
// gap: 16px;
|
|
740
700
|
}
|
|
701
|
+
|
|
702
|
+
/* WebView 内 display:none 会导致 file input 无法被程序化触发,改用视觉隐藏 */
|
|
703
|
+
.hidden-file-input {
|
|
704
|
+
position: fixed;
|
|
705
|
+
top: 0;
|
|
706
|
+
left: 0;
|
|
707
|
+
width: 1px;
|
|
708
|
+
height: 1px;
|
|
709
|
+
opacity: 0;
|
|
710
|
+
overflow: hidden;
|
|
711
|
+
pointer-events: none;
|
|
712
|
+
z-index: -1;
|
|
713
|
+
}
|
|
741
714
|
/* 让自定义上传按钮可点击:关闭 Vant 覆盖在上方的透明 input 的事件捕获 */
|
|
742
715
|
:deep(.custom-trigger-uploader .van-uploader__input) {
|
|
743
716
|
pointer-events: none;
|
|
@@ -809,44 +782,4 @@ defineExpose({
|
|
|
809
782
|
overflow: hidden;
|
|
810
783
|
}
|
|
811
784
|
}
|
|
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
|
-
}
|
|
852
785
|
</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>
|