Simplify scan opportunity list and speed quotes

This commit is contained in:
2569718930@qq.com
2026-04-24 14:26:47 +08:00
parent edc8559147
commit 42d4a4e198
4 changed files with 92 additions and 117 deletions
@@ -9572,7 +9572,7 @@
.root :global(.scan-table-header) {
display: grid;
grid-template-columns: 46px minmax(180px, 1.08fr) minmax(132px, 0.72fr) minmax(220px, 1.08fr) minmax(150px, 0.82fr) 92px 82px;
grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
gap: 10px;
align-items: center;
padding: 14px 18px;
@@ -9786,7 +9786,7 @@
.root :global(.scan-table-row) {
display: grid;
grid-template-columns: 46px minmax(180px, 1.08fr) minmax(132px, 0.72fr) minmax(220px, 1.08fr) minmax(150px, 0.82fr) 92px 82px;
grid-template-columns: minmax(210px, 1.1fr) minmax(132px, 0.72fr) minmax(220px, 1.12fr) minmax(150px, 0.82fr) 96px;
gap: 10px;
align-items: center;
min-height: 132px;
@@ -9813,38 +9813,12 @@
box-shadow: inset 0 0 0 1px rgba(23, 217, 139, 0.48);
}
.root :global(.scan-rank-cell),
.root :global(.scan-score-cell),
.root :global(.scan-edge-cell) {
display: flex;
align-items: center;
justify-content: center;
}
.root :global(.scan-rank-circle) {
width: 34px;
height: 34px;
font-size: 16px;
font-weight: 800;
color: #06150d;
background: linear-gradient(180deg, #24e495, #11b86f);
}
.root :global(.scan-rank-circle.yellow) {
color: #281a00;
background: linear-gradient(180deg, #ffd95f, #ffb61c);
}
.root :global(.scan-rank-circle.gray) {
color: #08131f;
background: linear-gradient(180deg, #d6e3f7, #94a7c4);
}
.root :global(.scan-rank-circle.red) {
color: #fff3f3;
background: linear-gradient(180deg, #ff7373, #df4747);
}
.root :global(.scan-city-cell) {
display: block;
}
@@ -9870,11 +9844,6 @@
-webkit-line-clamp: 2;
}
.root :global(.scan-city-volume) {
font-size: 14px;
color: #dbe7f8;
}
.root :global(.scan-time-cell) {
display: flex;
flex-direction: column;
@@ -10036,38 +10005,6 @@
color: #ff6868;
}
.root :global(.scan-score-ring) {
width: 58px;
height: 58px;
border-radius: 50%;
background: conic-gradient(#20df91 0 85%, rgba(255, 255, 255, 0.06) 85% 100%);
}
.root :global(.scan-score-ring.tone-yellow) {
background: conic-gradient(#ffbe26 0 72%, rgba(255, 255, 255, 0.06) 72% 100%);
}
.root :global(.scan-score-ring.tone-red) {
background: conic-gradient(#ff5d5d 0 58%, rgba(255, 255, 255, 0.06) 58% 100%);
}
.root :global(.scan-score-ring::after) {
content: "";
position: absolute;
inset: 5px;
border-radius: 50%;
background: #0b1728;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.root :global(.scan-score-ring span) {
position: relative;
z-index: 1;
font-size: 18px;
font-weight: 800;
color: #ecf4ff;
}
.root :global(.scan-row-fav) {
display: none;
}
@@ -24,14 +24,6 @@ function formatPercent(value?: number | null, signed = false) {
return `${signed && numeric >= 0 ? "+" : ""}${numeric.toFixed(1)}%`;
}
function formatVolume(value?: number | null) {
const numeric = Number(value || 0);
if (!Number.isFinite(numeric) || numeric <= 0) return "--";
if (numeric >= 1_000_000) return `$${(numeric / 1_000_000).toFixed(1)}M`;
if (numeric >= 1_000) return `$${(numeric / 1_000).toFixed(0)}K`;
return `$${numeric.toFixed(0)}`;
}
function formatWindowMinutes(value: number | null | undefined, locale: string) {
if (value == null || !Number.isFinite(Number(value))) return "--";
const minutes = Math.max(0, Math.round(Number(value)));
@@ -118,13 +110,6 @@ export function getWindowPhaseMeta(
};
}
function scoreTone(score?: number | null) {
const numeric = Number(score || 0);
if (numeric >= 85) return "green";
if (numeric >= 70) return "yellow";
return "gray";
}
function ProbabilityPreview({
row,
locale,
@@ -197,15 +182,6 @@ function ProbabilityPreview({
);
}
function ScoreRing({ score }: { score?: number | null }) {
const displayScore = Math.max(0, Math.min(100, Number(score || 0)));
return (
<div className={`scan-score-ring tone-${scoreTone(displayScore)}`}>
<span>{displayScore.toFixed(0)}</span>
</div>
);
}
export const OpportunityTable = React.memo(function OpportunityTable({
rows,
status,
@@ -271,17 +247,15 @@ export const OpportunityTable = React.memo(function OpportunityTable({
</div>
) : null}
<div className="scan-table-header">
<span />
<span>{isEn ? "City / Market" : "城市 / 市场"}</span>
<span>{isEn ? "Local Time / Phase" : "当前时间 / 阶段"}</span>
<span>{isEn ? "EMOS / Market" : "EMOS / 市场"}</span>
<span>{isEn ? "Quote / Model" : "买价 / 模型"}</span>
<span>{isEn ? "Edge" : "边际优势"}</span>
<span>{isEn ? "Score" : "综合得分"}</span>
</div>
<div className="scan-table-body">
{rows.map((row, index) => {
{rows.map((row) => {
const phaseMeta = getWindowPhaseMeta(row, locale);
const tempSymbol = normalizeTemperatureSymbol(row.target_unit || row.temp_symbol);
const localizedCityName = getLocalizedCityName(
@@ -290,7 +264,6 @@ export const OpportunityTable = React.memo(function OpportunityTable({
locale,
);
const selected = selectedRowId === row.id;
const scoreClass = scoreTone(row.final_score);
return (
<button
key={row.id}
@@ -298,19 +271,12 @@ export const OpportunityTable = React.memo(function OpportunityTable({
className={`scan-table-row ${selected ? "selected" : ""}`}
onClick={() => onSelectRow?.(row)}
>
<div className="scan-rank-cell">
<div className={`scan-rank-circle ${scoreClass}`}>
{index + 1}
</div>
</div>
<div className="scan-city-cell">
<div className="scan-city-copy">
<div className="scan-city-name">{localizedCityName}</div>
<div className="scan-city-sub">
{row.market_question || row.target_label || "--"}
</div>
<div className="scan-city-volume">{formatVolume(row.volume)}</div>
</div>
</div>
@@ -344,10 +310,6 @@ export const OpportunityTable = React.memo(function OpportunityTable({
<div className={`scan-edge-cell ${Number(row.edge_percent || 0) >= 0 ? "positive" : "negative"}`}>
{formatPercent(row.edge_percent, true)}
</div>
<div className="scan-score-cell">
<ScoreRing score={row.final_score} />
</div>
</button>
);
})}