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