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,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.3.50",
4
+ "version": "1.3.52",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -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"
@@ -729,7 +729,7 @@ function handleAddressConfirm(location) {
729
729
 
730
730
  // 重置方法,供父组件调用
731
731
  function reset() {
732
- modelData.value = null
732
+ modelData.value = getDefaultValue()
733
733
  errorMessage.value = ''
734
734
  treeValue.value = null
735
735
  area.value = null
@@ -12,8 +12,8 @@ const router = useRouter()
12
12
  const idKey = ref('o_id')
13
13
 
14
14
  // 简易crud表单测试
15
- const configName = ref('lngChargeAuditMobileCRUD')
16
- const serviceName = ref('af-gaslink')
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('AddConstructionForm')
7
- const serviceName = ref('af-linepatrol')
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
- const lastUploadResultObj = computed<UploadResult | null>(() => {
55
- if (!lastUploadResult.value)
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 uploadDisplayInfo = computed(() => {
69
- const result = lastUploadResultObj.value
70
- if (!result)
71
- return { error: null, info: null }
72
- // response.code 不为 200
73
- if (result.response && result.response.code !== 200) {
74
- return { error: result.response.msg || '未知错误', info: null }
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
- // location.errorCode 存在且 isError 为 true
78
- if (result.location && result.location.isError && result.location.errorCode) {
79
- return { error: result.location.errorCode, info: null }
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
- if (result.location) {
84
- return {
85
- error: null,
86
- info: {
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
- const uploadStatusTag = computed(() => {
98
- const result = uploadDisplayInfo.value
99
- if (result.error) {
100
- return { text: '上传定位失败', color: 'red' }
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
- if (result.info) {
103
- return { text: '正在上传定位', color: 'green' }
107
+ catch (err) {
108
+ console.error('获取定位信息异常:', err)
104
109
  }
105
- return null
106
- })
110
+ }
107
111
 
108
112
  onMounted(() => {
109
113
  if (roleName?.includes('需要定位人员')) {
110
- try {
111
- mobileUtil.execute({
112
- param: {},
113
- funcName: 'getLastRealtimeUploadResult',
114
- callbackFunc: (result: PhoneLocationStatus) => {
115
- try {
116
- if (result.status === 'success') {
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="uploadStatusTag"
212
- class="upload-status-tag" :class="[uploadStatusTag.color]"
182
+ v-if="locationStatus.statusTag"
183
+ class="upload-status-tag" :class="[locationStatus.statusTag.color]"
213
184
  >
214
- {{ uploadStatusTag.text }}
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 {