1- import { defineComponent , PropType , computed , h } from 'vue' ;
1+ import { defineComponent , PropType , computed , h , shallowRef } from 'vue' ;
22
33import Item from './Item' ;
44import { TreeNode , CascaderContextType } from '../types' ;
55import CascaderProps from '../props' ;
6- import { useConfig , usePrefixClass , useTNodeDefault } from '@tdesign/shared-hooks' ;
6+ import { useConfig , usePrefixClass , useTNodeDefault , useTNodeJSX } from '@tdesign/shared-hooks' ;
77
88import { getDefaultNode } from '@tdesign/shared-utils' ;
99import { getPanels , expandClickEffect , valueChangeEffect } from '../utils' ;
1010
11+ interface FilterState {
12+ filters : Record < number , string > ;
13+ cascade : boolean ;
14+ maxLevel : number ;
15+ }
16+
1117export default defineComponent ( {
1218 name : 'TCascaderSubPanel' ,
1319 props : {
@@ -22,17 +28,76 @@ export default defineComponent({
2228 type : Object as PropType < CascaderContextType > ,
2329 } ,
2430 } ,
25-
2631 setup ( props ) {
2732 const renderTNodeJSXDefault = useTNodeDefault ( ) ;
33+ const renderTNodeJSX = useTNodeJSX ( ) ;
2834 const COMPONENT_NAME = usePrefixClass ( 'cascader' ) ;
2935 const { globalConfig } = useConfig ( 'cascader' ) ;
3036
3137 const panels = computed ( ( ) => getPanels ( props . cascaderContext . treeNodes ) ) ;
3238
33- const handleExpand = ( node : TreeNode , trigger : 'hover' | 'click' ) => {
34- const { trigger : propsTrigger , cascaderContext } = props ;
35- expandClickEffect ( propsTrigger , trigger , node , cascaderContext ) ;
39+ // 过滤状态 - 惰性初始化,不使用时为 null,无性能开销
40+ const filterState = shallowRef < FilterState | null > ( null ) ;
41+
42+ // 处理过滤
43+ const handleFilter = ( index : number , keyword : string , options ?: { cascade ?: boolean } ) => {
44+ const prev = filterState . value ;
45+ const cascade = options ?. cascade ?? prev ?. cascade ?? false ;
46+ const filters = { ...prev ?. filters , [ index ] : keyword } ;
47+
48+ let maxLevel = prev ?. maxLevel ?? - 1 ;
49+ if ( cascade ) {
50+ // 有新搜索词时重置 maxLevel;全部清空时重置为 -1
51+ if ( keyword ?. trim ( ) ) {
52+ maxLevel = index ;
53+ } else if ( ! Object . values ( filters ) . some ( ( f ) => f ?. trim ( ) ) ) {
54+ maxLevel = - 1 ;
55+ }
56+ }
57+
58+ filterState . value = { filters, cascade, maxLevel } ;
59+ } ;
60+
61+ // 获取过滤后的节点 - 直接应用搜索词,确保父级变化后子级搜索仍生效
62+ const getFilteredNodes = ( nodes : TreeNode [ ] , index : number ) : TreeNode [ ] => {
63+ const state = filterState . value ;
64+ if ( ! state ) return nodes ;
65+
66+ // 直接应用当前层级的搜索词(无论级联模式还是基础模式)
67+ const keyword = state . filters [ index ] ?. trim ( ) . toLowerCase ( ) ;
68+ if ( ! keyword ) return nodes ;
69+
70+ return nodes . filter ( ( n ) => n . label ?. toLowerCase ( ) . includes ( keyword ) ) ;
71+ } ;
72+
73+ // 判断面板是否应该显示
74+ const shouldShowPanel = ( index : number ) : boolean => {
75+ const state = filterState . value ;
76+ if ( ! state ?. cascade || state . maxLevel < 0 ) return true ;
77+ // 只显示 maxLevel 范围内的面板
78+ return index <= state . maxLevel ;
79+ } ;
80+
81+ // 处理节点展开
82+ const handleExpand = ( node : TreeNode , trigger : 'hover' | 'click' , level : number ) => {
83+ const state = filterState . value ;
84+
85+ // 级联模式下更新 maxLevel 以显示子级面板
86+ const { children } = node ;
87+ if (
88+ state ?. cascade &&
89+ state . maxLevel >= 0 &&
90+ props . trigger === trigger &&
91+ Array . isArray ( children ) &&
92+ children . length
93+ ) {
94+ const childLevel = level + 1 ;
95+ if ( childLevel > state . maxLevel ) {
96+ filterState . value = { ...state , maxLevel : childLevel } ;
97+ }
98+ }
99+
100+ expandClickEffect ( props . trigger , trigger , node , props . cascaderContext ) ;
36101 } ;
37102
38103 const renderItem = ( node : TreeNode , index : number ) => {
@@ -42,7 +107,7 @@ export default defineComponent({
42107 params : {
43108 item : node . data ,
44109 index,
45- onExpand : ( ) => handleExpand ( node , 'click' ) ,
110+ onExpand : ( ) => handleExpand ( node , 'click' , index ) ,
46111 onChange : ( ) => valueChangeEffect ( node , props . cascaderContext ) ,
47112 } ,
48113 } ) ;
@@ -52,42 +117,53 @@ export default defineComponent({
52117 node = { node }
53118 optionChild = { optionChild }
54119 cascaderContext = { props . cascaderContext }
55- onClick = { ( ) => {
56- handleExpand ( node , 'click' ) ;
57- } }
58- onMouseenter = { ( ) => {
59- handleExpand ( node , 'hover' ) ;
60- } }
61- onChange = { ( ) => {
62- valueChangeEffect ( node , props . cascaderContext ) ;
63- } }
120+ onClick = { ( ) => handleExpand ( node , 'click' , index ) }
121+ onMouseenter = { ( ) => handleExpand ( node , 'hover' , index ) }
122+ onChange = { ( ) => valueChangeEffect ( node , props . cascaderContext ) }
64123 />
65124 ) ;
66125 } ;
67126
68- const renderList = ( treeNodes : TreeNode [ ] , isFilter = false , segment = true , index = 1 ) => (
69- < ul
70- class = { [
71- `${ COMPONENT_NAME . value } __menu` ,
72- 'narrow-scrollbar' ,
73- {
74- [ `${ COMPONENT_NAME . value } __menu--segment` ] : segment ,
75- [ `${ COMPONENT_NAME . value } __menu--filter` ] : isFilter ,
76- } ,
77- ] }
78- key = { `${ COMPONENT_NAME } __menu${ index } ` }
79- >
80- { treeNodes . map ( ( node : TreeNode ) => renderItem ( node , index ) ) }
81- </ ul >
82- ) ;
127+ const renderList = ( treeNodes : TreeNode [ ] , isFilter = false , segment = true , index = 0 ) => {
128+ const displayNodes = filterState . value ? getFilteredNodes ( treeNodes , index ) : treeNodes ;
129+
130+ return (
131+ < ul
132+ class = { [
133+ `${ COMPONENT_NAME . value } __menu` ,
134+ 'narrow-scrollbar' ,
135+ {
136+ [ `${ COMPONENT_NAME . value } __menu--segment` ] : segment ,
137+ [ `${ COMPONENT_NAME . value } __menu--filter` ] : isFilter ,
138+ } ,
139+ ] }
140+ key = { `${ COMPONENT_NAME } __menu${ index } ` }
141+ >
142+ { renderTNodeJSX ( 'panelHeader' , {
143+ params : {
144+ panelIndex : index ,
145+ options : treeNodes ,
146+ onFilter : ( filter : string , opts ?: { cascade ?: boolean } ) => handleFilter ( index , filter , opts ) ,
147+ } ,
148+ } ) }
149+ { displayNodes . map ( ( node : TreeNode ) => renderItem ( node , index ) ) }
150+ { renderTNodeJSX ( 'panelFooter' , { params : { panelIndex : index } } ) }
151+ </ ul >
152+ ) ;
153+ } ;
83154
84155 const renderPanels = ( ) => {
85156 const { inputVal, treeNodes } = props . cascaderContext ;
86- return inputVal
87- ? renderList ( treeNodes , true )
88- : panels . value . map ( ( treeNodes , index : number ) =>
89- renderList ( treeNodes , false , index !== panels . value . length - 1 , index ) ,
90- ) ;
157+ if ( inputVal ) return renderList ( treeNodes , true ) ;
158+
159+ const result = [ ] ;
160+ const len = panels . value . length ;
161+ for ( let i = 0 ; i < len ; i ++ ) {
162+ if ( shouldShowPanel ( i ) ) {
163+ result . push ( renderList ( panels . value [ i ] , false , i !== len - 1 , i ) ) ;
164+ }
165+ }
166+ return result ;
91167 } ;
92168
93169 return ( ) => {
0 commit comments