af-mobile-client-vue3 1.2.19 → 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.19",
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>
@@ -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"
@@ -976,14 +977,15 @@ defineExpose({
976
977
  border-radius: 10px;
977
978
  background-color: var(--van-background);
978
979
  color: #888;
979
- border: 1px solid rgba(0,0,0,0.06);
980
+ border: 1px solid rgba(0, 0, 0, 0.06);
980
981
  display: flex;
981
982
  align-items: center;
982
983
  justify-content: center;
983
984
  font-size: 22px;
984
985
  padding: 0;
985
986
  transition: all 0.2s;
986
- &:hover, &:active {
987
+ &:hover,
988
+ &:active {
987
989
  opacity: 0.85;
988
990
  background-color: rgba(25, 137, 250, 0.08);
989
991
  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[]>([])
@@ -439,10 +439,15 @@ async function onSubmit() {
439
439
  async function validate() {
440
440
  await xFormRef.value?.validate()
441
441
  }
442
+ function emitFunc(func, data, value) {
443
+ emits(func, data, value)
444
+ emits('xFormItemEmitFunc', func, data, value)
445
+ }
446
+
442
447
  watch(() => props.formData, (_val) => {
443
448
  form.value = _val
444
449
  })
445
- defineExpose({ init, form, formGroupName, validate, asyncSubmit })
450
+ defineExpose({ init, form, formGroupName, validate, asyncSubmit, setForm })
446
451
  </script>
447
452
 
448
453
  <template>
@@ -460,6 +465,7 @@ defineExpose({ init, form, formGroupName, validate, asyncSubmit })
460
465
  :service-name="myServiceName"
461
466
  :get-data-params="myGetDataParams"
462
467
  @set-form="setForm"
468
+ @x-form-item-emit-func="emitFunc"
463
469
  />
464
470
  <slot name="extraFormItem" />
465
471
  </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,38 @@ function onTreeSelectFinish(value) {
1154
1159
  <!-- 文本输入框 -->
1155
1160
  <VanField
1156
1161
  v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1157
- v-model="modelData"
1158
1162
  :label="labelData"
1159
1163
  :label-align="labelAlign"
1160
1164
  :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1161
1165
  :type="attr.type as FieldType"
1162
- :readonly="readonly"
1166
+ :readonly="readonly || (attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|'))"
1163
1167
  :disabled="attr.disabled"
1164
1168
  :placeholder="placeholder"
1165
1169
  :error-message="errorMessage"
1166
1170
  :clearable="attr.clearable"
1167
1171
  :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1168
1172
  @blur="() => formTypeCheck(attr, modelData as string)"
1169
- />
1173
+ >
1174
+ <template #input>
1175
+ <input
1176
+ v-model="modelData"
1177
+ :readonly="readonly || (attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|'))"
1178
+ class="van-field__control"
1179
+ :placeholder="placeholder"
1180
+ style="flex: 1; min-width: 0;"
1181
+ >
1182
+ <VanButton
1183
+ v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1184
+ class="action-btn"
1185
+ round
1186
+ type="primary"
1187
+ size="small"
1188
+ @click="emitFunc(attr.inputOnAfterFunc, attr)"
1189
+ >
1190
+ {{ attr.inputOnAfterName }}
1191
+ </VanButton>
1192
+ </template>
1193
+ </VanField>
1170
1194
 
1171
1195
  <!-- 地址选择器 -->
1172
1196
  <VanField
@@ -1232,4 +1256,10 @@ function onTreeSelectFinish(value) {
1232
1256
  .date-picker-overlay {
1233
1257
  background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1234
1258
  }
1259
+ .action-btn {
1260
+ border-radius: 10px;
1261
+ margin-left: 8px;
1262
+ min-width: 4rem;
1263
+ max-width: 6rem;
1264
+ }
1235
1265
  </style>