af-mobile-client-vue3 1.6.30 → 1.6.32
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
CHANGED
|
@@ -0,0 +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.value"
|
|
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>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { FieldType } from 'vant'
|
|
3
3
|
import type { Numeric } from 'vant/es/utils'
|
|
4
4
|
import ImageUploader from '@af-mobile-client-vue3/components/core/ImageUploader/index.vue'
|
|
5
|
-
import
|
|
5
|
+
import AdaptiveSignatureField from '@af-mobile-client-vue3/components/core/Signature/AdaptiveSignatureField.vue'
|
|
6
6
|
import XDatePicker from '@af-mobile-client-vue3/components/core/XDatePicker/index.vue'
|
|
7
7
|
import XGridDropOption from '@af-mobile-client-vue3/components/core/XGridDropOption/index.vue'
|
|
8
8
|
import XMultiSelect from '@af-mobile-client-vue3/components/core/XMultiSelect/index.vue'
|
|
@@ -1275,17 +1275,17 @@ function commChange() {
|
|
|
1275
1275
|
/>
|
|
1276
1276
|
</template>
|
|
1277
1277
|
</VanField>
|
|
1278
|
-
<!--
|
|
1279
|
-
<
|
|
1278
|
+
<!-- 签字:App 原生签字板,微信/浏览器 H5 在线签名 -->
|
|
1279
|
+
<AdaptiveSignatureField
|
|
1280
1280
|
v-if="attr.type === 'signature' && showItem"
|
|
1281
1281
|
:label="labelData"
|
|
1282
1282
|
upload-mode="server"
|
|
1283
1283
|
:image-list="(modelData as any[])"
|
|
1284
|
-
|
|
1285
|
-
:required="attr.rule?.required"
|
|
1284
|
+
:required="attr.rule?.required === 'true'"
|
|
1286
1285
|
:disabled="attr.disabled"
|
|
1287
1286
|
:form-readonly="readonly"
|
|
1288
1287
|
:is-async-upload="isAsyncUpload"
|
|
1288
|
+
:service-name="serviceName"
|
|
1289
1289
|
@signature-complete="signatureComplete"
|
|
1290
1290
|
/>
|
|
1291
1291
|
|
package/src/utils/environment.ts
CHANGED
|
@@ -44,6 +44,33 @@ function inferPlatformFromUserAgent(userAgent: string): string {
|
|
|
44
44
|
return 'unknown'
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 获取 Flutter / 原生桥接所在的 window(微前端子应用需用 rawWindow)
|
|
49
|
+
*/
|
|
50
|
+
export function getNativeBridgeWindow(): Record<string, any> {
|
|
51
|
+
if (typeof window === 'undefined')
|
|
52
|
+
return {}
|
|
53
|
+
const w = window as any
|
|
54
|
+
if (w.__MICRO_APP_ENVIRONMENT__ && w.rawWindow)
|
|
55
|
+
return w.rawWindow
|
|
56
|
+
return w
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 是否已注入 Flutter JS Bridge(如 showSignaturePad.postMessage)
|
|
61
|
+
*/
|
|
62
|
+
export function hasFlutterBridge(bridgeName = 'showSignaturePad'): boolean {
|
|
63
|
+
const win = getNativeBridgeWindow()
|
|
64
|
+
return typeof win[bridgeName]?.postMessage === 'function'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 是否在 App 原生壳内(含 Flutter WebView)
|
|
69
|
+
*/
|
|
70
|
+
export function isNativeApp(): boolean {
|
|
71
|
+
return detectEnvironment().isApp || hasFlutterBridge()
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
/**
|
|
48
75
|
* 检测当前运行环境
|
|
49
76
|
*/
|
|
@@ -63,10 +90,11 @@ export function detectEnvironment(): EnvironmentInfo {
|
|
|
63
90
|
// 检测微信小程序环境
|
|
64
91
|
const isMiniprogram = isWechat && /miniprogram/i.test(userAgent)
|
|
65
92
|
|
|
66
|
-
// 检测App
|
|
93
|
+
// 检测App环境:UA 标识 / iOS WKWebView / Flutter JS Bridge
|
|
67
94
|
const isApp = (
|
|
68
|
-
/myapp|customapp|afmobileview
|
|
95
|
+
/myapp|customapp|afmobileview|flutter/i.test(userAgent)
|
|
69
96
|
|| (Object.prototype.hasOwnProperty.call(window, 'webkit') && !!(window as any).webkit?.messageHandlers)
|
|
97
|
+
|| hasFlutterBridge()
|
|
70
98
|
)
|
|
71
99
|
|
|
72
100
|
// 检测浏览器环境
|