summaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/DatasetCard.svelte14
-rw-r--r--frontend/src/components/DatasetWizard.svelte31
-rw-r--r--frontend/src/components/Modal.svelte3
-rw-r--r--frontend/src/components/ProjectMetricRow.svelte83
-rw-r--r--frontend/src/components/Status.svelte14
5 files changed, 70 insertions, 75 deletions
diff --git a/frontend/src/components/DatasetCard.svelte b/frontend/src/components/DatasetCard.svelte
index 2d49e65..69083fb 100644
--- a/frontend/src/components/DatasetCard.svelte
+++ b/frontend/src/components/DatasetCard.svelte
@@ -153,7 +153,7 @@
</Table.Root>
</div>
{#if ds.status === 'failed'}
- <div class="banner error" role="alert"><span aria-hidden="true">✕</span><span>{ds.error ?? '数据获取失败'}</span></div>
+ <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]"><span aria-hidden="true">✕</span><span>{ds.error ?? '数据获取失败'}</span></Alert>
<div class="retry-row">
<Button variant="outline" size="sm"
disabled={recreateBusy || recreatedId !== null}
@@ -181,28 +181,28 @@
{#if preview}
{@const cov = summarizeCoverage(preview.coverage ?? [])}
{#if cov.warnings.length > 0}
- <div class="banner warn"><span aria-hidden="true">⚠</span><div>
+ <Alert variant="default" class="bg-[var(--warn-soft)] border-[#f0ddb4]"><span aria-hidden="true">⚠</span><div>
<div class="cov-caption">覆盖差异(共有区间 {cov.common.start ?? '—'} ~ {cov.common.end ?? '—'}):</div>
<ul class="cov-list">
{#each cov.warnings as w, i (i)}<li>{w}</li>{/each}
</ul>
- </div></div>
+ </div></Alert>
{/if}
{#if (preview.coverage ?? []).some((c) => (c.warnings ?? []).length > 0)}
- <div class="banner warn" role="alert"><span aria-hidden="true">⚠</span><div>
+ <Alert variant="default" class="bg-[var(--warn-soft)] border-[#f0ddb4]" role="alert"><span aria-hidden="true">⚠</span><div>
<div class="cov-caption">来源与口径说明:</div>
<ul class="cov-list">
{#each preview.coverage.filter((c) => (c.warnings ?? []).length > 0) as c (c.instrument)}
<li>{c.instrument}:{(c.warnings ?? []).join(';')}</li>
{/each}
</ul>
- </div></div>
+ </div></Alert>
{/if}
{#if cov.common.start && cov.warnings.length === 0}
- <div class="banner info">
+ <Alert variant="default" class="bg-[var(--brand-soft)] border-[#bfe9db]">
<span aria-hidden="true">✓</span>
<span>覆盖平稳:共有可用区间 {cov.common.start} ~ {cov.common.end}。可以直接用于回测。</span>
- </div>
+ </Alert>
{/if}
<div class="scrollx">
<Table.Root>
diff --git a/frontend/src/components/DatasetWizard.svelte b/frontend/src/components/DatasetWizard.svelte
index 1e61d92..d9e923a 100644
--- a/frontend/src/components/DatasetWizard.svelte
+++ b/frontend/src/components/DatasetWizard.svelte
@@ -1,5 +1,8 @@
<script lang="ts">
import Status from './Status.svelte';
+ import { Button } from '$lib/components/ui/button';
+ import { Badge } from '$lib/components/ui/badge';
+ import { Alert } from '$lib/components/ui/alert';
import { getCapabilities, searchInstruments, createDataset } from '../lib/client';
import { extractStatusMessage } from '../lib/state';
import { adjustmentLabel, assetTypeLabel, itemSourceLabel } from '../lib/format';
@@ -196,13 +199,13 @@
}}
aria-label="标的搜索"
/>
- <button class="btn" disabled={searchBusy || !query.trim()} onclick={() => doSearch()}>
+ <Button variant="outline" disabled={searchBusy || !query.trim()} onclick={() => doSearch()}>
{searchBusy ? '搜索中…' : '搜索'}
- </button>
+ </Button>
</div>
<Status busy={searchBusy} busyText="搜索中…" error={searchErr} empty={null} />
{#if searchErr && !searchBusy}
- <button class="btn small" onclick={() => doSearch()}>重试</button>
+ <Button variant="outline" size="sm" onclick={() => doSearch()}>重试</Button>
{/if}
{#if searchItems.length > 0}
<table class="data">
@@ -217,9 +220,9 @@
<td>{inst.name ?? '—'}</td>
<td class="hint">{itemSourceLabel(inst.source, searchSource)}</td>
<td>
- <button class="btn small" onclick={() => addSelection(inst)} disabled={inst.asset_type === 'index' && selected.some((s) => sameInstrument(s, inst))}>
+ <Button variant="outline" size="sm" onclick={() => addSelection(inst)} disabled={inst.asset_type === 'index' && selected.some((s) => sameInstrument(s, inst))}>
{inst.asset_type === 'index' ? '加入(基准研究)' : '加入'}
- </button>
+ </Button>
</td>
</tr>
{/each}
@@ -258,7 +261,7 @@
</label>
</div>
<div class="row">
- <button class="btn" onclick={addManual}>加入所选</button>
+ <Button onclick={addManual}>加入所选</Button>
</div>
</div>
</details>
@@ -274,8 +277,8 @@
<li>
<span class="num">{s.market}.{s.symbol}</span>
{#if s.name}<span class="hint">{s.name}</span>{/if}
- <span class="badge grey">{assetTypeLabel[s.asset_type] ?? s.asset_type}</span>
- <button class="btn small ghost" aria-label={`移除 ${s.symbol}`} onclick={() => selected.splice(i, 1)}>✕</button>
+ <Badge variant="outline">{assetTypeLabel[s.asset_type] ?? s.asset_type}</Badge>
+ <Button variant="ghost" size="icon-sm" aria-label={`移除 ${s.symbol}`} onclick={() => selected.splice(i, 1)}>✕</Button>
</li>
{/each}
</ul>
@@ -329,24 +332,24 @@
</details>
{#if formErr.length > 0}
- <div class="banner error" role="alert">
+ <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]" role="alert">
<span aria-hidden="true">⚠</span>
<ul class="err-list">
{#each formErr as msg, i (i)}<li>{msg}</li>{/each}
</ul>
- </div>
+ </Alert>
{/if}
{#if submitErr}
- <div class="banner error" role="alert">
+ <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]" role="alert">
<span aria-hidden="true">⚠</span>
<span>数据请求提交失败:{submitErr}</span>
- </div>
+ </Alert>
{/if}
<p class="hint formhint">数据请求会进入队列异步获取。配置仅保存你填写的请求,不预填演示数据。</p>
<div>
- <button class="btn primary" disabled={submitting || selected.length === 0} onclick={submit}>
+ <Button disabled={submitting || selected.length === 0} onclick={submit}>
{submitting ? '提交中…' : '提交数据请求'}
- </button>
+ </Button>
</div>
{/if}
diff --git a/frontend/src/components/Modal.svelte b/frontend/src/components/Modal.svelte
index c9f334c..209d1ab 100644
--- a/frontend/src/components/Modal.svelte
+++ b/frontend/src/components/Modal.svelte
@@ -1,5 +1,6 @@
<script lang="ts">
import type { Snippet } from 'svelte';
+ import { Button } from '$lib/components/ui/button';
interface Props {
title: string;
@@ -34,7 +35,7 @@
<div class="dialog card" role="dialog" aria-modal="true" aria-label={title}>
<div class="spread">
<h2>{title}</h2>
- <button class="btn ghost" onclick={onClose} aria-label="关闭">✕</button>
+ <Button variant="ghost" size="icon-sm" onclick={onClose} aria-label="关闭">✕</Button>
</div>
<div class="content">
{@render children?.()}
diff --git a/frontend/src/components/ProjectMetricRow.svelte b/frontend/src/components/ProjectMetricRow.svelte
index 7980622..6dd3f0e 100644
--- a/frontend/src/components/ProjectMetricRow.svelte
+++ b/frontend/src/components/ProjectMetricRow.svelte
@@ -1,7 +1,8 @@
<script lang="ts">
- // 首页指标卡行(leader round2 spec §1)
+ // 首页指标卡行(round2 spec §1;round3 迁移 shadcn-svelte Card)
// 数据通过 props 注入(ProjectsPage 拉取并共享聚合输入,见 dashboardMetrics.ts)。
import { aggregateDashboard } from '../lib/dashboardMetrics';
+ import * as Card from '$lib/components/ui/card';
interface Props {
/** 注入的聚合输入;null/undefined 视为"取不到"(占位 -) */
@@ -15,33 +16,34 @@
const metric = $derived(aggregateDashboard({ projects, datasets, runs, usage }));
</script>
-<div class="metric-row" role="list" aria-label="工作台概览">
- <div class="metric-card" data-testid="metric-card" role="listitem">
- <span class="mlabel">我的项目</span>
- <span class="mvalue">{metric.projectCount}</span>
+ <div class="metric-row" role="list" aria-label="工作台概览">
+ <Card.Root class="metric-r3" size="sm" data-testid="metric-card" role="listitem">
+ <span class="mlabel">我的项目</span>
+ <span class="mvalue">{metric.projectCount}</span>
+ </Card.Root>
+ <Card.Root class="metric-r3" size="sm" data-testid="metric-card" role="listitem">
+ <span class="mlabel">数据集</span>
+ <span class="mvalue">{metric.datasetCount}</span>
+ </Card.Root>
+ <Card.Root class="metric-r3" size="sm" data-testid="metric-card" role="listitem">
+ <span class="mlabel">回测</span>
+ <span class="mvalue">
+ <span class="run-split"><i>运行中</i> {metric.runningRuns}</span>
+ <span class="run-split"><i>已完成</i> {metric.doneRuns}</span>
+ </span>
+ </Card.Root>
+ <Card.Root
+ class="metric-r3"
+ size="sm"
+ data-testid="metric-card"
+ role="listitem"
+ data-todo={metric.usageLabel === '-' ? 'TODO: /api/ai/usage 未返回 totals.requests(使用记录为空)时保持占位 -' : undefined}
+ >
+ <span class="mlabel">AI 用量</span>
+ <span class="mvalue">{metric.usageLabel}</span>
+ {#if metric.usageTokens}<span class="mvalue-sub">{metric.usageTokens}</span>{/if}
+ </Card.Root>
</div>
- <div class="metric-card" data-testid="metric-card" role="listitem">
- <span class="mlabel">数据集</span>
- <span class="mvalue">{metric.datasetCount}</span>
- </div>
- <div class="metric-card" data-testid="metric-card" role="listitem">
- <span class="mlabel">回测</span>
- <span class="mvalue">
- <span class="run-split"><i>运行中</i> {metric.runningRuns}</span>
- <span class="run-split"><i>已完成</i> {metric.doneRuns}</span>
- </span>
- </div>
- <div
- class="metric-card"
- data-testid="metric-card"
- role="listitem"
- data-todo={metric.usageLabel === '-' ? 'TODO: /api/ai/usage 未返回 totals.requests(使用记录为空)时保持占位 -' : undefined}
- >
- <span class="mlabel">AI 用量</span>
- <span class="mvalue">{metric.usageLabel}</span>
- {#if metric.usageTokens}<span class="mvalue-sub">{metric.usageTokens}</span>{/if}
- </div>
-</div>
<style>
.metric-row {
@@ -50,26 +52,12 @@
gap: 0.6rem;
margin-bottom: 1.1rem;
}
- .metric-card {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: calc(var(--radius) * 1.4);
- padding: 0.65rem 0.85rem;
- display: flex;
- flex-direction: column;
- gap: 0.1rem;
+ .metric-row :global(.metric-r3) {
+ gap: 0.12rem;
min-width: 0;
- transition: box-shadow 150ms ease, transform 150ms ease;
}
- /* 桌面端 hover 轻微上浮阴影 */
- @media (hover: hover) and (min-width: 768px) {
- .metric-card:hover {
- box-shadow: var(--shadow);
- transform: translateY(-1px);
- }
- }
- .metric-card .mvalue {
- font-size: 1.35rem;
+ .metric-row :global(.metric-r3 .mvalue) {
+ font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--foreground);
@@ -77,7 +65,6 @@
line-height: 1.2;
overflow-wrap: anywhere;
}
- .run-split { display: inline-flex; align-items: baseline; gap: 0.3rem; }
- .run-split i { font-style: normal; font-size: 0.68rem; color: var(--text-3); font-weight: 500; }
- .mvalue-sub { font-size: 0.72rem; color: var(--text-3); }
+
+ .mvalue-sub { color: var(--text-3); }
</style>
diff --git a/frontend/src/components/Status.svelte b/frontend/src/components/Status.svelte
index 019c224..6ae2cfb 100644
--- a/frontend/src/components/Status.svelte
+++ b/frontend/src/components/Status.svelte
@@ -16,12 +16,12 @@
{busyText}
</div>
{:else if error}
- <div class="banner error" role="alert">
+ <Alert variant="destructive" class="bg-[var(--danger-soft)] text-[var(--destructive)] border-[#f2c7c7]">
<span aria-hidden="true">⚠</span>
<span>{error}</span>
- </div>
+ </Alert>
{:else if empty !== null && empty !== undefined && empty !== false}
- <div class="state empty">
+ <div class="state-empty">
<p class="empty-title">{empty}</p>
{#if emptyHint}<p class="hint">{emptyHint}</p>{/if}
</div>
@@ -29,11 +29,15 @@
{#if warnings && warnings.length > 0}
<div class="stack">
{#each warnings as w, i (i)}
- <div class="banner warn"><span aria-hidden="true">△</span><span>{w}</span></div>
+ <Alert variant="default" class="bg-[var(--warn-soft)] text-[var(--warn)] border-[#f0ddb4]"><span aria-hidden="true">△</span><span>{w}</span></Alert>
{/each}
</div>
{/if}
+<script lang="ts" module>
+ import { Alert } from '$lib/components/ui/alert';
+</script>
+
<style>
.status {
color: var(--text-3);
@@ -54,7 +58,7 @@
@keyframes spin {
to { transform: rotate(360deg); }
}
- .empty {
+ .state-empty {
text-align: center;
padding: 2.2rem 1rem;
border: 1px dashed var(--border-strong);