Merge branch 'feature_bugfix/251201_vf_h5_ui' into feature_bugfix/251201_py_h5_ui

* feature_bugfix/251201_vf_h5_ui: (31 commits)
  fix: CompactSearchBox 股票选择和行业筛选优化
  fix: stocks 字段支持对象格式 {code, name}
  refactor: EventDetailCard 重命名为 EventCard,支持多变体模式
  fix: UI调试
  fix: 修复 key 重复
  feat: 修复数据结构访问
  refactor: EventFormModal 从 Chakra UI 迁移到 Ant Design
  fix: 适配 watchlist 新数据结构
  refactor: 股票数据管理迁移到 Redux,新增类型化 Hooks
  fix: 修复ts报错
  feat: 添加mock数据
  style: EventFormModal 和 InvestmentCalendar H5 响应式适配
  style: EventFormModal 和 InvestmentCalendar H5 响应式适配
  fix: 补充 investment.ts 类型定义变更(df90fc2 遗漏)
  feat: h5隐藏日历视图
  perf: EventPanel 性能优化,EventDetailCard H5适配,清理冗余类型
  refactor: CalendarPanel 性能优化,统一弹窗状态管理
  feat: 添加"我的计划"和"我的复盘"的 mock 数据
  refactor: CalendarPanel 性能优化,统一弹窗状态管理
  feat: 新增 EventDetailModal 和 EventEmptyState 组件 用于展示某一天的所有投资事件
  ...
This commit is contained in:
zdl
2025-12-05 18:29:05 +08:00
35 changed files with 3179 additions and 5749 deletions

View File

@@ -14,6 +14,7 @@ import dayjs from 'dayjs';
import debounce from 'lodash/debounce';
import { useSelector, useDispatch } from 'react-redux';
import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '@store/slices/industrySlice';
import { loadAllStocks } from '@store/slices/stockSlice';
import { stockService } from '@services/stockService';
import { logger } from '@utils/logger';
import TradingTimeFilter from './TradingTimeFilter';
@@ -61,9 +62,12 @@ const CompactSearchBox = ({
const dispatch = useDispatch();
const industryData = useSelector(selectIndustryData);
const industryLoading = useSelector(selectIndustryLoading);
const reduxAllStocks = useSelector((state) => state.stock.allStocks);
// 防抖搜索
const debouncedSearchRef = useRef(null);
// 存储股票选择时的显示值(代码+名称),用于 useEffect 同步时显示完整信息
const stockDisplayValueRef = useRef(null);
const triggerSearch = useCallback((params) => {
logger.debug('CompactSearchBox', '触发搜索', { params });
@@ -82,16 +86,19 @@ const CompactSearchBox = ({
};
}, [triggerSearch]);
// 加载股票数据
// 加载股票数据(从 Redux 获取)
useEffect(() => {
const loadStocks = async () => {
const response = await stockService.getAllStocks();
if (response.success && response.data) {
setAllStocks(response.data);
}
};
loadStocks();
}, []);
if (!reduxAllStocks || reduxAllStocks.length === 0) {
dispatch(loadAllStocks());
}
}, [dispatch, reduxAllStocks]);
// 同步 Redux 数据到本地状态
useEffect(() => {
if (reduxAllStocks && reduxAllStocks.length > 0) {
setAllStocks(reduxAllStocks);
}
}, [reduxAllStocks]);
// 预加载行业数据(解决第一次点击无数据问题)
useEffect(() => {
@@ -143,9 +150,17 @@ const CompactSearchBox = ({
}
if (filters.q) {
setInputValue(filters.q);
// 如果是股票选择触发的搜索,使用存储的显示值(代码+名称)
if (stockDisplayValueRef.current && stockDisplayValueRef.current.code === filters.q) {
setInputValue(stockDisplayValueRef.current.displayValue);
} else {
setInputValue(filters.q);
// 清除已失效的显示值缓存
stockDisplayValueRef.current = null;
}
} else if (!filters.q) {
setInputValue('');
stockDisplayValueRef.current = null;
}
const hasTimeInFilters = filters.start_date || filters.end_date || filters.recent_days;
@@ -228,7 +243,7 @@ const CompactSearchBox = ({
sort: actualSort,
importance: importanceValue,
q: (overrides.q ?? filters.q) ?? '',
industry_code: overrides.industry_code ?? (industryValue?.[industryValue.length - 1] || ''),
industry_code: overrides.industry_code ?? (industryValue?.join(',') || ''),
start_date: overrides.start_date ?? (tradingTimeRange?.start_date || ''),
end_date: overrides.end_date ?? (tradingTimeRange?.end_date || ''),
recent_days: overrides.recent_days ?? (tradingTimeRange?.recent_days || ''),
@@ -264,10 +279,13 @@ const CompactSearchBox = ({
});
}
setInputValue(`${stockInfo.code} ${stockInfo.name}`);
const displayValue = `${stockInfo.code} ${stockInfo.name}`;
setInputValue(displayValue);
// 存储显示值,供 useEffect 同步时使用
stockDisplayValueRef.current = { code: stockInfo.code, displayValue };
const params = buildFilterParams({
q: stockInfo.code,
q: stockInfo.code, // 接口只传代码
industry_code: ''
});
triggerSearch(params);
@@ -330,7 +348,7 @@ const CompactSearchBox = ({
}
const params = buildFilterParams({
industry_code: value?.[value.length - 1] || ''
industry_code: value?.join(',') || ''
});
triggerSearch(params);
};

View File

@@ -469,13 +469,13 @@ const FlowingConcepts = () => {
const row3 = concepts.slice(20, 30);
// 渲染单个概念卡片
const renderConceptCard = (concept, globalIdx) => {
const renderConceptCard = (concept, globalIdx, uniqueIdx) => {
const colors = getColor(concept.change_pct);
const isActive = hoveredIdx === globalIdx;
return (
<Box
key={globalIdx}
key={`${globalIdx}-${uniqueIdx}`}
flexShrink={0}
px={3}
py={2}
@@ -582,7 +582,7 @@ const FlowingConcepts = () => {
>
{/* 复制两份实现无缝滚动 */}
{[...items, ...items].map((concept, idx) =>
renderConceptCard(concept, startIdx + (idx % items.length))
renderConceptCard(concept, startIdx + (idx % items.length), idx)
)}
</Flex>
</Box>

View File

@@ -1,9 +1,9 @@
// src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { useEffect, useCallback, useMemo } from 'react';
import { loadWatchlist, toggleWatchlist as toggleWatchlistAction } from '../../../../../store/slices/stockSlice';
import { loadWatchlist, toggleWatchlist as toggleWatchlistAction } from '@store/slices/stockSlice';
import { message } from 'antd';
import { logger } from '../../../../../utils/logger';
import { logger } from '@utils/logger';
/**
* 标准化股票代码为6位格式
@@ -41,8 +41,9 @@ export const useWatchlist = (shouldLoad = true) => {
const loading = useSelector(state => state.stock.loading.watchlist);
// 转换为 Set 方便快速查询标准化为6位代码
// 注意: watchlistArray 现在是 { stock_code, stock_name }[] 格式
const watchlistSet = useMemo(() => {
return new Set(watchlistArray.map(normalizeStockCode));
return new Set(watchlistArray.map(item => normalizeStockCode(item.stock_code)));
}, [watchlistArray]);
// 初始化时加载自选股列表(只在 shouldLoad 为 true 时)