wui-components-v2 1.1.57 → 1.1.59

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.
@@ -0,0 +1,602 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
3
+ import { enums, listData, pageConfig, pageKey } from '../../api/page'
4
+ import foldCard from '../fold-card/fold-card.vue'
5
+ import Search from '../search/search.vue'
6
+ import type { Config, Entities, Enums, FoldCardModel } from '../../type'
7
+ import { useGlobalToast } from '../../composables/useGlobalToast'
8
+ import listTopButtons from '../list-top-buttons/list-top-buttons.vue'
9
+
10
+ defineOptions({
11
+ name: 'WuiSelectPopup',
12
+ })
13
+
14
+ const props = withDefaults(
15
+ defineProps<{
16
+ modelValue: string | string[] // v-model 绑定值
17
+ sourceId: string // 数据源ID
18
+ title?: string // 标题(用于popup标题)
19
+ label?: string // 左侧标签文字(如"基地名称")
20
+ extControlType?: string // 选择模式:relselect(单选)、relselect-extdis等
21
+ placeholder?: string // 占位文本
22
+ foldCardModel?: FoldCardModel // 折叠卡片模式
23
+ readonly?: boolean // 只读模式,禁止打开弹窗
24
+ clearable?: boolean // 是否显示清空按钮
25
+ defaultSelectFirst?: boolean // 是否默认选择第一项
26
+ }>(),
27
+ {
28
+ title: '',
29
+ label: '',
30
+ extControlType: 'relselect',
31
+ placeholder: '请选择',
32
+ foldCardModel: 'simple',
33
+ readonly: false,
34
+ clearable: true,
35
+ defaultSelectFirst: false,
36
+ }
37
+ )
38
+
39
+ const emit = defineEmits<{
40
+ (e: 'update:modelValue', value: string | string[]): void
41
+ (e: 'confirm', value: string | string[]): void
42
+ (e: 'cancel'): void
43
+ (e: 'open'): void
44
+ (e: 'close'): void
45
+ }>()
46
+
47
+ const globalToast = useGlobalToast()
48
+ const Zpaging = ref<any>(null)
49
+ const config = ref<Config>({
50
+ columns: [],
51
+ primaryColumn: {
52
+ sourceId: '',
53
+ controlType: '',
54
+ mstrucId: '',
55
+ extControlType: '',
56
+ },
57
+ secondColumn: {
58
+ sourceId: '',
59
+ controlType: '',
60
+ mstrucId: '',
61
+ extControlType: '',
62
+ },
63
+ primaryCriteria: {
64
+ sourceId: '',
65
+ controlType: '',
66
+ mstrucId: '',
67
+ disabled: false,
68
+ defaultValue: '',
69
+ transDefaultValue: '',
70
+ required: false,
71
+ extControlType: '',
72
+ hidden: false,
73
+ },
74
+ criterias: [],
75
+ buttons: [],
76
+ id: '',
77
+ fields: [],
78
+ title: '',
79
+ type: '',
80
+ pointSourceId: '',
81
+ mstrucId: '',
82
+ relationNames: [],
83
+ readOnly: false,
84
+ displayConfig: [],
85
+ showType: 'table',
86
+ extShowConfig: {},
87
+ extDisplayConfig: {},
88
+ })
89
+ const datas = ref<Entities[]>([])
90
+ const searchData = ref('')
91
+ const enumColumn = ref<Enums>({})
92
+ const selectData = ref<Record<string, string>>({}) // 选择数据
93
+
94
+ // ★ 关键修改:使用独立的 ref 管理显示状态(参考 action-popup)
95
+ const show = ref(false)
96
+
97
+ // 格式化显示的选中值
98
+ const displayValue = computed(() => {
99
+ const values = Object.values(selectData.value)
100
+ if (!values.length) return ''
101
+
102
+ if (props.extControlType === 'relselect' || props.extControlType === 'relselect-extdis') {
103
+ return values[0]?.split('@R@')[1] || ''
104
+ }
105
+ return values.map(v => v.split('@R@')[1]).join(', ')
106
+ })
107
+
108
+ // 格式化选中的数据用于checkbox显示
109
+ const selectDataFormat = computed(() => {
110
+ const newdata: Record<string, string> = {}
111
+ if (typeof selectData.value === 'object' && selectData.value !== null) {
112
+ Object.values(selectData.value).forEach(item => {
113
+ newdata[item.split('@R@')[0]] = item.split('@R@')[0]
114
+ })
115
+ }
116
+ return newdata
117
+ })
118
+
119
+ // 监听外部modelValue变化,同步到内部选择数据
120
+ watch(
121
+ () => props.modelValue,
122
+ val => {
123
+ if (!val || (Array.isArray(val) && !val.length)) {
124
+ selectData.value = {}
125
+ return
126
+ }
127
+
128
+ const newData: Record<string, string> = {}
129
+ if (Array.isArray(val)) {
130
+ val.forEach(item => {
131
+ if (item && typeof item === 'string') {
132
+ const code = item.split('@R@')[0]
133
+ if (code) newData[code] = item
134
+ }
135
+ })
136
+ } else if (typeof val === 'string' && val) {
137
+ const code = val.split('@R@')[0]
138
+ if (code) newData[code] = val
139
+ }
140
+ selectData.value = newData
141
+ },
142
+ { immediate: true }
143
+ )
144
+
145
+ // 显示地址信息
146
+ const showAddressInfo = computed(() => {
147
+ return props.extControlType === 'relselect-extdis'
148
+ })
149
+
150
+ // checkbox类型
151
+ const checkboxType = computed(() => {
152
+ return ['relselect', 'relselect-extdis'].includes(props.extControlType) ? '' : 'square'
153
+ })
154
+
155
+ // 格式化地址
156
+ const formatAddress = (value: string) => {
157
+ return (value?.split(',') || [])[2] || ''
158
+ }
159
+
160
+ // 标记是否已获取配置
161
+ const isConfigLoaded = ref(false)
162
+ // ★ 防止重复加载
163
+ const isLoading = ref(false)
164
+
165
+ // ★ 是否已完成默认首项选中
166
+ const hasDefaultSelected = ref(false)
167
+
168
+ // ★ 默认选择第一项:数据加载完成后自动选中第一条并回显
169
+ watch(datas, val => {
170
+ if (!props.defaultSelectFirst || !val?.length || hasDefaultSelected.value || Object.keys(selectData.value).length > 0)
171
+ return
172
+ const first = val[0]
173
+ selectData.value[first.code] = `${first.code}@R@${first.fieldMap[config.value.primaryColumn.sourceId]}`
174
+ const result =
175
+ props.extControlType === 'relselect' || props.extControlType === 'relselect-extdis'
176
+ ? selectData.value[first.code]
177
+ : Object.values(selectData.value)
178
+ emit('update:modelValue', result)
179
+ hasDefaultSelected.value = true
180
+ })
181
+
182
+ // 获取页面配置
183
+ async function getPageConfig() {
184
+ if (isConfigLoaded.value && config.value.id) return // 避免重复请求
185
+
186
+ try {
187
+ const res = await pageConfig(props.sourceId)
188
+ if (res.ltmplConfig) {
189
+ config.value = res.ltmplConfig
190
+ isConfigLoaded.value = true
191
+ await getEnums()
192
+ console.log('[WuiSelectPopup] ✅ 配置加载完成', config.value.title)
193
+ }
194
+ } catch (error) {
195
+ console.error('[WuiSelectPopup] ❌ 配置加载失败:', error)
196
+ throw error
197
+ }
198
+ }
199
+
200
+ // 获取枚举数据
201
+ async function getEnums() {
202
+ try {
203
+ const params: string[] = []
204
+ config.value.criterias?.forEach((item: any) => {
205
+ if (item.controlType === 'select' || item.controlType === 'multiselect') {
206
+ params.push(`mstrucIds=${item.mstrucId}`)
207
+ }
208
+ })
209
+
210
+ if (!params.length) return
211
+ const res = await enums(params.join('&'))
212
+ enumColumn.value = res.enumMap || {}
213
+ } catch (error) {
214
+ console.log('error:', error)
215
+ }
216
+ }
217
+
218
+ // 查询列表数据(完整流程:config -> key -> data)
219
+ async function queryList(pageNo: number, pageSize: number) {
220
+ // ★ 首次加载时防重复(分页加载不拦截,pageNo > 1 时放行)
221
+ if (pageNo === 1 && isLoading.value) return
222
+ if (pageNo === 1) isLoading.value = true
223
+
224
+ try {
225
+ // 步骤1:确保配置已加载
226
+ if (!isConfigLoaded.value) {
227
+ console.log('[WuiSelectPopup] 正在加载配置...')
228
+ await getPageConfig()
229
+ console.log('[WuiSelectPopup] 配置已加载,开始查询数据')
230
+ }
231
+
232
+ // 步骤2:构建查询参数
233
+ const search = uni.getStorageSync('paramsData')?.changedData || {}
234
+ const newParams = {
235
+ thirdCriteria: search.relValueField3 || '',
236
+ secondCriteria: search.relValueField?.toString() || '',
237
+ }
238
+
239
+ const params = Object.entries(newParams)
240
+ .map(([key, value]) => `${key}=${value}`)
241
+ .join('&')
242
+ let qre = searchData.value ? `${searchData.value}&${params}` : params
243
+
244
+ if (config.value.defaultCriteriaValue) {
245
+ for (const key in config.value.defaultCriteriaValue) {
246
+ if (Object.prototype.hasOwnProperty.call(config.value.defaultCriteriaValue, key)) {
247
+ const element = config.value.defaultCriteriaValue[key]
248
+ if (!qre.includes(key)) {
249
+ qre = `${qre}&${key}=${element}`
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ // 步骤3:获取查询Key
256
+ console.log('[WuiSelectPopup] 请求 pageKey,sourceId:', props.sourceId)
257
+ const res = await pageKey(props.sourceId, '', qre)
258
+ if (!res?.key) {
259
+ throw new Error('未获取到查询Key')
260
+ }
261
+ console.log('[WuiSelectPopup] 获取到 key:', res.key)
262
+
263
+ // 步骤4:使用Key请求数据
264
+ const data = await listData(res.key, pageNo, pageSize)
265
+ console.log(`[WuiSelectPopup] 获取到数据,第${pageNo}页,共 ${data.entities?.length} 条`)
266
+ Zpaging.value.complete(data.entities)
267
+ } catch (error) {
268
+ console.error('[WuiSelectPopup] ❌ 数据加载失败:', error)
269
+ Zpaging.value.complete(false)
270
+ } finally {
271
+ if (pageNo === 1) isLoading.value = false
272
+ }
273
+ }
274
+
275
+ // 处理选项变化
276
+ function handleChange(item: Entities) {
277
+ if (selectData.value[item.code]) {
278
+ delete selectData.value[item.code]
279
+ return
280
+ }
281
+
282
+ // 单选模式清空已有选择
283
+ if (props.extControlType === 'relselect' || props.extControlType === 'relselect-extdis') {
284
+ selectData.value = {}
285
+ }
286
+
287
+ selectData.value[item.code] = `${item.code}@R@${item.fieldMap[config.value.primaryColumn.sourceId]}`
288
+ }
289
+
290
+ // 提交确认
291
+ function confirm() {
292
+ const keys = Object.keys(selectData.value)
293
+ if (!keys.length) {
294
+ globalToast.warning(`请选择${props.title}`)
295
+ return
296
+ }
297
+
298
+ const payload = Object.values(selectData.value)
299
+
300
+ // 单选返回字符串,多选返回数组
301
+ let result: string | string[]
302
+ if (props.extControlType === 'relselect' || props.extControlType === 'relselect-extdis') {
303
+ result = payload[0]
304
+ } else {
305
+ result = payload
306
+ }
307
+
308
+ emit('update:modelValue', result)
309
+ emit('confirm', result)
310
+ show.value = false
311
+ }
312
+
313
+ // 取消
314
+ function cancel() {
315
+ emit('cancel')
316
+ show.value = false
317
+ }
318
+
319
+ // 清空选择
320
+ function clear() {
321
+ selectData.value = {}
322
+ emit(
323
+ 'update:modelValue',
324
+ props.extControlType === 'relselect' || props.extControlType === 'relselect-extdis' ? '' : []
325
+ )
326
+ }
327
+
328
+ // 搜索提交
329
+ function submitSearch(data: any) {
330
+ searchData.value = data
331
+ Zpaging.value.reload()
332
+ }
333
+
334
+ // ★ 打开 popup - 直接操作内部 ref
335
+ function openPopup(e?: any) {
336
+ e?.stopPropagation?.()
337
+ if (props.readonly) return
338
+ console.log('[WuiSelectPopup] 🎯 点击触发打开 Popup')
339
+ show.value = true
340
+ emit('open')
341
+ }
342
+
343
+ // 打开popup时初始化数据
344
+ watch(show, async val => {
345
+ console.log('[WuiSelectPopup] 📢 show 状态变化:', val)
346
+ if (val) {
347
+ if (isLoading.value) return // ★ 防重复
348
+ isConfigLoaded.value = false
349
+ await nextTick()
350
+ if (Zpaging.value) {
351
+ Zpaging.value.reload()
352
+ } else {
353
+ setTimeout(() => {
354
+ Zpaging.value?.reload()
355
+ }, 300)
356
+ }
357
+ } else {
358
+ emit('close')
359
+ }
360
+ })
361
+
362
+ // ★ 监听 edit-page 返回后刷新数据(通过 sourceId 过滤,只响应自己的刷新)
363
+ function handleRefresh(refreshSourceId: string) {
364
+ if (refreshSourceId !== props.sourceId) return
365
+ console.log('[WuiSelectPopup] 🔄 收到刷新信号,重新加载数据')
366
+ if (show.value && Zpaging.value && !isLoading.value) {
367
+ isConfigLoaded.value = false
368
+ Zpaging.value.reload()
369
+ }
370
+ }
371
+
372
+ onMounted(() => {
373
+ uni.$on('selectPopup:refresh', handleRefresh)
374
+ })
375
+
376
+ onUnmounted(() => {
377
+ uni.$off('selectPopup:refresh', handleRefresh)
378
+ uni.removeStorageSync('paramsData')
379
+ })
380
+
381
+ // 暴露方法给父组件
382
+ defineExpose({
383
+ clear,
384
+ confirm,
385
+ cancel,
386
+ open: openPopup,
387
+ close: () => {
388
+ show.value = false
389
+ },
390
+ })
391
+ </script>
392
+
393
+ <template>
394
+ <view class="wui-select-popup-wrapper">
395
+ <!-- ★ 触发区域 - 原生表单样式(左标签 + 中内容 + 右箭头) -->
396
+ <view v-if="!show" class="trigger-cell" @click="openPopup">
397
+ <!-- 左侧标签 -->
398
+ <text v-if="label" class="cell-label">
399
+ {{ label }}
400
+ </text>
401
+
402
+ <!-- 中间内容区 -->
403
+ <view class="cell-content">
404
+ <text v-if="displayValue" class="cell-value">
405
+ {{ displayValue }}
406
+ </text>
407
+ <text v-else class="cell-placeholder">
408
+ {{ placeholder || `请选择${title || label}` }}
409
+ </text>
410
+ </view>
411
+
412
+ <!-- 右侧箭头 -->
413
+ <view class="cell-arrow">
414
+ <wd-icon v-if="clearable && displayValue" name="close-circle" size="16px" @click.stop="clear" />
415
+ <wd-icon name="right" v-else size="16px"></wd-icon>
416
+ </view>
417
+ </view>
418
+
419
+ <!-- ★ 底部弹出的Popup - 修复内容溢出 -->
420
+ <wd-popup
421
+ v-model="show"
422
+ position="bottom"
423
+ :z-index="999999"
424
+ custom-style="border-radius: 24rpx 24rpx 0 0; overflow: hidden;"
425
+ safe-area-inset-bottom
426
+ @close="
427
+ () => {
428
+ show = false
429
+ }
430
+ "
431
+ >
432
+ <view class="select-popup-container">
433
+ <!-- 标题栏 - 固定高度不参与滚动 -->
434
+ <view
435
+ class="popup-header flex items-center justify-between px-4 py-3 border-b border-gray-200 bg-white shrink-0"
436
+ >
437
+ <text class="text-base font-bold">选择{{ title }}</text>
438
+ <wd-icon name="close-circle" size="16px" @click="cancel" />
439
+ </view>
440
+
441
+ <!-- 列表内容区 - 占据剩余空间并可滚动 -->
442
+ <view class="popup-body">
443
+ <z-paging
444
+ ref="Zpaging"
445
+ v-model="datas"
446
+ :show-loading-more-when-reload="true"
447
+ :fixed="false"
448
+ @query="queryList"
449
+ >
450
+ <template #top>
451
+ <Search
452
+ :enum-column="enumColumn"
453
+ :criterias="config.criterias"
454
+ :primary-criteria="config.primaryCriteria"
455
+ @submit="submitSearch"
456
+ />
457
+ <view>
458
+ <listTopButtons :buttons="config.buttons" :source-id="sourceId" :page-title="pageTitle" />
459
+ </view>
460
+ <slot name="top" />
461
+ </template>
462
+
463
+ <foldCard
464
+ v-for="(item, index) in datas"
465
+ :key="item.code"
466
+ :index="index"
467
+ :data="item"
468
+ :columns="config.columns"
469
+ :primary-column="config.primaryColumn"
470
+ :second-column="config.secondColumn"
471
+ :model="foldCardModel"
472
+ :groups="config"
473
+ :source-id="sourceId"
474
+ @click="handleChange(item)"
475
+ >
476
+ <template #select>
477
+ <wd-checkbox
478
+ :true-value="`${item.code}`"
479
+ false-value=""
480
+ :model-value="selectDataFormat[item.code]"
481
+ :type="checkboxType"
482
+ />
483
+ </template>
484
+
485
+ <template #addressInfo v-if="showAddressInfo">
486
+ <div>{{ item.fieldMap[config.extDisplayConfig?.secondColumn?.sourceId] }}</div>
487
+ </template>
488
+ <template #detailAddress v-if="showAddressInfo">
489
+ <div>{{ formatAddress(item.fieldMap[config.extDisplayConfig?.cardShowCols?.[0]?.sourceId]) }}</div>
490
+ </template>
491
+ </foldCard>
492
+
493
+ <template #bottom>
494
+ <view class="popup-footer flex justify-center gap-4 p-4 bg-white border-t border-gray-200">
495
+ <wd-button plain custom-class="flex-1" @click="cancel">取消</wd-button>
496
+ <wd-button type="primary" custom-class="flex-1" @click="confirm">确定</wd-button>
497
+ </view>
498
+ </template>
499
+ </z-paging>
500
+ </view>
501
+ </view>
502
+ </wd-popup>
503
+ </view>
504
+ </template>
505
+
506
+ <style scoped lang="scss">
507
+ .wui-select-popup-wrapper {
508
+ width: 100%;
509
+ position: relative;
510
+ }
511
+
512
+ // ★ 触发区域 - 原生表单样式(类似微信picker)
513
+ .trigger-cell {
514
+ display: flex;
515
+ align-items: center;
516
+ justify-content: space-between;
517
+ background-color: #fff;
518
+ cursor: pointer;
519
+ position: relative;
520
+ transition: background-color 0.2s;
521
+
522
+ &:active {
523
+ background-color: #f5f7fa; // 点击反馈
524
+ }
525
+
526
+ // 左侧标签 - 固定宽度,不换行
527
+ .cell-label {
528
+ flex-shrink: 0;
529
+ color: #333;
530
+ margin-right: 24rpx;
531
+ white-space: nowrap;
532
+ }
533
+
534
+ // 中间内容区 - 占据剩余空间
535
+ .cell-content {
536
+ flex: 1;
537
+ min-width: 0; // 允许文本溢出省略
538
+
539
+ .cell-value {
540
+ color: #333;
541
+ text-align: left;
542
+ overflow: hidden;
543
+ text-overflow: ellipsis;
544
+ white-space: nowrap;
545
+ display: block;
546
+ }
547
+
548
+ .cell-placeholder {
549
+ color: #c0c4cc; // 灰色占位符
550
+ text-align: left;
551
+ }
552
+ }
553
+
554
+ // 右侧箭头
555
+ .cell-arrow {
556
+ flex-shrink: 0;
557
+ margin-left: 16rpx;
558
+ display: flex;
559
+ align-items: center;
560
+
561
+ .arrow-text {
562
+ color: #c0c4cc;
563
+ transform: scaleX(0.8); // 稍微压窄箭头
564
+ }
565
+ }
566
+ }
567
+
568
+ // Popup 容器 - 使用 flex 布局控制溢出
569
+ .select-popup-container {
570
+ display: flex;
571
+ flex-direction: column;
572
+ height: 75vh; // 固定高度
573
+ max-height: 80vh; // 最大高度
574
+ background-color: #fff;
575
+ overflow: hidden; // 防止内容溢出
576
+ }
577
+
578
+ // 标题栏
579
+ .popup-header {
580
+ flex-shrink: 0; // 不压缩
581
+ z-index: 10;
582
+ }
583
+
584
+ // 内容区域 - 可滚动
585
+ .popup-body {
586
+ flex: 1; // 占据剩余空间
587
+ overflow: hidden; // 超出隐藏,由 z-paging 管理滚动
588
+ min-height: 0; // ★ 关键:允许 flex 子元素收缩
589
+
590
+ // 让 z-paging 填满整个容器
591
+ :deep(.z-paging-content) {
592
+ height: 100% !important;
593
+ }
594
+ }
595
+
596
+ // 底部按钮区
597
+ .popup-footer {
598
+ flex-shrink: 0; // 不压缩
599
+ position: sticky;
600
+ bottom: 0;
601
+ }
602
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wui-components-v2",
3
- "version": "1.1.57",
3
+ "version": "1.1.59",
4
4
  "description": "wui 组件库",
5
5
  "author": "wgxshh",
6
6
  "license": "MIT",
@@ -142,7 +142,6 @@ ControlTypeSupportor.getControlType = function (fieldConfig: Fields, fieldValue?
142
142
  if (fieldValue && Array.isArray(fieldValue) && itemType === 'relselect' && fieldValue.length > 0 && fieldValue[0].includes('valid')) {
143
143
  itemType = 'file'
144
144
  }
145
- console.log('itemType', itemType)
146
145
  return itemType
147
146
  }
148
147