From 9bfdd56af1c489324bd885804c81998e446082d2 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 13 Jan 2026 14:57:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(community):=20=E6=96=B0=E5=A2=9E=20MarketO?= =?UTF-8?q?verviewBanner=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 股票 TOP10 弹窗功能 - 暗色主题表格样式 --- .../components/MarketOverviewBanner.js | 725 ++++++++++++++++++ 1 file changed, 725 insertions(+) create mode 100644 src/views/Community/components/MarketOverviewBanner.js diff --git a/src/views/Community/components/MarketOverviewBanner.js b/src/views/Community/components/MarketOverviewBanner.js new file mode 100644 index 00000000..98ad8651 --- /dev/null +++ b/src/views/Community/components/MarketOverviewBanner.js @@ -0,0 +1,725 @@ +/** + * MarketOverviewBanner - 市场与事件概览通栏组件 + * 顶部通栏展示市场涨跌分布和事件统计数据 + */ +import React, { useState, useEffect, useCallback, useRef } from "react"; +import { + Box, + Text, + HStack, + Spinner, + Tooltip, + Flex, + Grid, + Input, +} from "@chakra-ui/react"; +import { + FireOutlined, + RiseOutlined, + FallOutlined, + ThunderboltOutlined, + TrophyOutlined, + BarChartOutlined, + CalendarOutlined, + StockOutlined, +} 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} + + +); + +const MarketOverviewBanner = () => { + const [loading, setLoading] = useState(true); + const [stats, setStats] = useState(null); + const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split("T")[0]); + const [stockModalVisible, setStockModalVisible] = useState(false); + const dateInputRef = useRef(null); + + const fetchStats = useCallback(async (dateStr = "", showLoading = false) => { + if (showLoading) { + setLoading(true); + } + try { + const apiBase = getApiBase(); + const dateParam = dateStr ? `&date=${dateStr}` : ""; + const response = await fetch( + `${apiBase}/api/v1/events/effectiveness-stats?days=1${dateParam}` + ); + if (!response.ok) throw new Error("获取数据失败"); + const data = await response.json(); + if (data.success || data.code === 200) { + setStats(data.data); + } + } catch (err) { + console.error("获取市场统计失败:", err); + } finally { + setLoading(false); + } + }, []); + + // 首次加载显示 loading + useEffect(() => { + fetchStats(selectedDate, true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // 日期变化时静默刷新(不显示 loading) + useEffect(() => { + if (selectedDate) { + fetchStats(selectedDate, false); + } + }, [fetchStats, selectedDate]); + + // 自动刷新(每60秒,仅当选择今天时) + useEffect(() => { + if (!selectedDate) { + const interval = setInterval(() => fetchStats(""), 60 * 1000); + return () => clearInterval(interval); + } + }, [selectedDate, fetchStats]); + + const handleDateChange = (e) => { + setSelectedDate(e.target.value); + }; + + const handleCalendarClick = () => { + dateInputRef.current?.showPicker?.(); + }; + + if (loading) { + return ( + + + + ); + } + + if (!stats) return null; + + const { summary, marketStats, topStocks = [] } = stats; + + return ( + + {/* 标题行 */} + + + + 事件中心 + + {/* 交易状态指示器 */} + {isInTradingTime() && ( + + + + 交易中 + + + )} + {/* 实时标签 */} + {!selectedDate && ( + + + + 实时 + + + )} + + + {/* 返回今天按钮 - 当选择的不是今天时显示 */} + {selectedDate !== new Date().toISOString().split("T")[0] && ( + setSelectedDate(new Date().toISOString().split("T")[0])} + > + 返回今天 + + )} + {/* 日期选择器 - 金色边框 */} + + + + + + + + {/* 内容:左右布局 */} + + {/* 左侧:涨跌条形图 */} + + + + + {/* 右侧:6个指标卡片 - 1行6列 */} + + + + = 0 ? : } + color={summary?.avgChg >= 0 ? UP_COLOR : DOWN_COLOR} + highlight + /> + = 0 ? : } + color={summary?.maxChg >= 0 ? UP_COLOR : DOWN_COLOR} + highlight + /> + } + color="#F59E0B" + highlight + /> + {/* 关联股票 + TOP10标签 - 与 BannerStatCard 样式统一 */} + + + + + + + 关联股票 + + + setStockModalVisible(true)} + > + TOP10 + + + + + {summary?.totalStocks || 0} + + + + + + {/* 股票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)} + + ), + }, + ]} + /> + + + ); +}; + +export default MarketOverviewBanner;