summaryrefslogtreecommitdiff
path: root/frontend/src/pages/ProjectResultsTab.svelte
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-18 12:03:32 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-18 12:03:32 +0800
commit61f5c545953bb4ee20b123bf9671ac35d7f1f3c1 (patch)
tree9ede3483db5f7e0d81189f2d29514892b8f77867 /frontend/src/pages/ProjectResultsTab.svelte
parent1f16a357213611a6d7f84e506064fc0d9c6317c6 (diff)
downloadstrategy-lab-61f5c545953bb4ee20b123bf9671ac35d7f1f3c1.tar.gz
[变更性质] feat(frontend)
[新增功能] round3 页面组件全量迁移 shadcn-svelte(12 页面 + Modal/DatasetWizard/DatasetCard/Status) [实现方案] .btn→Button、.banner→Alert、table.data→Table.Root、.badge→Badge、.tabs→Tabs.Root、.metric-card/form.card→Card.Root;ProjectStrategyTab div 配对修复 [影响范围] src/pages/*.svelte、src/components/*.svelte
Diffstat (limited to 'frontend/src/pages/ProjectResultsTab.svelte')
-rw-r--r--frontend/src/pages/ProjectResultsTab.svelte131
1 files changed, 52 insertions, 79 deletions
diff --git a/frontend/src/pages/ProjectResultsTab.svelte b/frontend/src/pages/ProjectResultsTab.svelte
index c08bf3f..82a7009 100644
--- a/frontend/src/pages/ProjectResultsTab.svelte
+++ b/frontend/src/pages/ProjectResultsTab.svelte
@@ -15,6 +15,10 @@
metricsRow
} from '../lib/format';
import { capitalReporting } from '../lib/capitalMetrics';
+ import { Alert } from '$lib/components/ui/alert';
+ import { Badge } from '$lib/components/ui/badge';
+ import * as Card from '$lib/components/ui/card';
+ import * as Table from '$lib/components/ui/table';
import type { Run, BacktestResult } from '../lib/types';
interface Props {
@@ -117,7 +121,7 @@
{#if run}
<span class="row">
<span class="hint">Run {run.id.slice(0, 8)}…</span>
- <span class="badge {runStatusBadge[run.status]}">{runStatusLabel[run.status]}</span>
+ <Badge variant={runStatusBadge[run.status] === 'ok' ? 'secondary' : runStatusBadge[run.status] === 'warn' ? 'outline' : 'destructive'}>{runStatusLabel[run.status]}</Badge>
</span>
{/if}
</div>
@@ -141,67 +145,38 @@
{/if}
{#if run.error}
- <div class="banner error" role="alert"><span aria-hidden="true">✕</span><span>{run.error}</span></div>
+ <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]"><span aria-hidden="true">✕</span><span>{run.error}</span></Alert>
{/if}
{#if !result}
<Status busy={false} empty="该回测尚无结果" emptyHint={runStatusBadge[run.status] === "danger" ? "见错误提示" : "回测仍在执行中,稍后刷新"} />
{:else}
{#if result.warnings?.length}
- <div class="banner warn" role="alert">
+ <Alert variant="default" class="bg-[var(--warn-soft)] border-[#f0ddb4]">
<span aria-hidden="true">⚠</span>
- <div>
- <ul class="ul-plain">
- {#each result.warnings as w, i (i)}<li>{w}</li>{/each}
- </ul>
- </div>
- </div>
+ <ul class="ul-plain">
+ {#each result.warnings as w, i (i)}<li>{w}</li>{/each}
+ </ul>
+ </Alert>
{/if}
- <div class="card">
+ <Card.Root size="sm">
{#if result.metrics}
{@const mrow = result.metrics}
<div class="metric-grid">
- <div class="metric">
- <div class="mlabel">总收益率</div>
- <div class="mvalue">{fmtPct(mrow.total_return)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">年化收益率</div>
- <div class="mvalue">{fmtPct(mrow.annual_return)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">最大回撤</div>
- <div class="mvalue">{fmtPct(mrow.max_drawdown)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">夏普比率</div>
- <div class="mvalue">{fmtNum(mrow.sharpe)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">平仓回合数</div>
- <div class="mvalue">{mrow.trade_count ?? "—"}</div>
- </div>
- <div class="metric">
- <div class="mlabel">期末权益</div>
- <div class="mvalue">{formatMoney(mrow.final_equity)}</div>
- </div>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">总收益率</div><div class="mvalue">{fmtPct(mrow.total_return)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">年化收益率</div><div class="mvalue">{fmtPct(mrow.annual_return)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">最大回撤</div><div class="mvalue">{fmtPct(mrow.max_drawdown)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">夏普比率</div><div class="mvalue">{fmtNum(mrow.sharpe)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">平仓回合数</div><div class="mvalue">{mrow.trade_count ?? "—"}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">期末权益</div><div class="mvalue">{formatMoney(mrow.final_equity)}</div></Card.Root>
</div>
{/if}
{#if cap}
<div class="metric-grid" data-testid="capital-reporting">
- <div class="metric">
- <div class="mlabel">累计投入</div>
- <div class="mvalue">{formatMoney(cap.invested)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">最终持仓估值</div>
- <div class="mvalue">{formatMoney(cap.holdingValue)}</div>
- </div>
- <div class="metric">
- <div class="mlabel">投入回报率</div>
- <div class="mvalue">{cap.returnPct ?? '—'}</div>
- </div>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">累计投入</div><div class="mvalue">{formatMoney(cap.invested)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">最终持仓估值</div><div class="mvalue">{formatMoney(cap.holdingValue)}</div></Card.Root>
+ <Card.Root size="sm" class="m-tile"><div class="mlabel">投入回报率</div><div class="mvalue">{cap.returnPct ?? '—'}</div></Card.Root>
</div>
<p class="hint">投入口径不含初始账户闲置现金:累计投入 = 首日现金 − 末日现金;持仓估值 = 末日持仓 × 最新收盘价。</p>
{/if}
@@ -211,9 +186,9 @@
{#if result.elapsed_ms}<span> · 耗时 {(result.elapsed_ms / 1000).toFixed(1)}s</span>{/if}
{#if result.peak_rss_kb}<span> · 峰值内存 {fmtNum(result.peak_rss_kb / 1024)} MB</span>{/if}
</p>
- </div>
+ </Card.Root>
- <div class="card stack">
+ <Card.Root size="sm" class="stack">
<div class="row">
<Button variant={chartMode === "equity" ? "default" : "outline"} size="sm" aria-pressed={chartMode === "equity"} onclick={() => (chartMode = "equity")}>权益曲线({result.equity.length} 点)</Button>
<Button variant={chartMode === "drawdown" ? "default" : "outline"} size="sm" aria-pressed={chartMode === "drawdown"} onclick={() => (chartMode = "drawdown")}>回撤曲线</Button>
@@ -223,42 +198,40 @@
{:else}
<Chart data={drawdownData} series={drawdownSeries} ariaLabel="回撤曲线" height={300} valueFormatter={(v) => (typeof v === 'number' ? `${v.toFixed(2)}%` : String(v))} />
{/if}
- </div>
+ </Card.Root>
- <div class="card stack">
+ <Card.Root size="sm" class="stack">
<h2>订单与持仓记录</h2>
<div class="cols-2">
<div>
<h3>成交记录 ({result.trades.length})</h3>
{#if result.trades.length > 0}
- <div class="table-wrap">
- <table class="data">
- <thead>
- <tr>
- <th>日期</th>
- <th>标的</th>
- <th>方向</th>
- <th>数量</th>
- <th>价格</th>
- <th>金额</th>
- <th>佣金</th>
- </tr>
- </thead>
- <tbody>
- {#each result.trades.slice(0, 80) as t, i (i)}
- <tr>
- <td>{t.date}</td>
- <td class="num">{t.symbol}</td>
- <td>{t.side === "buy" ? "买入" : "卖出"}</td>
- <td class="num">{t.quantity}</td>
- <td class="num">{fmtNum(t.price, 3)}</td>
- <td class="num">{fmtNum(t.value)}</td>
- <td class="num">{fmtNum(t.commission, 4)}</td>
- </tr>
- {/each}
- </tbody>
- </table>
- </div>
+ <Table.Root>
+ <Table.Header>
+ <Table.Row>
+ <Table.Head>日期</Table.Head>
+ <Table.Head>标的</Table.Head>
+ <Table.Head>方向</Table.Head>
+ <Table.Head>数量</Table.Head>
+ <Table.Head>价格</Table.Head>
+ <Table.Head>金额</Table.Head>
+ <Table.Head>佣金</Table.Head>
+ </Table.Row>
+ </Table.Header>
+ <Table.Body>
+ {#each result.trades.slice(0, 80) as t, i (i)}
+ <Table.Row>
+ <Table.Cell>{t.date}</Table.Cell>
+ <Table.Cell class="num">{t.symbol}</Table.Cell>
+ <Table.Cell>{t.side === "buy" ? "买入" : "卖出"}</Table.Cell>
+ <Table.Cell class="num">{t.quantity}</Table.Cell>
+ <Table.Cell class="num">{fmtNum(t.price, 3)}</Table.Cell>
+ <Table.Cell class="num">{fmtNum(t.value)}</Table.Cell>
+ <Table.Cell class="num">{fmtNum(t.commission, 4)}</Table.Cell>
+ </Table.Row>
+ {/each}
+ </Table.Body>
+ </Table.Root>
{#if result.trades.length > 80}
<p class="hint">仅显示前 80 条,共 {result.trades.length} 条</p>
{/if}
@@ -283,7 +256,7 @@
结果由对应策略代码、配置、数据清单(数据哈希 {result.data_manifest_hash?.slice(0, 12)})以及当时引擎共同决定。
</p>
</div>
- </div>
+ </Card.Root>
{/if}
{/if}