From 61f5c545953bb4ee20b123bf9671ac35d7f1f3c1 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Fri, 18 Sep 2026 12:03:32 +0800 Subject: [变更性质] 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AccountPage.svelte | 112 +++++++------ frontend/src/pages/AdminPage.svelte | 237 ++++++++++++++------------- frontend/src/pages/AuthPages.svelte | 65 ++++---- frontend/src/pages/ProjectBacktestTab.svelte | 113 ++++++------- frontend/src/pages/ProjectResultsTab.svelte | 131 ++++++--------- frontend/src/pages/ProjectStrategyTab.svelte | 36 ++-- frontend/src/pages/ProjectsPage.svelte | 6 +- frontend/src/pages/RunsPage.svelte | 104 ++++++------ frontend/src/pages/UsagePage.svelte | 92 ++++++----- 9 files changed, 455 insertions(+), 441 deletions(-) (limited to 'frontend/src/pages') diff --git a/frontend/src/pages/AccountPage.svelte b/frontend/src/pages/AccountPage.svelte index ef6d1a3..9e90e78 100644 --- a/frontend/src/pages/AccountPage.svelte +++ b/frontend/src/pages/AccountPage.svelte @@ -6,6 +6,12 @@ import { extractStatusMessage } from '../lib/state'; import { fmtDateTime } from '../lib/format'; import type { SessionInfo } from '../lib/types'; + import { Button } from '$lib/components/ui/button'; + import { Input } from '$lib/components/ui/input'; + import { Label } from '$lib/components/ui/label'; + import * as Card from '$lib/components/ui/card'; + import { Badge } from '$lib/components/ui/badge'; + import * as Table from '$lib/components/ui/table'; let nameInput = $state(session.me?.name ?? ''); let profileBusy = $state(false); @@ -99,91 +105,91 @@
-
-

个人信息

+ + 个人信息
- +
+ + +
邮箱 -
{session.me?.email ?? '—'}
+
{session.me?.email ?? "—"}
角色
{session.me?.role === 'admin' ? '管理员' : '成员'}{session.me?.active === false ? ' · 已停用' : ''}
- +
-
+ -
-

修改密码

+ + 修改密码
- - - +
+ + +
+
+ + +
+
+ + +
- +
-
+ -
-

登录会话

+ + 登录会话 {#if !sessBusy && !sessErr && sessions.length === 0} {/if} {#if sessions.length > 0} - - - - - - - - - - + + + + 创建 + 最近活跃 + 设备/UA + + + + {#each sessions as s (s.id)} - - - - - - + + {/each} - -
创建最近活跃设备/UA
{fmtDateTime(s.created_at)}{fmtDateTime(s.last_seen_at)}{s.user_agent?.slice(0, 60) ?? '—'} + + {fmtDateTime(s.created_at)} + {fmtDateTime(s.last_seen_at)} + {s.user_agent?.slice(0, 60) ?? '—'} +
- {#if s.current}当前会话{/if} - +
-
+ + {/if}

更改密码或被管理员停用时,其他会话将自动失效。

-
+
-
+ \ No newline at end of file diff --git a/frontend/src/pages/AdminPage.svelte b/frontend/src/pages/AdminPage.svelte index 984962d..d9bea83 100644 --- a/frontend/src/pages/AdminPage.svelte +++ b/frontend/src/pages/AdminPage.svelte @@ -12,11 +12,20 @@ postAdminUserPassword } from '../lib/client'; import { extractStatusMessage } from '../lib/state'; + import { Button } from '$lib/components/ui/button'; + import { Input } from '$lib/components/ui/input'; + import { Alert } from '$lib/components/ui/alert'; + import { Badge } from '$lib/components/ui/badge'; + import { Label } from '$lib/components/ui/label'; + import * as Tabs from '$lib/components/ui/tabs'; + import * as Card from '$lib/components/ui/card'; + import * as Table from '$lib/components/ui/table'; import { fmtDateTime } from '../lib/format'; import type { User, Invitation } from '../lib/types'; let tab = $state<'users' | 'invites' | 'audit'>('users'); + let users: User[] = $state([]); let usersBusy = $state(true); let usersErr = $state(null); @@ -167,39 +176,41 @@ {#snippet actions()} -
- - - -
+ (tab = v as 'users' | 'invites' | 'audit')}> + + 用户 + 邀请与恢复 + 审计日志 + + {/snippet}
{#if tab === 'users'} -
+

用户

{#if !usersBusy && !usersErr && users.length > 0} - - - - - - - - - - - - - + + + + 邮箱 + 姓名 + 角色 + 状态 + AI 权限 + 日回测上限 + 操作 + + + {#each users as u (u.id)} - - - - - - - - - + + {/each} - -
邮箱姓名角色状态AI 权限日回测上限操作
{u.email}{u.name} + + {u.email} + {u.name} + - - {u.active ? '启用' : '停用'} - + + + {u.active ? '启用' : '停用'} + + - - + + void patchUser(u, { daily_run_limit: Number((e.target as HTMLInputElement).value) })} /> - + +
- - + +
-
+ + {/if}

停用账户会立即吊销其会话并阻止其回测与 AI 请求。角色不能作用于他人项目,私阅策略代码不提供。

-
+ {:else if tab === 'invites'} -
+

签发邀请

- - - +
+ + +
+
+ + +
+
{#if newInvite} - + {/if} -
+ -
+

历史邀请

{#if !inviteBusy && !inviteErr && invites.length === 0} {/if} {#if !inviteBusy && invites.length > 0} - - - - - - - - - - - - + + + + 邮箱备注 + 角色 + 创建时间 + 过期 + 状态 + + + + {#each invites as inv (inv.id)} - - - - - - - - + + {/each} - -
邮箱备注角色创建时间过期状态
{inv.email ?? '—'}{inv.role === 'admin' ? '管理员' : '成员'}{fmtDateTime(inv.created_at)}{fmtDateTime(inv.expires_at)} - {#if inv.used_at}已使用 - {:else if inv.revoked}已撤销 - {:else if inv.expires_at && new Date(inv.expires_at) < new Date()}已过期 - {:else}有效 + + {inv.email ?? '—'} + {inv.role === 'admin' ? '管理员' : '成员'} + {fmtDateTime(inv.created_at)} + {fmtDateTime(inv.expires_at)} + + {#if inv.used_at}已使用 + {:else if inv.revoked}已撤销 + {:else if inv.expires_at && new Date(inv.expires_at) < new Date()}已过期 + {:else}有效 {/if} - + + {#if !inv.used_at && !inv.revoked} - + {/if} -
+ + {/if}

恢复链接由“生成恢复”按钮产生,仅出现一次;不会自动发邮件。

-
+ {:else} -
+

安全审计(收尾尾部)

{#if !auditBusy && !auditErr && audit.length === 0} {/if} {#if !auditBusy && audit.length > 0} - - - - - - - - - - - + + + + 时间 + 行为者 + 事件 + 对象 + 结果 + + + {#each audit as e, i (i)} - - - - - - - + + {fmtDateTime(e.time)} + {e.actor ?? '—'} + {e.action} + {e.target ?? '—'} + {e.status} + {/each} - -
时间行为者事件对象结果
{fmtDateTime(e.time)}{e.actor ?? '—'}{e.action}{e.target ?? '—'}{e.status}
+ + {/if}

审计仅记录操作行为与结果,不含密码/密钥/策略代码内容。

-
+ {/if}
{#if resetFor} (resetFor = null)}>
- +

单次有效、限时;重置会吊销对方的全部历史会话。

diff --git a/frontend/src/pages/AuthPages.svelte b/frontend/src/pages/AuthPages.svelte index 7495d62..88ca07a 100644 --- a/frontend/src/pages/AuthPages.svelte +++ b/frontend/src/pages/AuthPages.svelte @@ -1,5 +1,12 @@
-
+

策研 Strategy Lab

@@ -96,63 +103,63 @@ {#if mode === 'register'}
- - + +

单次有效,来自管理员邀请

- - + +
- - + +
- - + +
至少 8 位
- {#if resultError}{/if} - + {#if resultError}{resultError}{/if} +

已有账户? 登录

{:else if mode === 'reset'} {#if done} - + {done}

前往登录

{:else}
- - + +

由管理员线下发放的恢复链接自动填入;不支持邮箱自动发送

- - + +
- - + +
- {#if resultError}{/if} - + {#if resultError}{resultError}{/if} +

想起密码了? 直接登录

{/if} {:else}
- - + +
- - + +
- {#if resultError}{/if} - + {#if resultError}{resultError}{/if} +

开通新账户需要管理员邀请: 用邀请令牌注册 {#if route.query.get('invite')}(邀请令牌已自动填入){/if} @@ -160,7 +167,7 @@

忘记密码?请联系管理员获取 恢复链接

{/if} -
+
diff --git a/frontend/src/pages/RunsPage.svelte b/frontend/src/pages/RunsPage.svelte index ad72b5d..911da2a 100644 --- a/frontend/src/pages/RunsPage.svelte +++ b/frontend/src/pages/RunsPage.svelte @@ -7,6 +7,10 @@ import { extractStatusMessage } from '../lib/state'; import { fmtDateTime, runStatusLabel, runStatusBadge, fmtPct, fmtNum, formatMoney } 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 } from '../lib/types'; let runs: Run[] = $state([]); @@ -107,25 +111,25 @@ import { capitalReporting } from '../lib/capitalMetrics'; {/if} {#if !busy && !error && runs.length > 0} -
- - - - - - - - - - - - - - - + + + + + + 时间 + 状态 + 项目 + 数据集 + 资金/佣金/滑点 + 收益 + 最大回撤 + 操作 + + + {#each runs as r (r.id)} - - - - - - - - - - - + + {/each} - -
时间状态项目数据集资金/佣金/滑点收益最大回撤操作
+ + = 4 && !chosenIds.includes(r.id)} /> - {fmtDateTime(r.created_at)}{runStatusLabel[r.status]}{r.project_id?.slice(0, 8)}…{r.dataset_id?.slice(0, 8)}…{formatMoney(r.config?.capital)} · {fmtPct(r.config?.commission, 4)} / {fmtPct(r.config?.slippage, 4)}{r.result ? fmtPct(r.result.metrics.total_return) : '—'}{r.result ? fmtPct(r.result.metrics.max_drawdown) : '—'} + + {fmtDateTime(r.created_at)} + {runStatusLabel[r.status]} + {r.project_id?.slice(0, 8)}… + {r.dataset_id?.slice(0, 8)}… + {formatMoney(r.config?.capital)} · {fmtPct(r.config?.commission, 4)} / {fmtPct(r.config?.slippage, 4)} + {r.result ? fmtPct(r.result.metrics.total_return) : '—'} + {r.result ? fmtPct(r.result.metrics.max_drawdown) : '—'} + -
+ +

最多同时选中 4 个已完成的回测用于比较。

-
+ {#if selected.length > 0} -
+

条件差异

- - - - - {#each selected as r (r.id)}{/each} - - - + + + + 维度 + {#each selected as r (r.id)}Run {r.id.slice(0, 6)}.{/each} + + + {#each configDiffRows as row, i (i)} - - + + {row.label} {#each row.vals as v, j (j)} - + {v} {/each} - + {/each} - -
维度Run {r.id.slice(0, 6)}
{row.label}{v}
-
+ + + {#if comparable.length > 1} -
+

收益曲线对齐比较(首日归一)

起点对齐为 1,仅叠加不同起点的曲线时只在各自区间内展示。

-
+ {:else} {/if} diff --git a/frontend/src/pages/UsagePage.svelte b/frontend/src/pages/UsagePage.svelte index 88dd8a5..cfd69ce 100644 --- a/frontend/src/pages/UsagePage.svelte +++ b/frontend/src/pages/UsagePage.svelte @@ -5,6 +5,10 @@ import { extractStatusMessage } from '../lib/state'; import { fmtDateTime, fmtNum } from '../lib/format'; import type { AIUsage } from '../lib/types'; + import * as Card from '$lib/components/ui/card'; + import { Badge } from '$lib/components/ui/badge'; + import { Separator } from '$lib/components/ui/separator'; + import * as Table from '$lib/components/ui/table'; let usage = $state(null); let busy = $state(true); @@ -31,51 +35,57 @@
{#if usage} -
-
-
-
模型请求
-
{usage.totals?.requests ?? usage.items.length}
+ + +
+
+
模型请求
+
{usage.totals?.requests ?? usage.items.length}
+
+ +
+
输入 tokens
+
{usage.totals?.input_tokens ?? '—'}
+
+ +
+
输出 tokens
+
{usage.totals?.output_tokens ?? '—'}
+
-
-
输入 tokens
-
{usage.totals?.input_tokens ?? '—'}
-
-
-
输出 tokens
-
{usage.totals?.output_tokens ?? '—'}
-
-
- {#if usage.internal_poc} -

内部 POC:请在当前配额内使用,不对实际价格负责;接受模型输出的决策需重新人工复核。

- {/if} -
+ {#if usage.internal_poc} +

内部 POC:请在当前配额内使用,不对实际价格负责;接受模型输出的决策需重新人工复核。

+ {/if} + + {#if usage.items.length > 0} -
- - - - - - - - - - - - {#each usage.items as it, i (it.id)} - - - - - - - + + + + + 时间 + 模型 + 输入 + 输出 + 状态 + + + + {#each usage.items as it (it.id)} + + {fmtDateTime(it.created_at)} + {it.model} + {it.input_tokens ?? '—'} + {it.output_tokens ?? '—'} + + {it.status ?? '—'} + + {/each} - -
时间模型输入输出状态
{fmtDateTime(it.created_at)}{it.model}{it.input_tokens ?? '—'}{it.output_tokens ?? '—'}{it.status ?? '—'}
-
+ + + {:else} {/if} -- cgit v1.2.3