diff options
Diffstat (limited to 'frontend/src/pages/ProjectsPage.svelte')
| -rw-r--r-- | frontend/src/pages/ProjectsPage.svelte | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/frontend/src/pages/ProjectsPage.svelte b/frontend/src/pages/ProjectsPage.svelte new file mode 100644 index 0000000..b29a856 --- /dev/null +++ b/frontend/src/pages/ProjectsPage.svelte @@ -0,0 +1,112 @@ +<script lang="ts"> + import Page from '../components/Page.svelte'; + import Status from '../components/Status.svelte'; + import { projectsStore } from '../lib/listsStore.svelte'; + import { createProject } from '../lib/client'; + import { extractStatusMessage } from '../lib/state'; + import { go } from '../lib/router'; + import { fmtDateTime } from '../lib/format'; + + let busy = $state(false); + let error = $state<string | null>(null); + let creating = $state(false); + let showCreate = $state(false); + let newName = $state(''); + let newDesc = $state(''); + let createErr = $state<string | null>(null); + + async function load() { + busy = true; + error = null; + try { + await projectsStore.load(); + } catch (e) { + error = extractStatusMessage(e); + } finally { + busy = false; + } + } + + async function create(e: SubmitEvent) { + e.preventDefault(); + if (!newName.trim()) return; + creating = true; + createErr = null; + try { + const p = await createProject(newName.trim(), newDesc.trim() || undefined); + projectsStore.bump(p); + showCreate = false; + newName = ''; + newDesc = ''; + go(`/projects/${p.id}/data`); + } catch (err) { + createErr = extractStatusMessage(err); + } finally { + creating = false; + } + } +</script> + +<Page title="我的项目" subtitle="每个项目持有独立的策略代码版本、数据集与回测记录"> + {#snippet actions()} + <button class="btn primary" onclick={() => (showCreate = !showCreate)} aria-expanded={showCreate}> + {showCreate ? '取消新建' : '新建项目'} + </button> + {/snippet} + + <div class="stack"> + <Status {busy} {error} busyText="加载项目列表…" empty={null} /> + {#if showCreate} + <form class="card stack" onsubmit={create}> + <h2>新建项目</h2> + <div> + <label for="pn">项目名称</label> + <input id="pn" bind:value={newName} required placeholder="例如:双均线ETF轮动" /> + </div> + <div> + <label for="pd">简介(可选)</label> + <input id="pd" bind:value={newDesc} placeholder="一句话说明研究思路" /> + </div> + {#if createErr}<div class="banner error" role="alert">{createErr}</div>{/if} + <div class="row"> + <button type="submit" class="btn primary" disabled={creating || !newName.trim()}> + {creating ? '创建中…' : '创建项目'} + </button> + </div> + </form> + {/if} + + {#if !busy && !error && projectsStore.all.length === 0} + <Status busy={false} empty="还没有项目" emptyHint="点击右上角「新建项目」开始你的第一个策略研究" /> + {/if} + + {#if projectsStore.all.length > 0} + <div class="card" style="padding:0.4rem 0.6rem;"> + <table class="data"> + <thead> + <tr> + <th>名称</th> + <th>简介</th> + <th>版本代</th> + <th>最近更新</th> + </tr> + </thead> + <tbody> + {#each projectsStore.all as p (p.id)} + <tr> + <td><a href={`#/projects/${p.id}/data`} class="p-name">{p.name}</a></td> + <td class="hint">{p.description ?? '—'}</td> + <td class="num">{p.draft_generation ?? '—'}</td> + <td>{fmtDateTime(p.updated_at)}</td> + </tr> + {/each} + </tbody> + </table> + </div> + {/if} + </div> +</Page> + +<style> + .p-name { font-weight: 600; } +</style> |
