This commit is contained in:
parent
d21432dad7
commit
e1eaf964f6
|
|
@ -44,11 +44,13 @@ export default function AnalysisPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-12">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
|
||||||
<div className="max-w-6xl mx-auto space-y-8">
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<div className="max-w-6xl mx-auto space-y-8">
|
||||||
{/* 標題區域 - 置中對齊 */}
|
{/* 標題區域 - 置中對齊 */}
|
||||||
<div className="text-center">
|
<div className="text-center relative">
|
||||||
<h1 className="text-4xl font-bold mb-2">交易分析</h1>
|
<div className="absolute inset-0 gradient-bg-radial opacity-40 -z-10" />
|
||||||
|
<h1 className="text-4xl font-bold mb-2 gradient-text-primary">交易分析</h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
配置並執行全面的多代理交易分析
|
配置並執行全面的多代理交易分析
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -66,6 +68,7 @@ export default function AnalysisPage() {
|
||||||
<p className="text-red-600 dark:text-red-400">{error}</p>
|
<p className="text-red-600 dark:text-red-400">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -129,12 +129,14 @@ export default function AnalysisResultsPage() {
|
||||||
const currentReport = getNestedValue(analysisResult.reports, currentAnalyst?.reportKey || "");
|
const currentReport = getNestedValue(analysisResult.reports, currentAnalyst?.reportKey || "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-12">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
|
||||||
<div className="max-w-7xl mx-auto space-y-8">
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<div className="max-w-7xl mx-auto space-y-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 animate-fade-in">
|
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 animate-fade-in relative">
|
||||||
|
<div className="absolute inset-0 gradient-bg-radial opacity-30 -z-10 rounded-lg" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-bold mb-2">
|
<h1 className="text-4xl font-bold mb-2 gradient-text-primary">
|
||||||
{analysisResult.ticker} 詳細分析結果
|
{analysisResult.ticker} 詳細分析結果
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
|
@ -178,7 +180,7 @@ export default function AnalysisResultsPage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 分析師報告 */}
|
{/* 分析師報告 */}
|
||||||
<Card className="animate-scale-up hover-lift">
|
<Card className="animate-scale-up hover-lift gradient-card gradient-shine">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{analyst.label} 報告</CardTitle>
|
<CardTitle>{analyst.label} 報告</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|
@ -219,6 +221,7 @@ export default function AnalysisResultsPage() {
|
||||||
reports={analysisResult.reports}
|
reports={analysisResult.reports}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -252,3 +252,282 @@
|
||||||
background-size: 200% auto;
|
background-size: 200% auto;
|
||||||
animation: shimmer 3s linear infinite;
|
animation: shimmer 3s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
GRADIENT UTILITIES
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
/* Gradient Backgrounds */
|
||||||
|
.gradient-bg-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-bg-primary {
|
||||||
|
background: linear-gradient(135deg, #4c51bf 0%, #553c9a 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg-secondary {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-bg-secondary {
|
||||||
|
background: linear-gradient(135deg, #c471ed 0%, #c94b4b 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg-accent {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-bg-accent {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg-radial {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at top right,
|
||||||
|
rgba(102, 126, 234, 0.15),
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at bottom left,
|
||||||
|
rgba(118, 75, 162, 0.15),
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-bg-radial {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at top right,
|
||||||
|
rgba(76, 81, 191, 0.2),
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at bottom left,
|
||||||
|
rgba(85, 60, 154, 0.2),
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg-mesh {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#667eea 0%,
|
||||||
|
#764ba2 25%,
|
||||||
|
#f093fb 50%,
|
||||||
|
#f5576c 75%,
|
||||||
|
#4facfe 100%
|
||||||
|
);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientShift 15s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-bg-mesh {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#4c51bf 0%,
|
||||||
|
#553c9a 25%,
|
||||||
|
#c471ed 50%,
|
||||||
|
#c94b4b 75%,
|
||||||
|
#3b82f6 100%
|
||||||
|
);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientShift 15s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientShift {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient Page Backgrounds - Enhanced visibility */
|
||||||
|
.gradient-page-bg {
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(102, 126, 234, 0.12) 0%,
|
||||||
|
rgba(240, 242, 255, 0.8) 25%,
|
||||||
|
rgba(255, 240, 245, 0.8) 50%,
|
||||||
|
rgba(245, 240, 255, 0.8) 75%,
|
||||||
|
rgba(118, 75, 162, 0.12) 100%
|
||||||
|
);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-page-bg {
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(76, 81, 191, 0.25) 0%,
|
||||||
|
rgba(30, 30, 45, 0.95) 25%,
|
||||||
|
rgba(40, 30, 45, 0.95) 50%,
|
||||||
|
rgba(35, 30, 50, 0.95) 75%,
|
||||||
|
rgba(85, 60, 154, 0.25) 100%
|
||||||
|
);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient Borders */
|
||||||
|
.gradient-border {
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background: linear-gradient(var(--background), var(--background)) padding-box,
|
||||||
|
linear-gradient(135deg, #667eea, #764ba2) border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-border {
|
||||||
|
background: linear-gradient(var(--background), var(--background)) padding-box,
|
||||||
|
linear-gradient(135deg, #4c51bf, #553c9a) border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-border-animated {
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background: linear-gradient(var(--background), var(--background)) padding-box,
|
||||||
|
linear-gradient(135deg, #667eea, #764ba2, #f093fb, #667eea) border-box;
|
||||||
|
background-size: 300% 300%;
|
||||||
|
animation: gradientBorder 4s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-border-animated {
|
||||||
|
background: linear-gradient(var(--background), var(--background)) padding-box,
|
||||||
|
linear-gradient(135deg, #4c51bf, #553c9a, #c471ed, #4c51bf) border-box;
|
||||||
|
background-size: 300% 300%;
|
||||||
|
animation: gradientBorder 4s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientBorder {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient Text */
|
||||||
|
.gradient-text-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text-secondary {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text-accent {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient Overlays */
|
||||||
|
.gradient-overlay {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-overlay::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(102, 126, 234, 0.1),
|
||||||
|
rgba(118, 75, 162, 0.1)
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-overlay::before {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(76, 81, 191, 0.15),
|
||||||
|
rgba(85, 60, 154, 0.15)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient Shine Effect */
|
||||||
|
.gradient-shine {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-shine::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(255, 255, 255, 0.1) 50%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
transform: translateX(-100%) translateY(-100%);
|
||||||
|
transition: transform 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-shine:hover::after {
|
||||||
|
transform: translateX(100%) translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-shine::after {
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent 30%,
|
||||||
|
rgba(255, 255, 255, 0.05) 50%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced Card Gradients */
|
||||||
|
.gradient-card {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(102, 126, 234, 0.05) 0%,
|
||||||
|
rgba(118, 75, 162, 0.05) 100%
|
||||||
|
);
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-card:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(102, 126, 234, 0.1) 0%,
|
||||||
|
rgba(118, 75, 162, 0.1) 100%
|
||||||
|
);
|
||||||
|
border-color: rgba(102, 126, 234, 0.4);
|
||||||
|
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-card {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(76, 81, 191, 0.1) 0%,
|
||||||
|
rgba(85, 60, 154, 0.1) 100%
|
||||||
|
);
|
||||||
|
border-color: rgba(76, 81, 191, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .gradient-card:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(76, 81, 191, 0.15) 0%,
|
||||||
|
rgba(85, 60, 154, 0.15) 100%
|
||||||
|
);
|
||||||
|
border-color: rgba(76, 81, 191, 0.5);
|
||||||
|
box-shadow: 0 10px 40px rgba(76, 81, 191, 0.3);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default function RootLayout({
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AnalysisProvider>
|
<AnalysisProvider>
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen gradient-page-bg">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@ import {
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-12">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-purple-900/20 dark:to-blue-900/20">
|
||||||
{/* Hero Section */}
|
<div className="container mx-auto px-4 py-12">
|
||||||
<div className="text-center mb-16 animate-fade-in">
|
{/* Hero Section */}
|
||||||
<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
<div className="text-center mb-16 animate-fade-in relative py-8">
|
||||||
|
<div className="absolute inset-0 gradient-bg-radial -z-10" />
|
||||||
|
<h1 className="text-5xl md:text-6xl font-bold mb-6 gradient-text-primary leading-tight">
|
||||||
TradingAgentsX
|
TradingAgentsX
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
|
<p className="text-xl text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||||
|
|
@ -24,7 +26,7 @@ export default function HomePage() {
|
||||||
<Link href="/analysis">
|
<Link href="/analysis">
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
|
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 shadow-lg hover:shadow-xl transition-shadow"
|
||||||
>
|
>
|
||||||
開始分析
|
開始分析
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -324,20 +326,22 @@ export default function HomePage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Call to Action Section */}
|
{/* Call to Action Section */}
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16 relative">
|
||||||
<h2 className="text-3xl font-bold mb-4">準備好開始智能交易分析了嗎?</h2>
|
<div className="absolute inset-0 gradient-bg-radial opacity-60 -z-10" />
|
||||||
|
<h2 className="text-3xl font-bold mb-4 gradient-text-primary">準備好開始智能交易分析了嗎?</h2>
|
||||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
|
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8 max-w-2xl mx-auto">
|
||||||
立即體驗 12 位專業 AI 代理協同工作,為您提供全方位的股票分析報告
|
立即體驗 12 位專業 AI 代理協同工作,為您提供全方位的股票分析報告
|
||||||
</p>
|
</p>
|
||||||
<Link href="/analysis">
|
<Link href="/analysis">
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-lg px-8 py-6"
|
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-lg px-8 py-6 shadow-lg hover:shadow-2xl transition-all"
|
||||||
>
|
>
|
||||||
開始分析 →
|
開始分析 →
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +356,7 @@ function FeatureCard({
|
||||||
icon: string;
|
icon: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className="hover-lift animate-slide-up">
|
<Card className="hover-lift animate-slide-up gradient-shine gradient-card">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="text-4xl mb-2">{icon}</div>
|
<div className="text-4xl mb-2">{icon}</div>
|
||||||
<CardTitle className="text-lg">{title}</CardTitle>
|
<CardTitle className="text-lg">{title}</CardTitle>
|
||||||
|
|
@ -376,7 +380,7 @@ function AgentCard({
|
||||||
responsibilities: string[];
|
responsibilities: string[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className="hover-lift animate-scale-up">
|
<Card className="hover-lift animate-scale-up gradient-card">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">{name}</CardTitle>
|
<CardTitle className="text-base">{name}</CardTitle>
|
||||||
<CardDescription className="text-xs">{role}</CardDescription>
|
<CardDescription className="text-xs">{role}</CardDescription>
|
||||||
|
|
@ -417,7 +421,7 @@ function LLMProviderCard({
|
||||||
const logoSrc = logoMap[name];
|
const logoSrc = logoMap[name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="hover-lift animate-slide-up animate-delay-100">
|
<Card className="hover-lift animate-slide-up animate-delay-100 gradient-card gradient-shine">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{logoSrc ? (
|
{logoSrc ? (
|
||||||
|
|
@ -458,7 +462,7 @@ function TechnicalCard({
|
||||||
features: string[];
|
features: string[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className="hover-lift animate-slide-up animate-delay-300">
|
<Card className="hover-lift animate-slide-up animate-delay-300 gradient-card">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">{title}</CardTitle>
|
<CardTitle className="text-lg">{title}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
) : (
|
) : (
|
||||||
|
// 真正的K線圖(蠟燭圖)
|
||||||
<ComposedChart data={priceData}>
|
<ComposedChart data={priceData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis
|
<XAxis
|
||||||
|
|
@ -121,41 +122,89 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
||||||
tickFormatter={(value) => `$${value.toFixed(0)}`}
|
tickFormatter={(value) => `$${value.toFixed(0)}`}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number, name: string) => [`$${formatNumber(value)}`, name]}
|
content={({ active, payload }) => {
|
||||||
labelFormatter={(label) => `日期: ${label}`}
|
if (active && payload && payload.length) {
|
||||||
|
const data = payload[0].payload;
|
||||||
|
return (
|
||||||
|
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
|
||||||
|
<p className="text-sm font-semibold mb-2">日期: {data.Date}</p>
|
||||||
|
<div className="space-y-1 text-sm">
|
||||||
|
<p className="text-green-600">開: ${formatNumber(data.Open)}</p>
|
||||||
|
<p className="text-red-600">收: ${formatNumber(data.Close)}</p>
|
||||||
|
<p className="text-blue-600">高: ${formatNumber(data.High)}</p>
|
||||||
|
<p className="text-orange-600">低: ${formatNumber(data.Low)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Bar
|
||||||
<Area
|
|
||||||
type="monotone"
|
|
||||||
dataKey="High"
|
|
||||||
stroke="#86efac"
|
|
||||||
fill="#86efac"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
name="最高價"
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
type="monotone"
|
|
||||||
dataKey="Low"
|
|
||||||
stroke="#fca5a5"
|
|
||||||
fill="#fca5a5"
|
|
||||||
fillOpacity={0.3}
|
|
||||||
name="最低價"
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="Open"
|
|
||||||
stroke="#f59e0b"
|
|
||||||
strokeWidth={2}
|
|
||||||
name="開盤價"
|
|
||||||
dot={false}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="Close"
|
dataKey="Close"
|
||||||
stroke="#2563eb"
|
shape={(props: any) => {
|
||||||
strokeWidth={2}
|
const { x, y, width, payload } = props;
|
||||||
name="收盤價"
|
const { Open, Close, High, Low } = payload;
|
||||||
dot={false}
|
|
||||||
|
// 計算Y軸的比例尺
|
||||||
|
const yAxis = props.yAxis;
|
||||||
|
const yScale = (value: number) => {
|
||||||
|
const { domain, height } = yAxis;
|
||||||
|
const [min, max] = domain;
|
||||||
|
return height - ((value - min) / (max - min)) * height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openY = yScale(Open);
|
||||||
|
const closeY = yScale(Close);
|
||||||
|
const highY = yScale(High);
|
||||||
|
const lowY = yScale(Low);
|
||||||
|
|
||||||
|
// 判斷漲跌
|
||||||
|
const isUp = Close >= Open;
|
||||||
|
const color = isUp ? '#22c55e' : '#ef4444'; // 綠色上漲,紅色下跌
|
||||||
|
const fillColor = isUp ? '#22c55e' : '#ef4444';
|
||||||
|
|
||||||
|
// K線寬度
|
||||||
|
const candleWidth = Math.min(width * 0.6, 8);
|
||||||
|
const centerX = x + width / 2;
|
||||||
|
|
||||||
|
// 實體高度
|
||||||
|
const bodyHeight = Math.abs(closeY - openY);
|
||||||
|
const bodyY = Math.min(openY, closeY);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{/* 上影線 */}
|
||||||
|
<line
|
||||||
|
x1={centerX}
|
||||||
|
y1={highY}
|
||||||
|
x2={centerX}
|
||||||
|
y2={Math.min(openY, closeY)}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
{/* 下影線 */}
|
||||||
|
<line
|
||||||
|
x1={centerX}
|
||||||
|
y1={Math.max(openY, closeY)}
|
||||||
|
x2={centerX}
|
||||||
|
y2={lowY}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
{/* K線實體 */}
|
||||||
|
<rect
|
||||||
|
x={centerX - candleWidth / 2}
|
||||||
|
y={bodyY}
|
||||||
|
width={candleWidth}
|
||||||
|
height={bodyHeight || 1} // 至少1px高度避免十字星消失
|
||||||
|
fill={fillColor}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={1}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue