调整扫描面板并新增今日分析入口

This commit is contained in:
2569718930@qq.com
2026-04-24 04:14:52 +08:00
parent 4950e31c69
commit 34594e6661
6 changed files with 76 additions and 161 deletions
@@ -9552,6 +9552,26 @@
background: rgba(8, 17, 30, 0.8);
}
.root :global(.scan-map-shell .map) {
position: absolute;
inset: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
border-radius: 18px;
box-shadow: none;
}
.root :global(.scan-map-shell .leaflet-container) {
width: 100%;
height: 100%;
background: #060d18;
}
.root :global(.scan-map-caption) {
color: #8fa4c3;
font-size: 13px;
@@ -10003,6 +10023,28 @@
font-weight: 700;
}
.root :global(.scan-detail-primary-actions) {
display: flex;
margin-bottom: 16px;
}
.root :global(.scan-detail-analysis-button) {
width: 100%;
border: 1px solid rgba(23, 217, 139, 0.3);
border-radius: 14px;
background: rgba(23, 217, 139, 0.12);
color: #27ea98;
padding: 12px 14px;
font-size: 14px;
font-weight: 800;
text-align: center;
}
.root :global(.scan-detail-analysis-button:hover) {
background: rgba(23, 217, 139, 0.18);
border-color: rgba(23, 217, 139, 0.4);
}
.root :global(.scan-detail-section) {
padding: 16px 0;
border-bottom: 1px solid rgba(90, 123, 166, 0.12);
@@ -77,12 +77,18 @@ export function getWindowPhaseMeta(
tone: "red",
};
}
if (mode === "setup_today" || mode === "early_today") {
if (mode === "setup_today") {
return {
label: locale === "en-US" ? "Touch Play" : "触达博弈",
tone: "red",
};
}
if (mode === "early_today") {
return {
label: locale === "en-US" ? "Early Today" : "日内早段",
tone: "blue",
};
}
if (mode === "tomorrow" || mode === "week_ahead") {
return {
label: locale === "en-US" ? "Early" : "早期机会",
@@ -223,7 +229,7 @@ export function OpportunityTable({
<span />
<span>{isEn ? "City / Market" : "城市 / 市场"}</span>
<span>{isEn ? "Local Time / Phase" : "当前时间 / 阶段"}</span>
<span>{isEn ? "Model vs Market" : "模型分布 vs 市场分布"}</span>
<span>{isEn ? "EMOS vs Market" : "EMOS 分布 vs 市场分布"}</span>
<span>{isEn ? "Best Opportunity" : "最佳机会"}</span>
<span>{isEn ? "Edge" : "边际优势"}</span>
<span>{isEn ? "Score" : "综合得分"}</span>
@@ -50,9 +50,6 @@ const SCAN_MODES = [
},
] as const;
const LIQUIDITY_OPTIONS = [500, 1000, 5000, 10000];
const EDGE_OPTIONS = [1, 2, 3, 5, 8];
export function ScanFilterPanel({
value,
onChange,
@@ -127,149 +124,6 @@ export function ScanFilterPanel({
</div>
</section>
<section className="scan-filter-section">
<div className="scan-filter-heading">
<span>{isEn ? "Filters" : "筛选条件"}</span>
<Info size={14} />
</div>
<div className="scan-range-card">
<div className="scan-filter-row-title">
{isEn ? "Price Range" : "价格范围"}
</div>
<div className="scan-range-track-wrap">
<input
type="range"
min={0}
max={100}
value={Math.round(value.min_price * 100)}
onChange={(e) =>
updateFilter(
"min_price",
Math.min(Number(e.target.value) / 100, value.max_price),
)
}
className="scan-range-slider min"
/>
<input
type="range"
min={0}
max={100}
value={Math.round(value.max_price * 100)}
onChange={(e) =>
updateFilter(
"max_price",
Math.max(Number(e.target.value) / 100, value.min_price),
)
}
className="scan-range-slider max"
/>
</div>
<div className="scan-range-labels">
<span>{value.min_price.toFixed(2)}</span>
<span>{value.max_price.toFixed(2)}</span>
</div>
</div>
<label className="scan-filter-row">
<span className="scan-filter-row-label">
{isEn ? "Min Liquidity" : "最小成交量"}
</span>
<select
className="scan-select"
value={value.min_liquidity}
onChange={(e) => updateFilter("min_liquidity", Number(e.target.value))}
>
{LIQUIDITY_OPTIONS.map((option) => (
<option key={option} value={option}>
${option.toLocaleString("en-US")}
</option>
))}
</select>
</label>
<label className="scan-filter-row">
<span className="scan-filter-row-label">
{isEn ? "Min Edge" : "最小边际优势"}
</span>
<select
className="scan-select"
value={value.min_edge_pct}
onChange={(e) => updateFilter("min_edge_pct", Number(e.target.value))}
>
{EDGE_OPTIONS.map((option) => (
<option key={option} value={option}>
{option}%
</option>
))}
</select>
</label>
<div className="scan-filter-row inline">
<span className="scan-filter-row-label">
{isEn ? "High Liquidity Only" : "只看高流动性"}
</span>
<button
type="button"
className={`scan-toggle ${value.high_liquidity_only ? "active" : ""}`}
onClick={() => {
const nextValue = !value.high_liquidity_only;
onChange?.({
...value,
high_liquidity_only: nextValue,
min_liquidity: nextValue
? Math.max(value.min_liquidity, 5000)
: value.min_liquidity,
});
}}
aria-pressed={value.high_liquidity_only}
>
<span className="scan-toggle-knob" />
</button>
</div>
<label className="scan-filter-row">
<span className="scan-filter-row-label">
{isEn ? "Market Type" : "市场类型"}
</span>
<select
className="scan-select"
value={value.market_type}
onChange={(e) =>
updateFilter(
"market_type",
e.target.value as FilterState["market_type"],
)
}
>
<option value="maxtemp">
{isEn ? "Max Temperature" : "最高温度"}
</option>
<option value="all">{isEn ? "All Markets" : "所有市场"}</option>
</select>
</label>
<label className="scan-filter-row">
<span className="scan-filter-row-label">
{isEn ? "Time Range" : "时间范围"}
</span>
<select
className="scan-select"
value={value.time_range}
onChange={(e) =>
updateFilter(
"time_range",
e.target.value as FilterState["time_range"],
)
}
>
<option value="today">{isEn ? "Today" : "今天"}</option>
<option value="tomorrow">{isEn ? "Tomorrow" : "明天"}</option>
<option value="week">{isEn ? "This Week" : "本周"}</option>
</select>
</label>
</section>
<button
type="button"
className="scan-cta-button"
@@ -7,7 +7,6 @@ import {
Menu,
RefreshCw,
UserRound,
X,
} from "lucide-react";
import {
startTransition,
@@ -27,6 +26,7 @@ import { MapCanvas } from "@/components/dashboard/MapCanvas";
import { getWindowPhaseMeta } from "@/components/dashboard/OpportunityTable";
import { ScanKPIBar } from "@/components/dashboard/ScanKPIBar";
import { OpportunityTable } from "@/components/dashboard/OpportunityTable";
import { FutureForecastModal } from "@/components/dashboard/FutureForecastModal";
import {
DashboardStoreProvider,
useDashboardStore,
@@ -247,8 +247,10 @@ function DetailPanel({
marketScan?: MarketScan | null;
loading?: boolean;
}) {
const store = useDashboardStore();
const { locale } = useI18n();
const isEn = locale === "en-US";
const isPro = store.proAccess.subscriptionActive;
if (!row) {
return (
@@ -287,6 +289,12 @@ function DetailPanel({
const scoreClass = scoreTone(displayRow.final_score);
const phaseMeta = getWindowPhaseMeta(displayRow, locale);
const openTodayAnalysis = async () => {
if (!row.city) return;
await store.selectCity(row.city);
await store.openTodayModal();
};
return (
<aside className="scan-detail-panel">
<div className="scan-detail-header">
@@ -301,9 +309,6 @@ function DetailPanel({
</div>
</div>
</div>
<button type="button" className="scan-detail-icon-button" aria-label="close">
<X size={16} />
</button>
</div>
<div className="scan-detail-volume-row">
@@ -319,6 +324,22 @@ function DetailPanel({
</button>
</div>
<div className="scan-detail-primary-actions">
<button
type="button"
className="scan-detail-analysis-button"
onClick={() => void openTodayAnalysis()}
>
{isPro
? isEn
? "Today's Intraday Analysis"
: "今日日内分析"
: isEn
? "Today's Intraday Analysis · Pro"
: "今日日内分析 · Pro"}
</button>
</div>
<section className="scan-detail-section">
<div className="scan-detail-section-title">
{isEn ? "Current Context" : "当前概况"}
@@ -804,15 +825,6 @@ function ScanTerminalScreen() {
selectedRowId={selectedRowId}
onSelectRow={(row) => setSelectedRowId(row.id)}
/>
{deferredRows.length ? (
<div className="scan-view-all-wrap">
<button type="button" className="scan-view-all-button">
{isEn
? `View all ${deferredRows.length} opportunities`
: `查看全部 ${deferredRows.length} 个机会`}
</button>
</div>
) : null}
</>
);
};
@@ -985,6 +997,7 @@ export function ScanTerminalDashboard() {
<I18nProvider>
<DashboardStoreProvider>
<ScanTerminalScreen />
<FutureForecastModal />
</DashboardStoreProvider>
</I18nProvider>
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB