feat: add svelte web workspace shell

This commit is contained in:
2026-07-18 16:40:09 +08:00
parent 833d9f094f
commit a758996799
26 changed files with 3090 additions and 7 deletions

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let apiBase: string;
const dispatch = createEventDispatcher<{ serverChange: { apiBase: string } }>();
let server = apiBase;
function saveServer() {
const normalized = normalizeServer(server);
server = normalized;
dispatch('serverChange', { apiBase: normalized });
}
function normalizeServer(value: string) {
const trimmed = value.trim();
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
return trimmed;
}
return `http://${trimmed}`;
}
</script>
<form aria-label="服务器登录" class="server-login" on:submit|preventDefault={saveServer}>
<label for="server-address">服务器 IP 或域名</label>
<input id="server-address" name="server" bind:value={server} placeholder="http://localhost:8080" />
<button type="submit">保存服务器</button>
</form>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import type { ProjectDashboardSummary } from '../../lib/api';
export let summary: ProjectDashboardSummary;
$: metrics = [
['待处理 Inbox', summary.pending_inbox_count],
['当前任务', summary.open_task_count],
['笔记资料', summary.recent_note_count],
['AI 会话', summary.recent_session_count],
] as const;
</script>
<section aria-label="项目总览" class="dashboard-grid">
{#each metrics as [label, value]}
<article class="dashboard-metric">
<span>{label}</span>
<strong>{value}</strong>
</article>
{/each}
</section>

View File

@@ -0,0 +1,25 @@
import '@testing-library/jest-dom/vitest';
import { render, screen } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import ProjectDashboard from './ProjectDashboard.svelte';
describe('ProjectDashboard', () => {
it('shows project counters', () => {
render(ProjectDashboard, {
props: {
summary: {
project_id: 1,
pending_inbox_count: 2,
open_task_count: 5,
recent_note_count: 3,
recent_session_count: 4,
},
},
});
expect(screen.getByText('待处理 Inbox')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('当前任务')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
});
});