af-mobile-client-vue3 1.3.96 → 1.3.97

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.96",
4
+ "version": "1.3.97",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -34,6 +34,14 @@ const props = defineProps({
34
34
  type: Boolean,
35
35
  default: true,
36
36
  },
37
+ lazyLoad: { // 是否启用懒加载
38
+ type: String,
39
+ default: 'false',
40
+ },
41
+ onSearch: { // 懒加载时的搜索函数
42
+ type: Function,
43
+ default: null,
44
+ },
37
45
  })
38
46
  const emits = defineEmits(['input', 'confirm', 'change', 'cancel'])
39
47
  const show = ref(false)
@@ -44,7 +52,24 @@ const checkedAll = ref(false)
44
52
  const resultValue = defineModel()
45
53
  const checkboxGroup = ref()
46
54
  const checkboxes = ref()
55
+ const isLoading = ref(false)
47
56
  function search(val) {
57
+ // 如果是懒加载模式
58
+ if (props.lazyLoad && props.lazyLoad === 'true' && props.onSearch) {
59
+ isLoading.value = true
60
+ // 调用父组件传递的搜索函数
61
+ props.onSearch(val).then((results) => {
62
+ columnsData.value = results || []
63
+ isLoading.value = false
64
+ }).catch((error) => {
65
+ console.error('懒加载搜索失败:', error)
66
+ columnsData.value = []
67
+ isLoading.value = false
68
+ })
69
+ return
70
+ }
71
+
72
+ // 普通搜索模式
48
73
  if (val) {
49
74
  columnsData.value = props.columns.filter((item) => {
50
75
  return item[props.option.text].includes(val)
@@ -140,7 +165,12 @@ const resultLabel = computed(() => {
140
165
  @update:model-value="search"
141
166
  @cancel="search"
142
167
  />
143
- <div class="x-multi-select-checkbox-group">
168
+ <div v-if="isLoading" class="loading-container">
169
+ <div class="loading-text">
170
+ 搜索中...
171
+ </div>
172
+ </div>
173
+ <div v-else class="x-multi-select-checkbox-group">
144
174
  <VanCell title="全选">
145
175
  <template #right-icon>
146
176
  <VanCheckbox v-model="checkedAll" name="all" @click="toggleAll" />
@@ -185,6 +215,16 @@ const resultLabel = computed(() => {
185
215
  padding: 0 16px;
186
216
  }
187
217
  }
218
+ .loading-container {
219
+ display: flex;
220
+ justify-content: center;
221
+ align-items: center;
222
+ height: 200px;
223
+ }
224
+ .loading-text {
225
+ color: #969799;
226
+ font-size: 14px;
227
+ }
188
228
  //.van-popup {
189
229
  // border-radius: 20px 20px 0 0;
190
230
  //}
@@ -1,148 +1,238 @@
1
- <script setup lang="ts">
2
- import {
3
- Field as VanField,
4
- Picker as VanPicker,
5
- Popup as VanPopup,
6
- } from 'vant'
7
- import { computed, defineEmits, defineModel, defineProps, onBeforeMount, ref, watch } from 'vue'
8
-
9
- const props = defineProps({
10
- columns: {
11
- type: Array,
12
- default() {
13
- return []
14
- },
15
- },
16
- option: {
17
- type: Object,
18
- default() {
19
- return { text: 'label', value: 'value', children: 'children' }
20
- },
21
- },
22
- isSearch: {
23
- type: Boolean,
24
- default: false,
25
- },
26
- offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
27
- type: Boolean,
28
- default: false,
29
- },
30
- border: { // 是否展示边框
31
- type: Boolean,
32
- default: false,
33
- },
34
- })
35
- const emits = defineEmits(['confirm', 'change', 'cancel', 'input'])
36
- const show = ref(false)
37
- const searchVal = ref('')
38
- const resultValue = defineModel()
39
- const columnsData = ref([])
40
- const selectedOption = ref([])
41
-
42
- // 转换空children为空字符串
43
- function transformColumns(data) {
44
- return data.map((item) => {
45
- if (item.children && item.children.length === 0) {
46
- return {
47
- ...item,
48
- children: '',
49
- }
50
- }
51
- else if (item.children && item.children.length !== 0) {
52
- return { ...item, children: transformColumns(item.children) }
53
- }
54
- else {
55
- return { ...item }
56
- }
57
- })
58
- }
59
-
60
- onBeforeMount(() => {
61
- columnsData.value = transformColumns(props.columns)
62
- })
63
-
64
- const resultLabel = computed({
65
- get() {
66
- if (!resultValue.value)
67
- return ''
68
- const res = props.columns.filter((item) => {
69
- const data = props.offOption ? item : item[props.option.value]
70
- return data === resultValue.value
71
- })
72
- return res.length ? (props.offOption ? res[0] : res[0][props.option.text]) : ''
73
- },
74
- set() {
75
-
76
- },
77
- })
78
-
79
- function search(val) {
80
- if (val) {
81
- columnsData.value = transformColumns(columnsData.value).filter((item) => {
82
- const data = props.offOption ? item : item[props.option.text]
83
- return data.includes(val)
84
- })
85
- }
86
- else {
87
- columnsData.value = transformColumns(props.columns)
88
- }
89
- }
90
-
91
- function onConfirm(value, _index) {
92
- resultValue.value = props.offOption ? value.selectedValues : value.selectedValues[0]
93
- // resultValue.value = value.selectedValues
94
- selectedOption.value = value.selectedOptions
95
- show.value = !show.value
96
- emits('confirm', value.selectedValues[0], value.selectedOptions)
97
- }
98
-
99
- function change(val, index) {
100
- emits('change', val, index, resultValue.value)
101
- }
102
-
103
- function cancel(val, index) {
104
- show.value = !show.value
105
- emits('cancel', val, index, resultValue.value)
106
- }
107
-
108
- function showPopu(disabled) {
109
- if (disabled !== undefined && disabled !== false)
110
- return false
111
- columnsData.value = transformColumns(props.columns)
112
- show.value = !show.value
113
- }
114
-
115
- watch(() => resultValue, (newVal, _oldVal) => {
116
- searchVal.value = ''
117
- columnsData.value = transformColumns(props.columns)
118
- emits('input', newVal)
119
- })
120
- </script>
121
-
122
- <template>
123
- <VanField
124
- v-model="resultLabel"
125
- v-bind="$attrs"
126
- readonly
127
- :is-link="true"
128
- :border="props.border"
129
- @click="showPopu($attrs.readonly)"
130
- />
131
- <VanPopup v-model:show="show" position="bottom">
132
- <VanField v-if="props.isSearch" v-model="searchVal" input-align="left" placeholder="搜索" @input="search" />
133
- <VanPicker
134
- v-bind="$attrs"
135
- :columns="columnsData"
136
- :columns-field-names="props.option"
137
- show-toolbar
138
- :value-key="props.option.text"
139
- @cancel="cancel"
140
- @confirm="onConfirm"
141
- @change="change"
142
- />
143
- </VanPopup>
144
- </template>
145
-
146
- <style scoped>
147
- /* 样式定义 */
148
- </style>
1
+ <script setup lang="ts">
2
+ import {
3
+ Field as VanField,
4
+ Picker as VanPicker,
5
+ Popup as VanPopup,
6
+ Search as VanSearch,
7
+ } from 'vant'
8
+ import { computed, defineEmits, defineModel, defineProps, onBeforeMount, ref, watch } from 'vue'
9
+
10
+ const props = defineProps({
11
+ columns: {
12
+ type: Array,
13
+ default() {
14
+ return []
15
+ },
16
+ },
17
+ option: {
18
+ type: Object,
19
+ default() {
20
+ return { text: 'label', value: 'value', children: 'children' }
21
+ },
22
+ },
23
+ offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
24
+ type: Boolean,
25
+ default: false,
26
+ },
27
+ border: { // 是否展示边框
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+ lazyLoad: { // 是否启用懒加载
32
+ type: String,
33
+ default: 'false',
34
+ },
35
+ onSearch: { // 懒加载时的搜索函数
36
+ type: Function,
37
+ default: null,
38
+ },
39
+ })
40
+ const emits = defineEmits(['confirm', 'change', 'cancel', 'input'])
41
+ const show = ref(false)
42
+ const resultValue = defineModel()
43
+ const columnsData = ref([])
44
+ const selectedOption = ref([])
45
+ const searchValue = ref('')
46
+ const filteredColumns = ref([])
47
+ const isLoading = ref(false)
48
+
49
+ // 转换空children为空字符串
50
+ function transformColumns(data) {
51
+ return data.map((item) => {
52
+ if (item.children && item.children.length === 0) {
53
+ return {
54
+ ...item,
55
+ children: '',
56
+ }
57
+ }
58
+ else if (item.children && item.children.length !== 0) {
59
+ return { ...item, children: transformColumns(item.children) }
60
+ }
61
+ else {
62
+ return { ...item }
63
+ }
64
+ })
65
+ }
66
+
67
+ // 搜索过滤函数
68
+ function handleSearch(value) {
69
+ // 确保 value 是字符串
70
+ const searchText = typeof value === 'string' ? value : value?.target?.value || ''
71
+ searchValue.value = searchText
72
+
73
+ // 如果是懒加载模式
74
+ if (props.lazyLoad && props.lazyLoad === 'true' && props.onSearch) {
75
+ if (!searchText) {
76
+ filteredColumns.value = []
77
+ return
78
+ }
79
+
80
+ isLoading.value = true
81
+ // 调用父组件传递的搜索函数
82
+ props.onSearch(searchText).then((results) => {
83
+ filteredColumns.value = transformColumns(results || [])
84
+ isLoading.value = false
85
+ }).catch((error) => {
86
+ console.error('懒加载搜索失败:', error)
87
+ filteredColumns.value = []
88
+ isLoading.value = false
89
+ })
90
+ return
91
+ }
92
+
93
+ // 普通搜索模式
94
+ if (!searchText) {
95
+ filteredColumns.value = columnsData.value
96
+ return
97
+ }
98
+
99
+ const searchTextLower = searchText.toLowerCase()
100
+
101
+ filteredColumns.value = columnsData.value.filter((item) => {
102
+ const text = props.offOption ? item : item[props.option.text]
103
+ return text?.toString().toLowerCase().includes(searchTextLower)
104
+ })
105
+ }
106
+
107
+ // 清空搜索
108
+ function handleSearchClear() {
109
+ searchValue.value = ''
110
+ if (props.lazyLoad) {
111
+ // 懒加载模式:清空搜索时重新获取初始数据
112
+ if (props.onSearch) {
113
+ props.onSearch('').then((results) => {
114
+ filteredColumns.value = transformColumns(results || [])
115
+ }).catch((error) => {
116
+ console.error('清空搜索后数据加载失败:', error)
117
+ filteredColumns.value = []
118
+ })
119
+ }
120
+ }
121
+ else {
122
+ filteredColumns.value = columnsData.value
123
+ }
124
+ }
125
+
126
+ onBeforeMount(() => {
127
+ columnsData.value = transformColumns(props.columns)
128
+ filteredColumns.value = columnsData.value
129
+ })
130
+
131
+ const resultLabel = computed({
132
+ get() {
133
+ if (!resultValue.value)
134
+ return ''
135
+ const res = props.columns.filter((item) => {
136
+ const data = props.offOption ? item : item[props.option.value]
137
+ return data === resultValue.value
138
+ })
139
+ return res.length ? (props.offOption ? res[0] : res[0][props.option.text]) : ''
140
+ },
141
+ set() {
142
+
143
+ },
144
+ })
145
+
146
+ function onConfirm(value, _index) {
147
+ resultValue.value = props.offOption ? value.selectedValues : value.selectedValues[0]
148
+ // resultValue.value = value.selectedValues
149
+ selectedOption.value = value.selectedOptions
150
+ show.value = !show.value
151
+ emits('confirm', value.selectedValues[0], value.selectedOptions)
152
+ }
153
+
154
+ function change(val, index) {
155
+ emits('change', val, index, resultValue.value)
156
+ }
157
+
158
+ function cancel(val, index) {
159
+ show.value = !show.value
160
+ emits('cancel', val, index, resultValue.value)
161
+ }
162
+
163
+ function showPopu(disabled) {
164
+ if (disabled !== undefined && disabled !== false)
165
+ return false
166
+ columnsData.value = transformColumns(props.columns)
167
+ filteredColumns.value = columnsData.value
168
+ searchValue.value = '' // 重置搜索
169
+ show.value = !show.value
170
+ }
171
+
172
+ watch(() => resultValue, (newVal, _oldVal) => {
173
+ columnsData.value = transformColumns(props.columns)
174
+ filteredColumns.value = columnsData.value
175
+ emits('input', newVal)
176
+ })
177
+
178
+ // 监听 columns 变化
179
+ watch(() => props.columns, () => {
180
+ columnsData.value = transformColumns(props.columns)
181
+ filteredColumns.value = columnsData.value
182
+ searchValue.value = ''
183
+ }, { deep: true })
184
+ </script>
185
+
186
+ <template>
187
+ <VanField
188
+ v-model="resultLabel"
189
+ v-bind="$attrs"
190
+ readonly
191
+ :is-link="true"
192
+ :border="props.border"
193
+ @click="showPopu($attrs.readonly)"
194
+ />
195
+ <VanPopup v-model:show="show" position="bottom">
196
+ <div class="x-select-popup">
197
+ <!-- 搜索框 -->
198
+ <VanSearch
199
+ v-model="searchValue"
200
+ placeholder="搜索"
201
+ @clear="handleSearchClear"
202
+ @input="handleSearch"
203
+ />
204
+
205
+ <!-- 选择器 -->
206
+ <div v-if="isLoading" class="loading-container">
207
+ <div class="loading-text">
208
+ 搜索中...
209
+ </div>
210
+ </div>
211
+ <VanPicker
212
+ v-else
213
+ v-bind="$attrs"
214
+ :columns="filteredColumns"
215
+ :columns-field-names="props.option"
216
+ show-toolbar
217
+ :value-key="props.option.text"
218
+ @cancel="cancel"
219
+ @confirm="onConfirm"
220
+ @change="change"
221
+ />
222
+ </div>
223
+ </VanPopup>
224
+ </template>
225
+
226
+ <style scoped>
227
+ .loading-container {
228
+ display: flex;
229
+ justify-content: center;
230
+ align-items: center;
231
+ height: 200px;
232
+ }
233
+
234
+ .loading-text {
235
+ color: #969799;
236
+ font-size: 14px;
237
+ }
238
+ </style>
@@ -169,6 +169,8 @@ const showArea = ref(false)
169
169
  const errorMessage = ref('')
170
170
  const showTreeSelect = ref(false)
171
171
  const treeValue = ref('')
172
+ // 懒加载 最后检索版本
173
+ let lastFetchId = ref(0)
172
174
 
173
175
  // 登录信息 (可以在配置的动态函数中使用 this.setupState 获取到当前组件内的全部函数和变量 例:this.setupState.userState)
174
176
  const userState = useUserStore().getLogin()
@@ -917,6 +919,106 @@ function findOptionInTree(options, value) {
917
919
  return null
918
920
  }
919
921
 
922
+ // 懒加载搜索函数
923
+ async function handleLazySearch(searchText: string) {
924
+ if (!attr.keyName || typeof attr.keyName !== 'string') {
925
+ return []
926
+ }
927
+
928
+ try {
929
+ // 如果是 search@ 类型的数据源
930
+ if (attr.keyName.includes('search@')) {
931
+ let source = attr.keyName.substring(7)
932
+ const userid = currUser.value
933
+ let roleName = 'roleName'
934
+
935
+ if (source.startsWith('根据角色[') && source.endsWith(']获取人员')) {
936
+ const startIndex = source.indexOf('[') + 1
937
+ const endIndex = source.indexOf(']', startIndex)
938
+ roleName = source.substring(startIndex, endIndex)
939
+ source = '根据角色获取人员'
940
+ }
941
+
942
+ const searchData = {
943
+ source,
944
+ userid,
945
+ roleName,
946
+ searchText, // 添加搜索关键词
947
+ }
948
+
949
+ return new Promise((resolve) => {
950
+ if (attr.type === 'select' || attr.type === 'checkbox') {
951
+ searchToListOption(searchData, (res) => {
952
+ // 根据搜索关键词过滤结果
953
+ const filtered = res.filter((item) => {
954
+ const text = item[columnsField.value.text] || item.label || ''
955
+ return text.toString().toLowerCase().includes(searchText.toLowerCase())
956
+ })
957
+ resolve(filtered)
958
+ })
959
+ }
960
+ else {
961
+ searchToOption(searchData, (res) => {
962
+ const filtered = res.filter((item) => {
963
+ const text = item[columnsField.value.text] || item.label || ''
964
+ return text.toString().toLowerCase().includes(searchText.toLowerCase())
965
+ })
966
+ resolve(filtered)
967
+ })
968
+ }
969
+ })
970
+ }
971
+
972
+ // logic 数据源
973
+ if (attr.keyName.includes('logic@')) {
974
+ const logicName = attr.keyName.substring(6)
975
+ const value = { word: searchText, ...userInfo.value }
976
+
977
+ // 调用logic前设置参数
978
+ if (getDataParams && getDataParams[attr.model]) {
979
+ Object.assign(value, getDataParams[attr.model])
980
+ }
981
+
982
+ lastFetchId.value ++
983
+ const fetchId = this.lastFetchId
984
+ // 根据搜索关键词过滤结果
985
+ const results = await runLogic(logicName, value, serviceName) as any
986
+ if (fetchId !== lastFetchId.value) {
987
+ return
988
+ }
989
+ if (searchText) {
990
+ return results.filter((item) => {
991
+ const text = item[columnsField.value.text] || item.label || ''
992
+ return text.toString().toLowerCase().includes(searchText.toLowerCase())
993
+ })
994
+ }
995
+ return results
996
+ }
997
+
998
+ // 自定义 js 函数
999
+ if (attr.keyName.includes('async ') || attr.keyName.includes('function ')) {
1000
+ const results = await executeStrFunctionByContext(currInst, attr.keyName, [
1001
+ { ...props.form, searchText },
1002
+ runLogic,
1003
+ props.mode,
1004
+ getConfigByNameAsync,
1005
+ post,
1006
+ ])
1007
+ // 根据搜索关键词过滤结果
1008
+ return results.filter((item) => {
1009
+ const text = item[columnsField.value.text] || item.label || ''
1010
+ return text.toString().toLowerCase().includes(searchText.toLowerCase())
1011
+ })
1012
+ }
1013
+
1014
+ return []
1015
+ }
1016
+ catch (error) {
1017
+ console.error('懒加载搜索失败:', error)
1018
+ return []
1019
+ }
1020
+ }
1021
+
920
1022
  // 扫码/NFC
921
1023
  function scanCodeOrNfc(attr) {
922
1024
  if (attr.type === 'scanCode') {
@@ -1015,7 +1117,7 @@ function scanCodeOrNfc(attr) {
1015
1117
  </VanField>
1016
1118
  <!-- 下拉 -->
1017
1119
  <XMultiSelect
1018
- v-if="attr.showMode === 'select' && mode === '查询'"
1120
+ v-if="(!attr.showMode || attr.showMode === 'select') && mode === '查询'"
1019
1121
  v-model="modelData"
1020
1122
  :label="labelData"
1021
1123
  :readonly="readonly"
@@ -1025,6 +1127,8 @@ function scanCodeOrNfc(attr) {
1025
1127
  :option="attr.option ? attr.option : columnsField"
1026
1128
  :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1027
1129
  :required="attr.rule.required === 'true'"
1130
+ :lazy-load="attr.lazyLoad"
1131
+ :on-search="attr.lazyLoad ? handleLazySearch : null"
1028
1132
  />
1029
1133
  </template>
1030
1134
 
@@ -1329,6 +1433,8 @@ function scanCodeOrNfc(attr) {
1329
1433
  :option="attr.option ? attr.option : columnsField"
1330
1434
  :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1331
1435
  :required="attr.rule.required === 'true'"
1436
+ :lazy-load="attr.lazyLoad"
1437
+ :on-search="attr.lazyLoad ? handleLazySearch : null"
1332
1438
  />
1333
1439
 
1334
1440
  <!-- 文本区域 -->
@@ -12,92 +12,8 @@ const router = useRouter()
12
12
  // const idKey = ref('o_id')
13
13
 
14
14
  // 简易crud表单测试
15
- const configName = ref('temporarySecurityCheckCRUD')
16
- const serviceName = ref('af-safecheck')
17
-
18
- // 资源权限测试
19
- // const configName = ref('crud_sources_test')
20
- // const serviceName = ref('af-system')
21
-
22
- // 实际业务测试
23
- // const configName = ref('lngChargeAuditMobileCRUD')
24
- // const serviceName = ref('af-gaslink')
25
-
26
- // 跳转到详情页面
27
- // function toDetail(item) {
28
- // router.push({
29
- // name: 'XCellDetailView',
30
- // params: { id: item[idKey.value] }, // 如果使用命名路由,推荐使用路由参数而不是直接构建 URL
31
- // query: {
32
- // operName: item[operNameKey.value],
33
- // method:item[methodKey.value],
34
- // requestMethod:item[requestMethodKey.value],
35
- // operatorType:item[operatorTypeKey.value],
36
- // operUrl:item[operUrlKey.value],
37
- // operIp:item[operIpKey.value],
38
- // costTime:item[costTimeKey.value],
39
- // operTime:item[operTimeKey.value],
40
- //
41
- // title: item[titleKey.value],
42
- // businessType: item[businessTypeKey.value],
43
- // status:item[statusKey.value]
44
- // }
45
- // })
46
- // }
47
-
48
- // 跳转到表单——以表单组来渲染纯表单
49
- // function toDetail(item) {
50
- // router.push({
51
- // name: 'XFormGroupView',
52
- // query: {
53
- // id: item[idKey.value],
54
- // // id: item.rr_id,
55
- // // o_id: item.o_id,
56
- // },
57
- // })
58
- // }
59
-
60
- // 新增功能
61
- // function addOption(totalCount) {
62
- // router.push({
63
- // name: 'XFormView',
64
- // params: { id: totalCount, openid: totalCount },
65
- // query: {
66
- // configName: configName.value,
67
- // serviceName: serviceName.value,
68
- // mode: '新增',
69
- // },
70
- // })
71
- // }
72
-
73
- // 修改功能
74
- // function updateRow(result) {
75
- // router.push({
76
- // name: 'XFormView',
77
- // params: { id: result.o_id, openid: result.o_id },
78
- // query: {
79
- // configName: configName.value,
80
- // serviceName: serviceName.value,
81
- // mode: '修改',
82
- // },
83
- // })
84
- // }
85
-
86
- // 删除功能
87
- // function deleteRow(result) {
88
- // emit('deleteRow', result.o_id)
89
- // }
90
- // const fixQueryForm = ref({
91
- // f_operator_id: '487184754014158848',
92
- // })
93
- function phone(row: any) {
94
- console.log('>>>> phone')
95
- }
96
-
97
- function mapView() {
98
- // 进行地图查看
99
- console.log('等待你处理地图查看事件')
100
- }
15
+ const configName = ref('ceshiCRUD')
16
+ const serviceName = ref('af-linepatrol')
101
17
  </script>
102
18
 
103
19
  <template>
@@ -106,8 +22,6 @@ function mapView() {
106
22
  <XCellList
107
23
  :config-name="configName"
108
24
  :service-name="serviceName"
109
- @phone="phone"
110
- @map-marked="mapView"
111
25
  />
112
26
  </template>
113
27
  </NormalDataLayout>