sdd-view.md
5.48 KB
View · UI 组件全集
所有前端组件签名 + 路由表 + i18n keys。Coder Agent (fe 类) 读这页能生成全部 Vue 代码。8 个 Vue 组件 + 1 个路由表 + 1 个 i18n key 表。
V-1 · DevLoopDashboard.vue
· web/src/views/DevLoopDashboard.vue
// Composition API · setup script
const props = defineProps<{}>(); // 无 props,从 route param 取 pid
const route = useRoute();
const pid = computed(() => route.params.pid as string);
const state = ref<ProjectState | null>(null);
const sse = useEventSource(`/api/devloop/projects/${pid.value}/stream`);
const modalOpen = ref(false);
const drawer = ref<Unit | null>(null);
// 组件树:
// DashboardHeader → LoopStatusBar → UnitsDAG → LiveAgentStream + PrdDiffPanel
// 浮层:FeedbackModal · UnitDrawer
- 挂载 5 秒内必须发起 GET /state 请求
- SSE 断线自动重连(指数退避,最长 30s)
- 页面卸载时关 SSE
V-2 · DashboardHeader.vue
· web/src/components/devloop/DashboardHeader.vue
defineProps<{
project: { id: string, name: string, status: string }
}>();
defineEmits<{
feedback: [] // 点击 + NEW REQUIREMENT
}>();
// 渲染:
// - 黑底标题栏
// - 项目名(Fraunces 22px)
// - 右上紫色按钮
V-3 · LoopStatusBar.vue
· web/src/components/devloop/LoopStatusBar.vue
defineProps<{
l1: { passed: number, testing: number, pending: number, failed: number },
l2: { lastRunAt?: string, status: 'idle'|'running'|'failed'|'shipped', rounds: number },
l3: { rev: number, awaitingFeedback: boolean }
}>();
- 三张卡片对应 emerald / amber / violet 三色,CSS 用
--L1/L2/L3token - L2 running 时 amber 卡片有 pulse 动画
- L3 awaitingFeedback=true 时显示 toast
V-4 · UnitsDAG.vue
· web/src/components/devloop/UnitsDAG.vue
defineProps<{
units: Unit[]
}>();
defineEmits<{
select: [unit: Unit]
}>();
// 内部使用 SVG <rect> + <line>
// > 50 unit 时切 d3-force 自动布局
- 节点配色由 unit.status 决定(见 ui-design-system 色板)
- 节点点击 emit select 事件,父组件打开 UnitDrawer
- 箭头方向 = depends_on (上游 → 下游)
V-5 · LiveAgentStream.vue
· web/src/components/devloop/LiveAgentStream.vue
defineProps<{
events: AgentStreamEvent[] // 由父组件从 SSE 过滤 agent.stream 事件
}>();
// 渲染:
// - 黑底 #0E0E10 终端样式
// - 最近 500 行,老的丢
// - PASS / FAIL 着色(绿/橙)
// - 自动滚到底部,除非用户手动滚
V-6 · PrdDiffPanel.vue
· web/src/components/devloop/PrdDiffPanel.vue
defineProps<{
prdHtml: string,
prevRev: number | null
}>();
// 内部用 diff-dom 客户端算 section 级 diff
// 新增 section 绿色 + 标记 / 删除红色删除线 / 修改黄色
V-7 · FeedbackModal.vue
· web/src/components/devloop/FeedbackModal.vue
defineProps<{ open: boolean }>();
defineEmits<{
submit: [text: string],
close: [],
}>();
// 单 textarea + 字数计数 + Ctrl+Enter 提交
// 提交后立即 emit close,父组件负责发请求
- tests: 空文本 → 提交按钮 disabled
- tests: > 4000 字 → 警告但不阻止
- tests: Esc 触发 close
V-8 · UnitDrawer.vue
· web/src/components/devloop/UnitDrawer.vue
defineProps<{
unit: Unit | null,
runs: RunResult[] // 最近 3 次 run
}>();
defineEmits<{ close: [] }>();
// 右侧抽屉,从 right 滑入
// 显示:spec.json 摘要 · contract.json · 最近 3 次 run 摘要 · 跳转完整日志按钮
V-9 · 路由表
· web/src/router/index.js
// 在现有 routes 数组追加
{
path: '/devloop/:pid',
name: 'DevLoop',
component: () => import('@/views/DevLoopDashboard.vue'),
meta: { requiresAuth: true },
},
{
path: '/devloop', // 项目列表
name: 'DevLoopList',
component: () => import('@/views/DevLoopList.vue'),
}
DevLoopList.vue 在 P4 是可选的,先用 GET /api/devloop/projects 列出。
V-10 · i18n key 清单
· web/src/i18n/{zh-CN,en-US,...}/devloop.json
// 每个 locale 一份扁平 JSON:Record<string, string>
{
"devloop.loop.l1": "...",
"devloop.state.pending": "...",
"devloop.btn.newRequirement":"..."
// ...完整 15 个 key 见下表
}
| key | zh-CN | en-US |
|---|---|---|
devloop.loop.l1 |
单元循环 | Unit Loop |
devloop.loop.l2 |
集成循环 | Integration Loop |
devloop.loop.l3 |
人类反馈 | Human Feedback |
devloop.state.pending |
待处理 | Pending |
devloop.state.coding |
编码中 | Coding |
devloop.state.testing |
测试中 | Testing |
devloop.state.failed |
失败 | Failed |
devloop.state.passed |
已通过 | Passed |
devloop.state.blocked |
阻塞 | Blocked |
devloop.btn.newRequirement |
+ 新增需求 | + New Requirement |
devloop.btn.signOff |
验收 | Sign-off |
devloop.btn.abandon |
放弃项目 | Abandon |
devloop.toast.shipped |
项目已交付! | Project shipped! |
devloop.toast.needHelp |
需要人工介入 | Need human help |
devloop.empty |
尚无项目 | No projects yet |
中英文必填,其他 4 种语言可走 fallback。
V-11 · 设计 token(CSS 变量)
· web/src/styles/devloop.css
:root {
--L1: #0E8C5A; --L1-tint: #E2F2EA;
--L2: #C77C2C; --L2-tint: #F7E8D2;
--L3: #6E48C3; --L3-tint: #ECE4FB;
--alert: #C7341A;
}
// 与 LemonBoss 全局 token 共存,前缀避免冲突