diff --git a/src/views/Community/components/HeroPanel/columns/eventColumns.js b/src/views/Community/components/HeroPanel/columns/eventColumns.js new file mode 100644 index 00000000..d4cd78e9 --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/eventColumns.js @@ -0,0 +1,132 @@ +// 事件表格列定义 +// 用于未来事件 Tab + +import React from "react"; +import { Button, Space, Tooltip, Typography } from "antd"; +import { + ClockCircleOutlined, + LinkOutlined, + RobotOutlined, + StockOutlined, +} from "@ant-design/icons"; +import { StarFilled } from "@ant-design/icons"; +import dayjs from "dayjs"; + +const { Text: AntText } = Typography; + +// 渲染星级评分 +const renderStars = (star) => { + const level = parseInt(star, 10) || 0; + return ( + + {[1, 2, 3, 4, 5].map((i) => ( + + ))} + + ); +}; + +/** + * 创建事件表格列 + * @param {Object} options - 配置选项 + * @param {Function} options.showContentDetail - 显示内容详情 + * @param {Function} options.showRelatedStocks - 显示相关股票 + */ +export const createEventColumns = ({ showContentDetail, showRelatedStocks }) => [ + { + title: "时间", + dataIndex: "calendar_time", + key: "time", + width: 80, + render: (time) => ( + + + {dayjs(time).format("HH:mm")} + + ), + }, + { + title: "重要度", + dataIndex: "star", + key: "star", + width: 120, + render: renderStars, + }, + { + title: "标题", + dataIndex: "title", + key: "title", + ellipsis: true, + render: (text) => ( + + + {text} + + + ), + }, + { + title: "背景", + dataIndex: "former", + key: "former", + width: 80, + render: (text) => ( + + ), + }, + { + title: "未来推演", + dataIndex: "forecast", + key: "forecast", + width: 90, + render: (text) => ( + + ), + }, + { + title: "相关股票", + dataIndex: "related_stocks", + key: "stocks", + width: 120, + render: (stocks, record) => { + const hasStocks = stocks && stocks.length > 0; + if (!hasStocks) { + return ; + } + return ( + + ); + }, + }, +]; diff --git a/src/views/Community/components/HeroPanel/columns/index.js b/src/views/Community/components/HeroPanel/columns/index.js new file mode 100644 index 00000000..570ce704 --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/index.js @@ -0,0 +1,6 @@ +// HeroPanel 表格列相关导出 +export * from './renderers'; +export { createStockColumns } from './stockColumns'; +export { createSectorColumns } from './sectorColumns'; +export { createZtStockColumns } from './ztStockColumns'; +export { createEventColumns } from './eventColumns'; diff --git a/src/views/Community/components/HeroPanel/columns/renderers.js b/src/views/Community/components/HeroPanel/columns/renderers.js new file mode 100644 index 00000000..0292eddf --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/renderers.js @@ -0,0 +1,184 @@ +// HeroPanel 表格列渲染器 +import { Tag, Space, Button, Typography, Tooltip } from "antd"; +import { + ClockCircleOutlined, + LinkOutlined, + RobotOutlined, + StockOutlined, + StarFilled, + StarOutlined, + LineChartOutlined, +} from "@ant-design/icons"; +import dayjs from "dayjs"; + +const { Text: AntText } = Typography; + +/** + * 渲染星级评分 + */ +export const renderStars = (star) => { + const level = parseInt(star, 10) || 0; + return ( + + {[1, 2, 3, 4, 5].map((i) => ( + + ★ + + ))} + + ); +}; + +/** + * 渲染涨跌幅 + */ +export const renderChangePercent = (val) => { + if (val === null || val === undefined) return "-"; + const num = parseFloat(val); + const color = num > 0 ? "#ff4d4f" : num < 0 ? "#52c41a" : "#888"; + const prefix = num > 0 ? "+" : ""; + return ( + + {prefix}{num.toFixed(2)}% + + ); +}; + +/** + * 渲染时间 + */ +export const renderTime = (time) => ( + + + {dayjs(time).format("HH:mm")} + +); + +/** + * 渲染标题(带tooltip) + */ +export const renderTitle = (text) => ( + + + {text} + + +); + +/** + * 渲染排名样式 + */ +export const getRankStyle = (index) => { + if (index === 0) { + return { + background: "linear-gradient(135deg, #FFD700 0%, #FFA500 100%)", + color: "#000", + }; + } + if (index === 1) { + return { + background: "linear-gradient(135deg, #C0C0C0 0%, #A9A9A9 100%)", + color: "#000", + }; + } + if (index === 2) { + return { + background: "linear-gradient(135deg, #CD7F32 0%, #8B4513 100%)", + color: "#fff", + }; + } + return { + background: "rgba(255, 255, 255, 0.08)", + color: "#888", + }; +}; + +/** + * 渲染排名徽章 + */ +export const renderRankBadge = (index) => { + const style = getRankStyle(index); + return ( +
+ {index + 1} +
+ ); +}; + +/** + * 创建查看按钮渲染器 + */ +export const createViewButtonRenderer = (onClick, iconType = "link") => { + const icons = { + link: , + robot: , + stock: , + chart: , + }; + + return (text, record) => ( + + ); +}; + +/** + * 创建自选按钮渲染器 + */ +export const createWatchlistButtonRenderer = (isInWatchlist, onAdd) => { + return (_, record) => { + const inWatchlist = isInWatchlist(record.code); + return ( + + ); + }; +}; + +/** + * 创建K线按钮渲染器 + */ +export const createKlineButtonRenderer = (showKline) => { + return (_, record) => ( + + ); +}; diff --git a/src/views/Community/components/HeroPanel/columns/sectorColumns.js b/src/views/Community/components/HeroPanel/columns/sectorColumns.js new file mode 100644 index 00000000..b4995731 --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/sectorColumns.js @@ -0,0 +1,368 @@ +// 涨停板块表格列定义 +// 用于涨停分析板块视图 + +import React from "react"; +import { Tag, Button, Tooltip, Typography } from "antd"; +import { FireOutlined } from "@ant-design/icons"; +import { Box, HStack, VStack } from "@chakra-ui/react"; +import { FileText } from "lucide-react"; + +const { Text: AntText } = Typography; + +// 获取排名样式 +const getRankStyle = (index) => { + if (index === 0) { + return { + background: "linear-gradient(135deg, #FFD700 0%, #FFA500 100%)", + color: "#000", + fontWeight: "bold", + }; + } + if (index === 1) { + return { + background: "linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%)", + color: "#000", + fontWeight: "bold", + }; + } + if (index === 2) { + return { + background: "linear-gradient(135deg, #CD7F32 0%, #A0522D 100%)", + color: "#fff", + fontWeight: "bold", + }; + } + return { background: "rgba(255,255,255,0.1)", color: "#888" }; +}; + +// 获取涨停数颜色 +const getCountColor = (count) => { + if (count >= 8) return { bg: "#ff4d4f", text: "#fff" }; + if (count >= 5) return { bg: "#fa541c", text: "#fff" }; + if (count >= 3) return { bg: "#fa8c16", text: "#fff" }; + return { bg: "rgba(255,215,0,0.2)", text: "#FFD700" }; +}; + +// 获取相关度颜色 +const getRelevanceColor = (score) => { + if (score >= 80) return "#10B981"; + if (score >= 60) return "#F59E0B"; + return "#6B7280"; +}; + +/** + * 创建涨停板块表格列 + * @param {Object} options - 配置选项 + * @param {Array} options.stockList - 股票列表数据 + * @param {Function} options.setSelectedSectorInfo - 设置选中板块信息 + * @param {Function} options.setSectorStocksModalVisible - 设置板块股票弹窗可见性 + * @param {Function} options.setSelectedRelatedEvents - 设置关联事件 + * @param {Function} options.setRelatedEventsModalVisible - 设置关联事件弹窗可见性 + */ +export const createSectorColumns = ({ + stockList, + setSelectedSectorInfo, + setSectorStocksModalVisible, + setSelectedRelatedEvents, + setRelatedEventsModalVisible, +}) => [ + { + title: "排名", + key: "rank", + width: 60, + align: "center", + render: (_, __, index) => { + const style = getRankStyle(index); + return ( +
+ {index + 1} +
+ ); + }, + }, + { + title: "板块名称", + dataIndex: "name", + key: "name", + width: 130, + render: (name, record, index) => ( + + + + {name} + + + ), + }, + { + title: "涨停数", + dataIndex: "count", + key: "count", + width: 90, + align: "center", + render: (count) => { + const colors = getCountColor(count); + return ( + + + + + {count} + + + + ); + }, + }, + { + title: "涨停股票", + dataIndex: "stocks", + key: "stocks", + render: (stocks, record) => { + // 根据股票代码查找股票详情,并按连板天数排序 + const getStockInfoList = () => { + return stocks + .map((code) => { + const stockInfo = stockList.find((s) => s.scode === code); + return stockInfo || { sname: code, scode: code, _continuousDays: 1 }; + }) + .sort((a, b) => (b._continuousDays || 1) - (a._continuousDays || 1)); + }; + + const stockInfoList = getStockInfoList(); + const displayStocks = stockInfoList.slice(0, 4); + + const handleShowAll = (e) => { + e.stopPropagation(); + setSelectedSectorInfo({ + name: record.name, + count: record.count, + stocks: stockInfoList, + }); + setSectorStocksModalVisible(true); + }; + + return ( + + {displayStocks.map((info) => ( + +
+ {info.sname} +
+
+ {info.scode} +
+ {info.continuous_days && ( +
+ {info.continuous_days} +
+ )} + + } + placement="top" + > + = 3 + ? "rgba(255, 77, 79, 0.2)" + : info._continuousDays >= 2 + ? "rgba(250, 140, 22, 0.2)" + : "rgba(59, 130, 246, 0.15)", + border: + info._continuousDays >= 3 + ? "1px solid rgba(255, 77, 79, 0.4)" + : info._continuousDays >= 2 + ? "1px solid rgba(250, 140, 22, 0.4)" + : "1px solid rgba(59, 130, 246, 0.3)", + borderRadius: "6px", + }} + > + = 3 + ? "#ff4d4f" + : info._continuousDays >= 2 + ? "#fa8c16" + : "#60A5FA", + fontSize: "13px", + }} + > + {info.sname} + {info._continuousDays > 1 && ( + + ({info._continuousDays}板) + + )} + + +
+ ))} + {stocks.length > 4 && ( + + )} +
+ ); + }, + }, + { + title: "涨停归因", + dataIndex: "related_events", + key: "related_events", + width: 280, + render: (events, record) => { + if (!events || events.length === 0) { + return ( + - + ); + } + + // 取相关度最高的事件 + const sortedEvents = [...events].sort( + (a, b) => (b.relevance_score || 0) - (a.relevance_score || 0) + ); + const topEvent = sortedEvents[0]; + + // 点击打开事件详情弹窗 + const handleClick = (e) => { + e.stopPropagation(); + setSelectedRelatedEvents({ + sectorName: record.name, + events: sortedEvents, + count: record.count, + }); + setRelatedEventsModalVisible(true); + }; + + return ( + + + + + + + {topEvent.title} + + + + 相关度 {topEvent.relevance_score || 0} + + {events.length > 1 && ( + + +{events.length - 1}条 + + )} + + + + + + ); + }, + }, +]; diff --git a/src/views/Community/components/HeroPanel/columns/stockColumns.js b/src/views/Community/components/HeroPanel/columns/stockColumns.js new file mode 100644 index 00000000..a3031782 --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/stockColumns.js @@ -0,0 +1,233 @@ +// 相关股票表格列定义 +// 用于事件关联股票弹窗 + +import React from "react"; +import { Tag, Button, Tooltip, Typography } from "antd"; +import { StarFilled, StarOutlined, LineChartOutlined } from "@ant-design/icons"; +import dayjs from "dayjs"; +import { getSixDigitCode } from "../utils"; + +const { Text: AntText } = Typography; + +/** + * 创建相关股票表格列 + * @param {Object} options - 配置选项 + * @param {Object} options.stockQuotes - 股票行情数据 + * @param {Object} options.expandedReasons - 展开状态 + * @param {Function} options.setExpandedReasons - 设置展开状态 + * @param {Function} options.showKline - 显示K线 + * @param {Function} options.isStockInWatchlist - 检查是否在自选 + * @param {Function} options.addSingleToWatchlist - 添加到自选 + */ +export const createStockColumns = ({ + stockQuotes, + expandedReasons, + setExpandedReasons, + showKline, + isStockInWatchlist, + addSingleToWatchlist, +}) => [ + { + title: "代码", + dataIndex: "code", + key: "code", + width: 90, + render: (code) => { + const sixDigitCode = getSixDigitCode(code); + return ( + + {sixDigitCode} + + ); + }, + }, + { + title: "名称", + dataIndex: "name", + key: "name", + width: 100, + render: (name, record) => { + const sixDigitCode = getSixDigitCode(record.code); + return ( + + {name} + + ); + }, + }, + { + title: "现价", + key: "price", + width: 80, + render: (_, record) => { + const quote = stockQuotes[record.code]; + if (quote && quote.price !== undefined) { + return ( + 0 ? "danger" : "success"}> + {quote.price?.toFixed(2)} + + ); + } + return -; + }, + }, + { + title: "涨跌幅", + key: "change", + width: 100, + render: (_, record) => { + const quote = stockQuotes[record.code]; + if (quote && quote.changePercent !== undefined) { + const changePercent = quote.changePercent || 0; + return ( + 0 + ? "red" + : changePercent < 0 + ? "green" + : "default" + } + > + {changePercent > 0 ? "+" : ""} + {changePercent.toFixed(2)}% + + ); + } + return -; + }, + }, + { + title: "关联理由", + dataIndex: "description", + key: "reason", + render: (description, record) => { + const stockCode = record.code; + const isExpanded = expandedReasons[stockCode] || false; + const reason = typeof description === "string" ? description : ""; + const shouldTruncate = reason && reason.length > 80; + + const toggleExpanded = () => { + setExpandedReasons((prev) => ({ + ...prev, + [stockCode]: !prev[stockCode], + })); + }; + + return ( +
+ + {isExpanded || !shouldTruncate + ? reason || "-" + : `${reason?.slice(0, 80)}...`} + + {shouldTruncate && ( + + )} + {reason && ( +
+ + (AI合成) + +
+ )} +
+ ); + }, + }, + { + title: "研报引用", + dataIndex: "report", + key: "report", + width: 180, + render: (report) => { + if (!report || !report.title) { + return -; + } + return ( +
+ +
+ + {report.title.length > 18 + ? `${report.title.slice(0, 18)}...` + : report.title} + + {report.author && ( + + {report.author} + + )} + {report.declare_date && ( + + {dayjs(report.declare_date).format("YYYY-MM-DD")} + + )} + {report.match_score && ( + + 匹配度: {report.match_score} + + )} +
+
+
+ ); + }, + }, + { + title: "K线图", + key: "kline", + width: 80, + render: (_, record) => ( + + ), + }, + { + title: "操作", + key: "action", + width: 90, + render: (_, record) => { + const inWatchlist = isStockInWatchlist(record.code); + return ( + + ); + }, + }, +]; diff --git a/src/views/Community/components/HeroPanel/columns/ztStockColumns.js b/src/views/Community/components/HeroPanel/columns/ztStockColumns.js new file mode 100644 index 00000000..1e2710ee --- /dev/null +++ b/src/views/Community/components/HeroPanel/columns/ztStockColumns.js @@ -0,0 +1,284 @@ +// 涨停股票详情表格列定义 +// 用于涨停分析个股视图 + +import React from "react"; +import { Tag, Button, Tooltip, Typography } from "antd"; +import { StarFilled, StarOutlined, LineChartOutlined } from "@ant-design/icons"; +import { Box, HStack, VStack } from "@chakra-ui/react"; +import { getTimeStyle, getDaysStyle } from "../utils"; + +const { Text: AntText } = Typography; + +/** + * 创建涨停股票详情表格列 + * @param {Object} options - 配置选项 + * @param {Function} options.showContentDetail - 显示内容详情 + * @param {Function} options.setSelectedKlineStock - 设置K线股票 + * @param {Function} options.setKlineModalVisible - 设置K线弹窗可见性 + * @param {Function} options.isStockInWatchlist - 检查是否在自选 + * @param {Function} options.addSingleToWatchlist - 添加到自选 + */ +export const createZtStockColumns = ({ + showContentDetail, + setSelectedKlineStock, + setKlineModalVisible, + isStockInWatchlist, + addSingleToWatchlist, +}) => [ + { + title: "股票信息", + key: "stock", + width: 140, + fixed: "left", + render: (_, record) => ( + + + {record.sname} + + + {record.scode} + + + ), + }, + { + title: "涨停时间", + dataIndex: "formatted_time", + key: "time", + width: 90, + align: "center", + render: (time) => { + const style = getTimeStyle(time || "15:00:00"); + return ( + + + {time?.substring(0, 5) || "-"} + + + {style.label} + + + ); + }, + }, + { + title: "连板", + dataIndex: "continuous_days", + key: "continuous", + width: 70, + align: "center", + render: (text) => { + if (!text || text === "首板") { + return ( + + 首板 + + ); + } + const match = text.match(/(\d+)/); + const days = match ? parseInt(match[1]) : 1; + const style = getDaysStyle(days); + return ( + + {text} + + ); + }, + }, + { + title: "核心板块", + dataIndex: "core_sectors", + key: "sectors", + width: 200, + render: (sectors) => ( + + {(sectors || []).slice(0, 3).map((sector, idx) => ( + + {sector} + + ))} + + ), + }, + { + title: "涨停简报", + dataIndex: "brief", + key: "brief", + width: 200, + render: (text, record) => { + if (!text) return -; + // 移除HTML标签 + const cleanText = text + .replace(//gi, " ") + .replace(/<[^>]+>/g, ""); + return ( + +
+ {record.sname} 涨停简报 +
+
+ {cleanText} +
+ + } + placement="topLeft" + overlayStyle={{ maxWidth: 450 }} + > + +
+ ); + }, + }, + { + title: "K线图", + key: "kline", + width: 80, + align: "center", + render: (_, record) => ( + + ), + }, + { + title: "操作", + key: "action", + width: 90, + align: "center", + render: (_, record) => { + const code = record.scode; + const inWatchlist = isStockInWatchlist(code); + return ( + + ); + }, + }, +];