blob: 13525b622ad051104f0a768a502791dfff2bd75e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
<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';
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import * as Table from '$lib/components/ui/table';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Alert } from '$lib/components/ui/alert';
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;
}
}
// 159399 QA regression (2026-09-17): load() existed but was wired nowhere,
// so the projects page kept rendering the store's stale/empty state and
// users saw 还没有项目 despite existing projects on the server.
$effect(() => {
void load();
});
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 variant={showCreate ? 'outline' : 'default'} 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轮动" class="mt-1" />
</div>
<div>
<Label for="pd">简介(可选)</Label>
<Input id="pd" bind:value={newDesc} placeholder="一句话说明研究思路" class="mt-1" />
</div>
{#if createErr}<Alert variant="destructive" class="banner error" role="alert">{createErr}</Alert>{/if}
<div class="row">
<Button type="submit" 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}
<Card.Root size="sm">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>名称</Table.Head>
<Table.Head>简介</Table.Head>
<Table.Head>版本代</Table.Head>
<Table.Head>最近更新</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each projectsStore.all as p (p.id)}
<Table.Row>
<Table.Cell>
<a href={`#/projects/${p.id}/data`} class="p-name">{p.name}</a>
</Table.Cell>
<Table.Cell class="hint">{p.description ?? '—'}</Table.Cell>
<Table.Cell class="num">{p.draft_generation ?? '—'}</Table.Cell>
<Table.Cell>{fmtDateTime(p.updated_at)}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</Card.Root>
{/if}
</div>
</Page>
<style>
.p-name { font-weight: 600; color: var(--teal-strong); }
.p-name:hover { text-decoration: underline; }
form.card h2 { margin-bottom: 0.2rem; }
</style>
|