refactor(HeroPanel): 提取 DetailModal 相关组件
- 主弹窗 DetailModal 使用 Hook 管理状态 - ZTSectorView/ZTStockListView 使用 memo 优化 - EventsTabView 添加空状态处理 - RelatedEventsModal 涨停归因详情 - SectorStocksModal 板块股票详情 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
|||||||
|
// 未来事件视图组件
|
||||||
|
// 展示选定日期的未来事件列表
|
||||||
|
|
||||||
|
import React, { memo } from "react";
|
||||||
|
import { Table, Typography } from "antd";
|
||||||
|
import { CalendarOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
const { Text: AntText } = Typography;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未来事件视图
|
||||||
|
* @param {Array} events - 事件列表
|
||||||
|
* @param {Array} columns - 表格列配置
|
||||||
|
*/
|
||||||
|
const EventsTabView = memo(({ events, columns }) => {
|
||||||
|
// 无数据时的空状态
|
||||||
|
if (!events?.length) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "200px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }}>
|
||||||
|
<CalendarOutlined style={{ fontSize: 48, color: "#666" }} />
|
||||||
|
<AntText type="secondary" style={{ fontSize: 16 }}>
|
||||||
|
暂无事件数据
|
||||||
|
</AntText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
dataSource={events}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: 900, y: 420 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
EventsTabView.displayName = "EventsTabView";
|
||||||
|
|
||||||
|
export default EventsTabView;
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
// HeroPanel - 关联事件弹窗(涨停归因详情)
|
||||||
|
// 使用 Ant Design Modal 保持与现有代码风格一致
|
||||||
|
import React from "react";
|
||||||
|
import { Modal as AntModal, Tag, ConfigProvider, theme } from "antd";
|
||||||
|
import { FileText } from "lucide-react";
|
||||||
|
import { GLASS_BLUR } from "@/constants/glassConfig";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取相关度颜色
|
||||||
|
*/
|
||||||
|
const getRelevanceColor = (score) => {
|
||||||
|
if (score >= 80) return "#10B981";
|
||||||
|
if (score >= 60) return "#F59E0B";
|
||||||
|
return "#6B7280";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联事件弹窗 - 涨停归因详情
|
||||||
|
*/
|
||||||
|
const RelatedEventsModal = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
sectorName = "",
|
||||||
|
events = [],
|
||||||
|
count = 0,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
algorithm: theme.darkAlgorithm,
|
||||||
|
token: { colorBgElevated: "rgba(15,15,30,0.98)" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AntModal
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
centered
|
||||||
|
title={
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "8px",
|
||||||
|
background: "rgba(96,165,250,0.15)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "1px solid rgba(96,165,250,0.3)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileText size={18} color="#60A5FA" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: "18px", fontWeight: "bold", color: "#60A5FA" }}>
|
||||||
|
{sectorName} - 涨停归因
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: "12px", marginTop: "4px" }}>
|
||||||
|
<span style={{ fontSize: "12px", color: "rgba(255,255,255,0.5)" }}>
|
||||||
|
涨停 <span style={{ color: "#EF4444", fontWeight: "bold" }}>{count}</span> 只
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: "12px", color: "rgba(255,255,255,0.5)" }}>
|
||||||
|
关联事件 <span style={{ color: "#60A5FA", fontWeight: "bold" }}>{events?.length || 0}</span> 条
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
styles={{
|
||||||
|
header: {
|
||||||
|
background: "rgba(25,25,50,0.98)",
|
||||||
|
borderBottom: "1px solid rgba(96,165,250,0.2)",
|
||||||
|
padding: "16px 24px",
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
background: "linear-gradient(135deg, rgba(15,15,30,0.98) 0%, rgba(25,25,50,0.98) 100%)",
|
||||||
|
padding: "16px 24px",
|
||||||
|
maxHeight: "65vh",
|
||||||
|
overflowY: "auto",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
background: "linear-gradient(135deg, rgba(15,15,30,0.98) 0%, rgba(25,25,50,0.98) 100%)",
|
||||||
|
borderRadius: "16px",
|
||||||
|
border: "1px solid rgba(96,165,250,0.3)",
|
||||||
|
},
|
||||||
|
mask: { background: "rgba(0,0,0,0.7)", backdropFilter: GLASS_BLUR.sm },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{events?.length > 0 ? (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
|
{events.map((event, idx) => {
|
||||||
|
const relevanceColor = getRelevanceColor(event.relevance_score || 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={event.event_id || idx}
|
||||||
|
style={{
|
||||||
|
padding: "16px",
|
||||||
|
background: "rgba(30,30,50,0.8)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.06)",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(`/community?event_id=${event.event_id}`, "_blank");
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = "rgba(40,40,70,0.9)";
|
||||||
|
e.currentTarget.style.borderColor = "rgba(96,165,250,0.3)";
|
||||||
|
e.currentTarget.style.transform = "translateY(-2px)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = "rgba(30,30,50,0.8)";
|
||||||
|
e.currentTarget.style.borderColor = "rgba(255,255,255,0.06)";
|
||||||
|
e.currentTarget.style.transform = "translateY(0)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
|
{/* 标题 */}
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
||||||
|
<div style={{ display: "flex", gap: "8px", flex: 1 }}>
|
||||||
|
<FileText size={16} color="#60A5FA" style={{ flexShrink: 0 }} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: "600",
|
||||||
|
color: "#E0E0E0",
|
||||||
|
display: "-webkit-box",
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: "vertical",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
background: `${relevanceColor}20`,
|
||||||
|
color: relevanceColor,
|
||||||
|
fontSize: "12px",
|
||||||
|
padding: "2px 8px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
相关度 {event.relevance_score || 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 相关原因 */}
|
||||||
|
{event.relevance_reason && (
|
||||||
|
<span style={{ fontSize: "12px", color: "rgba(255,255,255,0.6)", lineHeight: "1.6" }}>
|
||||||
|
{event.relevance_reason}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 匹配概念 */}
|
||||||
|
{event.matched_concepts?.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<span style={{ fontSize: "12px", color: "rgba(255,255,255,0.4)", marginBottom: "6px", display: "block" }}>
|
||||||
|
匹配概念:
|
||||||
|
</span>
|
||||||
|
<div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
|
||||||
|
{event.matched_concepts.slice(0, 6).map((concept, i) => (
|
||||||
|
<Tag
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
fontSize: "10px",
|
||||||
|
margin: "2px",
|
||||||
|
background: "rgba(139, 92, 246, 0.15)",
|
||||||
|
border: "none",
|
||||||
|
color: "#A78BFA",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "2px 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{concept}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
{event.matched_concepts.length > 6 && (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
fontSize: "10px",
|
||||||
|
margin: "2px",
|
||||||
|
background: "rgba(255,255,255,0.1)",
|
||||||
|
border: "none",
|
||||||
|
color: "#888",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "2px 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+{event.matched_concepts.length - 6}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "200px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.5)" }}>暂无关联事件</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AntModal>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RelatedEventsModal;
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
// HeroPanel - 板块股票弹窗
|
||||||
|
// 使用 Ant Design Modal 保持与现有代码风格一致
|
||||||
|
import React from "react";
|
||||||
|
import { Modal as AntModal, Table, Tag, Button, Typography, ConfigProvider, theme } from "antd";
|
||||||
|
import { TagsOutlined, LineChartOutlined, StarFilled, StarOutlined } from "@ant-design/icons";
|
||||||
|
import { GLASS_BLUR } from "@/constants/glassConfig";
|
||||||
|
|
||||||
|
const { Text: AntText } = Typography;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连板天数样式
|
||||||
|
*/
|
||||||
|
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" };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取涨停时间样式
|
||||||
|
*/
|
||||||
|
const getTimeStyle = (time) => {
|
||||||
|
if (time <= "09:30:00") return { bg: "#ff4d4f", text: "#fff" };
|
||||||
|
if (time <= "09:35:00") return { bg: "#fa541c", text: "#fff" };
|
||||||
|
if (time <= "10:00:00") return { bg: "#fa8c16", text: "#fff" };
|
||||||
|
return { bg: "rgba(255,255,255,0.1)", text: "#888" };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 板块股票弹窗
|
||||||
|
*/
|
||||||
|
const SectorStocksModal = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
sectorInfo,
|
||||||
|
onShowKline,
|
||||||
|
onAddToWatchlist,
|
||||||
|
isStockInWatchlist,
|
||||||
|
}) => {
|
||||||
|
if (!sectorInfo) return null;
|
||||||
|
|
||||||
|
const { name, count, stocks = [] } = sectorInfo;
|
||||||
|
|
||||||
|
// 连板统计
|
||||||
|
const stats = { 首板: 0, "2连板": 0, "3连板": 0, "4连板+": 0 };
|
||||||
|
stocks.forEach((s) => {
|
||||||
|
const days = s._continuousDays || 1;
|
||||||
|
if (days === 1) stats["首板"]++;
|
||||||
|
else if (days === 2) stats["2连板"]++;
|
||||||
|
else if (days === 3) stats["3连板"]++;
|
||||||
|
else stats["4连板+"]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "股票",
|
||||||
|
key: "stock",
|
||||||
|
width: 130,
|
||||||
|
render: (_, record) => (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<a
|
||||||
|
href={`https://valuefrontier.cn/company?scode=${record.scode}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: "#FFD700", fontWeight: "bold", fontSize: "14px" }}
|
||||||
|
>
|
||||||
|
{record.sname}
|
||||||
|
</a>
|
||||||
|
<AntText style={{ color: "#60A5FA", fontSize: "12px" }}>
|
||||||
|
{record.scode}
|
||||||
|
</AntText>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "连板",
|
||||||
|
dataIndex: "continuous_days",
|
||||||
|
key: "continuous",
|
||||||
|
width: 90,
|
||||||
|
align: "center",
|
||||||
|
render: (text, record) => {
|
||||||
|
const days = record._continuousDays || 1;
|
||||||
|
const style = getDaysStyle(days);
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
padding: "4px 12px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
background: style.bg,
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: style.text,
|
||||||
|
display: "inline-block",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text || "首板"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "涨停时间",
|
||||||
|
dataIndex: "formatted_time",
|
||||||
|
key: "time",
|
||||||
|
width: 90,
|
||||||
|
align: "center",
|
||||||
|
render: (time) => {
|
||||||
|
const style = getTimeStyle(time || "15:00:00");
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
padding: "2px 8px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
background: style.bg,
|
||||||
|
fontSize: "12px",
|
||||||
|
color: style.text,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{time?.substring(0, 5) || "-"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "核心板块",
|
||||||
|
dataIndex: "core_sectors",
|
||||||
|
key: "sectors",
|
||||||
|
render: (sectors) => (
|
||||||
|
<div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
|
||||||
|
{(sectors || []).slice(0, 2).map((sector, idx) => (
|
||||||
|
<Tag
|
||||||
|
key={idx}
|
||||||
|
style={{
|
||||||
|
margin: "2px",
|
||||||
|
background: "rgba(255,215,0,0.1)",
|
||||||
|
border: "1px solid rgba(255,215,0,0.2)",
|
||||||
|
borderRadius: "4px",
|
||||||
|
color: "#D4A84B",
|
||||||
|
fontSize: "11px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sector}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "K线图",
|
||||||
|
key: "kline",
|
||||||
|
width: 80,
|
||||||
|
align: "center",
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon={<LineChartOutlined />}
|
||||||
|
onClick={() => onShowKline(record)}
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%)",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "6px",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
width: 90,
|
||||||
|
align: "center",
|
||||||
|
render: (_, record) => {
|
||||||
|
const code = record.scode;
|
||||||
|
const inWatchlist = isStockInWatchlist(code);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type={inWatchlist ? "primary" : "default"}
|
||||||
|
size="small"
|
||||||
|
icon={inWatchlist ? <StarFilled /> : <StarOutlined />}
|
||||||
|
onClick={() => onAddToWatchlist({ code, name: record.sname })}
|
||||||
|
disabled={inWatchlist}
|
||||||
|
style={
|
||||||
|
inWatchlist
|
||||||
|
? {
|
||||||
|
background: "linear-gradient(135deg, #faad14 0%, #fa8c16 100%)",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "6px",
|
||||||
|
fontSize: "12px",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
background: "rgba(255,255,255,0.1)",
|
||||||
|
border: "1px solid rgba(255,215,0,0.3)",
|
||||||
|
borderRadius: "6px",
|
||||||
|
color: "#FFD700",
|
||||||
|
fontSize: "12px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{inWatchlist ? "已添加" : "加自选"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
algorithm: theme.darkAlgorithm,
|
||||||
|
token: { colorBgElevated: "rgba(15,15,30,0.98)" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AntModal
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={null}
|
||||||
|
width={900}
|
||||||
|
centered
|
||||||
|
title={
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "8px",
|
||||||
|
background: "rgba(255,215,0,0.15)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "1px solid rgba(255,215,0,0.3)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TagsOutlined style={{ color: "#FFD700", fontSize: "18px" }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
|
<span style={{ fontSize: "18px", fontWeight: "bold", color: "#FFD700" }}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "12px",
|
||||||
|
padding: "2px 8px",
|
||||||
|
background: "rgba(255,77,79,0.15)",
|
||||||
|
color: "#ff4d4f",
|
||||||
|
borderRadius: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{count} 只涨停
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "12px", color: "rgba(255,255,255,0.5)" }}>
|
||||||
|
按连板天数降序排列
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
styles={{
|
||||||
|
header: {
|
||||||
|
background: "rgba(25,25,50,0.98)",
|
||||||
|
borderBottom: "1px solid rgba(255,215,0,0.2)",
|
||||||
|
padding: "16px 24px",
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
background: "linear-gradient(135deg, rgba(15,15,30,0.98) 0%, rgba(25,25,50,0.98) 100%)",
|
||||||
|
padding: "16px 24px",
|
||||||
|
maxHeight: "70vh",
|
||||||
|
overflowY: "auto",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
background: "linear-gradient(135deg, rgba(15,15,30,0.98) 0%, rgba(25,25,50,0.98) 100%)",
|
||||||
|
borderRadius: "16px",
|
||||||
|
border: "1px solid rgba(255,215,0,0.2)",
|
||||||
|
},
|
||||||
|
mask: { background: "rgba(0,0,0,0.7)", backdropFilter: GLASS_BLUR.sm },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stocks.length > 0 ? (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
|
{/* 快速统计 */}
|
||||||
|
<div style={{ display: "flex", gap: "16px", flexWrap: "wrap" }}>
|
||||||
|
{Object.entries(stats).map(
|
||||||
|
([key, value]) =>
|
||||||
|
value > 0 && (
|
||||||
|
<span
|
||||||
|
key={key}
|
||||||
|
style={{
|
||||||
|
padding: "4px 12px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
background:
|
||||||
|
key === "4连板+"
|
||||||
|
? "rgba(255,77,79,0.15)"
|
||||||
|
: key === "3连板"
|
||||||
|
? "rgba(250,84,28,0.15)"
|
||||||
|
: key === "2连板"
|
||||||
|
? "rgba(250,140,22,0.15)"
|
||||||
|
: "rgba(255,255,255,0.05)",
|
||||||
|
border: `1px solid ${
|
||||||
|
key === "4连板+"
|
||||||
|
? "rgba(255,77,79,0.3)"
|
||||||
|
: key === "3连板"
|
||||||
|
? "rgba(250,84,28,0.3)"
|
||||||
|
: key === "2连板"
|
||||||
|
? "rgba(250,140,22,0.3)"
|
||||||
|
: "rgba(255,255,255,0.1)"
|
||||||
|
}`,
|
||||||
|
color:
|
||||||
|
key === "4连板+"
|
||||||
|
? "#ff4d4f"
|
||||||
|
: key === "3连板"
|
||||||
|
? "#fa541c"
|
||||||
|
: key === "2连板"
|
||||||
|
? "#fa8c16"
|
||||||
|
: "#888",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{key}: <strong>{value}</strong>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 股票列表 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,215,0,0.15)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
className="sector-stocks-table-wrapper"
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
dataSource={stocks}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="scode"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: 650, y: 450 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "200px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.5)" }}>暂无股票数据</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AntModal>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SectorStocksModal;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// 涨停板块视图组件
|
||||||
|
// 展示按板块分组的涨停数据表格
|
||||||
|
|
||||||
|
import React, { memo } from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涨停板块视图
|
||||||
|
* @param {Array} sectorList - 板块列表数据
|
||||||
|
* @param {Array} columns - 表格列配置
|
||||||
|
*/
|
||||||
|
const ZTSectorView = memo(({ sectorList, columns }) => {
|
||||||
|
if (!sectorList?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,215,0,0.15)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
dataSource={sectorList}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="name"
|
||||||
|
size="middle"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 380 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ZTSectorView.displayName = "ZTSectorView";
|
||||||
|
|
||||||
|
export default ZTSectorView;
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// 涨停个股视图组件
|
||||||
|
// 展示涨停个股列表,支持按板块筛选
|
||||||
|
|
||||||
|
import React, { memo } from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 涨停个股视图
|
||||||
|
* @param {Array} stockList - 完整股票列表
|
||||||
|
* @param {Array} filteredStockList - 筛选后的股票列表
|
||||||
|
* @param {Array} sectorList - 板块列表(用于筛选器)
|
||||||
|
* @param {Array} columns - 表格列配置
|
||||||
|
* @param {string|null} selectedSectorFilter - 当前选中的板块筛选
|
||||||
|
* @param {Function} onSectorFilterChange - 筛选变化回调
|
||||||
|
*/
|
||||||
|
const ZTStockListView = memo(({
|
||||||
|
stockList,
|
||||||
|
filteredStockList,
|
||||||
|
sectorList,
|
||||||
|
columns,
|
||||||
|
selectedSectorFilter,
|
||||||
|
onSectorFilterChange,
|
||||||
|
}) => {
|
||||||
|
if (!stockList?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
|
{/* 板块筛选器 */}
|
||||||
|
<div>
|
||||||
|
<div style={{ display: "flex", gap: "8px", alignItems: "center", marginBottom: "8px" }}>
|
||||||
|
<span style={{ fontSize: "12px", color: "rgba(255,255,255,0.5)" }}>
|
||||||
|
板块筛选:
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
padding: "4px 12px",
|
||||||
|
borderRadius: "9999px",
|
||||||
|
background: !selectedSectorFilter ? "rgba(255,215,0,0.2)" : "rgba(255,255,255,0.05)",
|
||||||
|
border: !selectedSectorFilter ? "1px solid rgba(255,215,0,0.4)" : "1px solid rgba(255,255,255,0.1)",
|
||||||
|
color: !selectedSectorFilter ? "#FFD700" : "#888",
|
||||||
|
fontSize: "12px",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onSectorFilterChange(null)}
|
||||||
|
>
|
||||||
|
全部 ({stockList.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
|
||||||
|
{sectorList.slice(0, 10).map((sector) => (
|
||||||
|
<button
|
||||||
|
key={sector.name}
|
||||||
|
style={{
|
||||||
|
padding: "4px 10px",
|
||||||
|
borderRadius: "9999px",
|
||||||
|
background: selectedSectorFilter === sector.name ? "rgba(59,130,246,0.2)" : "rgba(255,255,255,0.03)",
|
||||||
|
border: selectedSectorFilter === sector.name ? "1px solid rgba(59,130,246,0.4)" : "1px solid rgba(255,255,255,0.08)",
|
||||||
|
color: selectedSectorFilter === sector.name ? "#60A5FA" : "#888",
|
||||||
|
fontSize: "12px",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onSectorFilterChange(selectedSectorFilter === sector.name ? null : sector.name)}
|
||||||
|
>
|
||||||
|
{sector.name} ({sector.count})
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 筛选结果提示 */}
|
||||||
|
{selectedSectorFilter && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
padding: "8px 12px",
|
||||||
|
background: "rgba(59,130,246,0.1)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "1px solid rgba(59,130,246,0.2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: "#60A5FA", fontSize: "13px" }}>
|
||||||
|
{selectedSectorFilter}
|
||||||
|
</span>
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.5)", fontSize: "12px" }}>
|
||||||
|
共 {filteredStockList.length} 只涨停
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto",
|
||||||
|
padding: "2px 8px",
|
||||||
|
background: "transparent",
|
||||||
|
border: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
borderRadius: "4px",
|
||||||
|
color: "#888",
|
||||||
|
fontSize: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onSectorFilterChange(null)}
|
||||||
|
>
|
||||||
|
清除筛选
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 股票表格 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(59,130,246,0.15)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
dataSource={filteredStockList}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="scode"
|
||||||
|
size="small"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 320 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ZTStockListView.displayName = "ZTStockListView";
|
||||||
|
|
||||||
|
export default ZTStockListView;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HeroPanel - DetailModal 子组件导出
|
||||||
|
export { default as DetailModal } from "./DetailModal";
|
||||||
|
export { default as RelatedEventsModal } from "./RelatedEventsModal";
|
||||||
|
export { default as SectorStocksModal } from "./SectorStocksModal";
|
||||||
|
export { default as ZTSectorView } from "./ZTSectorView";
|
||||||
|
export { default as ZTStockListView } from "./ZTStockListView";
|
||||||
|
export { default as EventsTabView } from "./EventsTabView";
|
||||||
Reference in New Issue
Block a user