From 32bf1a108f089a908d88b90b4fcdd74e1c2164ef Mon Sep 17 00:00:00 2001
From: discreted66 <953831480@qq.com>
Date: Thu, 18 Dec 2025 19:02:32 -0800
Subject: [PATCH] =?UTF-8?q?feat:=E4=B8=8B=E6=8B=89=E6=A0=91=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E6=87=92=E5=8A=A0=E8=BD=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
examples/sites/demos/apis/tree-select.js | 41 ++++++++++++
.../mobile-first/app/tree-select/lazy.vue | 59 +++++++++++++++++
.../app/tree-select/webdoc/tree-select.js | 12 ++++
.../pc/app/select-wrapper/all-text.spec.ts | 2 +-
.../demos/pc/app/select-wrapper/all-text.vue | 2 +-
.../app/select-wrapper/allow-create.spec.ts | 10 +--
.../pc/app/select-wrapper/allow-create.vue | 9 ++-
.../select-wrapper/automatic-dropdown.spec.ts | 8 +--
.../app/select-wrapper/automatic-dropdown.vue | 2 +-
.../pc/app/select-wrapper/basic-usage.spec.ts | 10 +--
.../pc/app/select-wrapper/basic-usage.vue | 2 +-
.../pc/app/select-wrapper/binding-obj.spec.ts | 6 +-
.../pc/app/select-wrapper/binding-obj.vue | 2 +-
.../pc/app/select-wrapper/cache-usage.spec.ts | 6 +-
.../pc/app/select-wrapper/cache-usage.vue | 2 +-
.../clear-no-match-value.spec.ts | 4 +-
.../select-wrapper/clear-no-match-value.vue | 2 +-
.../pc/app/select-wrapper/clearable.spec.ts | 4 +-
.../demos/pc/app/select-wrapper/clearable.vue | 2 +-
.../app/select-wrapper/collapse-tags.spec.ts | 2 +-
.../pc/app/select-wrapper/collapse-tags.vue | 2 +-
.../pc/app/select-wrapper/copy-multi.spec.ts | 8 +--
.../pc/app/select-wrapper/copy-multi.vue | 2 +-
.../pc/app/select-wrapper/copy-single.spec.ts | 6 +-
.../pc/app/select-wrapper/copy-single.vue | 2 +-
.../pc/app/select-wrapper/disabled.spec.ts | 8 +--
.../demos/pc/app/select-wrapper/disabled.vue | 2 +-
.../pc/app/select-wrapper/events.spec.ts | 8 +--
.../demos/pc/app/select-wrapper/events.vue | 2 +-
.../app/select-wrapper/extra-query-params.vue | 2 +-
.../app/select-wrapper/filter-method.spec.ts | 4 +-
.../pc/app/select-wrapper/filter-method.vue | 2 +-
.../pc/app/select-wrapper/filter-mode.vue | 2 +-
.../pc/app/select-wrapper/hide-drop.spec.ts | 2 +-
.../demos/pc/app/select-wrapper/hide-drop.vue | 2 +-
.../pc/app/select-wrapper/init-label.vue | 2 +-
.../app/select-wrapper/input-box-type.spec.ts | 9 ++-
.../pc/app/select-wrapper/input-box-type.vue | 2 +-
.../is-drop-inherit-width.spec.ts | 4 +-
.../select-wrapper/is-drop-inherit-width.vue | 2 +-
.../select-wrapper/manual-focus-blur.spec.ts | 2 +-
.../app/select-wrapper/manual-focus-blur.vue | 2 +-
.../pc/app/select-wrapper/map-field.spec.ts | 4 +-
.../demos/pc/app/select-wrapper/map-field.vue | 2 +-
.../app/select-wrapper/memoize-usage.spec.ts | 2 +-
.../pc/app/select-wrapper/memoize-usage.vue | 2 +-
.../pc/app/select-wrapper/multiple-mix.vue | 2 +-
.../pc/app/select-wrapper/multiple.spec.ts | 4 +-
.../demos/pc/app/select-wrapper/multiple.vue | 2 +-
.../select-wrapper/native-properties.spec.ts | 2 +-
.../app/select-wrapper/native-properties.vue | 2 +-
.../select-wrapper/nest-grid-disable.spec.ts | 4 +-
.../app/select-wrapper/nest-grid-disable.vue | 2 +-
.../select-wrapper/nest-grid-init-query.vue | 2 +-
.../select-wrapper/nest-grid-remote.spec.ts | 24 +++----
.../app/select-wrapper/nest-grid-remote.vue | 2 +-
.../pc/app/select-wrapper/nest-grid.spec.ts | 12 ++--
.../demos/pc/app/select-wrapper/nest-grid.vue | 2 +-
.../nest-radio-grid-much-data.spec.ts | 4 +-
.../nest-radio-grid-much-data.vue | 2 +-
.../pc/app/select-wrapper/nest-tree.spec.ts | 10 +--
.../demos/pc/app/select-wrapper/nest-tree.vue | 2 +-
.../app/select-wrapper/no-data-text.spec.ts | 6 +-
.../pc/app/select-wrapper/no-data-text.vue | 2 +-
.../app/select-wrapper/optimization.spec.ts | 4 +-
.../app/select-wrapper/option-group.spec.ts | 2 +-
.../pc/app/select-wrapper/option-group.vue | 2 +-
.../popup-style-position.spec.ts | 4 +-
.../select-wrapper/popup-style-position.vue | 2 +-
.../app/select-wrapper/remote-method.spec.ts | 8 +--
.../pc/app/select-wrapper/remote-method.vue | 2 +-
.../pc/app/select-wrapper/searchable.spec.ts | 4 +-
.../pc/app/select-wrapper/searchable.vue | 2 +-
.../app/select-wrapper/show-alloption.spec.ts | 2 +-
.../pc/app/select-wrapper/show-alloption.vue | 2 +-
.../demos/pc/app/select-wrapper/show-tip.vue | 2 +-
.../demos/pc/app/select-wrapper/size.spec.ts | 8 +--
.../demos/pc/app/select-wrapper/size.vue | 2 +-
.../app/select-wrapper/slot-default.spec.ts | 2 +-
.../pc/app/select-wrapper/slot-default.vue | 2 +-
.../pc/app/select-wrapper/slot-empty.spec.ts | 2 +-
.../pc/app/select-wrapper/slot-empty.vue | 2 +-
.../select-wrapper/slot-header-footer.spec.ts | 2 +-
.../app/select-wrapper/slot-header-footer.vue | 2 +-
.../pc/app/select-wrapper/slot-label.vue | 2 +-
.../pc/app/select-wrapper/slot-prefix.spec.ts | 2 +-
.../pc/app/select-wrapper/slot-prefix.vue | 2 +-
.../app/select-wrapper/slot-reference.spec.ts | 2 +-
.../pc/app/select-wrapper/slot-reference.vue | 2 +-
.../pc/app/select-wrapper/tag-type.spec.ts | 2 +-
.../demos/pc/app/select-wrapper/tag-type.vue | 2 +-
.../app/tree-select/lazy-composition-api.vue | 51 ++++++++++++++
.../lazy-multiple-composition-api.vue | 58 ++++++++++++++++
.../pc/app/tree-select/lazy-multiple.vue | 66 +++++++++++++++++++
.../sites/demos/pc/app/tree-select/lazy.vue | 59 +++++++++++++++++
.../pc/app/tree-select/webdoc/tree-select.js | 14 ++++
packages/renderless/src/base-select/index.ts | 16 +++--
.../vue/src/base-select/src/mobile-first.vue | 2 +-
packages/vue/src/base-select/src/pc.vue | 2 +-
packages/vue/src/tree-select/src/index.ts | 9 ++-
.../vue/src/tree-select/src/mobile-first.vue | 20 +++++-
packages/vue/src/tree-select/src/pc.vue | 14 +++-
102 files changed, 574 insertions(+), 169 deletions(-)
create mode 100644 examples/sites/demos/mobile-first/app/tree-select/lazy.vue
create mode 100644 examples/sites/demos/pc/app/tree-select/lazy-composition-api.vue
create mode 100644 examples/sites/demos/pc/app/tree-select/lazy-multiple-composition-api.vue
create mode 100644 examples/sites/demos/pc/app/tree-select/lazy-multiple.vue
create mode 100644 examples/sites/demos/pc/app/tree-select/lazy.vue
diff --git a/examples/sites/demos/apis/tree-select.js b/examples/sites/demos/apis/tree-select.js
index da57527e2e..398999935d 100644
--- a/examples/sites/demos/apis/tree-select.js
+++ b/examples/sites/demos/apis/tree-select.js
@@ -264,6 +264,44 @@ export default {
},
mode: ['pc'],
pcDemo: 'map-field'
+ },
+ {
+ name: 'lazy',
+ type: 'boolean',
+ defaultValue: 'false',
+ desc: {
+ 'zh-CN': '是否懒加载子节点,配合 load 属性使用',
+ 'en-US': 'Whether to lazily load child nodes and use them in conjunction with the load attribute'
+ },
+ mode: ['pc', 'mobile-first'],
+ pcDemo: 'lazy',
+ mfDemo: 'lazy'
+ },
+ {
+ name: 'load',
+ type: '(node, resolve) => void',
+ defaultValue: '',
+ desc: {
+ 'zh-CN':
+ '加载子树数据的方法。点击节点后,组件开始调用load方法,只有在load函数内调用resolve(data),才表示返回下级的数据成功。',
+ 'en-US':
+ 'Method of loading subtree data. After the node is clicked, the component starts to call the load method. Only when resolve(data) is called in the load function, the data at the lower level is successfully returned.'
+ },
+ mode: ['pc', 'mobile-first'],
+ pcDemo: 'lazy',
+ mfDemo: 'lazy'
+ },
+ {
+ name: 'after-load',
+ type: 'Function',
+ defaultValue: '',
+ desc: {
+ 'zh-CN': '节点懒加载完成后的回调函数',
+ 'en-US': 'Callback function after node lazy loading is completed'
+ },
+ mode: ['pc', 'mobile-first'],
+ pcDemo: 'lazy',
+ mfDemo: 'lazy'
}
]
}
@@ -281,6 +319,9 @@ interface ITreeNode {
interface ITreeOption {
data: ITreeNode[] // 树数据,用法同 Tree
+ lazy?: boolean // 是否懒加载子节点
+ load?: (node: ITreeNodeVm, resolve: IResolveType) => void // 加载子树数据的方法
+ afterLoad?: (data: any) => void // 节点懒加载完成后的回调函数
}
`
},
diff --git a/examples/sites/demos/mobile-first/app/tree-select/lazy.vue b/examples/sites/demos/mobile-first/app/tree-select/lazy.vue
new file mode 100644
index 0000000000..7baf6f1bf3
--- /dev/null
+++ b/examples/sites/demos/mobile-first/app/tree-select/lazy.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
diff --git a/examples/sites/demos/mobile-first/app/tree-select/webdoc/tree-select.js b/examples/sites/demos/mobile-first/app/tree-select/webdoc/tree-select.js
index a9b03b86f5..ff82e55575 100644
--- a/examples/sites/demos/mobile-first/app/tree-select/webdoc/tree-select.js
+++ b/examples/sites/demos/mobile-first/app/tree-select/webdoc/tree-select.js
@@ -68,6 +68,18 @@ export default {
},
codeFiles: ['disabled.vue']
},
+ {
+ demoId: 'lazy',
+ name: {
+ 'zh-CN': '懒加载',
+ 'en-US': 'Lazy loading'
+ },
+ desc: {
+ 'zh-CN':
+ '通过 lazy 属性,启用懒加载模式。
通过 load 函数属性,触发加载,初始会执行一次。
通过 after-load 函数属性,监听下级节点加载完毕的事件。'
+ },
+ codeFiles: ['lazy.vue']
+ },
{
demoId: 'map-field',
name: {
diff --git a/examples/sites/demos/pc/app/select-wrapper/all-text.spec.ts b/examples/sites/demos/pc/app/select-wrapper/all-text.spec.ts
index b741baf220..27aa34906b 100644
--- a/examples/sites/demos/pc/app/select-wrapper/all-text.spec.ts
+++ b/examples/sites/demos/pc/app/select-wrapper/all-text.spec.ts
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'
test('多选时自定义全部的文本', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
- await page.goto('select#all-text')
+ await page.goto('select-wrapper#all-text')
const wrap = page.locator('#all-text')
const select = wrap.locator('.tiny-select').nth(0)
const dropdown = page.locator('body > .tiny-select-dropdown')
diff --git a/examples/sites/demos/pc/app/select-wrapper/all-text.vue b/examples/sites/demos/pc/app/select-wrapper/all-text.vue
index 03b4813983..dd728e4db8 100644
--- a/examples/sites/demos/pc/app/select-wrapper/all-text.vue
+++ b/examples/sites/demos/pc/app/select-wrapper/all-text.vue
@@ -13,7 +13,7 @@
+
+
+
diff --git a/examples/sites/demos/pc/app/tree-select/lazy-multiple-composition-api.vue b/examples/sites/demos/pc/app/tree-select/lazy-multiple-composition-api.vue
new file mode 100644
index 0000000000..98c172840c
--- /dev/null
+++ b/examples/sites/demos/pc/app/tree-select/lazy-multiple-composition-api.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
diff --git a/examples/sites/demos/pc/app/tree-select/lazy-multiple.vue b/examples/sites/demos/pc/app/tree-select/lazy-multiple.vue
new file mode 100644
index 0000000000..52a0f9ba90
--- /dev/null
+++ b/examples/sites/demos/pc/app/tree-select/lazy-multiple.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
diff --git a/examples/sites/demos/pc/app/tree-select/lazy.vue b/examples/sites/demos/pc/app/tree-select/lazy.vue
new file mode 100644
index 0000000000..19154054ee
--- /dev/null
+++ b/examples/sites/demos/pc/app/tree-select/lazy.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
diff --git a/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js b/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js
index a9b03b86f5..3aa9af4065 100644
--- a/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js
+++ b/examples/sites/demos/pc/app/tree-select/webdoc/tree-select.js
@@ -68,6 +68,20 @@ export default {
},
codeFiles: ['disabled.vue']
},
+ {
+ demoId: 'lazy',
+ name: {
+ 'zh-CN': '懒加载',
+ 'en-US': 'Lazy loading'
+ },
+ desc: {
+ 'zh-CN':
+ '通过 lazy 属性,启用懒加载模式。
通过 load 函数属性,触发加载,初始会执行一次。
通过 after-load 函数属性,监听下级节点加载完毕的事件。',
+ 'en-US':
+ 'Enable lazy loading mode with the lazy property.
load is triggered by the load function property, which is initially executed once.
afterLoad function properties are used to listen for events when a subordinate node has finished loading.'
+ },
+ codeFiles: ['lazy.vue']
+ },
{
demoId: 'map-field',
name: {
diff --git a/packages/renderless/src/base-select/index.ts b/packages/renderless/src/base-select/index.ts
index cc8dc91fdf..423b8b6d42 100644
--- a/packages/renderless/src/base-select/index.ts
+++ b/packages/renderless/src/base-select/index.ts
@@ -65,14 +65,20 @@ export const showTip =
}
export const defaultOnQueryChange =
- ({ props, state, constants, api, nextTick }) =>
+ ({ props, state, constants, api, nextTick, vm }) =>
(value, isInput) => {
- if (props.remote && (typeof props.remoteMethod === 'function' || typeof props.initQuery === 'function')) {
- state.hoverIndex = -1
- props.remoteMethod && props.remoteMethod(value, props.extraQueryParams)
- } else if (typeof props.filterMethod === 'function') {
+ // 如果 filterMethod 存在,优先调用它(用于 grid-select 等组件,它们的 filterMethod 内部会处理 remote 搜索)
+ if (typeof props.filterMethod === 'function') {
props.filterMethod(value)
state.selectEmitter.emit(constants.COMPONENT_NAME.OptionGroup, constants.EVENT_NAME.queryChange)
+ // 如果同时存在 remoteMethod 且没有使用 panel 插槽,也调用 remoteMethod(兼容其他场景)
+ if (props.remote && typeof props.remoteMethod === 'function' && !vm.$slots?.panel) {
+ state.hoverIndex = -1
+ props.remoteMethod(value, props.extraQueryParams)
+ }
+ } else if (props.remote && (typeof props.remoteMethod === 'function' || typeof props.initQuery === 'function')) {
+ state.hoverIndex = -1
+ props.remoteMethod && props.remoteMethod(value, props.extraQueryParams)
} else {
api.queryChange(value, isInput)
}
diff --git a/packages/vue/src/base-select/src/mobile-first.vue b/packages/vue/src/base-select/src/mobile-first.vue
index 90ec1e6f17..c357fa62d8 100644
--- a/packages/vue/src/base-select/src/mobile-first.vue
+++ b/packages/vue/src/base-select/src/mobile-first.vue
@@ -375,7 +375,7 @@
:is-drop-inherit-width="isDropInheritWidth"
:placement="placement"
:append-to-body="popperAppendToBody"
- v-show="!onCopying() && !hideDrop && state.visible && state.emptyText !== false"
+ v-show="!onCopying() && !hideDrop && state.visible && (slots.panel || state.emptyText !== false)"
:style="dropStyle"
:popper-options="popperOptions"
:class="m('duration-300')"
diff --git a/packages/vue/src/base-select/src/pc.vue b/packages/vue/src/base-select/src/pc.vue
index 56b54a7982..63523508ab 100644
--- a/packages/vue/src/base-select/src/pc.vue
+++ b/packages/vue/src/base-select/src/pc.vue
@@ -358,7 +358,7 @@
:is-drop-inherit-width="isDropInheritWidth"
:placement="placement"
:append-to-body="popperAppendToBody"
- v-show="!onCopying() && !hideDrop && state.visible && state.emptyText !== false"
+ v-show="!onCopying() && !hideDrop && state.visible && (slots.panel || state.emptyText !== false)"
:style="dropStyle"
:popper-options="popperOptions"
>
diff --git a/packages/vue/src/tree-select/src/index.ts b/packages/vue/src/tree-select/src/index.ts
index 191cafe888..4e23f23f16 100644
--- a/packages/vue/src/tree-select/src/index.ts
+++ b/packages/vue/src/tree-select/src/index.ts
@@ -100,7 +100,14 @@ export default defineComponent({
autocomplete: {
type: String,
default: 'off'
- }
+ },
+ // 懒加载相关
+ lazy: {
+ type: Boolean,
+ default: false
+ },
+ load: Function,
+ afterLoad: Function
},
setup(props, context) {
return $setup({ props, context, template })
diff --git a/packages/vue/src/tree-select/src/mobile-first.vue b/packages/vue/src/tree-select/src/mobile-first.vue
index 3376f96a62..f5f32debbb 100644
--- a/packages/vue/src/tree-select/src/mobile-first.vue
+++ b/packages/vue/src/tree-select/src/mobile-first.vue
@@ -39,13 +39,22 @@
:current-node-key="!multiple ? state.currentKey : ''"
:data="state.treeData"
:default-checked-keys="multiple ? state.defaultCheckedKeys : treeOp.defaultCheckedKeys || []"
- :default-expand-all="treeOp.defaultExpandAll !== undefined ? treeOp.defaultExpandAll : true"
+ :default-expand-all="
+ treeOp.defaultExpandAll !== undefined
+ ? treeOp.defaultExpandAll
+ : (lazy !== undefined ? lazy : treeOp.lazy)
+ ? false
+ : true
+ "
:expand-on-click-node="false"
:filter-node-method="filterMethod"
:icon-trigger-click-node="false"
:node-key="valueField"
:props="{ label: textField }"
:show-checkbox="multiple"
+ :lazy="lazy !== undefined ? lazy : treeOp.lazy"
+ :load="load || treeOp.load"
+ :after-load="afterLoad || treeOp.afterLoad"
@check="check"
@node-click="nodeClick"
v-bind="treeOp"
@@ -150,7 +159,14 @@ export default defineComponent({
autocomplete: {
type: String,
default: 'off'
- }
+ },
+ // 懒加载相关
+ lazy: {
+ type: Boolean,
+ default: false
+ },
+ load: Function,
+ afterLoad: Function
},
setup(props, context) {
return setup({ props, context, renderless, api })
diff --git a/packages/vue/src/tree-select/src/pc.vue b/packages/vue/src/tree-select/src/pc.vue
index 770956086b..e21219572e 100644
--- a/packages/vue/src/tree-select/src/pc.vue
+++ b/packages/vue/src/tree-select/src/pc.vue
@@ -24,13 +24,16 @@
:current-node-key="!multiple ? state.currentKey : ''"
:data="state.treeData"
:default-checked-keys="multiple ? state.defaultCheckedKeys : treeOp.defaultCheckedKeys || []"
- :default-expand-all="true"
+ :default-expand-all="treeOp.defaultExpandAll !== undefined ? treeOp.defaultExpandAll : ((lazy !== undefined ? lazy : treeOp.lazy) ? false : true)"
:expand-on-click-node="false"
:filter-node-method="filterMethod"
:icon-trigger-click-node="false"
:node-key="valueField"
:props="{ label: textField }"
:show-checkbox="multiple"
+ :lazy="lazy !== undefined ? lazy : treeOp.lazy"
+ :load="load || treeOp.load"
+ :after-load="afterLoad || treeOp.afterLoad"
@check="check"
@node-click="nodeClick"
v-bind="treeOp"
@@ -99,7 +102,14 @@ export default defineComponent({
// 标签相关
hoverExpand: Boolean,
clickExpand: Boolean,
- collapseTags: Boolean
+ collapseTags: Boolean,
+ // 懒加载相关
+ lazy: {
+ type: Boolean,
+ default: false
+ },
+ load: Function,
+ afterLoad: Function
},
setup(props, context) {
return setup({ props, context, renderless, api })