tang-ui-x 1.2.4 → 1.2.6
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.
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<script setup lang="uts" >
|
|
2
2
|
import Icon from '../TIcon'
|
|
3
|
-
import
|
|
3
|
+
import { useI18n } from '../../composables/useI18n.uts'
|
|
4
|
+
import type { SelectOption, SelectProps, SingleSelectValue, MultipleSelectValue, CascadeSelectValue } from './type.uts'
|
|
4
5
|
|
|
5
6
|
// 双向绑定值 - 根据 multiple 属性动态类型
|
|
6
|
-
const model = defineModel<SingleSelectValue | MultipleSelectValue>({ default: null })
|
|
7
|
+
const model = defineModel<SingleSelectValue | MultipleSelectValue | CascadeSelectValue>({ default: null })
|
|
7
8
|
|
|
8
9
|
const props = withDefaults(defineProps<SelectProps>(), {
|
|
9
10
|
options: (): SelectOption[] => [],
|
|
10
|
-
placeholder: '
|
|
11
|
+
placeholder: '',
|
|
11
12
|
disabled: false,
|
|
12
13
|
readonly: false,
|
|
13
14
|
multiple: false,
|
|
@@ -19,7 +20,9 @@
|
|
|
19
20
|
placement: 'bottom',
|
|
20
21
|
size: 'medium',
|
|
21
22
|
align: 'left',
|
|
22
|
-
activeColor: '#1890ff'
|
|
23
|
+
activeColor: '#1890ff',
|
|
24
|
+
changeOnSelect: false,
|
|
25
|
+
separator: ' / '
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
const emit = defineEmits<{
|
|
@@ -27,10 +30,27 @@
|
|
|
27
30
|
clear : []
|
|
28
31
|
}>()
|
|
29
32
|
|
|
33
|
+
const { $t } = useI18n()
|
|
34
|
+
|
|
30
35
|
// ========== 状态管理 ==========
|
|
31
36
|
const showPopup = ref(false)
|
|
32
37
|
const isHover = ref(false)
|
|
33
38
|
|
|
39
|
+
// ========== 级联状态管理 ==========
|
|
40
|
+
/** 是否为级联模式:自动检测 options 中是否有 children */
|
|
41
|
+
const isCascade = computed(() => {
|
|
42
|
+
return props.options?.some(opt => opt.children != null && opt.children!.length > 0) ?? false
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/** 级联面板栈:每个元素是该层级的选项列表 */
|
|
46
|
+
const cascadePanels = ref<SelectOption[][]>([])
|
|
47
|
+
|
|
48
|
+
/** 级联各层级选中的选项 */
|
|
49
|
+
const cascadeSelectedOptions = ref<SelectOption[]>([])
|
|
50
|
+
|
|
51
|
+
/** 当前显示的面板层级索引(从0开始) */
|
|
52
|
+
const currentCascadeLevel = ref(0)
|
|
53
|
+
|
|
34
54
|
// ========== 计算属性 ==========
|
|
35
55
|
const selectHeight = computed(() => {
|
|
36
56
|
if (typeof props.height === 'number') return `${props.height}rpx`
|
|
@@ -39,14 +59,14 @@
|
|
|
39
59
|
|
|
40
60
|
// 获取选中的选项(单选)
|
|
41
61
|
const selectedOption = computed(() => {
|
|
42
|
-
if (props.multiple) return null
|
|
62
|
+
if (isCascade.value || props.multiple) return null
|
|
43
63
|
if (model.value == null) return null
|
|
44
64
|
return props.options?.find(opt => opt.value === model.value) ?? null
|
|
45
65
|
})
|
|
46
66
|
|
|
47
67
|
// 获取选中的选项(多选)
|
|
48
68
|
const selectedOptions = computed(() => {
|
|
49
|
-
if (!props.multiple) return []
|
|
69
|
+
if (isCascade.value || !props.multiple) return []
|
|
50
70
|
if (model.value == null) return []
|
|
51
71
|
const values = model.value as MultipleSelectValue
|
|
52
72
|
return props.options?.filter(opt => values.includes(opt.value)) ?? []
|
|
@@ -54,6 +74,13 @@
|
|
|
54
74
|
|
|
55
75
|
// 显示的标签
|
|
56
76
|
const selectedLabels = computed(() => {
|
|
77
|
+
// 级联模式
|
|
78
|
+
if (isCascade.value) {
|
|
79
|
+
const options = cascadeSelectedOptions.value
|
|
80
|
+
if (options.length === 0) return ''
|
|
81
|
+
return options.map(opt => opt.label).join(props.separator)
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
if (!props.multiple) {
|
|
58
85
|
return selectedOption.value?.label ?? ''
|
|
59
86
|
}
|
|
@@ -72,13 +99,17 @@
|
|
|
72
99
|
// 显示文本
|
|
73
100
|
const displayText = computed(() => {
|
|
74
101
|
const label = selectedLabels.value
|
|
75
|
-
return label || props.placeholder
|
|
102
|
+
return label || props.placeholder || $t('form.selectPlaceholder')
|
|
76
103
|
})
|
|
77
104
|
|
|
78
105
|
// 是否显示清空图标
|
|
79
106
|
const showClearIcon = computed(() => {
|
|
80
107
|
if (!props.showClear || props.disabled || props.readonly) return false
|
|
81
108
|
|
|
109
|
+
if (isCascade.value) {
|
|
110
|
+
return cascadeSelectedOptions.value.length > 0
|
|
111
|
+
}
|
|
112
|
+
|
|
82
113
|
if (props.multiple) {
|
|
83
114
|
return (model.value as MultipleSelectValue)?.length > 0
|
|
84
115
|
}
|
|
@@ -126,16 +157,72 @@
|
|
|
126
157
|
}
|
|
127
158
|
})
|
|
128
159
|
|
|
160
|
+
/** 当前级联面板显示的选项列表 */
|
|
161
|
+
const currentCascadeOptions = computed(() => {
|
|
162
|
+
if (cascadePanels.value.length === 0) return []
|
|
163
|
+
const level = currentCascadeLevel.value
|
|
164
|
+
if (level < cascadePanels.value.length) {
|
|
165
|
+
return cascadePanels.value[level]
|
|
166
|
+
}
|
|
167
|
+
return []
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
/** 面包屑导航数据(作为 Tabs 使用) */
|
|
171
|
+
const breadcrumbItems = computed(() => {
|
|
172
|
+
const items: string[] = []
|
|
173
|
+
// 面包屑的标签数对应面板的数量(最大为 cascadePanels.length)
|
|
174
|
+
for (let i = 0; i < cascadePanels.value.length; i++) {
|
|
175
|
+
if (i < cascadeSelectedOptions.value.length) {
|
|
176
|
+
items.push(cascadeSelectedOptions.value[i].label)
|
|
177
|
+
} else {
|
|
178
|
+
items.push($t('form.selectPlaceholder'))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return items
|
|
182
|
+
})
|
|
183
|
+
|
|
129
184
|
// ========== 方法 ==========
|
|
130
185
|
const openPicker = () => {
|
|
131
186
|
if (props.disabled || props.readonly) return
|
|
132
187
|
showPopup.value = true
|
|
188
|
+
|
|
189
|
+
// 级联模式:初始化面板栈
|
|
190
|
+
if (isCascade.value) {
|
|
191
|
+
initCascadePanels()
|
|
192
|
+
}
|
|
133
193
|
}
|
|
134
194
|
|
|
135
195
|
const closePicker = () => {
|
|
136
196
|
showPopup.value = false
|
|
137
197
|
}
|
|
138
198
|
|
|
199
|
+
/** 初始化级联面板:根据已选值恢复面板状态 */
|
|
200
|
+
const initCascadePanels = () => {
|
|
201
|
+
const panels: SelectOption[][] = [props.options ?? []]
|
|
202
|
+
const selectedOpts = cascadeSelectedOptions.value
|
|
203
|
+
|
|
204
|
+
// 如果有已选值,恢复各级面板
|
|
205
|
+
if (selectedOpts.length > 0) {
|
|
206
|
+
let currentOptions = props.options ?? []
|
|
207
|
+
for (let i = 0; i < selectedOpts.length; i++) {
|
|
208
|
+
const selectedValue = selectedOpts[i].value
|
|
209
|
+
const found = currentOptions.find(opt => opt.value === selectedValue)
|
|
210
|
+
if (found != null && found!.children != null && found!.children!.length > 0) {
|
|
211
|
+
panels.push(found!.children!)
|
|
212
|
+
currentOptions = found!.children!
|
|
213
|
+
} else {
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// 定位到最后一级面板
|
|
218
|
+
currentCascadeLevel.value = panels.length - 1
|
|
219
|
+
} else {
|
|
220
|
+
currentCascadeLevel.value = 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
cascadePanels.value = panels
|
|
224
|
+
}
|
|
225
|
+
|
|
139
226
|
// 检查选项是否被选中
|
|
140
227
|
const isOptionSelected = (option: SelectOption): boolean => {
|
|
141
228
|
if (props.multiple && model.value != null) {
|
|
@@ -145,6 +232,64 @@
|
|
|
145
232
|
return model.value === option.value
|
|
146
233
|
}
|
|
147
234
|
|
|
235
|
+
/** 检查级联选项在当前层级是否被选中 */
|
|
236
|
+
const isCascadeOptionSelected = (option: SelectOption, level: number): boolean => {
|
|
237
|
+
if (level < cascadeSelectedOptions.value.length) {
|
|
238
|
+
return cascadeSelectedOptions.value[level].value === option.value
|
|
239
|
+
}
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 级联选项点击处理 */
|
|
244
|
+
const handleCascadeSelect = (option: SelectOption, level: number) => {
|
|
245
|
+
if (option.disabled || props.disabled || props.readonly) {
|
|
246
|
+
uni.showToast({ title: '该选项不可选', icon: 'none' })
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 更新选中状态:截断当前层级之后的选择
|
|
251
|
+
const newSelectedOptions = cascadeSelectedOptions.value.slice(0, level)
|
|
252
|
+
newSelectedOptions.push(option)
|
|
253
|
+
cascadeSelectedOptions.value = newSelectedOptions
|
|
254
|
+
|
|
255
|
+
// 判断是否有子级
|
|
256
|
+
const hasChildren = option.children != null && option.children!.length > 0
|
|
257
|
+
|
|
258
|
+
if (hasChildren && level < 3) {
|
|
259
|
+
// 有子级且未到第四级(最多四级:0,1,2,3):进入下一级
|
|
260
|
+
const newPanels = cascadePanels.value.slice(0, level + 1)
|
|
261
|
+
newPanels.push(option.children!)
|
|
262
|
+
cascadePanels.value = newPanels
|
|
263
|
+
currentCascadeLevel.value = level + 1
|
|
264
|
+
|
|
265
|
+
// 如果 changeOnSelect 为 true,选中中间层级也更新值
|
|
266
|
+
if (props.changeOnSelect) {
|
|
267
|
+
updateCascadeModel()
|
|
268
|
+
emit('change', newSelectedOptions[newSelectedOptions.length - 1])
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// 叶子节点或已到第四级:完成选择
|
|
272
|
+
updateCascadeModel()
|
|
273
|
+
emit('change', option)
|
|
274
|
+
closePicker()
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** 更新级联模式的 v-model 值 */
|
|
279
|
+
const updateCascadeModel = () => {
|
|
280
|
+
const values = cascadeSelectedOptions.value.map(opt => opt.value)
|
|
281
|
+
model.value = values
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** 面包屑点击,返回指定层级 */
|
|
285
|
+
const goToCascadeLevel = (level: number) => {
|
|
286
|
+
if (level < 0) level = 0
|
|
287
|
+
currentCascadeLevel.value = level
|
|
288
|
+
// 截断面板栈和选中项到目标层级
|
|
289
|
+
cascadePanels.value = cascadePanels.value.slice(0, level + 1)
|
|
290
|
+
cascadeSelectedOptions.value = cascadeSelectedOptions.value.slice(0, level)
|
|
291
|
+
}
|
|
292
|
+
|
|
148
293
|
// 选择选项
|
|
149
294
|
const selectOption = (option : SelectOption) => {
|
|
150
295
|
if (option.disabled || props.disabled || props.readonly) {
|
|
@@ -180,6 +325,17 @@
|
|
|
180
325
|
const clearValue = () => {
|
|
181
326
|
if (props.disabled || props.readonly) return
|
|
182
327
|
|
|
328
|
+
if (isCascade.value) {
|
|
329
|
+
// 级联模式清空
|
|
330
|
+
cascadeSelectedOptions.value = []
|
|
331
|
+
cascadePanels.value = [props.options ?? []]
|
|
332
|
+
currentCascadeLevel.value = 0
|
|
333
|
+
model.value = null
|
|
334
|
+
emit('clear')
|
|
335
|
+
emit('change', null)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
183
339
|
if (props.multiple) {
|
|
184
340
|
model.value = []
|
|
185
341
|
emit('clear')
|
|
@@ -216,6 +372,37 @@
|
|
|
216
372
|
const handleDropdownClick = (e : Event) => {
|
|
217
373
|
e.stopPropagation()
|
|
218
374
|
}
|
|
375
|
+
|
|
376
|
+
// ========== 监听 v-model 外部变化,同步级联状态 ==========
|
|
377
|
+
watch(() => model.value, (newVal) => {
|
|
378
|
+
if (!isCascade.value) return
|
|
379
|
+
if (newVal == null || (Array.isArray(newVal) && (newVal as CascadeSelectValue).length === 0)) {
|
|
380
|
+
cascadeSelectedOptions.value = []
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 从 values 路径反查 options 树,恢复 cascadeSelectedOptions
|
|
385
|
+
if (Array.isArray(newVal)) {
|
|
386
|
+
const values = newVal as CascadeSelectValue
|
|
387
|
+
const selectedOpts: SelectOption[] = []
|
|
388
|
+
let currentOptions = props.options ?? []
|
|
389
|
+
|
|
390
|
+
for (let i = 0; i < values.length; i++) {
|
|
391
|
+
const found = currentOptions.find(opt => opt.value === values[i])
|
|
392
|
+
if (found != null) {
|
|
393
|
+
selectedOpts.push(found!)
|
|
394
|
+
if (found!.children != null && found!.children!.length > 0) {
|
|
395
|
+
currentOptions = found!.children!
|
|
396
|
+
} else {
|
|
397
|
+
break
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
break
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
cascadeSelectedOptions.value = selectedOpts
|
|
404
|
+
}
|
|
405
|
+
}, { immediate: true })
|
|
219
406
|
</script>
|
|
220
407
|
|
|
221
408
|
<template>
|
|
@@ -228,14 +415,16 @@
|
|
|
228
415
|
'select-disabled': disabled,
|
|
229
416
|
'select-readonly': readonly,
|
|
230
417
|
'select-active': showPopup,
|
|
231
|
-
'has-value':
|
|
418
|
+
'has-value': isCascade
|
|
419
|
+
? cascadeSelectedOptions.length > 0
|
|
420
|
+
: (props.multiple ? (model as MultipleSelectValue)?.length : model) != null
|
|
232
421
|
}
|
|
233
422
|
]" :style="{
|
|
234
423
|
height: selectHeight,
|
|
235
424
|
backgroundColor: disabled ? '#f5f5f5' : background,
|
|
236
425
|
borderColor: showPopup ? activeColor : borderColor
|
|
237
426
|
}" @click="openPicker">
|
|
238
|
-
<text class="select-text truncate" :class="displayText === placeholder ? 'placeholder-text' : 'value-text'">
|
|
427
|
+
<text class="select-text truncate" :class="displayText === (placeholder || $t('form.selectPlaceholder')) ? 'placeholder-text' : 'value-text'">
|
|
239
428
|
{{ displayText }}
|
|
240
429
|
</text>
|
|
241
430
|
|
|
@@ -260,44 +449,100 @@
|
|
|
260
449
|
|
|
261
450
|
<!-- 下拉选项列表 -->
|
|
262
451
|
<transition name="dropdown">
|
|
263
|
-
<view v-if="showPopup" class="dropdown-list select-root" :class="
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
452
|
+
<view v-if="showPopup" class="dropdown-list select-root" :class="[
|
|
453
|
+
`placement-${props.placement}`,
|
|
454
|
+
{ 'cascade-dropdown': isCascade }
|
|
455
|
+
]" :style="dropdownStyle" @tap="handleDropdownClick">
|
|
456
|
+
|
|
457
|
+
<!-- ====== 级联模式 ====== -->
|
|
458
|
+
<template v-if="isCascade">
|
|
459
|
+
<!-- 面包屑导航 -->
|
|
460
|
+
<view v-if="breadcrumbItems.length > 0" class="cascade-breadcrumb">
|
|
461
|
+
<template v-for="(item, idx) in breadcrumbItems" :key="idx">
|
|
462
|
+
<text v-if="idx > 0" class="cascade-breadcrumb-separator">/</text>
|
|
463
|
+
<view class="cascade-breadcrumb-item" @click.stop="goToCascadeLevel(idx)">
|
|
464
|
+
<text class="cascade-breadcrumb-text" :style="{ color: idx === currentCascadeLevel ? activeColor : '#606266' }">{{ item }}</text>
|
|
465
|
+
</view>
|
|
466
|
+
</template>
|
|
467
|
+
</view>
|
|
468
|
+
|
|
469
|
+
<!-- 级联面板 -->
|
|
470
|
+
<scroll-view class="dropdown-scroll cascade-panel" scroll-y :scroll-top="0">
|
|
471
|
+
<view v-for="option in currentCascadeOptions" :key="option.value"
|
|
472
|
+
class="dropdown-item select-root"
|
|
473
|
+
:class="[
|
|
474
|
+
alignClass,
|
|
475
|
+
{
|
|
476
|
+
'dropdown-item-selected': isCascadeOptionSelected(option, currentCascadeLevel),
|
|
477
|
+
'dropdown-item-disabled': option.disabled || disabled
|
|
478
|
+
}
|
|
479
|
+
]"
|
|
480
|
+
@click="handleCascadeSelect(option, currentCascadeLevel)">
|
|
481
|
+
|
|
482
|
+
<text class="dropdown-item-text"
|
|
483
|
+
:style="isCascadeOptionSelected(option, currentCascadeLevel) ? { color: activeColor, fontWeight: '500' } : {}">
|
|
484
|
+
{{ option.label }}
|
|
485
|
+
</text>
|
|
486
|
+
|
|
487
|
+
<!-- 有子级时显示右箭头 -->
|
|
488
|
+
<view v-if="option.children != null && option.children.length > 0" class="cascade-arrow">
|
|
489
|
+
<Icon name="icon-xiajiantou-copy" :size="12" color="#999" />
|
|
490
|
+
</view>
|
|
491
|
+
|
|
492
|
+
<!-- 叶子节点被选中时显示 ✓ -->
|
|
493
|
+
<text v-else-if="isCascadeOptionSelected(option, currentCascadeLevel)"
|
|
494
|
+
class="dropdown-item-check" :style="{ color: activeColor }">
|
|
495
|
+
✓
|
|
496
|
+
</text>
|
|
269
497
|
</view>
|
|
270
|
-
|
|
271
|
-
|
|
498
|
+
|
|
499
|
+
<!-- 空状态 -->
|
|
500
|
+
<view v-if="currentCascadeOptions.length === 0" class="dropdown-empty" :class="alignClass">
|
|
501
|
+
<text class="empty-text">{{ $t('form.noOptions') }}</text>
|
|
502
|
+
</view>
|
|
503
|
+
</scroll-view>
|
|
504
|
+
</template>
|
|
505
|
+
|
|
506
|
+
<!-- ====== 普通模式(单选/多选) ====== -->
|
|
507
|
+
<template v-else>
|
|
508
|
+
<scroll-view class="dropdown-scroll" scroll-y :scroll-top="0">
|
|
509
|
+
<!-- 多选模式下的全选/清空按钮 -->
|
|
510
|
+
<view v-if="props.multiple" class="dropdown-actions">
|
|
511
|
+
<view class="dropdown-action-item" @click.stop="selectAll">
|
|
512
|
+
<text class="action-text" :style="{ color: activeColor }">{{ $t('form.selectAll') }}</text>
|
|
513
|
+
</view>
|
|
514
|
+
<view class="dropdown-action-item" @click.stop="clearValue">
|
|
515
|
+
<text class="action-text" :style="{ color: activeColor }">{{ $t('form.clear') }}</text>
|
|
516
|
+
</view>
|
|
272
517
|
</view>
|
|
273
|
-
</view>
|
|
274
518
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
519
|
+
<!-- 选项列表 -->
|
|
520
|
+
<view v-for="option in options" :key="option.value" class="dropdown-item select-root" :class="[
|
|
521
|
+
alignClass,
|
|
522
|
+
{
|
|
523
|
+
'dropdown-item-selected': isOptionSelected(option),
|
|
524
|
+
'dropdown-item-disabled': option.disabled || disabled
|
|
525
|
+
}
|
|
526
|
+
]" @click="selectOption(option)">
|
|
527
|
+
<text class="dropdown-item-text" :style="isOptionSelected(option) ? { color: activeColor, fontWeight: '500' } : {}">{{ option.label }}</text>
|
|
528
|
+
|
|
529
|
+
<!-- 单选模式的选中标记 -->
|
|
530
|
+
<text v-if="!props.multiple && isOptionSelected(option)" class="dropdown-item-check" :style="{ color: activeColor }">
|
|
531
|
+
✓
|
|
532
|
+
</text>
|
|
533
|
+
|
|
534
|
+
<!-- 多选模式的复选框 -->
|
|
535
|
+
<view v-if="props.multiple" class="dropdown-checkbox" :class="{ 'checkbox-checked': isOptionSelected(option) }" :style="isOptionSelected(option) ? { backgroundColor: activeColor, borderColor: activeColor } : {}">
|
|
536
|
+
<text v-if="isOptionSelected(option)" class="checkbox-icon">✓</text>
|
|
537
|
+
</view>
|
|
293
538
|
</view>
|
|
294
|
-
</view>
|
|
295
539
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
540
|
+
<!-- 空状态 -->
|
|
541
|
+
<view v-if="options.length === 0" class="dropdown-empty" :class="alignClass">
|
|
542
|
+
<text class="empty-text">{{ $t('form.noOptions') }}</text>
|
|
543
|
+
</view>
|
|
544
|
+
</scroll-view>
|
|
545
|
+
</template>
|
|
301
546
|
</view>
|
|
302
547
|
</transition>
|
|
303
548
|
</view>
|
|
@@ -449,6 +694,11 @@
|
|
|
449
694
|
z-index: 100;
|
|
450
695
|
overflow: hidden;
|
|
451
696
|
border: 1rpx solid #ebeef5;
|
|
697
|
+
|
|
698
|
+
/* 级联模式下允许更高的面板 */
|
|
699
|
+
&.cascade-dropdown {
|
|
700
|
+
max-height: 500rpx;
|
|
701
|
+
}
|
|
452
702
|
}
|
|
453
703
|
|
|
454
704
|
.dropdown-scroll {
|
|
@@ -649,4 +899,53 @@
|
|
|
649
899
|
}
|
|
650
900
|
}
|
|
651
901
|
|
|
902
|
+
/* ========== 级联选择样式 ========== */
|
|
903
|
+
|
|
904
|
+
/* 面包屑导航 */
|
|
905
|
+
.cascade-breadcrumb {
|
|
906
|
+
display: flex;
|
|
907
|
+
flex-direction: row;
|
|
908
|
+
align-items: center;
|
|
909
|
+
padding: 16rpx 24rpx;
|
|
910
|
+
background-color: #fafafa;
|
|
911
|
+
border-bottom: 1rpx solid #ebeef5;
|
|
912
|
+
flex-wrap: wrap;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.cascade-breadcrumb-item {
|
|
916
|
+
cursor: pointer;
|
|
917
|
+
padding: 4rpx 8rpx;
|
|
918
|
+
border-radius: 4rpx;
|
|
919
|
+
transition: background-color 0.2s ease;
|
|
920
|
+
|
|
921
|
+
&:active {
|
|
922
|
+
background-color: #ecf5ff;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.cascade-breadcrumb-text {
|
|
927
|
+
font-size: 24rpx;
|
|
928
|
+
color: #606266;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.cascade-breadcrumb-separator {
|
|
932
|
+
font-size: 22rpx;
|
|
933
|
+
color: #c0c4cc;
|
|
934
|
+
margin: 0 4rpx;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/* 级联面板 */
|
|
938
|
+
.cascade-panel {
|
|
939
|
+
max-height: 440rpx;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/* 级联箭头(指向右侧表示有下级) */
|
|
943
|
+
.cascade-arrow {
|
|
944
|
+
display: flex;
|
|
945
|
+
align-items: center;
|
|
946
|
+
justify-content: center;
|
|
947
|
+
margin-left: 16rpx;
|
|
948
|
+
transform: rotate(-90deg);
|
|
949
|
+
}
|
|
950
|
+
|
|
652
951
|
</style>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 选项数据结构
|
|
7
|
+
* 当包含 children 时自动启用级联模式,最多支持四级
|
|
7
8
|
*/
|
|
8
9
|
export type SelectOption = {
|
|
9
10
|
/** 选项显示的文本 */
|
|
@@ -12,6 +13,8 @@ export type SelectOption = {
|
|
|
12
13
|
value : string | number
|
|
13
14
|
/** 是否禁用该选项,默认为 false */
|
|
14
15
|
disabled ?: boolean
|
|
16
|
+
/** 子级选项列表,存在时启用级联选择,最多支持四级 */
|
|
17
|
+
children ?: SelectOption[]
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -24,6 +27,11 @@ export type SingleSelectValue = string | number | null
|
|
|
24
27
|
*/
|
|
25
28
|
export type MultipleSelectValue = (string | number)[]
|
|
26
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 级联选择模式下的值类型,数组中每个元素对应一个层级的选中值
|
|
32
|
+
*/
|
|
33
|
+
export type CascadeSelectValue = (string | number)[]
|
|
34
|
+
|
|
27
35
|
/**
|
|
28
36
|
* Props 类型定义
|
|
29
37
|
*/
|
|
@@ -56,4 +64,8 @@ export type SelectProps = {
|
|
|
56
64
|
align ?: 'left' | 'center' | 'right'
|
|
57
65
|
/** 选中状态的颜色,默认为 '#1890ff' */
|
|
58
66
|
activeColor ?: string
|
|
67
|
+
/** 级联模式:是否允许选中任意层级(非叶子节点也可选中),默认为 false */
|
|
68
|
+
changeOnSelect ?: boolean
|
|
69
|
+
/** 级联模式:显示文本的分隔符,默认为 ' / ' */
|
|
70
|
+
separator ?: string
|
|
59
71
|
}
|
package/locales/en-US/form.json
CHANGED
|
@@ -14,5 +14,8 @@
|
|
|
14
14
|
"submitButton": "Submit",
|
|
15
15
|
"resetButton": "Reset",
|
|
16
16
|
"validationFailed": "Form validation failed",
|
|
17
|
-
"submitFailed": "Form submission failed"
|
|
17
|
+
"submitFailed": "Form submission failed",
|
|
18
|
+
"noOptions": "No options",
|
|
19
|
+
"selectAll": "Select all",
|
|
20
|
+
"clear": "Clear"
|
|
18
21
|
}
|
package/locales/zh-CN/form.json
CHANGED
package/package.json
CHANGED