af-mobile-client-vue3 1.2.19 → 1.2.21

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.19",
4
+ "version": "1.2.21",
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>
@@ -200,10 +200,11 @@ function initComponent() {
200
200
  configContent.value = result
201
201
  // 扁平化 type=group 的 groupItems,保持顺序
202
202
  const flatFormJson = []
203
- result.formJson.forEach(item => {
203
+ result.formJson.forEach((item) => {
204
204
  if (item.type === 'group' && Array.isArray(item.groupItems)) {
205
205
  flatFormJson.push(...item.groupItems)
206
- } else {
206
+ }
207
+ else {
207
208
  flatFormJson.push(item)
208
209
  }
209
210
  })
@@ -542,7 +543,7 @@ defineExpose({
542
543
  />
543
544
  </VanCol>
544
545
  <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
545
- <VanCol class="add-col" 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)))) )">
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">
546
547
  <VanButton
547
548
  icon="plus"
548
549
  type="primary"
@@ -740,7 +741,6 @@ defineExpose({
740
741
  flex-direction: column;
741
742
  --van-search-padding: 3px;
742
743
  --van-dropdown-menu-title-padding: 3px;
743
- --van-cell-horizontal-padding: 0px;
744
744
  --van-text-color-2: rgb(75, 85, 99);
745
745
  --van-button-normal-font-size: 13px;
746
746
  .main {
@@ -976,14 +976,15 @@ defineExpose({
976
976
  border-radius: 10px;
977
977
  background-color: var(--van-background);
978
978
  color: #888;
979
- border: 1px solid rgba(0,0,0,0.06);
979
+ border: 1px solid rgba(0, 0, 0, 0.06);
980
980
  display: flex;
981
981
  align-items: center;
982
982
  justify-content: center;
983
983
  font-size: 22px;
984
984
  padding: 0;
985
985
  transition: all 0.2s;
986
- &:hover, &:active {
986
+ &:hover,
987
+ &:active {
987
988
  opacity: 0.85;
988
989
  background-color: rgba(25, 137, 250, 0.08);
989
990
  border-color: var(--van-primary-color);
@@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
53
53
  submitButton: true,
54
54
  isHandleFormKey: true,
55
55
  })
56
- const emits = defineEmits(['onSubmit'])
56
+ const emits = defineEmits(['onSubmit', 'xFormItemEmitFunc'])
57
57
  const userStore = useUserStore()
58
58
  const xFormRef = ref<FormInstance>()
59
59
  const myFormItems = ref<FormItem[]>([])
@@ -408,6 +408,10 @@ function prepareForm() {
408
408
  return cleanedForm
409
409
  }
410
410
 
411
+ function getFormData() {
412
+ return prepareForm()
413
+ }
414
+
411
415
  async function onSubmit() {
412
416
  await validate()
413
417
  // 清理表单数据
@@ -439,10 +443,15 @@ async function onSubmit() {
439
443
  async function validate() {
440
444
  await xFormRef.value?.validate()
441
445
  }
446
+ function emitFunc(func, data, value) {
447
+ emits(func, data, value)
448
+ emits('xFormItemEmitFunc', func, data, value)
449
+ }
450
+
442
451
  watch(() => props.formData, (_val) => {
443
452
  form.value = _val
444
453
  })
445
- defineExpose({ init, form, formGroupName, validate, asyncSubmit })
454
+ defineExpose({ init, form, formGroupName, validate, asyncSubmit, setForm, getFormData })
446
455
  </script>
447
456
 
448
457
  <template>
@@ -460,6 +469,7 @@ defineExpose({ init, form, formGroupName, validate, asyncSubmit })
460
469
  :service-name="myServiceName"
461
470
  :get-data-params="myGetDataParams"
462
471
  @set-form="setForm"
472
+ @x-form-item-emit-func="emitFunc"
463
473
  />
464
474
  <slot name="extraFormItem" />
465
475
  </VanCellGroup>
@@ -18,6 +18,7 @@ import dayjs from 'dayjs/esm/index'
18
18
  import { debounce } from 'lodash-es'
19
19
  import {
20
20
  Area as VanArea,
21
+ Button as VanButton,
21
22
  Calendar as VanCalendar,
22
23
  Cascader as VanCascader,
23
24
  Checkbox as VanCheckbox,
@@ -35,7 +36,7 @@ import {
35
36
  Switch as VanSwitch,
36
37
  TimePicker as VanTimePicker,
37
38
  } from 'vant'
38
- import { computed, defineEmits, defineModel, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
39
+ import { computed, defineEmits, defineModel, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
39
40
 
40
41
  const props = defineProps({
41
42
  attr: {
@@ -92,7 +93,7 @@ const props = defineProps({
92
93
 
93
94
  })
94
95
 
95
- const emits = defineEmits(['setForm'])
96
+ const emits = defineEmits(['setForm', 'xFormItemEmitFunc'])
96
97
 
97
98
  // 用 defineModel 替代 modelValue/emit
98
99
  const modelData = defineModel<string | number | boolean | any[] | Record<string, any>>()
@@ -748,6 +749,10 @@ function onTreeSelectFinish(value) {
748
749
  modelData.value = [value.value]
749
750
  showTreeSelect.value = false
750
751
  }
752
+
753
+ function emitFunc(func, data) {
754
+ emits('xFormItemEmitFunc', func, data, data?.model ? this.form[data.model] : this.form)
755
+ }
751
756
  </script>
752
757
 
753
758
  <template>
@@ -874,7 +879,7 @@ function onTreeSelectFinish(value) {
874
879
  :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
875
880
  >
876
881
  <template #input>
877
- <VanStepper v-model="modelData" :disabled="readonly" />
882
+ <VanStepper v-model="modelData as any" :disabled="readonly" />
878
883
  </template>
879
884
  </VanField>
880
885
 
@@ -1154,19 +1159,41 @@ function onTreeSelectFinish(value) {
1154
1159
  <!-- 文本输入框 -->
1155
1160
  <VanField
1156
1161
  v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1157
- v-model="modelData"
1162
+ v-model="(modelData as string)"
1158
1163
  :label="labelData"
1159
1164
  :label-align="labelAlign"
1160
1165
  :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1161
1166
  :type="attr.type as FieldType"
1162
- :readonly="readonly"
1167
+ :readonly="readonly || (attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|'))"
1163
1168
  :disabled="attr.disabled"
1164
1169
  :placeholder="placeholder"
1165
1170
  :error-message="errorMessage"
1166
1171
  :clearable="attr.clearable"
1167
1172
  :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1168
1173
  @blur="() => formTypeCheck(attr, modelData as string)"
1169
- />
1174
+ >
1175
+ <template #input>
1176
+ <input
1177
+ :value="modelData"
1178
+ :readonly="readonly || (attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|'))"
1179
+ class="van-field__control"
1180
+ :placeholder="placeholder"
1181
+ style="flex: 1; min-width: 0;"
1182
+ @input="e => modelData = (e.target as HTMLInputElement).value"
1183
+ @blur="() => formTypeCheck(attr, modelData as string)"
1184
+ >
1185
+ <VanButton
1186
+ v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1187
+ class="action-btn"
1188
+ round
1189
+ type="primary"
1190
+ size="small"
1191
+ @click="emitFunc(attr.inputOnAfterFunc, attr)"
1192
+ >
1193
+ {{ attr.inputOnAfterName }}
1194
+ </VanButton>
1195
+ </template>
1196
+ </VanField>
1170
1197
 
1171
1198
  <!-- 地址选择器 -->
1172
1199
  <VanField
@@ -1232,4 +1259,10 @@ function onTreeSelectFinish(value) {
1232
1259
  .date-picker-overlay {
1233
1260
  background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1234
1261
  }
1262
+ .action-btn {
1263
+ border-radius: 10px;
1264
+ margin-left: 8px;
1265
+ min-width: 4rem;
1266
+ max-width: 6rem;
1267
+ }
1235
1268
  </style>