Merge branch 'feat/cascader-first-level-display' of https://github.com/WANGXIAOYU1995/element-plus into feat/cascader-first-level-display

This commit is contained in:
wangxiaoyu
2025-06-12 02:54:23 +08:00
5 changed files with 87 additions and 23 deletions

View File

@@ -382,6 +382,8 @@ describe('Select', () => {
})
test('the scenario of rendering label when there is a default value and persistent is false', async () => {
// This is convenient for testing the default value label rendering when persistent is false.
process.env.RUN_TEST_WITH_PERSISTENT = 'true'
wrapper = _mount(
`
<el-select v-model="value" :persistent="false">
@@ -410,9 +412,12 @@ describe('Select', () => {
await nextTick()
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('双皮奶')
delete process.env.RUN_TEST_WITH_PERSISTENT
})
test('when there is a default value and persistent is false, render the label and dynamically modify options', async () => {
// This is convenient for testing the default value label rendering when persistent is false.
process.env.RUN_TEST_WITH_PERSISTENT = 'true'
wrapper = _mount(
`
<el-select v-model="value" :persistent="false">
@@ -444,9 +449,12 @@ describe('Select', () => {
await nextTick()
expect(wrapper.find(`.${PLACEHOLDER_CLASS_NAME}`).text()).toBe('双皮奶')
delete process.env.RUN_TEST_WITH_PERSISTENT
})
test('multiple is true and persistent is false', async () => {
// This is convenient for testing the default value label rendering when persistent is false.
process.env.RUN_TEST_WITH_PERSISTENT = 'true'
wrapper = _mount(
`
<el-select v-model="value" :persistent="false" multiple>
@@ -477,6 +485,7 @@ describe('Select', () => {
const tags = wrapper.findAll(`.${TAG_NAME}`)
expect(tags.length).toBe(1)
expect(tags[0].text()).toBe('双皮奶')
delete process.env.RUN_TEST_WITH_PERSISTENT
})
test('multiple is true and persistent is false, render the label and dynamically modify options', async () => {
@@ -1382,7 +1391,7 @@ describe('Select', () => {
test('multiple select with collapseTagsTooltip', async () => {
// This is convenient for testing the default value label rendering when persistent is false.
process.env.RUN_TEST_WITH_PERSISTENT = true
process.env.RUN_TEST_WITH_PERSISTENT = 'true'
wrapper = _mount(
`

View File

@@ -300,7 +300,7 @@
</template>
<script lang="ts">
import { Fragment, computed, defineComponent, h, nextTick, provide, reactive, toRefs, watchEffect } from 'vue'
import { computed, defineComponent, getCurrentInstance, provide, reactive, toRefs, watch } from 'vue'
import { ClickOutside } from '@element-plus/directives'
import ElTooltip from '@element-plus/components/tooltip'
import ElScrollbar from '@element-plus/components/scrollbar'
@@ -316,7 +316,7 @@ import { selectKey } from './token'
import ElOptions from './options'
import { selectProps } from './select'
import type { VNode, VNodeNormalizedChildren } from 'vue';
import type { VNode } from 'vue';
import type { SelectContext } from './type'
const COMPONENT_NAME = 'ElSelect'
@@ -346,6 +346,16 @@ export default defineComponent({
],
setup(props, { emit, slots }) {
const instance = getCurrentInstance()!
instance.appContext.config.warnHandler = (...args) => {
// Overrides warnings about slots not being executable outside of a render function.
// We call slot below just to simulate data when persist is false, this warning message should be ignored
if (!args[0] || args[0].includes('Slot "default" invoked outside of the render function')) {
return
}
// eslint-disable-next-line no-console
console.warn(...args)
}
const modelValue = computed(() => {
const { modelValue: rawModelValue, multiple } = props
const fallback = multiple ? [] : undefined
@@ -366,27 +376,54 @@ export default defineComponent({
const API = useSelect(_props, emit)
const { calculatorRef, inputStyle } = useCalcInputWidth()
const manuallyRenderSlots = (vnodes: VNodeNormalizedChildren) => {
const flatTreeSelectData = (data: any[]) => {
return data.reduce((acc, item) => {
acc.push(item)
if (item.children && item.children.length > 0) {
acc.push(...flatTreeSelectData(item.children))
}
return acc
}, [])
}
const manuallyRenderSlots = (vnodes: VNode[] | undefined) => {
// After option rendering is completed, the useSelect internal state can collect the value of each option.
// If the persistent value is false, option will not be rendered by default, so in this case,
// manually render and load option data here.
const children = flattedChildren(vnodes) as VNode[]
children.filter((item) => {
const children = flattedChildren(vnodes || []) as VNode[]
children.forEach((item) => {
// @ts-expect-error
return isObject(item) && item!.type.name === 'ElOption'
}).forEach(item => {
const obj = { ...item.props } as any
obj.currentLabel = obj.label || (isObject(obj.value) ? '' : obj.value)
API.onOptionCreate(obj)
if (isObject(item) && (item.type.name === 'ElOption' || item.type.name === 'ElTree')) {
// @ts-expect-error
const _name = item.type.name
if (_name === 'ElTree') {
// tree-select component is a special case.
// So we need to handle it separately.
const treeData = item.props?.data || []
const flatData = flatTreeSelectData(treeData)
flatData.forEach((treeItem: any) => {
treeItem.currentLabel = treeItem.label || (isObject(treeItem.value) ? '' : treeItem.value)
API.onOptionCreate(treeItem)
})
} else if (_name === 'ElOption') {
const obj = { ...item.props } as any
obj.currentLabel = obj.label || (isObject(obj.value) ? '' : obj.value)
API.onOptionCreate(obj)
}
}
})
}
watchEffect(() => {
if (!props.persistent) {
nextTick(() => {
const defaultSlots = h(Fragment, slots.default?.() ?? []).children
manuallyRenderSlots(defaultSlots)
})
watch(() => {
const slotsContent = slots.default?.()
return slotsContent
}, newSlot => {
if (props.persistent) {
// If persistent is true, we don't need to manually render slots.
return
}
manuallyRenderSlots(newSlot)
}, {
immediate: true,
})
provide(

View File

@@ -99,6 +99,24 @@ describe('TreeSelect.vue', () => {
expect(tree.findAll('.el-tree .el-tree-node').length).toBe(1)
})
test('render tree-select with default value and persistent is false', async () => {
// This is convenient for testing the default value label rendering when persistent is false.
process.env.RUN_TEST_WITH_PERSISTENT = 'true'
const { select, wrapper } = createComponent({
props: {
persistent: false,
modelValue: 1,
},
})
await nextTick()
expect(select.vm.modelValue).toBe(1)
expect(select.vm.states.selectedLabel).toBe('一级 1')
expect(wrapper.find('.el-select__placeholder').text()).toBe('一级 1')
delete process.env.RUN_TEST_WITH_PERSISTENT
})
test('render tree-select with dynamic class', async () => {
const isClass = ref(false)
const { wrapper } = createComponent({

View File

@@ -5,7 +5,7 @@ export default {
label: 'Хлебные крошки',
},
colorpicker: {
confirm: 'подтверждать',
confirm: 'Ок',
clear: 'Очистить',
},
datepicker: {
@@ -13,7 +13,7 @@ export default {
today: 'Сегодня',
cancel: 'Отмена',
clear: 'Очистить',
confirm: 'подтверждать',
confirm: 'Ок',
selectDate: 'Выбрать дату',
selectTime: 'Выбрать время',
startDate: 'Дата начала',
@@ -91,7 +91,7 @@ export default {
},
messagebox: {
title: 'Сообщение',
confirm: 'подтверждать',
confirm: 'Ок',
cancel: 'Отмена',
error: 'Недопустимый ввод данных',
},
@@ -103,7 +103,7 @@ export default {
},
table: {
emptyText: 'Нет данных',
confirmFilter: 'Подтвердить',
confirmFilter: 'Ок',
resetFilter: 'Сбросить',
clearFilter: 'Все',
sumText: 'Сумма',
@@ -131,7 +131,7 @@ export default {
title: 'Назад',
},
popconfirm: {
confirmButtonText: 'подтверждать',
confirmButtonText: 'Ок',
cancelButtonText: 'Отмена',
},
carousel: {

2
typings/env.d.ts vendored
View File

@@ -5,7 +5,7 @@ declare global {
const process: {
env: {
NODE_ENV: string
RUN_TEST_WITH_PERSISTENT: boolean
RUN_TEST_WITH_PERSISTENT: string
}
}