Files
KSP_project/docs/superpowers/specs/2026-07-09-guanghan-topology-presentation-db-design.md
T

628 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 广寒基地拓扑图表现层数据库化设计
日期:2026-07-09
状态:待用户审核
范围:广寒基地工程总图的视觉表现层数据设计,不包含本轮代码实现
## 1. 背景
当前 `guanghan_topology_demo.html` 已经从后端快照读取大部分拓扑数据:
- 任务与时间点决定快照时间;
- 组件、端口、连接、状态来自 PostgreSQL
- SVG 节点坐标、尺寸、分组、标签覆盖、显示提示和散热器标记来自 `diagram_layouts``diagram_nodes``diagram_node_members`
- 页面会按快照隐藏尚未出现的组件和连接。
但页面仍然保留了一部分视觉语义:
- `core-card``power-card``vehicle-card` 等样式键对应什么颜色,由前端 CSS 决定;
- `structural_mount``pressurized_passage``fuel``power` 等连接类型如何映射成线条颜色、线宽、虚实线,由前端 JS/CSS 决定;
- 图例项由 HTML 硬编码;
- 散热器虽然由数据库标记是否显示,但具体形状、颜色、标签仍由前端决定。
因此当前状态是“半数据库驱动”。本设计把它推进到 B1 级别:数据库决定工程图的主要视觉语义,前端只保留通用渲染和交互壳。
## 2. 目标
本设计的目标是让后端数据库决定以下内容:
1. 节点矩形的位置、尺寸、圆角、填充色、描边色、描边宽度。
2. 连接线的颜色、线宽、虚实线、图例文案。
3. 装饰件的基础视觉参数,例如散热器的颜色、描边、标签。
4. 工程图图例的项目、顺序、显示名称和示例样式。
5. 节点 `style_key`、连接 `connection_type`、装饰件 `decoration_key` 到具体视觉参数的映射。
验收时应能做到:
- 修改数据库中 `pressurized_passage` 的颜色后,页面加压走道颜色随之变化,不需要改 HTML/CSS;
- 修改数据库中 `core-card` 的填充/描边后,核心舱视觉随之变化;
- 图例完全由 API 返回,不再在页面硬编码;
- GHC-01 到 GHC-05 的历史快照仍按数据库事实正确显示;
- 当前已确认的空间结构不变。
## 3. 非目标
B1 方案不把整个页面变成数据库控制的 UI 系统。以下内容仍属于前端通用壳:
- 页面字体族、整体背景、工具栏、弹窗、按钮、hover/focus 效果;
- 文本截断算法、SVG 元素创建方式、键盘可访问性;
- 响应式断点和滚动容器;
- 模块详情弹窗的排版;
- 业务时态表的结构重写;
- 数据库直接存储完整 SVG 或 HTML。
这条边界很重要:数据库掌握“工程图是什么样”,前端掌握“页面如何交互”。
## 4. 设计原则
### 4.1 表现层不是业务事实
组件、任务、连接、状态仍然是业务事实。布局和样式是对这些事实的可视化表达。
因此:
- 快照时间继续只决定哪些组件、连接、状态存在;
- 默认情况下,视觉主题不随历史时间变化;
- 早期任务快照可以使用同一张当前工程总图布局,只隐藏当时尚不存在的组件。
这能保持不同时刻的空间比较稳定,避免 GHC-01、GHC-03、GHC-05 每次切换都“重排整张图”。
### 4.2 数据库保存语义映射,不保存前端实现
数据库可以保存:
- `stroke_color = '#51e89c'`
- `stroke_width = 9`
- `dash_array = null`
- `legend_label = '加压走道'`
数据库不保存:
- CSS 选择器;
- JavaScript 函数名;
- HTML 片段;
- 浏览器专用的布局技巧。
### 4.3 可迁移、可扩展、可回退
新增表现层表应与现有 `diagram_layouts` 兼容。实现时可以分阶段上线:
1. 先建表并 seed 当前样式;
2. API 同时返回旧字段和新 `presentation`
3. 前端切换到新字段;
4. 测试通过后,保留旧 CSS 类作为安全兜底,但不再作为事实来源。
## 5. 数据模型
### 5.1 `diagram_themes`
主题表表示一套工程图视觉风格。广寒基地初期只需要一个主题。
建议字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `uuid` | 主键 |
| `base_id` | `uuid` | 所属基地 |
| `theme_key` | `text` | 例如 `guanghan_dark_engineering_v1` |
| `name` | `text` | 显示名称 |
| `description` | `text` | 可空 |
| `tokens` | `jsonb` | 少量通用色值,例如文字色、弱文字色;不放完整 CSS |
| `is_default` | `boolean` | 是否默认主题 |
| `created_at` / `updated_at` | `timestamptz` | 审计字段 |
约束:
```sql
UNIQUE (base_id, theme_key)
```
`tokens` 示例:
```json
{
"textColor": "#eaf2ff",
"mutedTextColor": "#91a3bd",
"labelColor": "#f4df7b"
}
```
### 5.2 `diagram_node_styles`
节点样式表把现有 `diagram_nodes.style_key` 从“前端 CSS 类名”变成“数据库样式键”。
建议字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `uuid` | 主键 |
| `theme_id` | `uuid` | 所属主题 |
| `style_key` | `text` | 例如 `core-card``power-card` |
| `display_name` | `text` | 管理用途 |
| `fill_color` | `text` | SVG 填充色 |
| `stroke_color` | `text` | SVG 描边色 |
| `stroke_width` | `numeric` | SVG 描边宽度 |
| `corner_radius` | `numeric` | 矩形圆角 |
| `title_color` | `text` | 可空,默认使用主题文字色 |
| `meta_color` | `text` | 可空 |
| `hint_color` | `text` | 可空 |
| `created_at` / `updated_at` | `timestamptz` | 审计字段 |
约束:
```sql
UNIQUE (theme_id, style_key)
```
现有样式键继续保留:
- `node-card`
- `core-card`
- `power-card`
- `power-node-card`
- `vehicle-card`
这样数据迁移最小,页面也容易渐进切换。
### 5.3 `diagram_edge_styles`
连接样式表按业务连接类型定义线条表现。
建议字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `uuid` | 主键 |
| `theme_id` | `uuid` | 所属主题 |
| `connection_type` | `text` | 对应 `topology_connections.connection_type` |
| `display_name` | `text` | 例如 `加压走道` |
| `stroke_color` | `text` | 线条颜色 |
| `stroke_width` | `numeric` | 线宽 |
| `dash_array` | `text` | 可空,例如 `13 10` |
| `line_cap` | `text` | 例如 `square` |
| `legend_group` | `text` | 可空,用于多个连接类型共用一个图例 |
| `legend_order` | `integer` | 图例排序辅助 |
| `created_at` / `updated_at` | `timestamptz` | 审计字段 |
约束:
```sql
UNIQUE (theme_id, connection_type)
```
当前建议映射:
| `connection_type` | 默认图例 | 默认表现 |
|---|---|---|
| `structural_mount` | 结构直连 | 灰蓝色实线 |
| `pressurized_passage` | 加压走道 | 绿色实线 |
| `fuel` | 燃料 / 供电 | 紫色虚线 |
| `power` | 燃料 / 供电 | 紫色虚线 |
| `cooling` | 冷却 | 预留,可暂不显示图例 |
| `docking` | 对接口 | 预留 |
如果 `fuel``power` 继续共用图例,可以让两条记录使用同一个 `legend_group = 'utility'`
### 5.4 `diagram_decoration_styles`
装饰件样式表定义散热器等非组件元素的表现。装饰件是否出现仍由 `diagram_nodes.decorations` 决定。
建议字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `uuid` | 主键 |
| `theme_id` | `uuid` | 所属主题 |
| `decoration_key` | `text` | 例如 `radiator` |
| `display_name` | `text` | 例如 `大型折叠散热面板` |
| `fill_color` | `text` | 主体填充色 |
| `stroke_color` | `text` | 主体描边色 |
| `stroke_width` | `numeric` | 主体描边宽度 |
| `label` | `text` | 图上短标签,例如 `散热器` |
| `label_color` | `text` | 标签颜色 |
| `render_params` | `jsonb` | 小规模绘制参数 |
| `legend_order` | `integer` | 图例排序辅助 |
| `created_at` / `updated_at` | `timestamptz` | 审计字段 |
约束:
```sql
UNIQUE (theme_id, decoration_key)
```
`render_params` 示例:
```json
{
"widthRatio": 0.55,
"maxWidth": 130,
"height": 16,
"offsetXRatio": 0.55,
"offsetY": -18,
"ribCount": 3
}
```
`render_params` 只用于装饰件的小型形状参数。它不承载任意 SVG,也不允许注入脚本或 HTML。
### 5.5 `diagram_legend_items`
图例可以从节点、连接和装饰件样式自动推导,但为了保证顺序和文案稳定,建议单独建表。
建议字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `uuid` | 主键 |
| `theme_id` | `uuid` | 所属主题 |
| `legend_key` | `text` | 例如 `pressurized` |
| `label` | `text` | 例如 `加压走道` |
| `item_type` | `text` | `edge``decoration``node` |
| `style_ref` | `text` | 引用 `connection_type``decoration_key``style_key` |
| `display_order` | `integer` | 显示顺序 |
| `is_visible` | `boolean` | 是否显示 |
约束:
```sql
UNIQUE (theme_id, legend_key)
CHECK (item_type IN ('edge', 'decoration', 'node'))
```
当前图例建议:
1. `structural`:结构直连,引用 `structural_mount`
2. `pressurized`:加压走道,引用 `pressurized_passage`
3. `utility`:燃料 / 供电,引用 `fuel``power` 中任一代表样式
4. `radiator`:大型折叠散热面板,引用 `radiator`
### 5.6 `diagram_layouts` 扩展
为布局绑定默认主题:
| 字段 | 类型 | 说明 |
|---|---|---|
| `theme_id` | `uuid` | 引用 `diagram_themes(id)`,可空;为空时使用基地默认主题 |
建议约束:
```sql
ALTER TABLE topology.diagram_layouts
ADD COLUMN theme_id uuid REFERENCES topology.diagram_themes(id);
```
### 5.7 `diagram_nodes` 扩展
现有字段基本足够,但建议补一个可选字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `render_meta` | `jsonb` | 节点级小规模渲染参数,例如特殊文本锚点;首期可不使用 |
首期可以不新增 `render_meta`,只让 `style_key` 指向 `diagram_node_styles`。如果后续发现某个节点需要特殊文本位置,再引入该字段。
## 6. API 设计
### 6.1 快照响应结构
现有 `/api/v1/bases/guanghan/snapshot` 继续返回 `base``query``components``connections``layout`。新增 `layout.presentation`
建议结构:
```json
{
"layout": {
"layout": {
"key": "engineering_overview_v1",
"name": "工程总图 v1",
"canvasWidth": 1500,
"canvasHeight": 1900,
"themeKey": "guanghan_dark_engineering_v1"
},
"nodes": [
{
"nodeKey": "core_cabin",
"styleKey": "core-card",
"x": 630,
"y": 470,
"width": 240,
"height": 200,
"memberKeys": ["core_cabin"]
}
],
"presentation": {
"theme": {
"key": "guanghan_dark_engineering_v1",
"name": "广寒深色工程图",
"tokens": {
"textColor": "#eaf2ff",
"mutedTextColor": "#91a3bd"
}
},
"nodeStyles": {
"core-card": {
"fillColor": "#3b250d",
"strokeColor": "#ffad42",
"strokeWidth": 5,
"cornerRadius": 16,
"titleColor": "#eaf2ff",
"metaColor": "#a6b5c9",
"hintColor": "#71849e"
}
},
"edgeStyles": {
"pressurized_passage": {
"displayName": "加压走道",
"strokeColor": "#51e89c",
"strokeWidth": 9,
"dashArray": null,
"lineCap": "square",
"legendGroup": "pressurized"
}
},
"decorationStyles": {
"radiator": {
"displayName": "大型折叠散热面板",
"fillColor": "#6b5814",
"strokeColor": "#f0d869",
"strokeWidth": 2,
"label": "散热器",
"labelColor": "#f4df7b",
"renderParams": {
"widthRatio": 0.55,
"maxWidth": 130,
"height": 16,
"offsetXRatio": 0.55,
"offsetY": -18,
"ribCount": 3
}
}
},
"legendItems": [
{
"key": "pressurized",
"label": "加压走道",
"itemType": "edge",
"styleRef": "pressurized_passage",
"displayOrder": 20
}
]
}
}
}
```
### 6.2 缺省与错误处理
API 查询布局时:
1. 使用默认布局;
2. 如果布局绑定了主题,使用该主题;
3. 如果布局未绑定主题,使用基地默认主题;
4. 如果节点引用的 `style_key` 不存在,API 应返回明确错误或在响应中标记 `presentationWarnings`
建议首期采用“API 返回 500/配置错误,不静默降级”的策略。拓扑图是工程资料,缺失样式不应伪装成正确页面。
前端可以有开发兜底色,但只用于防止页面彻底空白,不能作为测试验收依据。
### 6.3 样式值安全
API 应只返回白名单字段。颜色值应校验为:
- `#RGB`
- `#RRGGBB`
- `rgb(...)`
- `rgba(...)`
- 少量受控命名色如 `transparent`
首期建议只使用 `#RRGGBB`,简单、稳定、可测试。
`dash_array` 只允许数字和空格,例如 `13 10`
`line_cap` 只允许 `butt``round``square`
`render_params` 只允许预定义键,不允许任意 SVG 属性透传。
## 7. 前端渲染设计
### 7.1 节点
当前逻辑:
```text
rect.class = node.styleKey
CSS 决定 fill/stroke/stroke-width
```
目标逻辑:
```text
style = presentation.nodeStyles[node.styleKey]
rect.setAttribute('fill', style.fillColor)
rect.setAttribute('stroke', style.strokeColor)
rect.setAttribute('stroke-width', style.strokeWidth)
rect.setAttribute('rx', style.cornerRadius)
```
文本颜色同理从节点样式或主题 token 获取。
### 7.2 连接
当前逻辑:
```text
connection.type -> connectionClass(type)
CSS 决定颜色和虚实线
```
目标逻辑:
```text
style = presentation.edgeStyles[connection.type]
line.stroke = style.strokeColor
line.strokeWidth = style.strokeWidth
line.strokeDasharray = style.dashArray
line.strokeLinecap = style.lineCap
```
连接合并逻辑保留:多个数据库连接如果落在同一对视觉节点之间,并且使用同一种连接类型或同一个视觉样式,可以合并成一条线。
### 7.3 装饰件
当前逻辑:
```text
if node.decorations.radiator:
createRadiator(node)
```
目标逻辑:
```text
for each decorationKey in node.decorations:
style = presentation.decorationStyles[decorationKey]
createKnownDecoration(decorationKey, node, style)
```
首期只实现 `radiator` 这一种已知装饰件。后续新增装饰件时,应先在前端注册一个安全的已知 renderer,再允许数据库使用该 `decoration_key`
### 7.4 图例
当前图例由 HTML 写死。目标是:
1. 页面保留空的 `<div id="legend">`
2. 前端遍历 `presentation.legendItems`
3. 根据 `itemType``styleRef` 找到对应样式;
4. 创建线段或色块示例。
这样图例顺序、文案和显示/隐藏由数据库决定。
## 8. Seed 数据建议
首期 seed 应把当前用户已认可的视觉效果完整落库:
### 8.1 节点样式
| `style_key` | 用途 |
|---|---|
| `core-card` | 广寒核心舱 |
| `node-card` | 普通舱段 |
| `power-card` | 电力模块分支 |
| `power-node-card` | 电力模块中心节点 |
| `vehicle-card` | 吴刚/冰轮等车辆 |
### 8.2 连接样式
| `connection_type` | 图例 | 要求 |
|---|---|---|
| `structural_mount` | 结构直连 | 灰蓝实线 |
| `pressurized_passage` | 加压走道 | 绿色实线 |
| `fuel` | 燃料 / 供电 | 紫色虚线 |
| `power` | 燃料 / 供电 | 紫色虚线 |
### 8.3 装饰件样式
| `decoration_key` | 用途 |
|---|---|
| `radiator` | 电力模块四个大分支顶部的大型折叠散热面板 |
散热器继续不是独立组件,不出现在弹窗里,不覆盖加压走道。
## 9. 测试策略
### 9.1 API 测试
新增测试应覆盖:
- snapshot 响应包含 `layout.presentation`
- `nodeStyles` 包含所有 `diagram_nodes.style_key`
- `edgeStyles` 包含当前快照里所有 `connections.type`
- `legendItems``displayOrder` 排序;
- `radiator` 装饰件样式存在;
- 缺失样式时返回明确配置错误。
### 9.2 前端源码测试
源码测试应确认:
- 页面不再硬编码图例文字;
- 页面不再通过 `.core-card``.pressurized` 等 CSS 类决定业务颜色;
- 页面读取 `presentation.nodeStyles``presentation.edgeStyles``presentation.decorationStyles``presentation.legendItems`
### 9.3 浏览器端到端测试
E2E 应覆盖:
- GHC-01、GHC-05 正常渲染;
- GHC-05 有两条绿色加压走道;
- 图例来自 API
- 打开模块弹窗正常;
- 修改测试数据库中的 `pressurized_passage.stroke_color` 后,浏览器渲染颜色变化;
- 修改 `core-card.fill_color` 后,核心舱填充颜色变化;
- 历史任务节点/连接/散热器数量不回退。
### 9.4 截图验收
实现阶段每次页面修改后仍遵循现有要求:必须自己截图检查效果。
至少保留:
- GHC-05 顶部/中部/底部截图;
- GHC-01 早期快照截图;
- 图例截图;
- 修改测试主题颜色后的验证截图。
## 10. 实施分期建议
### Phase 1:数据库与 API
- 新增表现层迁移;
- seed 当前已认可样式;
- API 返回 `layout.presentation`
- 增加 API 单元测试。
### Phase 2:前端切换
- 图例改为从 `presentation.legendItems` 生成;
- 节点、连接、散热器改为使用 API 样式;
- 移除业务颜色 CSS 的事实来源角色;
- 保留通用 CSS 和交互样式。
### Phase 3:验证与清理
- 跑完整单元测试和 E2E
- 截图核对 GHC-01 到 GHC-05
- 用测试数据临时改颜色,验证前端无代码修改即可变化;
- 更新验收报告。
## 11. 与未来扩展的关系
未来核心舱北、东、西三方向会继续扩展十字结构,北部成为多台吴刚着陆器连接点,西部成为多个 rover 连接点。B1 表现层设计支持这种扩展:
- 新节点继续写入 `diagram_nodes`
- 新节点使用已有或新增 `style_key`
- 新连接类型如果需要新视觉表现,只新增 `diagram_edge_styles``diagram_legend_items`
- 未来扩展锚点仍不在页面显示,只有实际组件在快照时间存在后才渲染。
如果将来需要同时支持“工程总图”“简化拓扑图”“施工阶段图”等多个视图,可以新增不同 `diagram_layouts.layout_key`,并让 API 支持 `layout=` 参数。但这不是本轮 B1 的首期目标。
## 12. 决策摘要
本设计采用 B1:表现层数据库化。
明确放入数据库:
- 节点位置和尺寸;
- 节点颜色、描边、圆角;
- 连接线颜色、线宽、虚实线;
- 散热器基础视觉参数;
- 图例项。
明确留在前端:
- 页面壳、弹窗、工具栏、响应式;
- hover/focus
- 文本截断;
- SVG 创建和交互事件;
- 安全的已知装饰件 renderer。
这个边界能满足“由后台数据库决定各组件渲染位置、颜色等信息”,同时避免把数据库变成一个难维护的 HTML/CSS 仓库。