af-mobile-client-vue3 1.2.18 → 1.2.20

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.2.18",
4
+ "version": "1.2.20",
5
5
  "packageManager": "pnpm@10.12.3",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -1,159 +1,159 @@
1
- <script setup lang="ts">
2
- import { deleteFile } from '@af-mobile-client-vue3/services/api/common'
3
- import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
4
- import {
5
- Button as vanButton,
6
- Uploader as vanUploader,
7
- } from 'vant'
8
- import { ref, watch } from 'vue'
9
-
10
- const props = defineProps({
11
- imageList: Array<any>,
12
- outerIndex: { default: undefined },
13
- authority: { default: 'user' },
14
- uploadMode: { default: 'server' },
15
- attr: { type: Object as () => { addOrEdit?: string }, default: () => ({}) },
16
- })
17
- const emit = defineEmits(['updateFileList'])
18
-
19
- const imageList = ref<Array<any>>(props.imageList ?? [])
20
-
21
- // 同步 props.imageList 到内部 imageList,保证每次变化都响应
22
- watch(() => props.imageList, (newVal) => {
23
- imageList.value = Array.isArray(newVal) ? [...newVal] : []
24
- }, { immediate: true })
25
-
26
- // 触发拍照
27
- function triggerCamera() {
28
- mobileUtil.execute({
29
- funcName: 'takePicture',
30
- param: {},
31
- callbackFunc: (result: any) => {
32
- if (result.status === 'success') {
33
- handlePhotoUpload(result.data)
34
- }
35
- },
36
- })
37
- }
38
-
39
- // 处理拍照后的上传
40
- function getImageMimeType(fileName: string): string {
41
- const ext = fileName.split('.').pop()?.toLowerCase()
42
- if (ext === 'jpg' || ext === 'jpeg')
43
- return 'image/jpeg'
44
- if (ext === 'png')
45
- return 'image/png'
46
- if (ext === 'gif')
47
- return 'image/gif'
48
- return 'image/png' // 默认
49
- }
50
-
51
- function handlePhotoUpload(photoData: any) {
52
- // 添加临时预览
53
- const mimeType = getImageMimeType(photoData.filePath)
54
- const tempFile = {
55
- uid: Date.now() + Math.random().toString(36).substr(2, 5),
56
- name: photoData.filePath.split('/').pop(),
57
- status: 'uploading',
58
- message: '上传中...',
59
- url: `data:${mimeType};base64,${photoData.content}`,
60
- }
61
-
62
- if (!imageList.value) {
63
- imageList.value = [tempFile]
64
- }
65
- else {
66
- imageList.value.push(tempFile)
67
- }
68
-
69
- const param = {
70
- resUploadMode: props.uploadMode,
71
- pathKey: 'Default',
72
- formType: 'image',
73
- useType: 'Default',
74
- resUploadStock: '1',
75
- filename: photoData.name,
76
- filesize: photoData.size,
77
- f_operator: 'server',
78
- imgPath: photoData.filePath,
79
- urlPath: `/api/${import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
80
- }
81
- // 上传到服务器
82
- mobileUtil.execute({
83
- funcName: 'uploadResource',
84
- param,
85
- callbackFunc: (result: any) => {
86
- if (result.status === 'success') {
87
- const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
88
- if (index !== -1) {
89
- imageList.value[index].uid = result.data.id
90
- imageList.value[index].id = result.data.id
91
- delete imageList.value[index].message
92
- imageList.value[index].status = 'done'
93
- imageList.value[index].url = result.data.f_downloadpath
94
- }
95
- }
96
- else {
97
- const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
98
- if (index !== -1) {
99
- imageList.value[index].status = 'failed'
100
- imageList.value[index].message = '上传失败'
101
- }
102
- }
103
-
104
- if (props.outerIndex !== undefined)
105
- emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
106
- else
107
- emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
108
- },
109
- })
110
- }
111
-
112
- // 删除图片
113
- function deleteFileFunction(file: any) {
114
- if (file.id) {
115
- deleteFile({ ids: [file.id], f_state: '删除' }).then((res: any) => {
116
- if (res.msg !== undefined) {
117
- const targetIndex = imageList.value.findIndex(item => item.id === file.id)
118
- if (targetIndex !== -1) {
119
- imageList.value.splice(targetIndex, 1)
120
- if (props.outerIndex !== undefined)
121
- emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
122
- else
123
- emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
124
- }
125
- }
126
- })
127
- }
128
- }
129
- </script>
130
-
131
- <template>
132
- <div class="uploader-container">
133
- <van-button
134
- v-if="props.attr?.addOrEdit !== 'readonly'"
135
- icon="photograph"
136
- type="primary"
137
- @click="triggerCamera"
138
- >
139
- 拍照
140
- </van-button>
141
-
142
- <van-uploader
143
- v-model="imageList"
144
- :show-upload="false"
145
- :deletable="props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin'"
146
- :multiple="props.authority === 'admin'"
147
- :preview-image="true"
148
- :before-delete="props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin' ? deleteFileFunction : undefined"
149
- />
150
- </div>
151
- </template>
152
-
153
- <style scoped lang="less">
154
- .uploader-container {
155
- display: flex;
156
- flex-direction: column;
157
- gap: 16px;
158
- }
159
- </style>
1
+ <script setup lang="ts">
2
+ import { deleteFile } from '@af-mobile-client-vue3/services/api/common'
3
+ import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
4
+ import {
5
+ Button as vanButton,
6
+ Uploader as vanUploader,
7
+ } from 'vant'
8
+ import { ref, watch } from 'vue'
9
+
10
+ const props = defineProps({
11
+ imageList: Array<any>,
12
+ outerIndex: { default: undefined },
13
+ authority: { default: 'user' },
14
+ uploadMode: { default: 'server' },
15
+ attr: { type: Object as () => { addOrEdit?: string }, default: () => ({}) },
16
+ })
17
+ const emit = defineEmits(['updateFileList'])
18
+
19
+ const imageList = ref<Array<any>>(props.imageList ?? [])
20
+
21
+ // 同步 props.imageList 到内部 imageList,保证每次变化都响应
22
+ watch(() => props.imageList, (newVal) => {
23
+ imageList.value = Array.isArray(newVal) ? [...newVal] : []
24
+ }, { immediate: true })
25
+
26
+ // 触发拍照
27
+ function triggerCamera() {
28
+ mobileUtil.execute({
29
+ funcName: 'takePicture',
30
+ param: {},
31
+ callbackFunc: (result: any) => {
32
+ if (result.status === 'success') {
33
+ handlePhotoUpload(result.data)
34
+ }
35
+ },
36
+ })
37
+ }
38
+
39
+ // 处理拍照后的上传
40
+ function getImageMimeType(fileName: string): string {
41
+ const ext = fileName.split('.').pop()?.toLowerCase()
42
+ if (ext === 'jpg' || ext === 'jpeg')
43
+ return 'image/jpeg'
44
+ if (ext === 'png')
45
+ return 'image/png'
46
+ if (ext === 'gif')
47
+ return 'image/gif'
48
+ return 'image/png' // 默认
49
+ }
50
+
51
+ function handlePhotoUpload(photoData: any) {
52
+ // 添加临时预览
53
+ const mimeType = getImageMimeType(photoData.filePath)
54
+ const tempFile = {
55
+ uid: Date.now() + Math.random().toString(36).substr(2, 5),
56
+ name: photoData.filePath.split('/').pop(),
57
+ status: 'uploading',
58
+ message: '上传中...',
59
+ url: `data:${mimeType};base64,${photoData.content}`,
60
+ }
61
+
62
+ if (!imageList.value) {
63
+ imageList.value = [tempFile]
64
+ }
65
+ else {
66
+ imageList.value.push(tempFile)
67
+ }
68
+
69
+ const param = {
70
+ resUploadMode: props.uploadMode,
71
+ pathKey: 'Default',
72
+ formType: 'image',
73
+ useType: 'Default',
74
+ resUploadStock: '1',
75
+ filename: photoData.name,
76
+ filesize: photoData.size,
77
+ f_operator: 'server',
78
+ imgPath: photoData.filePath,
79
+ urlPath: `/api/${import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
80
+ }
81
+ // 上传到服务器
82
+ mobileUtil.execute({
83
+ funcName: 'uploadResource',
84
+ param,
85
+ callbackFunc: (result: any) => {
86
+ if (result.status === 'success') {
87
+ const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
88
+ if (index !== -1) {
89
+ imageList.value[index].uid = result.data.id
90
+ imageList.value[index].id = result.data.id
91
+ delete imageList.value[index].message
92
+ imageList.value[index].status = 'done'
93
+ imageList.value[index].url = result.data.f_downloadpath
94
+ }
95
+ }
96
+ else {
97
+ const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
98
+ if (index !== -1) {
99
+ imageList.value[index].status = 'failed'
100
+ imageList.value[index].message = '上传失败'
101
+ }
102
+ }
103
+
104
+ if (props.outerIndex !== undefined)
105
+ emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
106
+ else
107
+ emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
108
+ },
109
+ })
110
+ }
111
+
112
+ // 删除图片
113
+ function deleteFileFunction(file: any) {
114
+ if (file.id) {
115
+ deleteFile({ ids: [file.id], f_state: '删除' }).then((res: any) => {
116
+ if (res.msg !== undefined) {
117
+ const targetIndex = imageList.value.findIndex(item => item.id === file.id)
118
+ if (targetIndex !== -1) {
119
+ imageList.value.splice(targetIndex, 1)
120
+ if (props.outerIndex !== undefined)
121
+ emit('updateFileList', imageList.value.filter(item => item.status === 'done'), props.outerIndex)
122
+ else
123
+ emit('updateFileList', imageList.value.filter(item => item.status === 'done'))
124
+ }
125
+ }
126
+ })
127
+ }
128
+ }
129
+ </script>
130
+
131
+ <template>
132
+ <div class="uploader-container">
133
+ <van-button
134
+ v-if="props.attr?.addOrEdit !== 'readonly'"
135
+ icon="photograph"
136
+ type="primary"
137
+ @click="triggerCamera"
138
+ >
139
+ 拍照
140
+ </van-button>
141
+
142
+ <van-uploader
143
+ v-model="imageList"
144
+ :show-upload="false"
145
+ :deletable="props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin'"
146
+ :multiple="props.authority === 'admin'"
147
+ :preview-image="true"
148
+ :before-delete="props.attr?.addOrEdit !== 'readonly' && props.authority === 'admin' ? deleteFileFunction : undefined"
149
+ />
150
+ </div>
151
+ </template>
152
+
153
+ <style scoped lang="less">
154
+ .uploader-container {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 16px;
158
+ }
159
+ </style>
@@ -131,9 +131,6 @@ const isLastPage = ref(false)
131
131
  // 条件参数(查询框)
132
132
  const conditionParams = ref(undefined)
133
133
 
134
- // 查询参数(配置自带的默认值)
135
- const queryDefaultParams = ref({})
136
-
137
134
  // 主要按钮的状态
138
135
  const buttonState = ref(undefined)
139
136
 
@@ -201,8 +198,18 @@ function initComponent() {
201
198
  }
202
199
  }
203
200
  configContent.value = result
204
- formQueryList.value = result.formJson
205
-
201
+ // 扁平化 type=group 的 groupItems,保持顺序
202
+ const flatFormJson = []
203
+ result.formJson.forEach((item) => {
204
+ if (item.type === 'group' && Array.isArray(item.groupItems)) {
205
+ flatFormJson.push(...item.groupItems)
206
+ }
207
+ else {
208
+ flatFormJson.push(item)
209
+ }
210
+ })
211
+ formQueryList.value = flatFormJson
212
+ console.log('formQueryList', formQueryList.value)
206
213
  if (result.buttonState) {
207
214
  buttonState.value = result.buttonState
208
215
  buttonPermissions.value = result.buttonPermissions
@@ -252,13 +259,12 @@ function initConditionParams(formItems, isQuery) {
252
259
  hasDefaults = true
253
260
 
254
261
  // 如果有默认值,则设置到条件参数中并立即执行查询
255
- queryDefaultParams.value = defaultParams
256
262
  isInitQuery.value = isQuery
257
263
  if (hasDefaults && isInitQuery.value) {
258
264
  // 延迟执行第一次查询,确保组件完全加载
259
265
  setTimeout(() => {
260
266
  loading.value = true
261
- onLoad()
267
+ onLoad(defaultParams)
262
268
  }, 100)
263
269
  }
264
270
  }
@@ -278,7 +284,7 @@ function onRefresh() {
278
284
  }
279
285
 
280
286
  // 加载数据
281
- function onLoad() {
287
+ function onLoad(defaultParams = {}) {
282
288
  if (refreshing.value) {
283
289
  list.value = []
284
290
  pageNo.value = 1
@@ -292,8 +298,7 @@ function onLoad() {
292
298
  // 确保conditionParams不是undefined
293
299
  if (conditionParams.value === undefined)
294
300
  conditionParams.value = {}
295
- const mergedParams = mergeParams(queryDefaultParams.value, conditionParams.value)
296
-
301
+ const mergedParams = mergeParams(defaultParams, conditionParams.value)
297
302
  // 输出查询条件,便于调试
298
303
  console.log('查询条件:', {
299
304
  pageNo: pageNo.value,
@@ -537,6 +542,16 @@ defineExpose({
537
542
  @search="onRefresh"
538
543
  />
539
544
  </VanCol>
545
+ <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
546
+ <VanCol v-if="buttonState?.add && buttonState.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr)))))" class="add-col">
547
+ <VanButton
548
+ icon="plus"
549
+ type="primary"
550
+ size="small"
551
+ class="add-action-btn"
552
+ @click="addOption"
553
+ />
554
+ </VanCol>
540
555
  <!-- 右侧动态插槽区域 -->
541
556
  <template v-for="(_, name) in slots" :key="name">
542
557
  <template v-if="typeof name === 'string' && name.startsWith('search-right-')">
@@ -558,7 +573,6 @@ defineExpose({
558
573
  :button-permissions="buttonPermissions"
559
574
  :scan-options="scanOptions"
560
575
  @on-refresh="onRefresh"
561
- @add-option="addOption"
562
576
  />
563
577
  </VanCol>
564
578
  </VanRow>
@@ -953,6 +967,32 @@ defineExpose({
953
967
  position: sticky;
954
968
  top: 0;
955
969
  z-index: 10;
970
+ .add-col {
971
+ display: flex;
972
+ align-items: center;
973
+ margin: 0 4px;
974
+ .add-action-btn {
975
+ width: 40px;
976
+ height: 40px;
977
+ border-radius: 10px;
978
+ background-color: var(--van-background);
979
+ color: #888;
980
+ border: 1px solid rgba(0, 0, 0, 0.06);
981
+ display: flex;
982
+ align-items: center;
983
+ justify-content: center;
984
+ font-size: 22px;
985
+ padding: 0;
986
+ transition: all 0.2s;
987
+ &:hover,
988
+ &:active {
989
+ opacity: 0.85;
990
+ background-color: rgba(25, 137, 250, 0.08);
991
+ border-color: var(--van-primary-color);
992
+ color: var(--van-primary-color);
993
+ }
994
+ }
995
+ }
956
996
  :deep(.van-search) {
957
997
  width: 100%;
958
998
  padding: var(--van-search-padding);
@@ -12,7 +12,7 @@ import {
12
12
  Icon as VanIcon,
13
13
  Row as VanRow,
14
14
  } from 'vant'
15
- import { computed, defineEmits, defineModel, defineProps, onMounted, ref } from 'vue'
15
+ import { computed, defineEmits, defineModel, defineProps, nextTick, onMounted, ref } from 'vue'
16
16
  import QrScanner from './QrScanner/index.vue'
17
17
  import VpnRecognition from './VpnRecognition/index.vue'
18
18
 
@@ -91,6 +91,9 @@ const colFieldNames = {
91
91
  // 查询条件参数 !!!!!建议最后点击确认的时候完成这个的整理
92
92
  const conditionParams = ref({})
93
93
 
94
+ // 用于存储每个 XFormItem 的 ref
95
+ const formItemRefs = ref([])
96
+
94
97
  // 视图模式 - 当前选中的扫描模式
95
98
  const scanType = ref(getDefaultScanType())
96
99
 
@@ -234,6 +237,15 @@ function resetOption() {
234
237
  Object.keys(conditionParams.value).forEach((key) => {
235
238
  conditionParams.value[key] = undefined
236
239
  })
240
+ // 关键:调用所有 XFormItem 的 reset 方法
241
+ nextTick(() => {
242
+ formItemRefs.value.forEach((refItem) => {
243
+ if (refItem && typeof refItem.reset === 'function') {
244
+ refItem.reset()
245
+ }
246
+ })
247
+ })
248
+ console.log('conditionParams.value重置', conditionParams.value)
237
249
  }
238
250
  function checkOrderOption() {
239
251
  let isCheck = true
@@ -251,22 +263,81 @@ function checkOrderOption() {
251
263
  }
252
264
  return isCheck
253
265
  }
266
+ // 清理查询条件参数,删除无效内容
267
+ function cleanConditionParams(params) {
268
+ const cleanedParams = {}
269
+
270
+ for (const key of Object.keys(params)) {
271
+ const value = params[key]
272
+
273
+ // 跳过无效值
274
+ if (value === null || value === undefined || value === '') {
275
+ continue
276
+ }
277
+
278
+ // 处理数组
279
+ if (Array.isArray(value)) {
280
+ // 过滤掉空字符串、null、undefined
281
+ const filteredArray = value.filter(item =>
282
+ item !== null && item !== undefined && item !== '',
283
+ )
284
+ // 只有当数组不为空时才添加
285
+ if (filteredArray.length > 0) {
286
+ cleanedParams[key] = filteredArray
287
+ }
288
+ continue
289
+ }
290
+
291
+ // 处理对象
292
+ if (typeof value === 'object' && value !== null) {
293
+ // 检查对象是否为空
294
+ const objectKeys = Object.keys(value)
295
+ if (objectKeys.length === 0) {
296
+ continue
297
+ }
298
+
299
+ // 递归清理对象内部
300
+ const cleanedObject = {}
301
+ let hasValidValue = false
302
+
303
+ for (const objKey of objectKeys) {
304
+ const objValue = value[objKey]
305
+ if (objValue !== null && objValue !== undefined && objValue !== '') {
306
+ cleanedObject[objKey] = objValue
307
+ hasValidValue = true
308
+ }
309
+ }
310
+
311
+ // 只有当对象有有效值时才添加
312
+ if (hasValidValue) {
313
+ cleanedParams[key] = cleanedObject
314
+ }
315
+ continue
316
+ }
317
+
318
+ // 处理基本类型
319
+ if (value !== null && value !== undefined && value !== '') {
320
+ cleanedParams[key] = value
321
+ }
322
+ }
323
+
324
+ return cleanedParams
325
+ }
326
+
254
327
  // 确认筛选条件
255
328
  function confirmOption() {
256
329
  const isCheck = checkOrderOption()
257
330
  if (isCheck) {
258
- const isEmptyObject = Object.keys(conditionParams.value).length === 0
259
- emit('update:conditionParams', isEmptyObject ? undefined : conditionParams.value)
331
+ // 清理查询条件参数
332
+ const cleanedParams = cleanConditionParams(conditionParams.value)
333
+
334
+ const isEmptyObject = Object.keys(cleanedParams).length === 0
335
+ emit('update:conditionParams', isEmptyObject ? undefined : cleanedParams)
260
336
  emit('onRefresh', {})
261
337
  listFilterMenu.value.close(false)
262
338
  }
263
339
  }
264
340
 
265
- // 新增按钮
266
- function addOption() {
267
- emit('addOption')
268
- }
269
-
270
341
  // 处理筛选菜单显示
271
342
  function handleFilterMenuChange(value: boolean) {
272
343
  filterMenuShow.value = value
@@ -387,20 +458,20 @@ function filterButtonPermissions(btn) {
387
458
  </div>
388
459
  </VanCol> -->
389
460
  </VanRow>
390
- <XFormItem v-model="conditionParams[item.model]" :form="conditionParams" :attr="item" :service-name="props.serviceName" :show-label="false" />
461
+ <XFormItem
462
+ ref="formItemRefs"
463
+ v-model="conditionParams[item.model]"
464
+ :form="conditionParams"
465
+ :attr="item"
466
+ :service-name="props.serviceName"
467
+ :show-label="false"
468
+ />
391
469
  </template>
392
470
  </div>
393
471
  <div class="operations-panel">
394
472
  <VanButton type="default" @click="resetOption">
395
473
  重置
396
474
  </VanButton>
397
- <VanButton
398
- v-if="props.buttonState.add && props.buttonState.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr)))))"
399
- type="primary"
400
- @click="addOption"
401
- >
402
- 新增
403
- </VanButton>
404
475
  <VanButton type="primary" @click="confirmOption">
405
476
  查询
406
477
  </VanButton>