af-mobile-client-vue3 1.3.50 → 1.3.52
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import cameraIcon from '@af-mobile-client-vue3/assets/img/component/camera.png'
|
|
3
|
-
import { deleteFile } from '@af-mobile-client-vue3/services/api/common'
|
|
3
|
+
import { deleteFile, upload } from '@af-mobile-client-vue3/services/api/common'
|
|
4
4
|
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
5
5
|
import {
|
|
6
6
|
ActionSheet,
|
|
@@ -20,6 +20,103 @@ const emit = defineEmits(['updateFileList', 'updateAllFileList'])
|
|
|
20
20
|
|
|
21
21
|
const imageList = ref<Array<any>>(props.imageList ?? [])
|
|
22
22
|
|
|
23
|
+
// 浏览器模式:隐藏的文件输入(选择 / 拍照)
|
|
24
|
+
const fileInputRef = ref<HTMLInputElement | undefined>()
|
|
25
|
+
const cameraInputRef = ref<HTMLInputElement | undefined>()
|
|
26
|
+
|
|
27
|
+
function openBrowserFilePicker() {
|
|
28
|
+
if (fileInputRef.value) {
|
|
29
|
+
fileInputRef.value.value = ''
|
|
30
|
+
fileInputRef.value.click()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function openBrowserCameraPicker() {
|
|
35
|
+
if (cameraInputRef.value) {
|
|
36
|
+
cameraInputRef.value.value = ''
|
|
37
|
+
cameraInputRef.value.click()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function emitUpdatesAfterChange() {
|
|
42
|
+
// 新增:对外抛出完整与ID列表
|
|
43
|
+
(emit as any)('updateAllFileList', imageList.value.filter(item => item.status === 'done').map(item => item))
|
|
44
|
+
const doneIds = Object.values(imageList.value)
|
|
45
|
+
.filter(item => item.status === 'done')
|
|
46
|
+
.map(item => item.id)
|
|
47
|
+
if (props.outerIndex !== undefined) {
|
|
48
|
+
(emit as any)('updateFileList', doneIds, props.outerIndex)
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
(emit as any)('updateFileList', doneIds)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createTempUploadingItem(name: string, previewUrl: string) {
|
|
56
|
+
const temp = {
|
|
57
|
+
uid: Date.now() + Math.random().toString(36).substr(2, 5),
|
|
58
|
+
name,
|
|
59
|
+
status: 'uploading',
|
|
60
|
+
message: '上传中...',
|
|
61
|
+
url: previewUrl,
|
|
62
|
+
}
|
|
63
|
+
if (!imageList.value)
|
|
64
|
+
imageList.value = [temp]
|
|
65
|
+
else imageList.value.push(temp)
|
|
66
|
+
return temp
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function uploadFileViaHttp(file: File, previewUrl: string, temp: any) {
|
|
70
|
+
const formData = new FormData()
|
|
71
|
+
formData.append('avatar', file)
|
|
72
|
+
formData.append('resUploadMode', props.uploadMode)
|
|
73
|
+
formData.append('pathKey', 'Default')
|
|
74
|
+
formData.append('formType', 'image')
|
|
75
|
+
formData.append('useType', 'Default')
|
|
76
|
+
formData.append('resUploadStock', '1')
|
|
77
|
+
formData.append('filename', file.name)
|
|
78
|
+
formData.append('filesize', (file.size / 1024 / 1024).toFixed(4))
|
|
79
|
+
formData.append('f_operator', 'server')
|
|
80
|
+
|
|
81
|
+
upload(formData, import.meta.env.VITE_APP_SYSTEM_NAME, { 'Content-Type': 'multipart/form-data' }).then((res: any) => {
|
|
82
|
+
const index = imageList.value.findIndex(item => item.uid === temp.uid)
|
|
83
|
+
if (res?.data?.id) {
|
|
84
|
+
if (index !== -1) {
|
|
85
|
+
imageList.value[index].uid = res.data.id
|
|
86
|
+
imageList.value[index].id = res.data.id
|
|
87
|
+
delete imageList.value[index].message
|
|
88
|
+
imageList.value[index].status = 'done'
|
|
89
|
+
imageList.value[index].url = res.data.f_downloadpath
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (index !== -1) {
|
|
94
|
+
imageList.value[index].status = 'failed'
|
|
95
|
+
imageList.value[index].message = '上传失败'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
emitUpdatesAfterChange()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleBrowserFiles(files: FileList | null) {
|
|
103
|
+
if (!files || files.length === 0)
|
|
104
|
+
return
|
|
105
|
+
const max = props.attr?.acceptCount ?? Number.POSITIVE_INFINITY
|
|
106
|
+
for (let i = 0; i < files.length; i++) {
|
|
107
|
+
if (imageList.value.length >= max)
|
|
108
|
+
break
|
|
109
|
+
const file = files[i]
|
|
110
|
+
const reader = new FileReader()
|
|
111
|
+
reader.onload = (e: any) => {
|
|
112
|
+
const previewUrl = e.target?.result as string
|
|
113
|
+
const temp = createTempUploadingItem(file.name, previewUrl)
|
|
114
|
+
uploadFileViaHttp(file, previewUrl, temp)
|
|
115
|
+
}
|
|
116
|
+
reader.readAsDataURL(file)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
23
120
|
// 触发拍照
|
|
24
121
|
function triggerCamera() {
|
|
25
122
|
mobileUtil.execute({
|
|
@@ -29,6 +126,10 @@ function triggerCamera() {
|
|
|
29
126
|
if (result.status === 'success') {
|
|
30
127
|
handlePhotoUpload(result.data)
|
|
31
128
|
}
|
|
129
|
+
else {
|
|
130
|
+
// 浏览器模式:调起相机输入
|
|
131
|
+
openBrowserCameraPicker()
|
|
132
|
+
}
|
|
32
133
|
},
|
|
33
134
|
})
|
|
34
135
|
}
|
|
@@ -185,6 +286,10 @@ function handleActionSelect(option: any) {
|
|
|
185
286
|
handlePhotoUpload(photo)
|
|
186
287
|
})
|
|
187
288
|
}
|
|
289
|
+
else {
|
|
290
|
+
// 浏览器模式:打开文件选择
|
|
291
|
+
openBrowserFilePicker()
|
|
292
|
+
}
|
|
188
293
|
},
|
|
189
294
|
})
|
|
190
295
|
}
|
|
@@ -193,6 +298,24 @@ function handleActionSelect(option: any) {
|
|
|
193
298
|
|
|
194
299
|
<template>
|
|
195
300
|
<div class="uploader-container">
|
|
301
|
+
<!-- 浏览器模式隐藏输入:文件选择 -->
|
|
302
|
+
<input
|
|
303
|
+
ref="fileInputRef"
|
|
304
|
+
type="file"
|
|
305
|
+
multiple
|
|
306
|
+
accept="image/*"
|
|
307
|
+
style="display:none"
|
|
308
|
+
@change="(e:any) => handleBrowserFiles(e.target.files)"
|
|
309
|
+
>
|
|
310
|
+
<!-- 浏览器模式隐藏输入:拍照(相机) -->
|
|
311
|
+
<input
|
|
312
|
+
ref="cameraInputRef"
|
|
313
|
+
type="file"
|
|
314
|
+
accept="image/*"
|
|
315
|
+
capture="environment"
|
|
316
|
+
style="display:none"
|
|
317
|
+
@change="(e:any) => handleBrowserFiles(e.target.files)"
|
|
318
|
+
>
|
|
196
319
|
<div
|
|
197
320
|
v-if="props.mode !== '预览' && (imageList.length < props.attr?.acceptCount && props.attr?.addOrEdit !== 'readonly')"
|
|
198
321
|
class="custom-upload-area"
|
|
@@ -12,8 +12,8 @@ const router = useRouter()
|
|
|
12
12
|
const idKey = ref('o_id')
|
|
13
13
|
|
|
14
14
|
// 简易crud表单测试
|
|
15
|
-
const configName = ref('
|
|
16
|
-
const serviceName = ref('af-
|
|
15
|
+
const configName = ref('crud_patroltask_managePhoneCRUD')
|
|
16
|
+
const serviceName = ref('af-linepatrol')
|
|
17
17
|
|
|
18
18
|
// 资源权限测试
|
|
19
19
|
// const configName = ref('crud_sources_test')
|
|
@@ -95,8 +95,6 @@ function deleteRow(result) {
|
|
|
95
95
|
<XCellList
|
|
96
96
|
:config-name="configName"
|
|
97
97
|
:service-name="serviceName"
|
|
98
|
-
:fix-query-form="{ o_f_oper_name: 'edu_test' }"
|
|
99
|
-
:id-key="idKey"
|
|
100
98
|
@to-detail="toDetail"
|
|
101
99
|
@delete-row="deleteRow"
|
|
102
100
|
/>
|
|
@@ -3,10 +3,13 @@ import XForm from '@af-mobile-client-vue3/components/data/XForm/index.vue'
|
|
|
3
3
|
import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
|
|
4
4
|
import { ref } from 'vue'
|
|
5
5
|
|
|
6
|
-
const configName = ref('
|
|
7
|
-
const serviceName = ref('af-
|
|
6
|
+
const configName = ref('sealCheckInForm')
|
|
7
|
+
const serviceName = ref('af-safecheck')
|
|
8
8
|
|
|
9
9
|
const formGroupAddConstruction = ref(null)
|
|
10
|
+
function submit(s) {
|
|
11
|
+
console.log('>>>> ss: ', JSON.stringify(s))
|
|
12
|
+
}
|
|
10
13
|
</script>
|
|
11
14
|
|
|
12
15
|
<template>
|
|
@@ -17,6 +20,7 @@ const formGroupAddConstruction = ref(null)
|
|
|
17
20
|
mode="新增"
|
|
18
21
|
:config-name="configName"
|
|
19
22
|
:service-name="serviceName"
|
|
23
|
+
@on-submit="submit"
|
|
20
24
|
/>
|
|
21
25
|
</template>
|
|
22
26
|
</NormalDataLayout>
|
|
@@ -18,7 +18,7 @@ const showModifyPassword = ref(false)
|
|
|
18
18
|
// 退出登录确认弹窗显示状态
|
|
19
19
|
const showLogoutConfirm = ref(false)
|
|
20
20
|
// 在setup函数中
|
|
21
|
-
let intervalId = null
|
|
21
|
+
let intervalId: number | null = null
|
|
22
22
|
|
|
23
23
|
// 定义一个变量来存储上一次的上传结果
|
|
24
24
|
const lastUploadResult = ref(null)
|
|
@@ -51,102 +51,73 @@ interface UploadResult {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// 兼容 lastUploadResult 可能为字符串或对象
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return null
|
|
57
|
-
if (typeof lastUploadResult.value === 'string') {
|
|
58
|
-
try {
|
|
59
|
-
return JSON.parse(lastUploadResult.value)
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return lastUploadResult.value
|
|
66
|
-
})
|
|
54
|
+
interface LocationStatusTag { text: string, color: 'red' | 'green' | 'yellow' }
|
|
55
|
+
interface LocationStatusInfo { uploadTime: string, latitude: number, longitude: number }
|
|
67
56
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
return { error: null,
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
57
|
+
const locationStatus = computed(() => {
|
|
58
|
+
console.warn('上次定位信息:', lastUploadResult.value)
|
|
59
|
+
if (lastUploadResult.value === null) {
|
|
60
|
+
return { error: null, statusTag: { text: '正在获取上次上传定位信息', color: 'yellow' } }
|
|
61
|
+
}
|
|
62
|
+
if (lastUploadResult.value.status === 'error') {
|
|
63
|
+
console.warn('上次定位信息解析失败:', lastUploadResult.value)
|
|
64
|
+
return { error: null, statusTag: { text: lastUploadResult.value.msg, color: 'yellow' } }
|
|
75
65
|
}
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return { error
|
|
67
|
+
if (lastUploadResult.value.data.location && lastUploadResult.value.data.location.isError) {
|
|
68
|
+
const error = lastUploadResult.value.data.location.errorCode
|
|
69
|
+
return { error, info: null, statusTag: { text: '获取定位失败', color: 'red' } }
|
|
80
70
|
}
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
uploadTime: result.uploadTime,
|
|
88
|
-
latitude: result.location.f_latitude,
|
|
89
|
-
longitude: result.location.f_longitude,
|
|
90
|
-
},
|
|
72
|
+
if (lastUploadResult.value.data.location) {
|
|
73
|
+
const info: LocationStatusInfo = {
|
|
74
|
+
uploadTime: lastUploadResult.value.data.uploadTime,
|
|
75
|
+
latitude: lastUploadResult.value.data.location.f_latitude,
|
|
76
|
+
longitude: lastUploadResult.value.data.location.f_longitude,
|
|
91
77
|
}
|
|
78
|
+
return { error: null, info, statusTag: { text: '正在上传定位', color: 'green' } }
|
|
92
79
|
}
|
|
93
80
|
|
|
94
|
-
return { error: null, info: null }
|
|
81
|
+
return { error: null, info: null, statusTag: null }
|
|
95
82
|
})
|
|
96
83
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
84
|
+
// 安全获取上次实时上传结果(封装 try/catch,避免初始化失败)
|
|
85
|
+
function fetchLastRealtimeUploadResult(): void {
|
|
86
|
+
try {
|
|
87
|
+
mobileUtil.execute({
|
|
88
|
+
param: {},
|
|
89
|
+
funcName: 'getLastRealtimeUploadResult',
|
|
90
|
+
callbackFunc: (res: PhoneLocationStatus) => {
|
|
91
|
+
try {
|
|
92
|
+
console.warn('获取到的上次定位信息:', res)
|
|
93
|
+
if (res && res.status === 'success') {
|
|
94
|
+
lastUploadResult.value = res
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// 失败也不抛错,保留可能的返回内容用于显示错误状态
|
|
98
|
+
lastUploadResult.value = res ?? null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error('处理定位回调异常:', err)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
})
|
|
101
106
|
}
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error('获取定位信息异常:', err)
|
|
104
109
|
}
|
|
105
|
-
|
|
106
|
-
})
|
|
110
|
+
}
|
|
107
111
|
|
|
108
112
|
onMounted(() => {
|
|
109
113
|
if (roleName?.includes('需要定位人员')) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lastUploadResult.value = result.data
|
|
118
|
-
|
|
119
|
-
// 首次成功才启动定时器
|
|
120
|
-
intervalId = setInterval(() => {
|
|
121
|
-
try {
|
|
122
|
-
mobileUtil.execute({
|
|
123
|
-
param: {},
|
|
124
|
-
funcName: 'getLastRealtimeUploadResult',
|
|
125
|
-
callbackFunc: (res: PhoneLocationStatus) => {
|
|
126
|
-
if (res.status === 'success') {
|
|
127
|
-
lastUploadResult.value = res.data
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
console.error('定时获取定位信息异常:', err)
|
|
134
|
-
}
|
|
135
|
-
}, 10000)
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
console.warn('首次定位获取失败,不启动定时器:', result)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch (err) {
|
|
142
|
-
console.error('处理首次定位结果异常:', err)
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
console.error('首次获取定位信息异常:', err)
|
|
149
|
-
}
|
|
114
|
+
// 立即尝试获取一次(失败也不会阻断初始化)
|
|
115
|
+
fetchLastRealtimeUploadResult()
|
|
116
|
+
|
|
117
|
+
// 无论首次结果如何,都启动定时器持续获取
|
|
118
|
+
intervalId = window.setInterval(() => {
|
|
119
|
+
fetchLastRealtimeUploadResult()
|
|
120
|
+
}, 10000)
|
|
150
121
|
}
|
|
151
122
|
})
|
|
152
123
|
|
|
@@ -208,10 +179,10 @@ const webMobileConfig = useSettingStore().getSetting()
|
|
|
208
179
|
<h2 class="username">
|
|
209
180
|
{{ username }}
|
|
210
181
|
<span
|
|
211
|
-
v-if="
|
|
212
|
-
class="upload-status-tag" :class="[
|
|
182
|
+
v-if="locationStatus.statusTag"
|
|
183
|
+
class="upload-status-tag" :class="[locationStatus.statusTag.color]"
|
|
213
184
|
>
|
|
214
|
-
{{
|
|
185
|
+
{{ locationStatus.statusTag.text }}
|
|
215
186
|
</span>
|
|
216
187
|
</h2>
|
|
217
188
|
<p class="user-role">
|
|
@@ -403,6 +374,11 @@ const webMobileConfig = useSettingStore().getSetting()
|
|
|
403
374
|
background: #fdeaea;
|
|
404
375
|
border: 1px solid #ef4444;
|
|
405
376
|
}
|
|
377
|
+
&.yellow {
|
|
378
|
+
color: #f59e0b;
|
|
379
|
+
background: #fffbeb;
|
|
380
|
+
border: 1px solid #f59e0b;
|
|
381
|
+
}
|
|
406
382
|
}
|
|
407
383
|
|
|
408
384
|
.user-role {
|