66 ReloadOutlined ,
77} from "@ant-design/icons" ;
88import { useEffect , useState } from "react" ;
9+ import React from "react" ;
910import { useTranslation } from "react-i18next" ;
1011
1112interface FilterOption {
@@ -56,7 +57,8 @@ export function SearchControls({
5657 showViewToggle = true ,
5758 searchPlaceholder,
5859 filters = [ ] ,
59- dateRange,
60+ selectedFilters : externalSelectedFilters ,
61+ dateRange : externalDateRange ,
6062 showDatePicker = false ,
6163 showReload = true ,
6264 onReload,
@@ -67,10 +69,36 @@ export function SearchControls({
6769 onClearFilters,
6870} : SearchControlsProps ) {
6971 const { t } = useTranslation ( ) ;
70- const [ selectedFilters , setSelectedFilters ] = useState < {
72+
73+ // 内部状态(如果外部没有传入 selectedFilters)
74+ const [ internalSelectedFilters , setInternalSelectedFilters ] = useState < {
7175 [ key : string ] : string [ ] ;
7276 } > ( { } ) ;
7377
78+ // 使用外部传入的 selectedFilters,如果没有则使用内部状态
79+ const selectedFilters = externalSelectedFilters !== undefined
80+ ? externalSelectedFilters
81+ : internalSelectedFilters ;
82+
83+ // 内部 dateRange 状态(用于禁用逻辑)
84+ const [ internalDateRange , setInternalDateRange ] = useState < typeof externalDateRange > ( externalDateRange ) ;
85+
86+ // 同步外部 dateRange 到内部状态
87+ useEffect ( ( ) => {
88+ setInternalDateRange ( externalDateRange ) ;
89+ } , [ externalDateRange ] ) ;
90+
91+ // 更新筛选值的函数
92+ const updateSelectedFilters = ( newFilters : Record < string , string [ ] > ) => {
93+ if ( externalSelectedFilters !== undefined ) {
94+ // 受控模式:直接调用 onFiltersChange
95+ onFiltersChange ?.( newFilters ) ;
96+ } else {
97+ // 非受控模式:更新内部状态
98+ setInternalSelectedFilters ( newFilters ) ;
99+ }
100+ } ;
101+
74102 const filtersMap : Record < string , FilterOption > = filters . reduce (
75103 ( prev , cur ) => ( { ...prev , [ cur . key ] : cur } ) ,
76104 { }
@@ -80,42 +108,51 @@ export function SearchControls({
80108 const handleFilterChange = ( filterKey : string , value : string ) => {
81109 const filteredValues = {
82110 ...selectedFilters ,
83- [ filterKey ] : ! value ? [ ] : [ value ] ,
111+ [ filterKey ] : ! value || value === 'all' ? [ ] : [ value ] ,
84112 } ;
85- setSelectedFilters ( filteredValues ) ;
113+ updateSelectedFilters ( filteredValues ) ;
86114 } ;
87115
88116 // 清除已选筛选
89117 const handleClearFilter = ( filterKey : string , value : string | string [ ] ) => {
90118 const isMultiple = filtersMap [ filterKey ] ?. mode === "multiple" ;
91119 if ( ! isMultiple ) {
92- setSelectedFilters ( {
120+ updateSelectedFilters ( {
93121 ...selectedFilters ,
94122 [ filterKey ] : [ ] ,
95123 } ) ;
96124 } else {
97125 const currentValues = selectedFilters [ filterKey ] ?. [ 0 ] || [ ] ;
98126 const newValues = currentValues . filter ( ( v ) => v !== value ) ;
99- setSelectedFilters ( {
127+ updateSelectedFilters ( {
100128 ...selectedFilters ,
101129 [ filterKey ] : [ newValues ] ,
102130 } ) ;
103131 }
104132 } ;
105133
106134 const handleClearAllFilters = ( ) => {
107- setSelectedFilters ( { } ) ;
135+ updateSelectedFilters ( { } ) ;
108136 onClearFilters ?.( ) ;
109137 } ;
110138
111139 const hasActiveFilters = Object . values ( selectedFilters ) . some (
112- ( values ) => values ?. [ 0 ] ?. length > 0
140+ ( values ) => Array . isArray ( values ) && values . length > 0 && values [ 0 ] !== undefined
113141 ) ;
114142
143+ // 同步外部 selectedFilters 到内部状态
115144 useEffect ( ( ) => {
145+ if ( externalSelectedFilters !== undefined ) {
146+ setInternalSelectedFilters ( externalSelectedFilters ) ;
147+ }
148+ } , [ externalSelectedFilters ] ) ;
149+
150+ // 非受控模式下,当内部状态变化时通知父组件
151+ useEffect ( ( ) => {
152+ if ( externalSelectedFilters !== undefined ) return ; // 受控模式不需要这个 effect
116153 if ( Object . keys ( selectedFilters ) . length === 0 ) return ;
117154 onFiltersChange ?.( selectedFilters ) ;
118- } , [ selectedFilters ] ) ;
155+ } , [ selectedFilters , onFiltersChange , externalSelectedFilters ] ) ;
119156
120157 return (
121158 < div className = { className } >
@@ -160,11 +197,79 @@ export function SearchControls({
160197
161198 { showDatePicker && (
162199 < DatePicker . RangePicker
163- value = { dateRange as any }
164- onChange = { onDateChange }
165- style = { { width : 260 } }
200+ value = { internalDateRange as any }
201+ onChange = { ( date ) => {
202+ setInternalDateRange ( date ) ;
203+ onDateChange ?.( date ) ;
204+ } }
205+ showTime = { { format : 'HH:mm:ss' } }
206+ format = "YYYY-MM-DD HH:mm:ss"
207+ style = { { width : 380 } }
166208 allowClear
167209 placeholder = { [ t ( 'components.searchControls.startTime' ) , t ( 'components.searchControls.endTime' ) ] }
210+ disabledDate = { ( current , info ) => {
211+ // 只禁用日期部分,同一天不禁用
212+ const startDate = info . from ;
213+ if ( ! startDate ) {
214+ return false ;
215+ }
216+ // 如果是同一天,不禁用(让时间选择器处理)
217+ if ( current . isSame ( startDate , 'day' ) ) {
218+ return false ;
219+ }
220+ // 禁用早于开始日期的日期
221+ return current . isBefore ( startDate , 'day' ) ;
222+ } }
223+ disabledTime = { ( current , partial , info ) => {
224+ // partial 是 'start' 或 'end',表示当前正在选择哪个
225+ // info.from 是已选择的开始日期
226+ const startDate = info . from ;
227+
228+ if ( partial !== 'end' || ! startDate ) {
229+ return {
230+ disabledHours : ( ) => [ ] ,
231+ disabledMinutes : ( ) => [ ] ,
232+ disabledSeconds : ( ) => [ ] ,
233+ } ;
234+ }
235+
236+ const startHour = startDate . hour ( ) ;
237+ const startMinute = startDate . minute ( ) ;
238+ const startSecond = startDate . second ( ) ;
239+
240+ return {
241+ disabledHours : ( ) => {
242+ const hours = [ ] ;
243+ for ( let i = 0 ; i < startHour ; i ++ ) {
244+ hours . push ( i ) ;
245+ }
246+ return hours ;
247+ } ,
248+ disabledMinutes : ( selectedHour ) => {
249+ if ( selectedHour > startHour ) {
250+ return [ ] ;
251+ }
252+ const minutes = [ ] ;
253+ for ( let i = 0 ; i < startMinute ; i ++ ) {
254+ minutes . push ( i ) ;
255+ }
256+ return minutes ;
257+ } ,
258+ disabledSeconds : ( selectedHour , selectedMinute ) => {
259+ if ( selectedHour > startHour ) {
260+ return [ ] ;
261+ }
262+ if ( selectedHour === startHour && selectedMinute > startMinute ) {
263+ return [ ] ;
264+ }
265+ const seconds = [ ] ;
266+ for ( let i = 0 ; i < startSecond ; i ++ ) {
267+ seconds . push ( i ) ;
268+ }
269+ return seconds ;
270+ } ,
271+ } ;
272+ } }
168273 />
169274 ) }
170275
@@ -191,15 +296,16 @@ export function SearchControls({
191296 </ div >
192297
193298 { /* Active Filters Display */ }
194- { hasActiveFilters && (
299+ { ( hasActiveFilters || internalDateRange ) && (
195300 < div className = "mt-4 pt-4 border-t border-gray-200" >
196301 < div className = "flex items-center justify-between" >
197302 < div className = "flex items-center gap-2 flex-wrap flex-1" >
198303 < span className = "text-sm font-medium text-gray-700" >
199304 { t ( 'components.searchControls.selectedFilters' ) }
200305 </ span >
201306 { Object . entries ( selectedFilters ) . map ( ( [ filterKey , values ] ) =>
202- values . map ( ( value ) => {
307+ // 只处理数组类型的筛选值
308+ Array . isArray ( values ) && values . map ( ( value ) => {
203309 const filter = filtersMap [ filterKey ] ;
204310
205311 const getLabeledValue = ( item : string ) => {
@@ -222,16 +328,39 @@ export function SearchControls({
222328 : getLabeledValue ( value ) ;
223329 } )
224330 ) }
331+ { /* 显示时间范围标签 */ }
332+ { internalDateRange && internalDateRange [ 0 ] && (
333+ < Tag closable onClose = { ( ) => {
334+ setInternalDateRange ( null ) ;
335+ onDateChange ?.( null ) ;
336+ } } color = "blue" >
337+ { t ( 'components.searchControls.startTime' ) } : { internalDateRange [ 0 ] ?. format ( 'YYYY-MM-DD HH:mm:ss' ) }
338+ </ Tag >
339+ ) }
340+ { internalDateRange && internalDateRange [ 1 ] && (
341+ < React . Fragment key = "time-separator" >
342+ < span className = "text-gray-400 mx-1" > ~</ span >
343+ < Tag closable onClose = { ( ) => {
344+ setInternalDateRange ( null ) ;
345+ onDateChange ?.( null ) ;
346+ } } color = "blue" >
347+ { t ( 'components.searchControls.endTime' ) } : { internalDateRange [ 1 ] ?. format ( 'YYYY-MM-DD HH:mm:ss' ) }
348+ </ Tag >
349+ </ React . Fragment >
350+ ) }
225351 </ div >
226352
227353 { /* Clear all filters button on the right */ }
228354 < Button
229355 type = "text"
230356 size = "small"
231- onClick = { handleClearAllFilters }
357+ onClick = { ( ) => {
358+ handleClearAllFilters ( ) ;
359+ onDateChange ?.( null ) ;
360+ } }
232361 className = "text-gray-500 hover:text-gray-700"
233362 >
234- { t ( 'components.searchControls.clearAll' ) }
363+ { t ( 'components.searchControls.filters. clearAll' ) }
235364 </ Button >
236365 </ div >
237366 </ div >
0 commit comments