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;