af-mobile-client-vue3 1.6.42 → 1.6.44
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/common/otherCharge/ChargePrintSelectorAndRemarks.vue +4 -1
- package/src/components/common/otherCharge/GridFileUploader.vue +4 -2
- package/src/components/core/ImageUploader/index.vue +69 -242
- package/src/components/core/Uploader/index.vue +6 -2
- package/src/components/core/XDatePicker/index.vue +8 -2
- package/src/components/core/XMultiSelect/index.vue +6 -2
- package/src/components/core/XSelect/index.vue +40 -15
- package/src/components/data/OtherCharge/OtherChargeGroupModal.vue +542 -542
- package/src/components/data/XCellList/index.vue +16 -9
- package/src/components/data/XCellListFilter/index.vue +26 -3
- package/src/components/data/XForm/index.vue +10 -5
- package/src/components/data/XFormItem/index.vue +57 -73
- package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
- package/src/components/data/XReportGrid/print.js +184 -184
- package/src/stores/modules/safecheckStore.ts +47 -47
- package/src/utils/PhotoClean.ts +21 -21
- package/src/utils/appBackManager.ts +23 -23
- package/src/utils/nativeFilePicker.ts +10 -1
- package/src/utils/safeBack.ts +38 -38
- package/src/utils/timeUtil.ts +27 -27
- package/src/views/SafeInspection/SecurityCertificate/AddDevice/index.vue +9 -20
- package/src/views/SafeInspection/SecurityCertificate/OverallHiddenDangers/index.vue +21 -8
- package/src/views/SafeInspection/SecurityCertificate/contractSign/index.vue +0 -1
- package/src/views/SafeInspection/SecurityCertificate/index.vue +1 -2
- package/src/views/SafeInspection/SecurityCertificate/photoAiRecognition/components/PhotoCaptureCard.vue +21 -5
- package/src/views/SafeInspection/SecurityCertificate/photoSignature/SignatureComponent/SignatureComponent.vue +13 -3
- package/src/views/SafeInspection/SecurityCertificate/photoSignature/index.vue +9 -5
- package/src/views/SafeInspection/SecurityCertificate/userInfo/index.vue +11 -15
- package/src/views/SafeInspection/SecurityCertificate/userInfo/upaddress.vue +5 -4
- package/src/views/SafeInspection/SecurityFormItem/CameraView/index.vue +3 -9
- package/src/views/SafeInspection/SecurityFormItem/FormItem.vue +23 -15
- package/src/views/SafeInspection/SecurityFormItem/XMultiSelect/index.vue +8 -10
- package/src/views/SafeInspection/SecurityFormItem/index.vue +16 -10
- package/src/views/component/XFormView/index.vue +2 -2
- package/vite.config.ts +1 -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,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 {
|
|
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,15 +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
|
-
openNativeFilePicker,
|
|
10
|
-
triggerBrowserFileInput,
|
|
11
|
-
} from '@af-mobile-client-vue3/utils/nativeFilePicker'
|
|
12
|
-
import { toDataUrl } from '@af-mobile-client-vue3/utils/resourceUrl'
|
|
13
6
|
import { formatNow } from '@af-mobile-client-vue3/utils/timeUtil'
|
|
14
7
|
import {
|
|
15
8
|
ActionSheet,
|
|
@@ -18,7 +11,7 @@ import {
|
|
|
18
11
|
Icon as VanIcon,
|
|
19
12
|
Uploader as vanUploader,
|
|
20
13
|
} from 'vant'
|
|
21
|
-
import {
|
|
14
|
+
import { inject, onBeforeUnmount, ref } from 'vue'
|
|
22
15
|
|
|
23
16
|
interface WatermarkConfig {
|
|
24
17
|
fontSize?: number | string
|
|
@@ -35,7 +28,7 @@ const props = defineProps({
|
|
|
35
28
|
outerIndex: { default: undefined },
|
|
36
29
|
authority: { default: 'user' },
|
|
37
30
|
uploadMode: { default: 'server' },
|
|
38
|
-
attr: { type: Object as () => {
|
|
31
|
+
attr: { type: Object as () => { addOrEdit?: string, acceptCount?: number, minAcceptCount?: number, uploadImage?: boolean, watermark?: WatermarkConfig, useType?: string }, default: () => ({}) },
|
|
39
32
|
mode: { default: '新增' }, // 预览
|
|
40
33
|
serviceName: { type: String, default: '' },
|
|
41
34
|
// 整体只读:只允许预览,禁止拍照/上传/删除
|
|
@@ -46,18 +39,28 @@ const emit = defineEmits(['updateFileList', 'updateAllFileList'])
|
|
|
46
39
|
|
|
47
40
|
const imageList = ref<Array<any>>(props.imageList ?? [])
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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>()
|
|
58
58
|
|
|
59
|
-
function
|
|
60
|
-
|
|
59
|
+
function openBrowserFilePicker() {
|
|
60
|
+
if (fileInputRef.value) {
|
|
61
|
+
fileInputRef.value.value = ''
|
|
62
|
+
fileInputRef.value.click()
|
|
63
|
+
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
function emitUpdatesAfterChange() {
|
|
@@ -74,31 +77,13 @@ function emitUpdatesAfterChange() {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
|
|
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:
|
|
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
|
|
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',
|
|
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
|
-
|
|
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
|
|
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,117 +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
|
|
186
|
-
uploadFileViaHttp(file, previewUrl, temp
|
|
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 triggerDocumentPicker() {
|
|
264
|
-
const startedNative = openNativeFilePicker({
|
|
265
|
-
accept: documentAccept.value,
|
|
266
|
-
multiple: true,
|
|
267
|
-
onSuccess: files => files.forEach(handleNativeDocumentUpload),
|
|
268
|
-
onError: (msg) => {
|
|
269
|
-
if (msg)
|
|
270
|
-
showFailToast(msg)
|
|
271
|
-
},
|
|
272
|
-
})
|
|
273
|
-
if (startedNative)
|
|
274
|
-
return
|
|
275
|
-
|
|
276
|
-
// 与「相册中选择」一致:ActionSheet 点击后立即调起系统文件选择器
|
|
277
|
-
openDocumentFilePicker()
|
|
278
|
-
}
|
|
279
|
-
|
|
280
165
|
// 触发拍照
|
|
281
166
|
function triggerCamera() {
|
|
282
167
|
mobileUtil.execute({
|
|
283
168
|
funcName: 'takePicture',
|
|
284
169
|
param: (() => {
|
|
285
170
|
const param: any = {}
|
|
286
|
-
const watermark = (props.attr as any)?.watermark
|
|
171
|
+
const watermark = { ...(props.attr as any)?.watermark }
|
|
287
172
|
if (watermark) {
|
|
288
173
|
const txt = buildWatermarkText()
|
|
289
174
|
if (txt)
|
|
@@ -302,9 +187,6 @@ function triggerCamera() {
|
|
|
302
187
|
rePhotoCheck(result.data)
|
|
303
188
|
}
|
|
304
189
|
else if (result.status === 'error') {
|
|
305
|
-
if (result.message && result.message.startsWith('图片质量检测失败')) {
|
|
306
|
-
parentData?.onPhotoQualityFailed?.()
|
|
307
|
-
}
|
|
308
190
|
const env = detectEnvironment()
|
|
309
191
|
// App 环境下弹窗提示错误
|
|
310
192
|
if (env.isApp) {
|
|
@@ -314,8 +196,8 @@ function triggerCamera() {
|
|
|
314
196
|
})
|
|
315
197
|
return
|
|
316
198
|
}
|
|
317
|
-
//
|
|
318
|
-
|
|
199
|
+
// 浏览器模式:打开文件选择
|
|
200
|
+
openBrowserFilePicker()
|
|
319
201
|
}
|
|
320
202
|
},
|
|
321
203
|
})
|
|
@@ -348,7 +230,6 @@ function rePhotoCheck(photoData: any) {
|
|
|
348
230
|
if (result && result.status === 'success') {
|
|
349
231
|
const checkRePhotoResult = result.data
|
|
350
232
|
if (checkRePhotoResult.is_rephoto === true || checkRePhotoResult.is_rephoto === 'true') {
|
|
351
|
-
parentData?.onPhotoRephotodetected?.()
|
|
352
233
|
console.warn(`检测到当前图片属于翻拍照片\n请上传合法图片!`)
|
|
353
234
|
showFailToast({
|
|
354
235
|
message: `当前图片属于翻拍照片\n请上传合法图片!`,
|
|
@@ -393,14 +274,13 @@ function handlePhotoUpload(photoData: any) {
|
|
|
393
274
|
console.log('图片上传前的参数:', JSON.stringify(photoData))
|
|
394
275
|
// 添加临时预览
|
|
395
276
|
const mimeType = getImageMimeType(photoData.filePath)
|
|
396
|
-
const previewUrl = toDataUrl(photoData.content, mimeType)
|
|
397
277
|
const tempFile = {
|
|
398
278
|
uid: Date.now() + Math.random().toString(36).substr(2, 5),
|
|
399
279
|
name: photoData.filePath.split('/').pop(),
|
|
400
280
|
status: 'uploading',
|
|
401
281
|
message: '上传中...',
|
|
402
|
-
url:
|
|
403
|
-
content:
|
|
282
|
+
url: `data:${mimeType};base64,${photoData.content}`,
|
|
283
|
+
content: `data:image/png;base64,${photoData.content}`,
|
|
404
284
|
isImage: true,
|
|
405
285
|
type: mimeType,
|
|
406
286
|
}
|
|
@@ -415,7 +295,7 @@ function handlePhotoUpload(photoData: any) {
|
|
|
415
295
|
const param = {
|
|
416
296
|
resUploadMode: props.uploadMode,
|
|
417
297
|
pathKey: 'Default',
|
|
418
|
-
formType: 'image'
|
|
298
|
+
formType: 'image',
|
|
419
299
|
useType: props.attr?.useType || 'Default',
|
|
420
300
|
resUploadStock: '1',
|
|
421
301
|
filename: photoData.name,
|
|
@@ -423,7 +303,7 @@ function handlePhotoUpload(photoData: any) {
|
|
|
423
303
|
f_operator: 'server',
|
|
424
304
|
imgPath: photoData.filePath,
|
|
425
305
|
urlPath: `/api/${props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
|
|
426
|
-
commonId:
|
|
306
|
+
commonId: parentCommonId,
|
|
427
307
|
}
|
|
428
308
|
if (props.isAsyncUpload) {
|
|
429
309
|
// 添加上传队列
|
|
@@ -474,12 +354,9 @@ function handlePhotoUpload(photoData: any) {
|
|
|
474
354
|
if (index !== -1) {
|
|
475
355
|
imageList.value[index].uid = result.data.id
|
|
476
356
|
imageList.value[index].id = result.data.id
|
|
477
|
-
imageList.value[index].f_downloadpath = result.data.f_downloadpath
|
|
478
|
-
imageList.value[index].photo_name = result.data.f_filename
|
|
479
|
-
imageList.value[index].isImage = true
|
|
480
357
|
delete imageList.value[index].message
|
|
481
358
|
imageList.value[index].status = 'done'
|
|
482
|
-
|
|
359
|
+
imageList.value[index].url = `${import.meta.env.VITE_APP_RESOURCE_PATH || ''}${result.data.f_downloadpath}`
|
|
483
360
|
}
|
|
484
361
|
}
|
|
485
362
|
else {
|
|
@@ -566,28 +443,34 @@ const showActionSheet = ref(false)
|
|
|
566
443
|
const uploaderRef = ref()
|
|
567
444
|
|
|
568
445
|
const actionOptions = [
|
|
569
|
-
{ name: '
|
|
570
|
-
{ name: '
|
|
571
|
-
{ name: '文件上传', key: 'document' },
|
|
446
|
+
{ name: '拍照', key: 'camera' },
|
|
447
|
+
{ name: '上传', key: 'file' },
|
|
572
448
|
]
|
|
573
449
|
|
|
574
450
|
function handleUploadAreaClick() {
|
|
575
451
|
if (props.readonly)
|
|
576
452
|
return
|
|
577
|
-
|
|
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
|
+
}
|
|
578
465
|
}
|
|
579
466
|
|
|
580
467
|
function handleActionSelect(option: any) {
|
|
581
|
-
if (option.key === 'document') {
|
|
582
|
-
triggerDocumentPicker()
|
|
583
|
-
showActionSheet.value = false
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
468
|
showActionSheet.value = false
|
|
587
469
|
if (option.key === 'camera') {
|
|
588
470
|
triggerCamera()
|
|
589
471
|
}
|
|
590
|
-
else if (option.key === '
|
|
472
|
+
else if (option.key === 'file') {
|
|
473
|
+
// 手机文件上传
|
|
591
474
|
mobileUtil.execute({
|
|
592
475
|
funcName: 'photoAlbum',
|
|
593
476
|
param: {},
|
|
@@ -598,7 +481,8 @@ function handleActionSelect(option: any) {
|
|
|
598
481
|
})
|
|
599
482
|
}
|
|
600
483
|
else {
|
|
601
|
-
|
|
484
|
+
// 手机文件选择失败,则走浏览器式文件选择
|
|
485
|
+
openBrowserFilePicker()
|
|
602
486
|
}
|
|
603
487
|
},
|
|
604
488
|
})
|
|
@@ -623,27 +507,27 @@ function getUploadStatus() {
|
|
|
623
507
|
defineExpose({
|
|
624
508
|
getUploadStatus,
|
|
625
509
|
})
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 组件卸载时清理:关闭 ActionSheet,清空图片列表以释放闭包引用
|
|
513
|
+
*/
|
|
514
|
+
onBeforeUnmount(() => {
|
|
515
|
+
showActionSheet.value = false
|
|
516
|
+
// 清空图片列表,释放闭包中对 props.attr 等对象的引用,防止内存泄漏
|
|
517
|
+
imageList.value = []
|
|
518
|
+
})
|
|
626
519
|
</script>
|
|
627
520
|
|
|
628
521
|
<template>
|
|
629
522
|
<div class="uploader-container">
|
|
630
|
-
<!--
|
|
523
|
+
<!-- 浏览器 / 微信 H5:统一隐藏文件选择(多选图片) -->
|
|
631
524
|
<input
|
|
632
|
-
ref="
|
|
525
|
+
ref="fileInputRef"
|
|
633
526
|
type="file"
|
|
634
527
|
multiple
|
|
635
528
|
accept="image/*"
|
|
636
|
-
|
|
637
|
-
@change="(e:any) =>
|
|
638
|
-
>
|
|
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)"
|
|
529
|
+
style="display:none"
|
|
530
|
+
@change="(e:any) => handleBrowserFiles(e.target.files)"
|
|
647
531
|
>
|
|
648
532
|
<van-uploader
|
|
649
533
|
ref="uploaderRef"
|
|
@@ -655,16 +539,6 @@ defineExpose({
|
|
|
655
539
|
:preview-image="true"
|
|
656
540
|
:before-delete="!props.readonly && props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin' ? deleteFileFunction : undefined"
|
|
657
541
|
>
|
|
658
|
-
<template #preview-cover="{ index }">
|
|
659
|
-
<div
|
|
660
|
-
v-if="imageList[index] && !isFileItemImage(imageList[index])"
|
|
661
|
-
class="file-type-tile"
|
|
662
|
-
:style="{ background: getItemFileMeta(imageList[index]).color }"
|
|
663
|
-
>
|
|
664
|
-
<span class="file-type-tile__ext">{{ getItemFileMeta(imageList[index]).ext }}</span>
|
|
665
|
-
<span class="file-type-tile__label">{{ getItemFileMeta(imageList[index]).label }}</span>
|
|
666
|
-
</div>
|
|
667
|
-
</template>
|
|
668
542
|
<template #default>
|
|
669
543
|
<div
|
|
670
544
|
v-if="!props.readonly && props.mode !== '预览' && (imageList.length < props.attr?.acceptCount && props.attr?.addOrEdit !== 'readonly')"
|
|
@@ -698,19 +572,6 @@ defineExpose({
|
|
|
698
572
|
// 该属性会影响表单布局
|
|
699
573
|
// gap: 16px;
|
|
700
574
|
}
|
|
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
|
-
}
|
|
714
575
|
/* 让自定义上传按钮可点击:关闭 Vant 覆盖在上方的透明 input 的事件捕获 */
|
|
715
576
|
:deep(.custom-trigger-uploader .van-uploader__input) {
|
|
716
577
|
pointer-events: none;
|
|
@@ -748,38 +609,4 @@ defineExpose({
|
|
|
748
609
|
margin-top: 0;
|
|
749
610
|
}
|
|
750
611
|
}
|
|
751
|
-
|
|
752
|
-
.file-type-tile {
|
|
753
|
-
position: absolute;
|
|
754
|
-
inset: 0;
|
|
755
|
-
display: flex;
|
|
756
|
-
flex-direction: column;
|
|
757
|
-
align-items: center;
|
|
758
|
-
justify-content: center;
|
|
759
|
-
color: #fff;
|
|
760
|
-
border-radius: 8px;
|
|
761
|
-
padding: 4px;
|
|
762
|
-
box-sizing: border-box;
|
|
763
|
-
text-align: center;
|
|
764
|
-
pointer-events: none;
|
|
765
|
-
|
|
766
|
-
&__ext {
|
|
767
|
-
font-size: 16px;
|
|
768
|
-
font-weight: 700;
|
|
769
|
-
line-height: 1.2;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
&__label {
|
|
773
|
-
font-size: 10px;
|
|
774
|
-
margin-top: 2px;
|
|
775
|
-
opacity: 0.95;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
:deep(.custom-trigger-uploader) {
|
|
780
|
-
.van-uploader__preview {
|
|
781
|
-
position: relative;
|
|
782
|
-
overflow: hidden;
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
612
|
</style>
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
Button as vanButton,
|
|
5
5
|
Uploader as vanUploader,
|
|
6
6
|
} from 'vant'
|
|
7
|
-
import { ref, watch } from 'vue'
|
|
7
|
+
import { onBeforeUnmount, ref, watch } from 'vue'
|
|
8
8
|
|
|
9
9
|
const props = defineProps({
|
|
10
10
|
imageList: Array<any>,
|
|
@@ -17,7 +17,7 @@ const emit = defineEmits(['updateFileList'])
|
|
|
17
17
|
const imageList = ref(props.imageList)
|
|
18
18
|
|
|
19
19
|
// 监听 props.imageList 的变化
|
|
20
|
-
watch(() => props.imageList, (newVal) => {
|
|
20
|
+
const stopWatchImageList = watch(() => props.imageList, (newVal) => {
|
|
21
21
|
if (newVal?.length > 0) {
|
|
22
22
|
const existingIds = imageList.value ? imageList.value.map(item => item.id) : []
|
|
23
23
|
const newItems = newVal.filter(item => !item.id || !existingIds.includes(item.id)).map((item) => {
|
|
@@ -33,6 +33,10 @@ watch(() => props.imageList, (newVal) => {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}, { immediate: true, deep: true })
|
|
36
|
+
|
|
37
|
+
onBeforeUnmount(() => {
|
|
38
|
+
stopWatchImageList()
|
|
39
|
+
})
|
|
36
40
|
// 上传图片
|
|
37
41
|
function fileUpload(file: any): void {
|
|
38
42
|
const formData = new FormData()
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
Popup as VanPopup,
|
|
8
8
|
TimePicker as VanTimePicker,
|
|
9
9
|
} from 'vant'
|
|
10
|
-
import { computed, ref } from 'vue'
|
|
10
|
+
import { computed, onBeforeUnmount, ref } from 'vue'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
attr: any
|
|
@@ -258,6 +258,13 @@ function onDateTimePickerConfirm() {
|
|
|
258
258
|
function onPickerCancel() {
|
|
259
259
|
showDatePicker.value = false
|
|
260
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 组件卸载时清理:关闭弹窗
|
|
264
|
+
*/
|
|
265
|
+
onBeforeUnmount(() => {
|
|
266
|
+
showDatePicker.value = false
|
|
267
|
+
})
|
|
261
268
|
</script>
|
|
262
269
|
|
|
263
270
|
<template>
|
|
@@ -300,7 +307,6 @@ function onPickerCancel() {
|
|
|
300
307
|
<VanPopup
|
|
301
308
|
v-model:show="showDatePicker"
|
|
302
309
|
position="bottom"
|
|
303
|
-
teleport="body"
|
|
304
310
|
overlay-class="date-picker-overlay"
|
|
305
311
|
>
|
|
306
312
|
<VanPickerGroup
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Popup as VanPopup,
|
|
9
9
|
Search as VanSearch,
|
|
10
10
|
} from 'vant'
|
|
11
|
-
import { computed, ref, watch } from 'vue'
|
|
11
|
+
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
12
12
|
|
|
13
13
|
const props = defineProps({
|
|
14
14
|
columns: {
|
|
@@ -124,9 +124,13 @@ function showPopu(disabled) {
|
|
|
124
124
|
watch(() => columnsData.value, (newVal, _oldVal) => {
|
|
125
125
|
checkedAll.value = newVal.length && newVal.length === checkboxValue.value.length
|
|
126
126
|
})
|
|
127
|
-
watch(() => checkboxValue.value.length, (newVal, _oldVal) => {
|
|
127
|
+
const stopWatchCheckboxValue = watch(() => checkboxValue.value.length, (newVal, _oldVal) => {
|
|
128
128
|
checkedAll.value = newVal && newVal === columnsData.value.length
|
|
129
129
|
})
|
|
130
|
+
|
|
131
|
+
onBeforeUnmount(() => {
|
|
132
|
+
stopWatchCheckboxValue()
|
|
133
|
+
})
|
|
130
134
|
const resultLabel = computed(() => {
|
|
131
135
|
const res = props.columns.filter((item) => {
|
|
132
136
|
return (resultValue.value as unknown as string[])?.includes(item[props.option.value])
|