diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-18 15:49:44 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-18 15:49:44 +0800 |
| commit | a7aab01f051487327e5ce52b6a8905208c546e8c (patch) | |
| tree | 630a624523bc15d1a9f59ab20693385eaf55befb /frontend/src/pages/ProjectStrategyTab.svelte | |
| parent | 375584174fb7191a57c5423fea45924ad6d812d5 (diff) | |
| download | strategy-lab-a7aab01f051487327e5ce52b6a8905208c546e8c.tar.gz | |
[变更性质] feat(frontend)
[新增功能] 12 页面组件全量 shadcn:Card/Button/Table/Tabs/Select/Alert;375 底部 Tab Bar 改用 Tabs 组件实现
[实现方案] 页面级手写 class 全部移除(.btn/.banner/table.data/.badge/.tabs/.metric-row/.m-tile/.hint 等改为 shadcn + tailwind 工具类);Modal/DatasetWizard/DatasetCard/DiffView/CodeEditor/Status/Layout 同步
[影响范围] src/pages/*.svelte、src/components/*.svelte
Diffstat (limited to 'frontend/src/pages/ProjectStrategyTab.svelte')
| -rw-r--r-- | frontend/src/pages/ProjectStrategyTab.svelte | 182 |
1 files changed, 91 insertions, 91 deletions
diff --git a/frontend/src/pages/ProjectStrategyTab.svelte b/frontend/src/pages/ProjectStrategyTab.svelte index 8614cc0..e4269e3 100644 --- a/frontend/src/pages/ProjectStrategyTab.svelte +++ b/frontend/src/pages/ProjectStrategyTab.svelte @@ -9,6 +9,7 @@ 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 { projectsStore } from '../lib/listsStore.svelte'; import { getProject, @@ -185,7 +186,7 @@ try { const [a, b] = await Promise.all([getVersionCode(projectId, v.id), getVersionCode(projectId, prev.id)]); comparing = { - label: `${a.code ? '' : ''}${v.hash.slice(0, 8)}(新)对比 ${prev.hash.slice(0, 8)}(旧)`, + label: `${v.hash.slice(0, 8)}(新)对比 ${prev.hash.slice(0, 8)}(旧)`, before: b.code, after: a.code }; @@ -261,29 +262,29 @@ } </script> -<div class="stack"> +<div class="flex flex-col gap-3"> <Status busy={initBusy} error={initErr} busyText="加载策略草稿…" empty={null} /> {#if !initBusy && !initErr} - <Card.Root size="sm" class="stack"> - <div class="spread"> - <h2>策略代码(Backtrader Python)</h2> - <span class="save-status" role="status"> + <Card.Root size="sm" class="p-4 flex flex-col gap-3"> + <div class="flex flex-wrap justify-between items-center gap-2"> + <h2 class="text-lg font-semibold">策略代码(Backtrader Python)</h2> + <span class="text-sm text-muted-foreground" role="status"> {saveStatus === 'saved' && `已保存${saveMsg ? ` · ${saveMsg}` : ''}`} {#if saveStatus === 'dirty'}<Badge variant="outline">有未保存修改</Badge>{/if} {#if saveStatus === 'saving'}<Badge variant="secondary">保存中…</Badge>{/if} {#if saveStatus === 'conflict'}<Badge variant="destructive">冲突</Badge>{/if} </span> </div> - <p class="hint"> + <p class="text-sm text-muted-foreground"> 编辑器修改会自动保存(约 0.8 秒停顿后提交)。草稿代 {baseGeneration} · 回测与获取 AI 建议使用当前草稿快照 </p> <CodeEditor value={code} onInput={onEditorInput} readOnly={false} label="策略源码" height={430} /> - <div class="spread wrap"> + <div class="flex flex-wrap justify-between items-center gap-2"> <Input bind:value={snapshotMessage} placeholder="快照说明,例如:加入RSI过滤器(可选)" - style="flex:2 1 240px" + class="flex-[2_1_240px] min-w-[200px]" onkeydown={(ev) => { if (ev.key === 'Enter') void snapshot(); }} @@ -302,119 +303,118 @@ <Status busy={false} error={snapshotErr} empty={snapshotMsg} /> {#if conflictServer} - <Alert variant="default" class="bg-[var(--warn-soft)] border-[#f0ddb4]"> + <Alert variant="default"> <span aria-hidden="true">⚠</span> <span> 服务器上的草稿已被其他修改更新(代 {conflictServer.generation}),你正在编辑的本地内容不会自动覆盖。 </span> </Alert> <DiffView before={conflictServer.code} after={code} beforeLabel="服务器" afterLabel="本机" /> - <div class="row"> + <div class="flex items-center gap-2 flex-wrap"> <Button onclick={keepMine}>保留本机修改(覆盖服务器)</Button> <Button variant="outline" onclick={loadServer}>采用服务器版本</Button> </div> {/if} </Card.Root> - <div class="stack"> - <h2>AI 助手</h2> - <Card.Root size="sm" class="stack"> - <label for="ai-instruction">希望 AI 帮你做什么?<span class="hint">例如:把均线周期改为两个可调参数并解释影响</span></label> - <textarea id="ai-instruction" rows="3" bind:value={instruction} placeholder="描述对策略的修改意图,AI 返回完整新源码与差异预览"></textarea> - <div class="row"> - <Button disabled={aiBusy || !instruction.trim()} onclick={askAI}> - {aiBusy ? 'AI 处理中…' : '获取建议'} - </Button> - </div> - {#if aiErr} - <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]"> - <span aria-hidden="true">⚠</span> - <span>{aiErr}</span> - </Alert> - {/if} + <Card.Root size="sm" class="p-4 flex flex-col gap-3"> + <h2 class="text-lg font-semibold">AI 助手</h2> + <Label for="ai-instruction">希望 AI 帮你做什么?<span class="text-sm text-muted-foreground">例如:把均线周期改为两个可调参数并解释影响</span></Label> + <textarea id="ai-instruction" class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50" rows="3" bind:value={instruction} placeholder="描述对策略的修改意图,AI 返回完整新源码与差异预览"></textarea> + <div class="flex items-center gap-2 flex-wrap"> + <Button class="w-fit" disabled={aiBusy || !instruction.trim()} onclick={askAI}> + {aiBusy ? 'AI 处理中…' : '获取建议'} + </Button> + </div> + {#if aiErr} + <Alert variant="destructive"> + <span aria-hidden="true">⚠</span> + <span>{aiErr}</span> + </Alert> + {/if} - {#if aiResult} - <div class="stack"> - <div class="spread"> - <h3>建议解释 <span class="hint">{aiResult.model}</span></h3> - <span class="hint"> - tokens {aiResult.usage?.input_tokens ?? '—'} 入 / {aiResult.usage?.output_tokens ?? '—'} 出 - </span> - </div> - <p>{aiResult.explanation}</p> - <h3>完整建议源码</h3> - <CodeEditor value={aiResult.proposed_code} onInput={() => {}} readOnly={true} label="AI 返回代码(只读)" height={280} /> - <h3>与当前草稿的差异</h3> - <DiffView before={code} after={aiResult.proposed_code} beforeLabel="当前草稿" afterLabel="AI 建议" /> - <div class="row"> - <Button - disabled={aiAcceptBusy} - onclick={acceptAI} - title={aiResult.base_generation !== baseGeneration ? '草稿代已变化,建议如需采用请重新获取' : ''} - > - {aiAcceptBusy ? '接受中…' : '接受为新版本'} - </Button> - <Button variant="ghost" disabled={aiAcceptBusy} onclick={discardAI}>放弃建议</Button> - {#if aiResult.base_generation !== baseGeneration} - <span class="text-[var(--warn)]">草稿代 {aiResult.base_generation} 与当前 {baseGeneration} 不一致,接受会失败;请重新发起说明</span> - {/if} - {#if aiAcceptErr} - <Alert variant="destructive" class="bg-[var(--danger-soft)] border-[#f2c7c7]">{aiAcceptErr}</Alert> - {/if} - </div> + {#if aiResult} + <div class="flex flex-col gap-2"> + <div class="flex flex-wrap justify-between items-center gap-2"> + <h3 class="text-base font-semibold">建议解释 <span class="text-sm text-muted-foreground">{aiResult.model}</span></h3> + <span class="text-sm text-muted-foreground"> + tokens {aiResult.usage?.input_tokens ?? '—'} 入 / {aiResult.usage?.output_tokens ?? '—'} 出 + </span> </div> - {/if} - {#if aiNote} - <Alert variant="default" class="bg-[var(--brand-soft)] border-[#bfe9db]">{aiNote}</Alert> - {/if} - </Card.Root> - </div> + <p class="m-0">{aiResult.explanation}</p> + <h3 class="text-base font-semibold">完整建议源码</h3> + <CodeEditor value={aiResult.proposed_code} onInput={() => {}} readOnly={true} label="AI 返回代码(只读)" height={280} /> + <h3 class="text-base font-semibold">与当前草稿的差异</h3> + <DiffView before={code} after={aiResult.proposed_code} beforeLabel="当前草稿" afterLabel="AI 建议" /> + <div class="flex items-center gap-2 flex-wrap"> + <Button + class="w-fit" + disabled={aiAcceptBusy} + onclick={acceptAI} + title={aiResult.base_generation !== baseGeneration ? '草稿代已变化,建议如需采用请重新获取' : ''} + > + {aiAcceptBusy ? '接受中…' : '接受为新版本'} + </Button> + <Button variant="ghost" class="w-fit" disabled={aiAcceptBusy} onclick={discardAI}>放弃建议</Button> + {#if aiResult.base_generation !== baseGeneration} + <span class="text-destructive">草稿代 {aiResult.base_generation} 与当前 {baseGeneration} 不一致,接受会失败;请重新发起说明</span> + {/if} + {#if aiAcceptErr} + <Alert variant="destructive">{aiAcceptErr}</Alert> + {/if} + </div> + </div> + {/if} + {#if aiNote} + <Alert variant="default">{aiNote}</Alert> + {/if} + </Card.Root> - <div class="stack"> - <h2>版本历史</h2> + <Card.Root size="sm" class="p-4 flex flex-col gap-3"> + <h2 class="text-lg font-semibold">版本历史</h2> <Status busy={versionsBusy} error={versionsErr} busyText="载入版本…" empty={null} /> {#if !versionsBusy && versions.length === 0} <Status busy={false} empty="还没有版本快照" emptyHint="保存第一个快照后可以在历史中查看完整代码与恢复" /> {/if} {#if !versionsBusy && versions.length > 0} - <div class="px-3 py-2 rounded-[var(--radius-lg)] border border-border bg-card"> - <table class="data"> - <thead> - <tr> - <th>时间</th> - <th>摘要</th> - <th>哈希</th> - <th>来源</th> - <th>操作</th> - </tr> - </thead> - <tbody> + <div class="overflow-x-auto rounded-lg border border-border"> + <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.Row> + </Table.Header> + <Table.Body> {#each versions as v, i (v.id)} - <tr> - <td>{fmtDateTime(v.created_at)}</td> - <td> + <Table.Row> + <Table.Cell>{fmtDateTime(v.created_at)}</Table.Cell> + <Table.Cell> {v.message?.trim() || '(无说明)'} - <span class="hint">版本代 {v.hash?.slice(0, 8) || '—'}</span> - </td> - <td class="num">{v.hash?.slice(0, 12)}</td> - <td><Badge variant="outline">{versionSourceLabel[v.source]}</Badge></td> - <td> - <div class="row"> + <span class="text-sm text-muted-foreground ml-1">版本代 {v.hash?.slice(0, 8) || '—'}</span> + </Table.Cell> + <Table.Cell class="tabular-nums">{v.hash?.slice(0, 12)}</Table.Cell> + <Table.Cell><Badge variant="outline">{versionSourceLabel[v.source]}</Badge></Table.Cell> + <Table.Cell> + <div class="flex items-center gap-1 flex-wrap"> <Button variant="outline" size="sm" onclick={() => void compareWithDraft(v)}>对比当前</Button> {#if i + 1 < versions.length} <Button variant="outline" size="sm" onclick={() => void compareTwoVersions(i)}>与相邻旧版对比</Button> {/if} <Button size="sm" onclick={() => void restore(v)}>恢复为新草稿</Button> </div> - </td> - </tr> + </Table.Cell> + </Table.Row> {/each} - </tbody> - </table> + </Table.Body> + </Table.Root> </div> {/if} - <p class="hint">版本为不可变记录,恢复均创建新草稿,从不改写历史。获取到最新草稿修改内容请刷新页面查看。</p> - </div> + <p class="text-sm text-muted-foreground">版本为不可变记录,恢复均创建新草稿,从不改写历史。获取到最新草稿修改内容请刷新页面查看。</p> + </Card.Root> {#if comparing} <Modal title={comparing.label} onClose={() => (comparing = null)}> |
