diff --git a/src/views/Community/components/MarketOverviewBanner.js b/src/views/Community/components/MarketOverviewBanner.js index 98ad8651..1c03ac1c 100644 --- a/src/views/Community/components/MarketOverviewBanner.js +++ b/src/views/Community/components/MarketOverviewBanner.js @@ -17,295 +17,31 @@ import { FireOutlined, RiseOutlined, FallOutlined, - ThunderboltOutlined, - TrophyOutlined, - BarChartOutlined, CalendarOutlined, - StockOutlined, + BarChartOutlined, } from "@ant-design/icons"; -import { Modal, Table } from "antd"; import { getApiBase } from "@utils/apiConfig"; -// 涨跌颜色常量 -const UP_COLOR = "#FF4D4F"; // 涨 - 红色 -const DOWN_COLOR = "#52C41A"; // 跌 - 绿色 -const FLAT_COLOR = "#888888"; // 平 - 灰色 - -/** - * 判断是否在交易时间内 (9:30-15:00) - */ -const isInTradingTime = () => { - const now = new Date(); - const hours = now.getHours(); - const minutes = now.getMinutes(); - const time = hours * 60 + minutes; - return time >= 570 && time <= 900; // 9:30-15:00 -}; - -// 注入脉冲动画样式 -if (typeof document !== "undefined") { - const styleId = "market-banner-animations"; - if (!document.getElementById(styleId)) { - const styleSheet = document.createElement("style"); - styleSheet.id = styleId; - styleSheet.innerText = ` - @keyframes pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.6; transform: scale(1.1); } - } - `; - document.head.appendChild(styleSheet); - } -} - -/** - * 格式化涨跌幅 - */ -const formatChg = (val) => { - if (val === null || val === undefined) return "-"; - const num = parseFloat(val); - if (isNaN(num)) return "-"; - return (num >= 0 ? "+" : "") + num.toFixed(2) + "%"; -}; - -/** - * 沪深实时涨跌条形图组件 - 紧凑版 - */ -const MarketStatsBarCompact = ({ marketStats }) => { - if (!marketStats || marketStats.totalCount === 0) return null; - - const { - risingCount = 0, - flatCount = 0, - fallingCount = 0, - totalCount = 0, - } = marketStats; - const risePercent = totalCount > 0 ? (risingCount / totalCount) * 100 : 0; - const flatPercent = totalCount > 0 ? (flatCount / totalCount) * 100 : 0; - const fallPercent = totalCount > 0 ? (fallingCount / totalCount) * 100 : 0; - - return ( - - {/* 标题 */} - - - 沪深实时涨跌 - - - ({totalCount}只) - - - - {/* 进度条 */} - - - - - - - - - {/* 数值标签 */} - - - - - {risingCount} - - - 涨 - - - - - - - {flatCount} - - - 平 - - - - - - - {fallingCount} - - - 跌 - - - - - ); -}; - -/** - * 环形进度图组件 - 仿图片样式 - * @param {boolean} noBorder - 是否不显示边框(用于嵌套在其他容器中) - */ -const CircularProgressCard = ({ label, value, color = "#EC4899", size = 44, highlight = false, noBorder = false }) => { - const percentage = parseFloat(value) || 0; - const strokeWidth = 3; - const radius = (size - strokeWidth) / 2; - // 270度圆弧(底部有缺口) - const arcLength = (270 / 360) * 2 * Math.PI * radius; - const progressLength = (percentage / 100) * arcLength; - - return ( - - - {label} - - - - {/* 背景圆弧 */} - - {/* 进度圆弧 */} - - - {/* 中心数值 */} - - - {percentage.toFixed(1)}% - - - - - ); -}; - -/** - * 紧凑数据卡片 - 通栏版 - */ -const BannerStatCard = ({ label, value, icon, color = "#7C3AED", highlight = false }) => ( - - - - {icon} - - - {label} - - - - {value} - - -); +// 模块化导入 +import { + UP_COLOR, + DOWN_COLOR, + isInTradingTime, + formatChg, +} from "./MarketOverviewBanner/constants"; +import { + MarketStatsBarCompact, + CircularProgressCard, + BannerStatCard, +} from "./MarketOverviewBanner/components"; +import StockTop10Modal from "./MarketOverviewBanner/StockTop10Modal"; const MarketOverviewBanner = () => { const [loading, setLoading] = useState(true); const [stats, setStats] = useState(null); - const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split("T")[0]); + const [selectedDate, setSelectedDate] = useState( + new Date().toISOString().split("T")[0] + ); const [stockModalVisible, setStockModalVisible] = useState(false); const dateInputRef = useRef(null); @@ -337,7 +73,7 @@ const MarketOverviewBanner = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // 日期变化时静默刷新(不显示 loading) + // 日期变化时静默刷新 useEffect(() => { if (selectedDate) { fetchStats(selectedDate, false); @@ -371,6 +107,7 @@ const MarketOverviewBanner = () => { if (!stats) return null; const { summary, marketStats, topStocks = [] } = stats; + const today = new Date().toISOString().split("T")[0]; return ( @@ -420,9 +157,10 @@ const MarketOverviewBanner = () => { )} + - {/* 返回今天按钮 - 当选择的不是今天时显示 */} - {selectedDate !== new Date().toISOString().split("T")[0] && ( + {/* 返回今天按钮 */} + {selectedDate !== today && ( { borderColor: "rgba(255, 215, 0, 0.6)", }} transition="all 0.2s" - onClick={() => setSelectedDate(new Date().toISOString().split("T")[0])} + onClick={() => setSelectedDate(today)} > 返回今天 )} - {/* 日期选择器 - 金色边框 */} + {/* 日期选择器 */} { value={selectedDate} onChange={handleDateChange} onClick={handleCalendarClick} - max={new Date().toISOString().split("T")[0]} + max={today} bg="transparent" border="none" color="#FFD700" @@ -503,7 +241,7 @@ const MarketOverviewBanner = () => { - {/* 右侧:6个指标卡片 - 1行6列 */} + {/* 右侧:6个指标卡片 */} { color="#F59E0B" highlight /> - {/* 关联股票 + TOP10标签 - 与 BannerStatCard 样式统一 */} + {/* 关联股票卡片 */} { {/* 股票TOP10弹窗 */} - - - 股票 TOP10 - - } - open={stockModalVisible} - onCancel={() => setStockModalVisible(false)} - footer={null} - width={600} - closeIcon={×} - styles={{ - content: { - background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)", - border: "1px solid rgba(236, 72, 153, 0.3)", - }, - header: { - background: "transparent", - borderBottom: "1px solid rgba(255,255,255,0.1)", - }, - }} - > - - record.stockCode || record.stockName} - pagination={false} - size="small" - className="stock-top10-table" - columns={[ - { - title: "排名", - dataIndex: "rank", - key: "rank", - width: 60, - render: (_, __, index) => ( - - {index + 1} - - ), - }, - { - title: "股票代码", - dataIndex: "stockCode", - key: "stockCode", - width: 100, - render: (code) => ( - - {code?.split(".")[0] || "-"} - - ), - }, - { - title: "股票名称", - dataIndex: "stockName", - key: "stockName", - render: (name) => ( - - {name || "-"} - - ), - }, - { - title: "最大涨幅", - dataIndex: "maxChg", - key: "maxChg", - width: 100, - align: "right", - render: (val) => ( - = 0 ? UP_COLOR : DOWN_COLOR} - > - {formatChg(val)} - - ), - }, - ]} - /> - + setStockModalVisible(false)} + topStocks={topStocks} + /> ); }; diff --git a/src/views/Community/components/MarketOverviewBanner/StockTop10Modal.js b/src/views/Community/components/MarketOverviewBanner/StockTop10Modal.js new file mode 100644 index 00000000..db6704d3 --- /dev/null +++ b/src/views/Community/components/MarketOverviewBanner/StockTop10Modal.js @@ -0,0 +1,138 @@ +// 股票 TOP10 弹窗组件 + +import React from "react"; +import { Text, HStack } from "@chakra-ui/react"; +import { StockOutlined } from "@ant-design/icons"; +import { Modal, Table } from "antd"; +import { UP_COLOR, DOWN_COLOR, formatChg } from "./constants"; + +// 弹窗内表格样式 +const modalTableStyles = ` + .stock-top10-table .ant-table { + background: transparent !important; + } + .stock-top10-table .ant-table-thead > tr > th { + background: rgba(236, 72, 153, 0.1) !important; + color: rgba(255, 255, 255, 0.8) !important; + border-bottom: 1px solid rgba(236, 72, 153, 0.2) !important; + font-weight: 600; + } + .stock-top10-table .ant-table-tbody > tr > td { + border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important; + padding: 10px 8px !important; + } + .stock-top10-table .ant-table-tbody > tr:nth-child(odd) { + background: rgba(255, 255, 255, 0.02) !important; + } + .stock-top10-table .ant-table-tbody > tr:nth-child(even) { + background: rgba(0, 0, 0, 0.1) !important; + } + .stock-top10-table .ant-table-tbody > tr:hover > td { + background: rgba(236, 72, 153, 0.15) !important; + } + .stock-top10-table .ant-table-cell { + background: transparent !important; + } +`; + +// 表格列定义 +const tableColumns = [ + { + title: "排名", + dataIndex: "rank", + key: "rank", + width: 60, + render: (_, __, index) => ( + + {index + 1} + + ), + }, + { + title: "股票代码", + dataIndex: "stockCode", + key: "stockCode", + width: 100, + render: (code) => ( + + {code?.split(".")[0] || "-"} + + ), + }, + { + title: "股票名称", + dataIndex: "stockName", + key: "stockName", + render: (name) => ( + + {name || "-"} + + ), + }, + { + title: "最大涨幅", + dataIndex: "maxChg", + key: "maxChg", + width: 100, + align: "right", + render: (val) => ( + = 0 ? UP_COLOR : DOWN_COLOR}> + {formatChg(val)} + + ), + }, +]; + +/** + * 股票 TOP10 弹窗组件 + */ +const StockTop10Modal = ({ visible, onClose, topStocks = [] }) => { + return ( + + + 股票 TOP10 + + } + open={visible} + onCancel={onClose} + footer={null} + width={600} + closeIcon={×} + styles={{ + content: { + background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)", + border: "1px solid rgba(236, 72, 153, 0.3)", + }, + header: { + background: "transparent", + borderBottom: "1px solid rgba(255,255,255,0.1)", + }, + }} + > + +
record.stockCode || record.stockName} + pagination={false} + size="small" + className="stock-top10-table" + columns={tableColumns} + /> + + ); +}; + +export default StockTop10Modal; diff --git a/src/views/Community/components/MarketOverviewBanner/components.js b/src/views/Community/components/MarketOverviewBanner/components.js new file mode 100644 index 00000000..03df4b3e --- /dev/null +++ b/src/views/Community/components/MarketOverviewBanner/components.js @@ -0,0 +1,261 @@ +// MarketOverviewBanner 子组件 + +import React from "react"; +import { Box, Text, HStack, Flex } from "@chakra-ui/react"; +import { UP_COLOR, DOWN_COLOR, FLAT_COLOR } from "./constants"; + +/** + * 沪深实时涨跌条形图组件 - 紧凑版 + */ +export const MarketStatsBarCompact = ({ marketStats }) => { + if (!marketStats || marketStats.totalCount === 0) return null; + + const { + risingCount = 0, + flatCount = 0, + fallingCount = 0, + totalCount = 0, + } = marketStats; + const risePercent = totalCount > 0 ? (risingCount / totalCount) * 100 : 0; + const flatPercent = totalCount > 0 ? (flatCount / totalCount) * 100 : 0; + const fallPercent = totalCount > 0 ? (fallingCount / totalCount) * 100 : 0; + + return ( + + + + 沪深实时涨跌 + + + ({totalCount}只) + + + + + + + + + + + + + + + + {risingCount} + + + 涨 + + + + + + + {flatCount} + + + 平 + + + + + + + {fallingCount} + + + 跌 + + + + + ); +}; + +/** + * 环形进度图组件 + */ +export const CircularProgressCard = ({ + label, + value, + color = "#EC4899", + size = 44, + highlight = false, + noBorder = false, +}) => { + const percentage = parseFloat(value) || 0; + const strokeWidth = 3; + const radius = (size - strokeWidth) / 2; + const arcLength = (270 / 360) * 2 * Math.PI * radius; + const progressLength = (percentage / 100) * arcLength; + + return ( + + + {label} + + + + + + + + + {percentage.toFixed(1)}% + + + + + ); +}; + +/** + * 紧凑数据卡片 + */ +export const BannerStatCard = ({ + label, + value, + icon, + color = "#7C3AED", + highlight = false, +}) => ( + + + + {icon} + + + {label} + + + + {value} + + +); diff --git a/src/views/Community/components/MarketOverviewBanner/constants.js b/src/views/Community/components/MarketOverviewBanner/constants.js new file mode 100644 index 00000000..822a01ef --- /dev/null +++ b/src/views/Community/components/MarketOverviewBanner/constants.js @@ -0,0 +1,43 @@ +// MarketOverviewBanner 常量定义 + +// 涨跌颜色常量 +export const UP_COLOR = "#FF4D4F"; // 涨 - 红色 +export const DOWN_COLOR = "#52C41A"; // 跌 - 绿色 +export const FLAT_COLOR = "#888888"; // 平 - 灰色 + +/** + * 判断是否在交易时间内 (9:30-15:00) + */ +export const isInTradingTime = () => { + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + const time = hours * 60 + minutes; + return time >= 570 && time <= 900; // 9:30-15:00 +}; + +/** + * 格式化涨跌幅 + */ +export const formatChg = (val) => { + if (val === null || val === undefined) return "-"; + const num = parseFloat(val); + if (isNaN(num)) return "-"; + return (num >= 0 ? "+" : "") + num.toFixed(2) + "%"; +}; + +// 注入脉冲动画样式 +if (typeof document !== "undefined") { + const styleId = "market-banner-animations"; + if (!document.getElementById(styleId)) { + const styleSheet = document.createElement("style"); + styleSheet.id = styleId; + styleSheet.innerText = ` + @keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.6; transform: scale(1.1); } + } + `; + document.head.appendChild(styleSheet); + } +} diff --git a/src/views/Community/components/MarketOverviewBanner/index.js b/src/views/Community/components/MarketOverviewBanner/index.js new file mode 100644 index 00000000..3ccca49a --- /dev/null +++ b/src/views/Community/components/MarketOverviewBanner/index.js @@ -0,0 +1,5 @@ +// MarketOverviewBanner 模块导出 + +export { UP_COLOR, DOWN_COLOR, FLAT_COLOR, isInTradingTime, formatChg } from "./constants"; +export { MarketStatsBarCompact, CircularProgressCard, BannerStatCard } from "./components"; +export { default as StockTop10Modal } from "./StockTop10Modal";