Compare commits

..

5 Commits

Author SHA1 Message Date
zdl
6e5eaa9089 feat: 添加serverworker注册事件 2025-11-04 15:34:17 +08:00
zdl
8ed65b062b pref: 日志管理优化 2025-11-04 15:19:49 +08:00
zdl
868b4ccebc feat: 筛选添加收益率筛选 2025-11-04 15:19:24 +08:00
zdl
67981f21a2 feat:拆分 handlePageChange 为子函数(减少复杂度) 2025-11-04 15:05:25 +08:00
zdl
0a10270ab0 feat: 提取 usePagination Hook 2025-11-04 14:58:02 +08:00
4 changed files with 524 additions and 266 deletions

92
public/service-worker.js Normal file
View File

@@ -0,0 +1,92 @@
// public/service-worker.js
/**
* Service Worker for Browser Notifications
* 主要功能:支持浏览器通知的稳定运行
*/
const CACHE_NAME = 'valuefrontier-v1';
// Service Worker 安装事件
self.addEventListener('install', (event) => {
console.log('[Service Worker] Installing...');
// 跳过等待,立即激活
self.skipWaiting();
});
// Service Worker 激活事件
self.addEventListener('activate', (event) => {
console.log('[Service Worker] Activating...');
// 立即接管所有页面
event.waitUntil(self.clients.claim());
});
// 通知点击事件
self.addEventListener('notificationclick', (event) => {
console.log('[Service Worker] Notification clicked:', event.notification.tag);
event.notification.close();
// 获取通知数据中的链接
const urlToOpen = event.notification.data?.link;
if (urlToOpen) {
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then((windowClients) => {
// 查找是否已有打开的窗口
for (let client of windowClients) {
if (client.url.includes(window.location.origin) && 'focus' in client) {
// 聚焦现有窗口并导航到目标页面
return client.focus().then(client => {
return client.navigate(urlToOpen);
});
}
}
// 如果没有打开的窗口,打开新窗口
if (clients.openWindow) {
return clients.openWindow(urlToOpen);
}
})
);
}
});
// 通知关闭事件
self.addEventListener('notificationclose', (event) => {
console.log('[Service Worker] Notification closed:', event.notification.tag);
});
// Fetch 事件 - 基础的网络优先策略
self.addEventListener('fetch', (event) => {
// 对于通知相关的资源,使用网络优先策略
event.respondWith(
fetch(event.request)
.catch(() => {
// 网络失败时,尝试从缓存获取
return caches.match(event.request);
})
);
});
// 推送消息事件(预留,用于未来的 Push API 集成)
self.addEventListener('push', (event) => {
console.log('[Service Worker] Push message received:', event);
if (event.data) {
const data = event.data.json();
const options = {
body: data.body || '您有新消息',
icon: data.icon || '/favicon.png',
badge: '/favicon.png',
data: data.data || {},
requireInteraction: data.requireInteraction || false,
tag: data.tag || `notification_${Date.now()}`,
};
event.waitUntil(
self.registration.showNotification(data.title || '价值前沿', options)
);
}
});
console.log('[Service Worker] Loaded successfully');

View File

@@ -11,6 +11,40 @@ import './styles/brainwave-colors.css';
// Import the main App component
import App from './App';
// 注册 Service Worker用于支持浏览器通知
function registerServiceWorker() {
// 仅在支持 Service Worker 的浏览器中注册
if ('serviceWorker' in navigator) {
// 在页面加载完成后注册
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then((registration) => {
console.log('[App] Service Worker registered successfully:', registration.scope);
// 监听更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
console.log('[App] Service Worker update found');
if (newWorker) {
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
console.log('[App] Service Worker activated');
}
});
}
});
})
.catch((error) => {
console.error('[App] Service Worker registration failed:', error);
});
});
} else {
console.warn('[App] Service Worker is not supported in this browser');
}
}
// 启动 Mock Service Worker如果启用
async function startApp() {
// 只在开发环境启动 MSW
@@ -35,6 +69,9 @@ async function startApp() {
</Router>
</React.StrictMode>
);
// 注册 Service Worker
registerServiceWorker();
}
// 启动应用

View File

@@ -24,6 +24,356 @@ import EventScrollList from './DynamicNewsCard/EventScrollList';
import DynamicNewsDetailPanel from './DynamicNewsDetail';
import UnifiedSearchBox from './UnifiedSearchBox';
import { fetchDynamicNews, toggleEventFollow, selectEventFollowStatus } from '../../../store/slices/communityDataSlice';
import { logger } from '../../../utils/logger';
/**
* 分页逻辑自定义 Hook
* @param {Object} options - Hook 配置选项
* @param {Array} options.allCachedEvents - 完整缓存事件列表
* @param {number} options.total - 服务端总数量
* @param {number} options.cachedCount - 已缓存数量
* @param {Function} options.dispatch - Redux dispatch 函数
* @param {Function} options.toast - Toast 通知函数
* @returns {Object} 分页状态和方法
*/
const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, toast }) => {
// 本地状态
const [currentPage, setCurrentPage] = useState(1);
const [loadingPage, setLoadingPage] = useState(null);
const [mode, setMode] = useState('carousel'); // 'carousel' 或 'grid'
// 根据模式决定每页显示数量
const pageSize = mode === 'carousel' ? 5 : 10;
// 计算总页数(基于服务端总数据量)
const totalPages = Math.ceil(total / pageSize) || 1;
// 检查是否还有更多数据
const hasMore = cachedCount < total;
// 从缓存中切片获取当前页数据(过滤 null 占位符)
const currentPageEvents = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return allCachedEvents.slice(startIndex, endIndex).filter(event => event !== null);
}, [allCachedEvents, currentPage, pageSize]);
/**
* 子函数1: 检查目标页缓存状态
* @param {number} targetPage - 目标页码
* @returns {Object} { isTargetPageCached, targetPageInfo }
*/
const checkTargetPageCache = useCallback((targetPage) => {
const targetPageStartIndex = (targetPage - 1) * pageSize;
const targetPageEndIndex = targetPageStartIndex + pageSize;
const targetPageData = allCachedEvents.slice(targetPageStartIndex, targetPageEndIndex);
const validTargetData = targetPageData.filter(e => e !== null);
const expectedCount = Math.min(pageSize, total - targetPageStartIndex);
const isTargetPageCached = validTargetData.length >= expectedCount;
logger.debug('DynamicNewsCard', '目标页缓存检查', {
targetPage,
targetPageStartIndex,
targetPageEndIndex,
targetPageDataLength: targetPageData.length,
validTargetDataLength: validTargetData.length,
expectedCount,
isTargetPageCached
});
return {
isTargetPageCached,
targetPageInfo: {
startIndex: targetPageStartIndex,
endIndex: targetPageEndIndex,
validCount: validTargetData.length,
expectedCount
}
};
}, [allCachedEvents, pageSize, total]);
/**
* 子函数2: 计算预加载范围
* @param {number} targetPage - 目标页码
* @param {number} fromPage - 来源页码
* @returns {Array<number>} 预加载页码数组
*/
const calculatePreloadRange = useCallback((targetPage, fromPage) => {
const isSequentialNavigation = Math.abs(targetPage - fromPage) === 1;
let preloadRange;
if (isSequentialNavigation) {
// 连续翻页前后各2页共5页
const start = Math.max(1, targetPage - 2);
const end = Math.min(totalPages, targetPage + 2);
preloadRange = Array.from(
{ length: end - start + 1 },
(_, i) => start + i
);
} else {
// 跳转翻页:只加载当前页
preloadRange = [targetPage];
}
logger.debug('DynamicNewsCard', '计算预加载范围', {
targetPage,
fromPage,
isSequentialNavigation,
preloadRange
});
return preloadRange;
}, [totalPages]);
/**
* 子函数3: 查找缺失页面
* @param {Array<number>} preloadRange - 预加载范围
* @returns {Array<number>} 缺失页码数组
*/
const findMissingPages = useCallback((preloadRange) => {
const missingPages = preloadRange.filter(page => {
const pageStartIndex = (page - 1) * pageSize;
const pageEndIndex = pageStartIndex + pageSize;
// 如果该页超出数组范围,说明未缓存
if (pageEndIndex > allCachedEvents.length) {
logger.debug('DynamicNewsCard', `页面${page}超出数组范围`, {
pageStartIndex,
pageEndIndex,
allCachedEventsLength: allCachedEvents.length
});
return true;
}
// 检查该页的数据是否包含 null 占位符或数据不足
const pageData = allCachedEvents.slice(pageStartIndex, pageEndIndex);
const validData = pageData.filter(e => e !== null);
const expectedCount = Math.min(pageSize, total - pageStartIndex);
const hasNullOrIncomplete = validData.length < expectedCount;
logger.debug('DynamicNewsCard', `页面${page}数据检查`, {
pageStartIndex,
pageEndIndex,
pageDataLength: pageData.length,
validDataLength: validData.length,
expectedCount,
hasNullOrIncomplete
});
return hasNullOrIncomplete;
});
logger.debug('DynamicNewsCard', '缺失页面检测完成', {
preloadRange,
missingPages,
missingPagesCount: missingPages.length
});
return missingPages;
}, [allCachedEvents, pageSize, total]);
/**
* 子函数4: 加载页面数据
* @param {Array<number>} missingPages - 缺失页码数组
* @param {number} targetPage - 目标页码
* @param {boolean} silentMode - 静默模式(后台预加载)
* @returns {Promise<boolean>} 是否加载成功
*/
const loadPages = useCallback(async (missingPages, targetPage, silentMode = false) => {
if (!silentMode) {
// 显示 loading 状态
setLoadingPage(targetPage);
}
try {
logger.debug('DynamicNewsCard', '开始加载页面数据', {
missingPages,
targetPage,
silentMode,
pageSize
});
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
for (const page of missingPages) {
logger.debug('DynamicNewsCard', `开始加载第 ${page}`);
await dispatch(fetchDynamicNews({
page: page,
per_page: pageSize, // 固定值5或10不使用动态计算
pageSize: pageSize,
clearCache: false
})).unwrap();
logger.debug('DynamicNewsCard', `${page} 页加载完成`);
}
logger.debug('DynamicNewsCard', '所有页面加载完成', {
missingPages,
silentMode
});
return true;
} catch (error) {
logger.error('DynamicNewsCard', 'loadPages', error, {
targetPage,
silentMode,
missingPages
});
if (!silentMode) {
// 非静默模式下显示错误提示
toast({
title: '加载失败',
description: `无法加载第 ${targetPage} 页数据,请稍后重试`,
status: 'error',
duration: 3000,
isClosable: true,
position: 'top'
});
}
return false;
} finally {
if (!silentMode) {
// 清除加载状态
setLoadingPage(null);
}
}
}, [dispatch, pageSize, toast]);
// 翻页处理(智能预加载)- 使用子函数重构
const handlePageChange = useCallback(async (newPage) => {
// 🔍 诊断日志 - 记录翻页开始状态
logger.debug('DynamicNewsCard', '开始翻页', {
currentPage,
newPage,
pageSize,
totalPages,
hasMore,
total,
allCachedEventsLength: allCachedEvents.length,
cachedCount
});
// 步骤1: 检查目标页缓存状态
const { isTargetPageCached } = checkTargetPageCache(newPage);
// 步骤2: 计算预加载范围
const preloadRange = calculatePreloadRange(newPage, currentPage);
// 步骤3: 查找缺失页面
const missingPages = findMissingPages(preloadRange);
// 步骤4: 根据情况加载数据
if (isTargetPageCached && missingPages.length > 0 && hasMore) {
// 场景A: 目标页已缓存,立即切换,后台静默预加载其他页
logger.debug('DynamicNewsCard', '目标页已缓存,立即切换 + 后台预加载', {
currentPage,
newPage,
缺失页面: missingPages
});
setCurrentPage(newPage);
await loadPages(missingPages, newPage, true); // 静默模式
} else if (missingPages.length > 0 && hasMore) {
// 场景B: 目标页未缓存,显示 loading 并等待加载完成
logger.debug('DynamicNewsCard', '目标页未缓存,显示 loading', {
currentPage,
newPage,
缺失页面: missingPages
});
const success = await loadPages(missingPages, newPage, false); // 非静默模式
if (success) {
setCurrentPage(newPage);
}
} else if (missingPages.length === 0) {
// 场景C: 所有页面均已缓存,直接切换
logger.debug('DynamicNewsCard', '无需加载,直接切换', {
currentPage,
newPage,
reason: '所有页面均已缓存'
});
setCurrentPage(newPage);
} else {
// 场景D: 意外分支(有缺失页面但 hasMore=false
logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', {
missingPages,
hasMore,
currentPage,
newPage,
total,
cachedCount
});
setCurrentPage(newPage);
toast({
title: '数据不完整',
description: `${newPage} 页数据可能不完整`,
status: 'warning',
duration: 2000,
isClosable: true,
position: 'top'
});
}
}, [
currentPage,
pageSize,
totalPages,
hasMore,
total,
allCachedEvents.length,
cachedCount,
checkTargetPageCache,
calculatePreloadRange,
findMissingPages,
loadPages,
toast
]);
// 模式切换处理
const handleModeToggle = useCallback((newMode) => {
if (newMode === mode) return;
setMode(newMode);
setCurrentPage(1);
const newPageSize = newMode === 'carousel' ? 5 : 10;
// 检查第1页的数据是否完整排除 null
const firstPageData = allCachedEvents.slice(0, newPageSize);
const validFirstPageCount = firstPageData.filter(e => e !== null).length;
const needsRefetch = validFirstPageCount < Math.min(newPageSize, total);
if (needsRefetch) {
// 第1页数据不完整清空缓存重新请求
dispatch(fetchDynamicNews({
page: 1,
per_page: newPageSize,
pageSize: newPageSize, // 传递 pageSize 确保索引计算一致
clearCache: true
}));
}
// 如果第1页数据完整不发起请求直接切换
}, [mode, allCachedEvents, total, dispatch]);
return {
// 状态
currentPage,
mode,
loadingPage,
pageSize,
totalPages,
hasMore,
currentPageEvents,
// 方法
handlePageChange,
handleModeToggle
};
};
/**
* 实时要闻·动态追踪 - 事件展示卡片组件
@@ -69,271 +419,25 @@ const DynamicNewsCard = forwardRef(({
// 本地状态
const [selectedEvent, setSelectedEvent] = useState(null);
const [mode, setMode] = useState('carousel'); // 'carousel' 或 'grid',默认单排
const [currentPage, setCurrentPage] = useState(1); // 当前页码
const [loadingPage, setLoadingPage] = useState(null); // 正在加载的目标页码(用于 UX 提示)
// 根据模式决定每页显示数量
const pageSize = mode === 'carousel' ? 5 : 10;
// 计算总页数(基于服务端总数据量)
const totalPages = Math.ceil(total / pageSize) || 1;
// 检查是否还有更多数据
const hasMore = cachedCount < total;
// 从缓存中切片获取当前页数据(过滤 null 占位符)
const currentPageEvents = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return allCachedEvents.slice(startIndex, endIndex).filter(event => event !== null);
}, [allCachedEvents, currentPage, pageSize]);
// 翻页处理(智能预加载)
const handlePageChange = useCallback(async (newPage) => {
// 🔍 诊断日志 - 记录翻页开始状态
console.log('[handlePageChange] 开始翻页', {
currentPage,
newPage,
pageSize,
totalPages,
hasMore,
total,
allCachedEventsLength: allCachedEvents.length,
cachedCount
});
// 0. 首先检查目标页数据是否已完整缓存
const targetPageStartIndex = (newPage - 1) * pageSize;
const targetPageEndIndex = targetPageStartIndex + pageSize;
const targetPageData = allCachedEvents.slice(targetPageStartIndex, targetPageEndIndex);
const validTargetData = targetPageData.filter(e => e !== null);
const expectedCount = Math.min(pageSize, total - targetPageStartIndex);
const isTargetPageCached = validTargetData.length >= expectedCount;
console.log('[handlePageChange] 目标页缓存检查', {
newPage,
targetPageStartIndex,
targetPageEndIndex,
targetPageDataLength: targetPageData.length,
validTargetDataLength: validTargetData.length,
expectedCount,
isTargetPageCached
});
// 1. 判断翻页类型:连续翻页(上一页/下一页)还是跳转翻页(点击页码/输入跳转)
const isSequentialNavigation = Math.abs(newPage - currentPage) === 1;
// 2. 计算预加载范围
let preloadRange;
if (isSequentialNavigation) {
// 连续翻页前后各2页共5页
const start = Math.max(1, newPage - 2);
const end = Math.min(totalPages, newPage + 2);
preloadRange = Array.from(
{ length: end - start + 1 },
(_, i) => start + i
);
} else {
// 跳转翻页:只加载当前页
preloadRange = [newPage];
}
// 3. 检查哪些页面的数据还未缓存(检查是否包含 null 或超出数组长度)
const missingPages = preloadRange.filter(page => {
const pageStartIndex = (page - 1) * pageSize;
const pageEndIndex = pageStartIndex + pageSize;
// 如果该页超出数组范围,说明未缓存
if (pageEndIndex > allCachedEvents.length) {
console.log(`[missingPages] 页面${page}超出数组范围`, {
pageStartIndex,
pageEndIndex,
allCachedEventsLength: allCachedEvents.length
});
return true;
}
// 检查该页的数据是否包含 null 占位符或数据不足
const pageData = allCachedEvents.slice(pageStartIndex, pageEndIndex);
const validData = pageData.filter(e => e !== null);
const expectedCount = Math.min(pageSize, total - pageStartIndex);
const hasNullOrIncomplete = validData.length < expectedCount;
console.log(`[missingPages] 页面${page}检查`, {
pageStartIndex,
pageEndIndex,
pageDataLength: pageData.length,
validDataLength: validData.length,
expectedCount,
hasNullOrIncomplete
});
return hasNullOrIncomplete;
});
console.log('[handlePageChange] 缺失页面检测完成', {
preloadRange,
missingPages,
missingPagesCount: missingPages.length
});
// 4. 如果目标页已缓存,立即切换页码,然后在后台静默预加载其他页
if (isTargetPageCached && missingPages.length > 0 && hasMore) {
console.log('[DynamicNewsCard] 目标页已缓存,立即切换', {
currentPage,
newPage,
缺失页面: missingPages,
目标页已缓存: true
});
// 立即切换页码(用户无感知延迟)
setCurrentPage(newPage);
// 在后台静默预加载其他缺失页面(拆分为单页请求)
try {
console.log('[DynamicNewsCard] 开始后台预加载', {
缺失页面: missingPages,
每页数量: pageSize
});
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
for (const page of missingPages) {
await dispatch(fetchDynamicNews({
page: page,
per_page: pageSize, // 固定值5或10不使用动态计算
pageSize: pageSize,
clearCache: false
})).unwrap();
console.log(`[DynamicNewsCard] 后台预加载第 ${page} 页完成`);
}
console.log('[DynamicNewsCard] 后台预加载全部完成', {
预加载页面: missingPages
});
} catch (error) {
console.error('[DynamicNewsCard] 后台预加载失败', error);
// 静默失败,不影响用户体验
}
return; // 提前返回,不执行下面的加载逻辑
}
// 5. 如果目标页未缓存,显示 loading 并等待加载完成
if (missingPages.length > 0 && hasMore) {
console.log('[DynamicNewsCard] 目标页未缓存显示loading', {
currentPage,
newPage,
翻页类型: isSequentialNavigation ? '连续翻页' : '跳转翻页',
预加载范围: preloadRange,
缺失页面: missingPages,
每页数量: pageSize,
目标页已缓存: false
});
try {
// 设置加载状态(显示"正在加载第X页..."
setLoadingPage(newPage);
// 拆分为单页请求,避免 per_page 动态值导致后端返回空数据
for (const page of missingPages) {
console.log(`[DynamicNewsCard] 开始加载第 ${page}`);
await dispatch(fetchDynamicNews({
page: page,
per_page: pageSize, // 固定值5或10不使用动态计算
pageSize: pageSize, // 传递原始 pageSize用于正确计算索引
clearCache: false
})).unwrap();
console.log(`[DynamicNewsCard] 第 ${page} 页加载完成`);
}
console.log('[DynamicNewsCard] 所有缺失页面加载完成', {
缺失页面: missingPages
});
// 数据加载成功后才更新当前页码
setCurrentPage(newPage);
} catch (error) {
console.error('[DynamicNewsCard] 翻页加载失败', error);
// 显示错误提示
toast({
title: '加载失败',
description: `无法加载第 ${newPage} 页数据,请稍后重试`,
status: 'error',
duration: 3000,
isClosable: true,
position: 'top'
});
// 加载失败时不更新页码,保持在当前页
} finally {
// 清除加载状态
setLoadingPage(null);
}
} else if (missingPages.length === 0) {
// 只有在确实不需要加载时才直接切换
console.log('[handlePageChange] 无需加载,直接切换', {
currentPage,
newPage,
preloadRange,
missingPages,
reason: '所有页面均已缓存'
});
setCurrentPage(newPage);
} else {
// 理论上不应该到这里missingPages.length > 0 但 hasMore=false
console.warn('[handlePageChange] 意外分支:有缺失页面但无法加载', {
missingPages,
hasMore,
currentPage,
newPage,
total,
cachedCount
});
// 尝试切换页码,但可能会显示空数据
setCurrentPage(newPage);
toast({
title: '数据不完整',
description: `${newPage} 页数据可能不完整`,
status: 'warning',
duration: 2000,
isClosable: true,
position: 'top'
});
}
}, [currentPage, allCachedEvents, pageSize, totalPages, hasMore, dispatch, total, toast, cachedCount]);
// 模式切换处理
const handleModeToggle = useCallback((newMode) => {
if (newMode === mode) return;
setMode(newMode);
setCurrentPage(1);
const newPageSize = newMode === 'carousel' ? 5 : 10;
// 检查第1页的数据是否完整排除 null
const firstPageData = allCachedEvents.slice(0, newPageSize);
const validFirstPageCount = firstPageData.filter(e => e !== null).length;
const needsRefetch = validFirstPageCount < Math.min(newPageSize, total);
if (needsRefetch) {
// 第1页数据不完整清空缓存重新请求
dispatch(fetchDynamicNews({
page: 1,
per_page: newPageSize,
pageSize: newPageSize, // 传递 pageSize 确保索引计算一致
clearCache: true
}));
}
// 如果第1页数据完整不发起请求直接切换
}, [mode, allCachedEvents, total, dispatch]);
// 使用分页 Hook
const {
currentPage,
mode,
loadingPage,
pageSize,
totalPages,
hasMore,
currentPageEvents,
handlePageChange,
handleModeToggle
} = usePagination({
allCachedEvents,
total,
cachedCount,
dispatch,
toast
});
// 初始加载
useEffect(() => {

View File

@@ -388,9 +388,22 @@ const UnifiedSearchBox = ({
}
});
// 处理排序参数 - 将 returns_avg/returns_week 转换为 sort=returns + return_type
const sortValue = overrides.sort ?? sort;
let actualSort = sortValue;
let returnType;
if (sortValue === 'returns_avg') {
actualSort = 'returns';
returnType = 'avg';
} else if (sortValue === 'returns_week') {
actualSort = 'returns';
returnType = 'week';
}
const result = {
// 基础参数overrides 优先级高于本地状态)
sort: overrides.sort ?? sort,
sort: actualSort,
importance: overrides.importance ?? importance,
date_range: dateRange ? `${dateRange[0].format('YYYY-MM-DD')}${dateRange[1].format('YYYY-MM-DD')}` : '',
page: 1,
@@ -404,6 +417,11 @@ const UnifiedSearchBox = ({
...overrides
};
// 添加 return_type 参数(如果需要)
if (returnType) {
result.return_type = returnType;
}
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
return result;
}, [sort, importance, dateRange, filters.q, industryValue]);
@@ -472,7 +490,12 @@ const UnifiedSearchBox = ({
// 排序标签(排除默认值 'new'
if (sort && sort !== 'new') {
const sortLabel = sort === 'hot' ? '最热' : sort === 'importance' ? '重要性' : sort;
let sortLabel;
if (sort === 'hot') sortLabel = '最热';
else if (sort === 'importance') sortLabel = '重要性';
else if (sort === 'returns_avg') sortLabel = '平均收益率';
else if (sort === 'returns_week') sortLabel = '周收益率';
else sortLabel = sort;
tags.push({ key: 'sort', label: `排序: ${sortLabel}` });
}
@@ -663,6 +686,8 @@ const UnifiedSearchBox = ({
<Option value="new">最新</Option>
<Option value="hot">最热</Option>
<Option value="importance">重要性</Option>
<Option value="returns_avg">平均收益率</Option>
<Option value="returns_week">周收益率</Option>
</AntSelect>
</Space>
</Space>