feat: 调整时间中心搜索逻辑
This commit is contained in:
@@ -35,23 +35,18 @@ const UnifiedSearchBox = ({
|
||||
const [importance, setImportance] = useState('all'); // 重要性
|
||||
const [dateRange, setDateRange] = useState(null); // 日期范围
|
||||
|
||||
// ✅ 本地输入状态 - 管理用户的实时输入
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
// 使用全局行业数据
|
||||
const { industryData, loadIndustryData, loading: industryLoading } = useIndustry();
|
||||
|
||||
// ✅ 从 filters 推导搜索框显示值(受控状态)
|
||||
const searchValue = useMemo(() => {
|
||||
if (filters.stock_code && allStocks.length > 0) {
|
||||
// 股票搜索模式:显示 "代码 名称"
|
||||
const stock = allStocks.find(s => s.code === filters.stock_code);
|
||||
return stock ? `${stock.code} ${stock.name}` : filters.stock_code;
|
||||
}
|
||||
// 话题搜索模式:显示关键词
|
||||
return filters.q || '';
|
||||
}, [filters.stock_code, filters.q, allStocks]);
|
||||
|
||||
// 搜索触发函数
|
||||
const triggerSearch = useCallback((params) => {
|
||||
logger.debug('UnifiedSearchBox', '触发搜索', params);
|
||||
logger.debug('UnifiedSearchBox', '【5/5】✅ 最终触发搜索 - 调用onSearch回调', {
|
||||
params: params,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
onSearch(params);
|
||||
}, [onSearch]);
|
||||
|
||||
@@ -61,7 +56,10 @@ const UnifiedSearchBox = ({
|
||||
useEffect(() => {
|
||||
// 创建防抖函数,使用 triggerSearch 而不是直接调用 onSearch
|
||||
debouncedSearchRef.current = debounce((params) => {
|
||||
logger.debug('UnifiedSearchBox', '防抖搜索触发', params);
|
||||
logger.debug('UnifiedSearchBox', '⏱️ 防抖延迟结束,执行搜索', {
|
||||
params: params,
|
||||
delayMs: 300
|
||||
});
|
||||
triggerSearch(params);
|
||||
}, 300);
|
||||
|
||||
@@ -116,7 +114,7 @@ const UnifiedSearchBox = ({
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
// ✅ 从 props.filters 初始化筛选条件(不包括搜索值,搜索值由 searchValue useMemo 推导)
|
||||
// ✅ 从 props.filters 初始化筛选条件和输入框值
|
||||
useEffect(() => {
|
||||
if (!filters) return;
|
||||
|
||||
@@ -146,7 +144,15 @@ const UnifiedSearchBox = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [filters.sort, filters.importance, filters.date_range, filters.industry_code, industryData, findIndustryPath]);
|
||||
|
||||
// ✅ 同步 filters.q 到输入框显示值
|
||||
if (filters.q) {
|
||||
setInputValue(filters.q);
|
||||
} else if (!filters.q) {
|
||||
// 如果 filters 中没有搜索关键词,清空输入框
|
||||
setInputValue('');
|
||||
}
|
||||
}, [filters.sort, filters.importance, filters.date_range, filters.industry_code, filters.q, industryData, findIndustryPath]);
|
||||
|
||||
// AutoComplete 搜索股票(模糊匹配 code 或 name)
|
||||
const handleSearch = (value) => {
|
||||
@@ -179,7 +185,7 @@ const UnifiedSearchBox = ({
|
||||
});
|
||||
};
|
||||
|
||||
// ✅ 选中股票(从下拉选择) - 直接构建参数并触发搜索
|
||||
// ✅ 选中股票(从下拉选择) - 更新输入框并触发搜索
|
||||
const handleStockSelect = (_value, option) => {
|
||||
const stockInfo = option.stockInfo;
|
||||
if (stockInfo) {
|
||||
@@ -188,12 +194,13 @@ const UnifiedSearchBox = ({
|
||||
name: stockInfo.name
|
||||
});
|
||||
|
||||
// 直接构建参数并触发搜索(股票搜索模式)
|
||||
// 更新输入框显示
|
||||
setInputValue(`${stockInfo.code} ${stockInfo.name}`);
|
||||
|
||||
// 直接构建参数并触发搜索 - 使用股票代码作为 q 参数
|
||||
const params = buildFilterParams({
|
||||
stock_code: stockInfo.code,
|
||||
q: '',
|
||||
industry_code: '',
|
||||
industry_classification: ''
|
||||
q: stockInfo.code, // 使用股票代码作为搜索关键词
|
||||
industry_code: ''
|
||||
});
|
||||
logger.debug('UnifiedSearchBox', '自动触发股票搜索', params);
|
||||
triggerSearch(params);
|
||||
@@ -202,53 +209,119 @@ const UnifiedSearchBox = ({
|
||||
|
||||
// ✅ 日期范围变化(使用防抖)
|
||||
const handleDateRangeChange = (dates) => {
|
||||
logger.debug('UnifiedSearchBox', '【1/5】日期范围值改变', {
|
||||
oldValue: dateRange,
|
||||
newValue: dates
|
||||
});
|
||||
setDateRange(dates);
|
||||
// 使用防抖搜索(与其他筛选器保持一致)
|
||||
const params = buildFilterParams();
|
||||
logger.debug('UnifiedSearchBox', '日期范围变化,准备触发搜索', { dates, params });
|
||||
|
||||
// ⚠️ 注意:setState是异步的,此时dateRange仍是旧值
|
||||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||||
dateRange: dateRange, // 旧值
|
||||
sort: sort,
|
||||
importance: importance,
|
||||
industryValue: industryValue
|
||||
});
|
||||
|
||||
// 使用防抖搜索(需要从新值推导参数)
|
||||
const params = {
|
||||
...buildFilterParams(),
|
||||
date_range: dates ? `${dates[0].format('YYYY-MM-DD')} 至 ${dates[1].format('YYYY-MM-DD')}` : ''
|
||||
};
|
||||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||||
|
||||
if (debouncedSearchRef.current) {
|
||||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||||
debouncedSearchRef.current(params);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 重要性变化(使用防抖)
|
||||
const handleImportanceChange = (value) => {
|
||||
logger.debug('UnifiedSearchBox', '【1/5】重要性值改变', {
|
||||
oldValue: importance,
|
||||
newValue: value
|
||||
});
|
||||
setImportance(value);
|
||||
|
||||
// ⚠️ 注意:setState是异步的,此时importance仍是旧值
|
||||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||||
importance: importance, // 旧值
|
||||
sort: sort,
|
||||
dateRange: dateRange,
|
||||
industryValue: industryValue
|
||||
});
|
||||
|
||||
// 使用防抖搜索
|
||||
const params = buildFilterParams({ importance: value });
|
||||
logger.debug('UnifiedSearchBox', '重要性变化,准备触发搜索', params);
|
||||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||||
|
||||
if (debouncedSearchRef.current) {
|
||||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||||
debouncedSearchRef.current(params);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 排序变化(使用防抖)
|
||||
const handleSortChange = (value) => {
|
||||
logger.debug('UnifiedSearchBox', '【1/5】排序值改变', {
|
||||
oldValue: sort,
|
||||
newValue: value
|
||||
});
|
||||
setSort(value);
|
||||
|
||||
// ⚠️ 注意:setState是异步的,此时sort仍是旧值
|
||||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||||
sort: sort, // 旧值
|
||||
importance: importance,
|
||||
dateRange: dateRange,
|
||||
industryValue: industryValue
|
||||
});
|
||||
|
||||
// 使用防抖搜索
|
||||
const params = buildFilterParams({ sort: value });
|
||||
logger.debug('UnifiedSearchBox', '排序变化,准备触发搜索', params);
|
||||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||||
|
||||
if (debouncedSearchRef.current) {
|
||||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||||
debouncedSearchRef.current(params);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 行业分类变化(使用防抖)
|
||||
const handleIndustryChange = (value) => {
|
||||
setIndustryValue(value);
|
||||
// 使用防抖搜索
|
||||
const params = buildFilterParams();
|
||||
logger.debug('UnifiedSearchBox', '行业分类变化,准备触发搜索', {
|
||||
industry: value,
|
||||
params
|
||||
logger.debug('UnifiedSearchBox', '【1/5】行业分类值改变', {
|
||||
oldValue: industryValue,
|
||||
newValue: value
|
||||
});
|
||||
setIndustryValue(value);
|
||||
|
||||
// ⚠️ 注意:setState是异步的,此时industryValue仍是旧值
|
||||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||||
industryValue: industryValue, // 旧值
|
||||
sort: sort,
|
||||
importance: importance,
|
||||
dateRange: dateRange
|
||||
});
|
||||
|
||||
// 使用防抖搜索 (需要从新值推导参数)
|
||||
const params = {
|
||||
...buildFilterParams(),
|
||||
industry_code: value?.[value.length - 1] || ''
|
||||
};
|
||||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||||
|
||||
if (debouncedSearchRef.current) {
|
||||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||||
debouncedSearchRef.current(params);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 热门概念点击处理(立即搜索,不使用防抖) - 直接构建参数并触发搜索
|
||||
// ✅ 热门概念点击处理(立即搜索,不使用防抖) - 更新输入框并触发搜索
|
||||
const handleKeywordClick = (keyword) => {
|
||||
// 更新输入框显示
|
||||
setInputValue(keyword);
|
||||
|
||||
// 立即触发搜索(取消之前的防抖)
|
||||
if (debouncedSearchRef.current) {
|
||||
debouncedSearchRef.current.cancel();
|
||||
@@ -256,9 +329,7 @@ const UnifiedSearchBox = ({
|
||||
|
||||
const params = buildFilterParams({
|
||||
q: keyword,
|
||||
stock_code: '',
|
||||
industry_code: '',
|
||||
industry_classification: ''
|
||||
industry_code: ''
|
||||
});
|
||||
logger.debug('UnifiedSearchBox', '热门概念点击,立即触发搜索', {
|
||||
keyword,
|
||||
@@ -269,37 +340,62 @@ const UnifiedSearchBox = ({
|
||||
|
||||
// 主搜索(点击搜索按钮或回车)
|
||||
const handleMainSearch = () => {
|
||||
// 直接输入文本时,构建参数并触发搜索
|
||||
logger.debug('UnifiedSearchBox', '主搜索触发', { searchValue });
|
||||
handleApplyFilters();
|
||||
// 取消之前的防抖
|
||||
if (debouncedSearchRef.current) {
|
||||
debouncedSearchRef.current.cancel();
|
||||
}
|
||||
|
||||
// 构建参数并触发搜索 - 使用用户输入作为 q 参数
|
||||
const params = buildFilterParams({
|
||||
q: inputValue, // 使用用户输入(可能是话题、股票代码、股票名称等)
|
||||
industry_code: ''
|
||||
});
|
||||
|
||||
logger.debug('UnifiedSearchBox', '主搜索触发', {
|
||||
inputValue,
|
||||
params
|
||||
});
|
||||
triggerSearch(params);
|
||||
};
|
||||
|
||||
// ✅ 处理输入变化 - AutoComplete 内部处理显示,不需要更新状态
|
||||
// ✅ 处理输入变化 - 更新本地输入状态
|
||||
const handleInputChange = (value) => {
|
||||
// AutoComplete 组件会自动处理显示值
|
||||
// 这里可以添加其他逻辑(如实时搜索建议)
|
||||
logger.debug('UnifiedSearchBox', '输入变化', { value });
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
// ✅ 生成完整的筛选参数对象 - 直接从 filters 和本地筛选器状态构建
|
||||
const buildFilterParams = useCallback((overrides = {}) => {
|
||||
return {
|
||||
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输入参数', {
|
||||
overrides: overrides,
|
||||
currentState: {
|
||||
sort,
|
||||
importance,
|
||||
dateRange,
|
||||
industryValue,
|
||||
'filters.q': filters.q
|
||||
}
|
||||
});
|
||||
|
||||
const result = {
|
||||
// 基础参数(overrides 优先级高于本地状态)
|
||||
sort: overrides.sort ?? sort,
|
||||
importance: overrides.importance ?? importance,
|
||||
date_range: dateRange ? `${dateRange[0].format('YYYY-MM-DD')} 至 ${dateRange[1].format('YYYY-MM-DD')}` : '',
|
||||
page: 1,
|
||||
|
||||
// 搜索参数(从 filters 继承,overrides 可覆盖)
|
||||
// 搜索参数: 统一使用 q 参数进行搜索(话题/股票/关键词)
|
||||
q: overrides.q ?? filters.q ?? '',
|
||||
stock_code: overrides.stock_code ?? filters.stock_code ?? '',
|
||||
// 行业代码: 取选中路径的最后一级(最具体的行业代码)
|
||||
industry_code: overrides.industry_code ?? (industryValue?.[industryValue.length - 1] || ''),
|
||||
industry_classification: overrides.industry_classification ?? (industryValue?.[0] || ''),
|
||||
|
||||
// 最终 overrides 具有最高优先级
|
||||
...overrides
|
||||
};
|
||||
}, [sort, importance, dateRange, filters.q, filters.stock_code, industryValue]);
|
||||
|
||||
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
|
||||
return result;
|
||||
}, [sort, importance, dateRange, filters.q, industryValue]);
|
||||
|
||||
// ✅ 应用筛选(立即搜索,取消防抖)
|
||||
const handleApplyFilters = () => {
|
||||
@@ -316,6 +412,7 @@ const UnifiedSearchBox = ({
|
||||
// ✅ 重置筛选 - 清空所有筛选器并触发搜索
|
||||
const handleReset = () => {
|
||||
// 重置所有筛选器状态
|
||||
setInputValue(''); // 清空输入框
|
||||
setStockOptions([]);
|
||||
setIndustryValue([]);
|
||||
setSort('new');
|
||||
@@ -325,9 +422,7 @@ const UnifiedSearchBox = ({
|
||||
// 输出重置后的完整参数
|
||||
const resetParams = {
|
||||
q: '',
|
||||
stock_code: '',
|
||||
industry_code: '',
|
||||
industry_classification: '',
|
||||
sort: 'new',
|
||||
importance: 'all',
|
||||
date_range: '',
|
||||
@@ -342,13 +437,9 @@ const UnifiedSearchBox = ({
|
||||
const filterTags = useMemo(() => {
|
||||
const tags = [];
|
||||
|
||||
// 股票/话题标签 - 从 filters 读取
|
||||
if (filters.stock_code) {
|
||||
const stock = allStocks.find(s => s.code === filters.stock_code);
|
||||
const label = stock ? `${stock.code} ${stock.name}` : filters.stock_code;
|
||||
tags.push({ key: 'stock', label: `股票: ${label}` });
|
||||
} else if (filters.q) {
|
||||
tags.push({ key: 'topic', label: `话题: ${filters.q}` });
|
||||
// 搜索关键词标签 - 从 filters.q 读取
|
||||
if (filters.q) {
|
||||
tags.push({ key: 'search', label: `搜索: ${filters.q}` });
|
||||
}
|
||||
|
||||
// 行业标签
|
||||
@@ -375,21 +466,22 @@ const UnifiedSearchBox = ({
|
||||
}
|
||||
|
||||
return tags;
|
||||
}, [filters.stock_code, filters.q, allStocks, industryValue, dateRange, importance, sort]);
|
||||
}, [filters.q, industryValue, dateRange, importance, sort]);
|
||||
|
||||
// ✅ 移除单个标签 - 构建新参数并触发搜索
|
||||
const handleRemoveTag = (key) => {
|
||||
logger.debug('UnifiedSearchBox', '移除标签', { key });
|
||||
|
||||
if (key === 'topic' || key === 'stock') {
|
||||
// 清除搜索关键词/股票,立即触发搜索
|
||||
const params = buildFilterParams({ q: '', stock_code: '' });
|
||||
if (key === 'search') {
|
||||
// 清除搜索关键词和输入框,立即触发搜索
|
||||
setInputValue(''); // 清空输入框
|
||||
const params = buildFilterParams({ q: '' });
|
||||
logger.debug('UnifiedSearchBox', '移除搜索标签后触发搜索', { key, params });
|
||||
triggerSearch(params);
|
||||
} else if (key === 'industry') {
|
||||
// 清除行业选择
|
||||
setIndustryValue([]);
|
||||
const params = buildFilterParams({ industry_code: '', industry_classification: '' });
|
||||
const params = buildFilterParams({ industry_code: '' });
|
||||
triggerSearch(params);
|
||||
} else if (key === 'date_range') {
|
||||
// 清除日期范围
|
||||
@@ -425,7 +517,7 @@ const UnifiedSearchBox = ({
|
||||
color: '#666'
|
||||
}} />
|
||||
<AutoComplete
|
||||
value={searchValue}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleStockSelect}
|
||||
@@ -434,7 +526,7 @@ const UnifiedSearchBox = ({
|
||||
onPressEnter={handleMainSearch}
|
||||
style={{ flex: 1 }}
|
||||
size="large"
|
||||
notFoundContent={searchValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
||||
notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -23,15 +23,33 @@ export const useEventData = (filters, pageSize = 10) => {
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
|
||||
|
||||
// 加载事件列表
|
||||
const loadEvents = useCallback(async (page = 1) => {
|
||||
logger.debug('useEventData', 'loadEvents 被调用', { page });
|
||||
// ✅ 修复闭包陷阱: 接受 currentFilters 参数,避免使用闭包中的旧 filters
|
||||
const loadEvents = useCallback(async (page = 1, currentFilters = null) => {
|
||||
// 使用传入的 currentFilters 或回退到闭包中的 filters
|
||||
const filtersToUse = currentFilters || filters;
|
||||
|
||||
const requestParams = {
|
||||
...filtersToUse,
|
||||
page,
|
||||
per_page: pagination.pageSize
|
||||
};
|
||||
|
||||
logger.debug('useEventData', '📡 【准备发起API请求】loadEvents 被调用', {
|
||||
page,
|
||||
currentFilters,
|
||||
filtersToUse,
|
||||
requestParams
|
||||
});
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await eventService.getEvents({
|
||||
...filters,
|
||||
page,
|
||||
per_page: pagination.pageSize
|
||||
logger.debug('useEventData', '🌐 正在调用 eventService.getEvents', { requestParams });
|
||||
const response = await eventService.getEvents(requestParams);
|
||||
logger.debug('useEventData', '✅ API响应成功', {
|
||||
success: response.success,
|
||||
eventCount: response.data?.events?.length,
|
||||
total: response.data?.pagination?.total
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
@@ -49,9 +67,9 @@ export const useEventData = (filters, pageSize = 10) => {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('useEventData', 'loadEvents', error, {
|
||||
logger.error('useEventData', '❌ loadEvents 失败', error, {
|
||||
page,
|
||||
filters
|
||||
filtersToUse
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -59,20 +77,34 @@ export const useEventData = (filters, pageSize = 10) => {
|
||||
}, [filters, pagination.pageSize]);
|
||||
|
||||
// 创建防抖的 loadEvents 函数(500ms 防抖延迟)
|
||||
// ✅ 修复闭包陷阱: 防抖函数接受 filters 参数并传递给 loadEvents
|
||||
const debouncedLoadEvents = useRef(
|
||||
debounce((page) => {
|
||||
logger.debug('useEventData', '防抖后执行 loadEvents', { page });
|
||||
loadEvents(page);
|
||||
debounce((page, filters) => {
|
||||
logger.debug('useEventData', '⏱️ 【防抖延迟500ms结束】即将执行 loadEvents', {
|
||||
page,
|
||||
filters
|
||||
});
|
||||
loadEvents(page, filters);
|
||||
}, 500)
|
||||
).current;
|
||||
|
||||
// 监听 filters 变化,自动加载数据
|
||||
// 防抖优化:用户快速切换筛选条件时,只执行最后一次请求
|
||||
useEffect(() => {
|
||||
logger.debug('useEventData', 'useEffect 触发,filters 变化', { filters });
|
||||
logger.debug('useEventData', '🔔 【filters变化触发useEffect】完整filters对象:', filters);
|
||||
logger.debug('useEventData', '详细参数:', {
|
||||
page: filters.page || 1,
|
||||
sort: filters.sort,
|
||||
importance: filters.importance,
|
||||
date_range: filters.date_range,
|
||||
q: filters.q,
|
||||
industry_code: filters.industry_code,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 使用防抖加载事件
|
||||
debouncedLoadEvents(filters.page || 1);
|
||||
// ✅ 使用防抖加载事件,将当前 filters 传递给防抖函数
|
||||
logger.debug('useEventData', '⏰ 启动防抖计时器(500ms),传递最新filters');
|
||||
debouncedLoadEvents(filters.page || 1, filters);
|
||||
|
||||
// 组件卸载时取消防抖
|
||||
return () => {
|
||||
|
||||
@@ -23,18 +23,21 @@ export const useEventFilters = ({ navigate, onEventClick, eventTimelineRef } = {
|
||||
importance: searchParams.get('importance') || 'all',
|
||||
date_range: searchParams.get('date_range') || '',
|
||||
q: searchParams.get('q') || '',
|
||||
industry_classification: searchParams.get('industry_classification') || '',
|
||||
industry_code: searchParams.get('industry_code') || '',
|
||||
stock_code: searchParams.get('stock_code') || '',
|
||||
page: parseInt(searchParams.get('page') || '1', 10)
|
||||
};
|
||||
});
|
||||
|
||||
// 更新筛选参数 - 直接替换(由 UnifiedSearchBox 输出完整参数)
|
||||
const updateFilters = useCallback((newFilters) => {
|
||||
logger.debug('useEventFilters', 'updateFilters 接收到完整参数', newFilters);
|
||||
logger.debug('useEventFilters', '🔄 【接收到onSearch回调】updateFilters 接收到完整参数', {
|
||||
newFilters: newFilters,
|
||||
oldFilters: filters,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
setFilters(newFilters);
|
||||
}, []);
|
||||
logger.debug('useEventFilters', '✅ setFilters 已调用 (React异步更新中...)');
|
||||
}, [filters]);
|
||||
|
||||
// 处理分页变化
|
||||
const handlePageChange = useCallback((page) => {
|
||||
|
||||
Reference in New Issue
Block a user