diff options
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/DatasetCard.svelte | 14 | ||||
| -rw-r--r-- | frontend/src/components/DatasetWizard.svelte | 31 | ||||
| -rw-r--r-- | frontend/src/components/Modal.svelte | 3 | ||||
| -rw-r--r-- | frontend/src/components/ProjectMetricRow.svelte | 83 | ||||
| -rw-r--r-- | frontend/src/components/Status.svelte | 14 |
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); |
