From 478eead899c4f4874143a680634eafdad235c4a0 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Wed, 21 Jan 2026 14:57:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(Community):=20=E9=A2=98=E6=9D=90?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=AD=9B=E9=80=89=E6=8E=A7=E4=BB=B6=E7=A7=BB?= =?UTF-8?q?=E8=87=B3=E6=90=9C=E7=B4=A2=E6=A1=86=20+=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=B1=95=E5=BC=80/=E6=8A=98=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainlineTimelineView: 通过 onControlsChange 暴露控件状态 - MainlineTimelineView: 修复 toggleAll 使用错误的 groupId 键 - CompactSearchBox: mainline 模式下渲染概念/排序选择器和操作按钮 - DynamicNewsCard: 管理 mainlineControls 状态并传递给子组件 - EventScrollList: 传递 onMainlineControlsChange 回调 布局变更: 搜索行:[搜索框] | [时间筛选] | [按三级概念▼] [按事件数量▼] [展开] [折叠] [刷新] 统计行:📈 51条主线 · 132个事件 · 15个未归类(小字) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/DynamicNews/DynamicNewsCard.js | 10 + .../components/DynamicNews/EventScrollList.js | 3 + .../layouts/MainlineTimelineView.js | 182 +++++------------- .../SearchFilters/CompactSearchBox.js | 114 ++++++++++- 4 files changed, 178 insertions(+), 131 deletions(-) diff --git a/src/views/Community/components/DynamicNews/DynamicNewsCard.js b/src/views/Community/components/DynamicNews/DynamicNewsCard.js index 794860ed..c6e38a61 100644 --- a/src/views/Community/components/DynamicNews/DynamicNewsCard.js +++ b/src/views/Community/components/DynamicNews/DynamicNewsCard.js @@ -90,6 +90,14 @@ const DynamicNewsCardComponent = forwardRef(({ const cardBodyRef = useRef(null); const mainlineRef = useRef(null); // MainlineTimelineView 的 ref + // 主线模式控件状态(用于传递给 CompactSearchBox) + const [mainlineControls, setMainlineControls] = useState(null); + + // 主线模式控件变化回调 + const handleMainlineControlsChange = useCallback((controls) => { + setMainlineControls(controls); + }, []); + // 从 Redux 读取关注状态 const eventFollowStatus = useSelector(selectEventFollowStatus); @@ -747,6 +755,7 @@ const [currentMode, setCurrentMode] = useState('vertical'); pageSize={pageSize} trackingFunctions={trackingFunctions} isMobile={isMobile} + mainlineControls={mainlineControls} /> @@ -808,6 +817,7 @@ const [currentMode, setCurrentMode] = useState('vertical'); onToggleFollow={handleToggleFollow} onVoteChange={handleVoteChange} mainlineRef={mainlineRef} + onMainlineControlsChange={handleMainlineControlsChange} /> diff --git a/src/views/Community/components/DynamicNews/EventScrollList.js b/src/views/Community/components/DynamicNews/EventScrollList.js index 8607bbb3..353df9e2 100644 --- a/src/views/Community/components/DynamicNews/EventScrollList.js +++ b/src/views/Community/components/DynamicNews/EventScrollList.js @@ -24,6 +24,7 @@ import VerticalModeLayout from "./layouts/VerticalModeLayout"; * @param {Function} onToggleFollow - 关注按钮回调 * @param {Function} onVoteChange - 投票变化回调 { eventId, voteType: 'bullish' | 'bearish' } * @param {React.Ref} mainlineRef - MainlineTimelineView 的 ref + * @param {Function} onMainlineControlsChange - 主线模式控件状态变化回调 */ const EventScrollList = React.memo( ({ @@ -43,6 +44,7 @@ const EventScrollList = React.memo( onToggleFollow, onVoteChange, mainlineRef, + onMainlineControlsChange, }) => { const scrollContainerRef = useRef(null); @@ -82,6 +84,7 @@ const EventScrollList = React.memo( eventFollowStatus={eventFollowStatus} onToggleFollow={onToggleFollow} borderColor={borderColor} + onControlsChange={onMainlineControlsChange} /> {/* 纵向分栏模式 */} diff --git a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js index 92c66467..6a85106a 100644 --- a/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js +++ b/src/views/Community/components/DynamicNews/layouts/MainlineTimelineView.js @@ -20,10 +20,8 @@ import { Spinner, Center, IconButton, - Tooltip, } from "@chakra-ui/react"; -import { ChevronDown, ChevronUp, RefreshCw, TrendingUp, Zap } from "lucide-react"; -import { Select } from "antd"; +import { RefreshCw, TrendingUp, Zap } from "lucide-react"; import { getApiBase } from "@utils/apiConfig"; import "../../SearchFilters/CompactSearchBox.css"; @@ -44,6 +42,7 @@ const MainlineTimelineViewComponent = forwardRef( eventFollowStatus = {}, onToggleFollow, borderColor, + onControlsChange, // 控件状态变化回调 }, ref ) => { @@ -112,21 +111,6 @@ const MainlineTimelineViewComponent = forwardRef( } }, [display, filters.start_date, filters.end_date, filters.recent_days, groupBy]); - // 初始加载 & 筛选变化时刷新 - useEffect(() => { - fetchMainlineData(); - }, [fetchMainlineData]); - - // 暴露方法给父组件 - useImperativeHandle( - ref, - () => ({ - refresh: fetchMainlineData, - getScrollPosition: () => null, - }), - [fetchMainlineData] - ); - // 切换分组展开/折叠 const toggleGroup = useCallback((lv2Id) => { setExpandedGroups((prev) => ({ @@ -141,13 +125,53 @@ const MainlineTimelineViewComponent = forwardRef( if (!mainlineData?.mainlines) return; const newState = {}; mainlineData.mainlines.forEach((mainline) => { - newState[mainline.lv2_id] = expand; + const groupId = mainline.group_id || mainline.lv2_id || mainline.lv1_id || "ungrouped"; + newState[groupId] = expand; }); setExpandedGroups(newState); }, [mainlineData] ); + // 初始加载 & 筛选变化时刷新 + useEffect(() => { + fetchMainlineData(); + }, [fetchMainlineData]); + + // 暴露方法给父组件 + useImperativeHandle( + ref, + () => ({ + refresh: fetchMainlineData, + getScrollPosition: () => null, + // 暴露控件状态和方法 + getControls: () => ({ + groupBy, + sortBy, + hierarchyOptions, + }), + setGroupBy, + setSortBy, + toggleAll, + }), + [fetchMainlineData, groupBy, sortBy, hierarchyOptions, toggleAll] + ); + + // 控件状态变化时通知父组件 + useEffect(() => { + if (onControlsChange) { + onControlsChange({ + groupBy, + sortBy, + hierarchyOptions, + setGroupBy, + setSortBy, + toggleAll, + refresh: fetchMainlineData, + }); + } + }, [groupBy, sortBy, hierarchyOptions, onControlsChange, toggleAll, fetchMainlineData]); + // 根据排序方式排序主线列表 const sortedMainlines = useMemo(() => { const rawMainlines = mainlineData?.mainlines; @@ -219,132 +243,30 @@ const MainlineTimelineViewComponent = forwardRef( return ( - {/* 顶部统计栏 */} + {/* 顶部统计栏 - 筛选控件已移至搜索框 */} - - - - - {mainline_count} 条主线 - + {/* 统计信息(小字) */} + + + + {mainline_count} 条主线 - - 共 {total_events} 个事件 - + · {total_events} 个事件 {ungrouped_count > 0 && ( - + {ungrouped_count} 个未归类 )} - - - {/* 概念级别选择器 */} - - - } - size="sm" - variant="ghost" - color={COLORS.secondaryTextColor} - onClick={() => toggleAll(true)} - aria-label="全部展开" - _hover={{ bg: COLORS.headerHoverBg }} - /> - - - } - size="sm" - variant="ghost" - color={COLORS.secondaryTextColor} - onClick={() => toggleAll(false)} - aria-label="全部折叠" - _hover={{ bg: COLORS.headerHoverBg }} - /> - - - } - size="sm" - variant="ghost" - color={COLORS.secondaryTextColor} - onClick={fetchMainlineData} - aria-label="刷新" - _hover={{ bg: COLORS.headerHoverBg }} - /> - - {/* 横向滚动容器 */} diff --git a/src/views/Community/components/SearchFilters/CompactSearchBox.js b/src/views/Community/components/SearchFilters/CompactSearchBox.js index f336605f..f330634f 100644 --- a/src/views/Community/components/SearchFilters/CompactSearchBox.js +++ b/src/views/Community/components/SearchFilters/CompactSearchBox.js @@ -22,6 +22,8 @@ import { SortAscendingOutlined, ReloadOutlined, ThunderboltOutlined, + DownOutlined, + UpOutlined, } from "@ant-design/icons"; import debounce from "lodash/debounce"; import { useSelector, useDispatch } from "react-redux"; @@ -58,6 +60,7 @@ const CompactSearchBox = ({ pageSize, trackingFunctions = {}, isMobile = false, + mainlineControls = null, // 主线模式控件状态 }) => { // 状态 const [stockOptions, setStockOptions] = useState([]); @@ -451,7 +454,7 @@ const CompactSearchBox = ({ /> - {/* 筛选控件 */} + {/* 筛选控件 - 列表模式 */} {mode !== "mainline" && ( <> {!isMobile && ( @@ -545,6 +548,115 @@ const CompactSearchBox = ({ )} + + {/* 筛选控件 - 题材模式 */} + {mode === "mainline" && mainlineControls && ( + <> + {!isMobile && ( + + )} + + {/* 概念级别选择器 */} + 0 + ? [{ + label: "一级概念", + options: mainlineControls.hierarchyOptions.lv1.map((opt) => ({ + value: opt.id, + label: opt.name, + })), + }] + : []), + ...(mainlineControls.hierarchyOptions?.lv2?.length > 0 + ? [{ + label: "二级概念", + options: mainlineControls.hierarchyOptions.lv2.map((opt) => ({ + value: opt.id, + label: opt.name, + })), + }] + : []), + ...(mainlineControls.hierarchyOptions?.lv3?.length > 0 + ? [{ + label: "三级概念", + options: mainlineControls.hierarchyOptions.lv3.map((opt) => ({ + value: opt.id, + label: opt.name, + })), + }] + : []), + ]} + /> + + {/* 排序方式选择器 */} + + + {/* 全部展开 */} + +