mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [cascader] lazyLoad support reject (#22283)
* feat(components): [cascader] `lazyLoad` support reject * feat: update * fix: update * feat: remove unnecessary change * test: add new test * fix: update * feat: update * docs: update * docs: update
This commit is contained in:
@@ -301,23 +301,23 @@ cascader/custom-header-footer
|
|||||||
|
|
||||||
## CascaderProps
|
## CascaderProps
|
||||||
|
|
||||||
| Attribute | Description | Type | Default |
|
| Attribute | Description | Type | Default |
|
||||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------- |
|
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------- |
|
||||||
| expandTrigger | trigger mode of expanding options | ^[enum]`'click' \| 'hover'` | click |
|
| expandTrigger | trigger mode of expanding options | ^[enum]`'click' \| 'hover'` | click |
|
||||||
| multiple | whether multiple selection is enabled | ^[boolean] | false |
|
| multiple | whether multiple selection is enabled | ^[boolean] | false |
|
||||||
| checkStrictly | whether checked state of a node not affects its parent and child nodes | ^[boolean] | false |
|
| checkStrictly | whether checked state of a node not affects its parent and child nodes | ^[boolean] | false |
|
||||||
| emitPath | when checked nodes change, whether to emit an array of node's path, if false, only emit the value of node. | ^[boolean] | true |
|
| emitPath | when checked nodes change, whether to emit an array of node's path, if false, only emit the value of node. | ^[boolean] | true |
|
||||||
| lazy | whether to dynamic load child nodes, use with `lazyload` attribute | ^[boolean] | false |
|
| lazy | whether to dynamic load child nodes, use with `lazyload` attribute | ^[boolean] | false |
|
||||||
| lazyLoad | method for loading child nodes data, only works when `lazy` is true | ^[Function]`(node: Node, resolve: Resolve) => void` | — |
|
| lazyLoad | method for loading child nodes data, only works when `lazy` is true. The reject parameter is supported after version ^(2.11.5). | ^[Function]`(node: Node, resolve: Resolve, reject: () => void) => void` | — |
|
||||||
| value | specify which key of node object is used as the node's value | ^[string] | value |
|
| value | specify which key of node object is used as the node's value | ^[string] | value |
|
||||||
| label | specify which key of node object is used as the node's label | ^[string] | label |
|
| label | specify which key of node object is used as the node's label | ^[string] | label |
|
||||||
| children | specify which key of node object is used as the node's children | ^[string] | children |
|
| children | specify which key of node object is used as the node's children | ^[string] | children |
|
||||||
| disabled | specify which key of node object is used as the node's disabled | ^[string] | disabled |
|
| disabled | specify which key of node object is used as the node's disabled | ^[string] | disabled |
|
||||||
| leaf | specify which key of node object is used as the node's leaf field | ^[string] | leaf |
|
| leaf | specify which key of node object is used as the node's leaf field | ^[string] | leaf |
|
||||||
| hoverThreshold | hover threshold of expanding options | ^[number] | 500 |
|
| hoverThreshold | hover threshold of expanding options | ^[number] | 500 |
|
||||||
| checkOnClickNode ^(2.10.5) | whether to check or uncheck node when clicking on the node | ^[boolean] | false |
|
| checkOnClickNode ^(2.10.5) | whether to check or uncheck node when clicking on the node | ^[boolean] | false |
|
||||||
| checkOnClickLeaf ^(2.10.5) | whether to check or uncheck node when clicking on leaf node (last children). | ^[boolean] | true |
|
| checkOnClickLeaf ^(2.10.5) | whether to check or uncheck node when clicking on leaf node (last children). | ^[boolean] | true |
|
||||||
| showPrefix ^(2.10.5) | whether to show the radio or checkbox prefix | ^[boolean] | true |
|
| showPrefix ^(2.10.5) | whether to show the radio or checkbox prefix | ^[boolean] | true |
|
||||||
|
|
||||||
## Type Declarations
|
## Type Declarations
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ type Resolve = (data: any) => void
|
|||||||
|
|
||||||
type ExpandTrigger = 'click' | 'hover'
|
type ExpandTrigger = 'click' | 'hover'
|
||||||
|
|
||||||
type LazyLoad = (node: Node, resolve: Resolve) => void
|
type LazyLoad = (node: Node, resolve: Resolve, reject: () => void) => void
|
||||||
|
|
||||||
type isDisabled = (data: CascaderOption, node: Node) => boolean
|
type isDisabled = (data: CascaderOption, node: Node) => boolean
|
||||||
|
|
||||||
|
|||||||
@@ -598,6 +598,128 @@ describe('CascaderPanel.vue', () => {
|
|||||||
vi.useRealTimers()
|
vi.useRealTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('lazy load with loaded fails', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
const value = ref([])
|
||||||
|
const props: CascaderProps = {
|
||||||
|
lazy: true,
|
||||||
|
lazyLoad(node, resolve, reject) {
|
||||||
|
const { level } = node
|
||||||
|
setTimeout(() => {
|
||||||
|
const nodes = Array.from({ length: level + 1 }).map(() => ({
|
||||||
|
value: ++id,
|
||||||
|
label: `option${id}`,
|
||||||
|
leaf: level >= 2,
|
||||||
|
}))
|
||||||
|
if (level === 1) {
|
||||||
|
// Simulate loading failure for the second level nodes
|
||||||
|
reject()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(nodes)
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const wrapper = mount(() => (
|
||||||
|
<CascaderPanel v-model={value.value} props={props} />
|
||||||
|
))
|
||||||
|
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
const firstOption = wrapper.find(NODE)
|
||||||
|
expect(firstOption.exists()).toBe(true)
|
||||||
|
await firstOption.trigger('click')
|
||||||
|
expect(firstOption.findComponent(Loading).exists()).toBe(true)
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
expect(firstOption.findComponent(Loading).exists()).toBe(false)
|
||||||
|
expect(wrapper.findAll(MENU).length).toBe(1)
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('lazy load with first level loaded fails', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
const value = ref([])
|
||||||
|
const props: CascaderProps = {
|
||||||
|
lazy: true,
|
||||||
|
lazyLoad(node, resolve, reject) {
|
||||||
|
const { level } = node
|
||||||
|
setTimeout(() => {
|
||||||
|
const nodes = Array.from({ length: level + 1 }).map(() => ({
|
||||||
|
value: ++id,
|
||||||
|
label: `option${id}`,
|
||||||
|
leaf: level >= 2,
|
||||||
|
}))
|
||||||
|
if (level === 0) {
|
||||||
|
// Simulate loading failure for the first level nodes
|
||||||
|
reject()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(nodes)
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const wrapper = mount(() => (
|
||||||
|
<CascaderPanel v-model={value.value} props={props} />
|
||||||
|
))
|
||||||
|
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
const firstOption = wrapper.find(NODE)
|
||||||
|
expect(firstOption.exists()).toBe(false)
|
||||||
|
expect(wrapper.findAll(MENU).length).toBe(1)
|
||||||
|
expect(wrapper.findComponent(Loading).exists()).toBe(false)
|
||||||
|
expect(wrapper.find('.is-empty').exists()).toBe(true)
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('lazy load with first and second level loaded success and third level loaded fails', async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
const value = ref([])
|
||||||
|
const props: CascaderProps = {
|
||||||
|
lazy: true,
|
||||||
|
lazyLoad(node, resolve, reject) {
|
||||||
|
const { level } = node
|
||||||
|
setTimeout(() => {
|
||||||
|
const nodes = Array.from({ length: level + 1 }).map(() => ({
|
||||||
|
value: ++id,
|
||||||
|
label: `option${id}`,
|
||||||
|
leaf: level >= 2,
|
||||||
|
}))
|
||||||
|
if (level === 2) {
|
||||||
|
// Simulate loading failure for the second level nodes
|
||||||
|
reject()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(nodes)
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const wrapper = mount(() => (
|
||||||
|
<CascaderPanel v-model={value.value} props={props} />
|
||||||
|
))
|
||||||
|
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
const firstOption = wrapper.find(NODE)
|
||||||
|
expect(firstOption.exists()).toBe(true)
|
||||||
|
await firstOption.trigger('click')
|
||||||
|
expect(firstOption.findComponent(Loading).exists()).toBe(true)
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
expect(firstOption.findComponent(Loading).exists()).toBe(false)
|
||||||
|
expect(wrapper.findAll(MENU).length).toBe(2)
|
||||||
|
const secondMenu = wrapper.findAll(MENU)[1]
|
||||||
|
const secondOption = secondMenu.find(NODE)
|
||||||
|
await secondOption.trigger('click')
|
||||||
|
expect(secondOption.findComponent(Loading).exists()).toBe(true)
|
||||||
|
vi.runAllTimers()
|
||||||
|
await nextTick()
|
||||||
|
expect(secondOption.findComponent(Loading).exists()).toBe(false)
|
||||||
|
expect(wrapper.findAll(MENU).length).toBe(2)
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
test('lazy load with default primitive value', async () => {
|
test('lazy load with default primitive value', async () => {
|
||||||
vi.useFakeTimers()
|
vi.useFakeTimers()
|
||||||
const props = {
|
const props = {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const slots = useSlots()
|
|||||||
|
|
||||||
let store: Store
|
let store: Store
|
||||||
const initialLoaded = ref(true)
|
const initialLoaded = ref(true)
|
||||||
|
const initialLoadedOnce = ref(false)
|
||||||
const menuList = ref<CascaderMenuInstance[]>([])
|
const menuList = ref<CascaderMenuInstance[]>([])
|
||||||
const checkedValue = ref<CascaderValue>()
|
const checkedValue = ref<CascaderValue>()
|
||||||
const menus = ref<CascaderNode[][]>([])
|
const menus = ref<CascaderNode[][]>([])
|
||||||
@@ -128,9 +129,20 @@ const lazyLoad: ElCascaderPanelContext['lazyLoad'] = (node, cb) => {
|
|||||||
_node.childrenData = _node.childrenData || []
|
_node.childrenData = _node.childrenData || []
|
||||||
dataList && store?.appendNodes(dataList, parent as Node)
|
dataList && store?.appendNodes(dataList, parent as Node)
|
||||||
dataList && cb?.(dataList)
|
dataList && cb?.(dataList)
|
||||||
|
if (node.level === 0) {
|
||||||
|
initialLoadedOnce.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.lazyLoad(node, resolve)
|
const reject = () => {
|
||||||
|
node!.loading = false
|
||||||
|
node!.loaded = false
|
||||||
|
if (node!.level === 0) {
|
||||||
|
initialLoaded.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.lazyLoad(node, resolve, reject)
|
||||||
}
|
}
|
||||||
|
|
||||||
const expandNode: ElCascaderPanelContext['expandNode'] = (node, silent) => {
|
const expandNode: ElCascaderPanelContext['expandNode'] = (node, silent) => {
|
||||||
@@ -376,6 +388,11 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const loadLazyRootNodes = () => {
|
||||||
|
if (initialLoadedOnce.value) return
|
||||||
|
initStore()
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeUpdate(() => (menuList.value = []))
|
onBeforeUpdate(() => (menuList.value = []))
|
||||||
|
|
||||||
onMounted(() => !isEmpty(props.modelValue) && syncCheckedValue())
|
onMounted(() => !isEmpty(props.modelValue) && syncCheckedValue())
|
||||||
@@ -397,5 +414,6 @@ defineExpose({
|
|||||||
clearCheckedNodes,
|
clearCheckedNodes,
|
||||||
calculateCheckedValue,
|
calculateCheckedValue,
|
||||||
scrollToExpandingNode,
|
scrollToExpandingNode,
|
||||||
|
loadLazyRootNodes,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ export type ExpandTrigger = 'click' | 'hover'
|
|||||||
export type isDisabled = (data: CascaderOption, node: CascaderNode) => boolean
|
export type isDisabled = (data: CascaderOption, node: CascaderNode) => boolean
|
||||||
export type isLeaf = (data: CascaderOption, node: CascaderNode) => boolean
|
export type isLeaf = (data: CascaderOption, node: CascaderNode) => boolean
|
||||||
export type Resolve = (dataList?: CascaderOption[]) => void
|
export type Resolve = (dataList?: CascaderOption[]) => void
|
||||||
export type LazyLoad = (node: CascaderNode, resolve: Resolve) => void
|
export type LazyLoad = (
|
||||||
|
node: CascaderNode,
|
||||||
|
resolve: Resolve,
|
||||||
|
reject: () => void
|
||||||
|
) => void
|
||||||
export interface RenderLabelProps {
|
export interface RenderLabelProps {
|
||||||
node: CascaderNode
|
node: CascaderNode
|
||||||
data: CascaderOption
|
data: CascaderOption
|
||||||
|
|||||||
@@ -749,6 +749,15 @@ watch(realSize, async () => {
|
|||||||
|
|
||||||
watch(presentText, syncPresentTextValue, { immediate: true })
|
watch(presentText, syncPresentTextValue, { immediate: true })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => popperVisible.value,
|
||||||
|
(val) => {
|
||||||
|
if (val && props.props.lazy && props.props.lazyLoad) {
|
||||||
|
cascaderPanelRef.value?.loadLazyRootNodes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const inputInner = inputRef.value!.input!
|
const inputInner = inputRef.value!.input!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user