refactor(HeroPanel): 提取 hooks/constants/utils/styles 基础模块
- hooks/useDetailModalState: 整合弹窗 17 个状态为单一 Hook - constants: 主题色配置、热度级别常量、日期常量 - utils: 交易时间判断、热度颜色、日期/股票代码格式化 - styles/animations.css: 深色主题覆盖、动画、滚动条样式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
68
src/views/Community/components/HeroPanel/constants/index.js
Normal file
68
src/views/Community/components/HeroPanel/constants/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// HeroPanel 常量配置
|
||||
|
||||
// 主题色配置
|
||||
export const goldColors = {
|
||||
primary: "#D4AF37",
|
||||
light: "#F4D03F",
|
||||
dark: "#B8860B",
|
||||
glow: "rgba(212, 175, 55, 0.4)",
|
||||
};
|
||||
|
||||
export const textColors = {
|
||||
primary: "#ffffff",
|
||||
secondary: "rgba(255, 255, 255, 0.85)",
|
||||
muted: "rgba(255, 255, 255, 0.5)",
|
||||
};
|
||||
|
||||
// 热度级别配置
|
||||
export const HEAT_LEVELS = [
|
||||
{
|
||||
key: "high",
|
||||
threshold: 80,
|
||||
colors: {
|
||||
bg: "rgba(147, 51, 234, 0.55)",
|
||||
text: "#d8b4fe",
|
||||
border: "rgba(147, 51, 234, 0.65)",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
threshold: 60,
|
||||
colors: {
|
||||
bg: "rgba(239, 68, 68, 0.50)",
|
||||
text: "#fca5a5",
|
||||
border: "rgba(239, 68, 68, 0.60)",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "low",
|
||||
threshold: 40,
|
||||
colors: {
|
||||
bg: "rgba(251, 146, 60, 0.45)",
|
||||
text: "#fed7aa",
|
||||
border: "rgba(251, 146, 60, 0.55)",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "cold",
|
||||
threshold: 0,
|
||||
colors: {
|
||||
bg: "rgba(59, 130, 246, 0.35)",
|
||||
text: "#93c5fd",
|
||||
border: "rgba(59, 130, 246, 0.45)",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_HEAT_COLORS = {
|
||||
bg: "rgba(60, 60, 70, 0.12)",
|
||||
text: textColors.muted,
|
||||
border: "transparent",
|
||||
};
|
||||
|
||||
// 日期常量
|
||||
export const WEEK_DAYS = ["日", "一", "二", "三", "四", "五", "六"];
|
||||
export const MONTH_NAMES = [
|
||||
"1月", "2月", "3月", "4月", "5月", "6月",
|
||||
"7月", "8月", "9月", "10月", "11月", "12月",
|
||||
];
|
||||
2
src/views/Community/components/HeroPanel/hooks/index.js
Normal file
2
src/views/Community/components/HeroPanel/hooks/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// HeroPanel Hooks 导出
|
||||
export { useDetailModalState } from "./useDetailModalState";
|
||||
@@ -0,0 +1,197 @@
|
||||
// HeroPanel - DetailModal 状态管理 Hook
|
||||
// 整合 DetailModal 组件的所有状态,使主组件更简洁
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
/**
|
||||
* DetailModal 状态管理 Hook
|
||||
* 将 17 个 useState 整合为一个自定义 hook
|
||||
*/
|
||||
export const useDetailModalState = () => {
|
||||
// ========== UI 状态 ==========
|
||||
// 视图模式:板块视图 | 个股视图
|
||||
const [ztViewMode, setZtViewMode] = useState("sector"); // 'sector' | 'stock'
|
||||
// 板块筛选(个股视图时使用)
|
||||
const [selectedSectorFilter, setSelectedSectorFilter] = useState(null);
|
||||
// 展开的涨停原因
|
||||
const [expandedReasons, setExpandedReasons] = useState({});
|
||||
|
||||
// ========== 弹窗/抽屉状态 ==========
|
||||
// 内容详情抽屉
|
||||
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
||||
const [selectedContent, setSelectedContent] = useState(null);
|
||||
// 板块股票弹窗
|
||||
const [sectorStocksModalVisible, setSectorStocksModalVisible] = useState(false);
|
||||
const [selectedSectorInfo, setSelectedSectorInfo] = useState(null);
|
||||
// 事件关联股票抽屉
|
||||
const [stocksDrawerVisible, setStocksDrawerVisible] = useState(false);
|
||||
const [selectedEventStocks, setSelectedEventStocks] = useState([]);
|
||||
const [selectedEventTime, setSelectedEventTime] = useState(null);
|
||||
const [selectedEventTitle, setSelectedEventTitle] = useState("");
|
||||
// K线弹窗
|
||||
const [klineModalVisible, setKlineModalVisible] = useState(false);
|
||||
const [selectedKlineStock, setSelectedKlineStock] = useState(null);
|
||||
// 关联事件弹窗
|
||||
const [relatedEventsModalVisible, setRelatedEventsModalVisible] = useState(false);
|
||||
const [selectedRelatedEvents, setSelectedRelatedEvents] = useState({
|
||||
sectorName: "",
|
||||
events: [],
|
||||
});
|
||||
|
||||
// ========== 数据加载状态 ==========
|
||||
const [stockQuotes, setStockQuotes] = useState({});
|
||||
const [stockQuotesLoading, setStockQuotesLoading] = useState(false);
|
||||
|
||||
// ========== 操作方法 ==========
|
||||
|
||||
// 打开内容详情
|
||||
const openContentDetail = useCallback((content) => {
|
||||
setSelectedContent(content);
|
||||
setDetailDrawerVisible(true);
|
||||
}, []);
|
||||
|
||||
// 关闭内容详情
|
||||
const closeContentDetail = useCallback(() => {
|
||||
setDetailDrawerVisible(false);
|
||||
setSelectedContent(null);
|
||||
}, []);
|
||||
|
||||
// 打开板块股票弹窗
|
||||
const openSectorStocks = useCallback((sectorInfo) => {
|
||||
setSelectedSectorInfo(sectorInfo);
|
||||
setSectorStocksModalVisible(true);
|
||||
}, []);
|
||||
|
||||
// 关闭板块股票弹窗
|
||||
const closeSectorStocks = useCallback(() => {
|
||||
setSectorStocksModalVisible(false);
|
||||
setSelectedSectorInfo(null);
|
||||
}, []);
|
||||
|
||||
// 打开事件关联股票
|
||||
const openEventStocks = useCallback((stocks, time, title) => {
|
||||
setSelectedEventStocks(stocks);
|
||||
setSelectedEventTime(time);
|
||||
setSelectedEventTitle(title);
|
||||
setStocksDrawerVisible(true);
|
||||
}, []);
|
||||
|
||||
// 关闭事件关联股票
|
||||
const closeEventStocks = useCallback(() => {
|
||||
setStocksDrawerVisible(false);
|
||||
setSelectedEventStocks([]);
|
||||
setSelectedEventTime(null);
|
||||
setSelectedEventTitle("");
|
||||
}, []);
|
||||
|
||||
// 打开 K 线弹窗
|
||||
const openKlineModal = useCallback((stock) => {
|
||||
setSelectedKlineStock(stock);
|
||||
setKlineModalVisible(true);
|
||||
}, []);
|
||||
|
||||
// 关闭 K 线弹窗
|
||||
const closeKlineModal = useCallback(() => {
|
||||
setKlineModalVisible(false);
|
||||
setSelectedKlineStock(null);
|
||||
}, []);
|
||||
|
||||
// 打开关联事件弹窗
|
||||
const openRelatedEvents = useCallback((sectorName, events) => {
|
||||
setSelectedRelatedEvents({ sectorName, events });
|
||||
setRelatedEventsModalVisible(true);
|
||||
}, []);
|
||||
|
||||
// 关闭关联事件弹窗
|
||||
const closeRelatedEvents = useCallback(() => {
|
||||
setRelatedEventsModalVisible(false);
|
||||
setSelectedRelatedEvents({ sectorName: "", events: [] });
|
||||
}, []);
|
||||
|
||||
// 切换展开原因
|
||||
const toggleExpandedReason = useCallback((stockCode) => {
|
||||
setExpandedReasons((prev) => ({
|
||||
...prev,
|
||||
[stockCode]: !prev[stockCode],
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 重置所有状态(用于关闭弹窗时)
|
||||
const resetAllState = useCallback(() => {
|
||||
setZtViewMode("sector");
|
||||
setSelectedSectorFilter(null);
|
||||
setExpandedReasons({});
|
||||
setDetailDrawerVisible(false);
|
||||
setSelectedContent(null);
|
||||
setSectorStocksModalVisible(false);
|
||||
setSelectedSectorInfo(null);
|
||||
setStocksDrawerVisible(false);
|
||||
setSelectedEventStocks([]);
|
||||
setSelectedEventTime(null);
|
||||
setSelectedEventTitle("");
|
||||
setKlineModalVisible(false);
|
||||
setSelectedKlineStock(null);
|
||||
setRelatedEventsModalVisible(false);
|
||||
setSelectedRelatedEvents({ sectorName: "", events: [] });
|
||||
setStockQuotes({});
|
||||
setStockQuotesLoading(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// UI 状态 + setters
|
||||
ztViewMode,
|
||||
setZtViewMode,
|
||||
selectedSectorFilter,
|
||||
setSelectedSectorFilter,
|
||||
expandedReasons,
|
||||
setExpandedReasons,
|
||||
|
||||
// 弹窗状态 + setters
|
||||
detailDrawerVisible,
|
||||
setDetailDrawerVisible,
|
||||
selectedContent,
|
||||
setSelectedContent,
|
||||
sectorStocksModalVisible,
|
||||
setSectorStocksModalVisible,
|
||||
selectedSectorInfo,
|
||||
setSelectedSectorInfo,
|
||||
stocksDrawerVisible,
|
||||
setStocksDrawerVisible,
|
||||
selectedEventStocks,
|
||||
setSelectedEventStocks,
|
||||
selectedEventTime,
|
||||
setSelectedEventTime,
|
||||
selectedEventTitle,
|
||||
setSelectedEventTitle,
|
||||
klineModalVisible,
|
||||
setKlineModalVisible,
|
||||
selectedKlineStock,
|
||||
setSelectedKlineStock,
|
||||
relatedEventsModalVisible,
|
||||
setRelatedEventsModalVisible,
|
||||
selectedRelatedEvents,
|
||||
setSelectedRelatedEvents,
|
||||
|
||||
// 数据状态 + setters
|
||||
stockQuotes,
|
||||
setStockQuotes,
|
||||
stockQuotesLoading,
|
||||
setStockQuotesLoading,
|
||||
|
||||
// 操作方法(高级封装)
|
||||
openContentDetail,
|
||||
closeContentDetail,
|
||||
openSectorStocks,
|
||||
closeSectorStocks,
|
||||
openEventStocks,
|
||||
closeEventStocks,
|
||||
openKlineModal,
|
||||
closeKlineModal,
|
||||
openRelatedEvents,
|
||||
closeRelatedEvents,
|
||||
toggleExpandedReason,
|
||||
resetAllState,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDetailModalState;
|
||||
159
src/views/Community/components/HeroPanel/styles/animations.css
Normal file
159
src/views/Community/components/HeroPanel/styles/animations.css
Normal file
@@ -0,0 +1,159 @@
|
||||
/* HeroPanel 动画和深色主题样式 */
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.6; transform: scale(1.1); }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
/* Ant Design 深色主题覆盖 - 弹窗专用 */
|
||||
.hero-panel-modal .ant-tabs {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-tabs-nav::before {
|
||||
border-color: rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-tabs-tab {
|
||||
color: rgba(255, 255, 255, 0.65) !important;
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-tabs-tab:hover {
|
||||
color: #FFD700 !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: #FFD700 !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-tabs-ink-bar {
|
||||
background: linear-gradient(90deg, #FFD700, #FFA500) !important;
|
||||
}
|
||||
|
||||
/* 表格深色主题 */
|
||||
.hero-panel-modal .ant-table {
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-thead > tr > th {
|
||||
background: rgba(255, 215, 0, 0.1) !important;
|
||||
color: #FFD700 !important;
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.2) !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-tbody > tr > td {
|
||||
background: transparent !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-tbody > tr:hover > td {
|
||||
background: rgba(255, 215, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: rgba(255, 215, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-cell-row-hover {
|
||||
background: rgba(255, 215, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-placeholder {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-empty-description {
|
||||
color: rgba(255, 255, 255, 0.45) !important;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.hero-panel-modal .ant-table-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-body::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-body::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 215, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-table-body::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
/* 板块股票表格滚动 - 针对 Ant Design 5.x */
|
||||
.sector-stocks-table-wrapper {
|
||||
max-height: 450px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sector-stocks-table-wrapper .ant-table-wrapper,
|
||||
.sector-stocks-table-wrapper .ant-table,
|
||||
.sector-stocks-table-wrapper .ant-table-container {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.sector-stocks-table-wrapper .ant-table-body {
|
||||
max-height: 380px !important;
|
||||
overflow-y: auto !important;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 215, 0, 0.4) rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 相关股票表格滚动 */
|
||||
.related-stocks-table-wrapper .ant-table-body {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 215, 0, 0.4) rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Tag 样式优化 */
|
||||
.hero-panel-modal .ant-tag {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Button link 样式 */
|
||||
.hero-panel-modal .ant-btn-link {
|
||||
color: #FFD700 !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-btn-link:hover {
|
||||
color: #FFA500 !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-btn-link:disabled {
|
||||
color: rgba(255, 255, 255, 0.25) !important;
|
||||
}
|
||||
|
||||
/* Typography 样式 */
|
||||
.hero-panel-modal .ant-typography {
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-typography-secondary {
|
||||
color: rgba(255, 255, 255, 0.45) !important;
|
||||
}
|
||||
|
||||
/* Spin 加载样式 */
|
||||
.hero-panel-modal .ant-spin-text {
|
||||
color: #FFD700 !important;
|
||||
}
|
||||
|
||||
.hero-panel-modal .ant-spin-dot-item {
|
||||
background-color: #FFD700 !important;
|
||||
}
|
||||
84
src/views/Community/components/HeroPanel/utils/index.js
Normal file
84
src/views/Community/components/HeroPanel/utils/index.js
Normal file
@@ -0,0 +1,84 @@
|
||||
// HeroPanel 工具函数
|
||||
|
||||
import { HEAT_LEVELS, DEFAULT_HEAT_COLORS } from '../constants';
|
||||
|
||||
/**
|
||||
* 判断当前是否在交易时间内 (9:30-15:00)
|
||||
*/
|
||||
export const isInTradingTime = () => {
|
||||
const now = new Date();
|
||||
const timeInMinutes = now.getHours() * 60 + now.getMinutes();
|
||||
return timeInMinutes >= 570 && timeInMinutes <= 900;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据涨停数获取热度颜色
|
||||
*/
|
||||
export const getHeatColor = (count) => {
|
||||
if (!count) return DEFAULT_HEAT_COLORS;
|
||||
const level = HEAT_LEVELS.find((l) => count >= l.threshold);
|
||||
return level?.colors || DEFAULT_HEAT_COLORS;
|
||||
};
|
||||
|
||||
/**
|
||||
* 日期格式化为 YYYYMMDD
|
||||
*/
|
||||
export const formatDateStr = (date) => {
|
||||
if (!date) return "";
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}${month}${day}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取六位股票代码(去掉后缀)
|
||||
*/
|
||||
export const getSixDigitCode = (code) => {
|
||||
if (!code) return code;
|
||||
return code.split(".")[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加交易所后缀
|
||||
*/
|
||||
export const addExchangeSuffix = (code) => {
|
||||
const sixDigitCode = getSixDigitCode(code);
|
||||
if (code.includes(".")) return code;
|
||||
if (sixDigitCode.startsWith("6")) {
|
||||
return `${sixDigitCode}.SH`;
|
||||
} else if (sixDigitCode.startsWith("0") || sixDigitCode.startsWith("3")) {
|
||||
return `${sixDigitCode}.SZ`;
|
||||
}
|
||||
return sixDigitCode;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析连板天数
|
||||
*/
|
||||
export const parseContinuousDays = (text) => {
|
||||
if (!text || text === "首板") return 1;
|
||||
const match = text.match(/(\d+)/);
|
||||
return match ? parseInt(match[1]) : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取涨停时间样式
|
||||
*/
|
||||
export const getTimeStyle = (time) => {
|
||||
if (time <= "09:30:00") return { bg: "#ff4d4f", text: "#fff", label: "秒板" };
|
||||
if (time <= "09:35:00") return { bg: "#fa541c", text: "#fff", label: "早板" };
|
||||
if (time <= "10:00:00") return { bg: "#fa8c16", text: "#fff", label: "盘初" };
|
||||
if (time <= "11:00:00") return { bg: "#52c41a", text: "#fff", label: "盘中" };
|
||||
return { bg: "rgba(255,255,255,0.1)", text: "#888", label: "尾盘" };
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取连板天数样式
|
||||
*/
|
||||
export const getDaysStyle = (days) => {
|
||||
if (days >= 5) return { bg: "linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%)", text: "#fff" };
|
||||
if (days >= 3) return { bg: "linear-gradient(135deg, #fa541c 0%, #ff7a45 100%)", text: "#fff" };
|
||||
if (days >= 2) return { bg: "linear-gradient(135deg, #fa8c16 0%, #ffc53d 100%)", text: "#fff" };
|
||||
return { bg: "rgba(255,255,255,0.1)", text: "#888" };
|
||||
};
|
||||
Reference in New Issue
Block a user