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

18 KiB
Raw Blame History

广寒基地拓扑图表现层数据库化设计

日期:2026-07-09 状态:待用户审核 范围:广寒基地工程总图的视觉表现层数据设计,不包含本轮代码实现

1. 背景

当前 guanghan_topology_demo.html 已经从后端快照读取大部分拓扑数据:

  • 任务与时间点决定快照时间;
  • 组件、端口、连接、状态来自 PostgreSQL;
  • SVG 节点坐标、尺寸、分组、标签覆盖、显示提示和散热器标记来自 diagram_layoutsdiagram_nodesdiagram_node_members
  • 页面会按快照隐藏尚未出现的组件和连接。

但页面仍然保留了一部分视觉语义:

  • core-cardpower-cardvehicle-card 等样式键对应什么颜色,由前端 CSS 决定;
  • structural_mountpressurized_passagefuelpower 等连接类型如何映射成线条颜色、线宽、虚实线,由前端 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 审计字段

约束:

UNIQUE (base_id, theme_key)

tokens 示例:

{
  "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-cardpower-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 审计字段

约束:

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 审计字段

约束:

UNIQUE (theme_id, connection_type)

当前建议映射:

connection_type 默认图例 默认表现
structural_mount 结构直连 灰蓝色实线
pressurized_passage 加压走道 绿色实线
fuel 燃料 / 供电 紫色虚线
power 燃料 / 供电 紫色虚线
cooling 冷却 预留,可暂不显示图例
docking 对接口 预留

如果 fuelpower 继续共用图例,可以让两条记录使用同一个 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 审计字段

约束:

UNIQUE (theme_id, decoration_key)

render_params 示例:

{
  "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 edgedecorationnode
style_ref text 引用 connection_typedecoration_keystyle_key
display_order integer 显示顺序
is_visible boolean 是否显示

约束:

UNIQUE (theme_id, legend_key)
CHECK (item_type IN ('edge', 'decoration', 'node'))

当前图例建议:

  1. structural:结构直连,引用 structural_mount
  2. pressurized:加压走道,引用 pressurized_passage
  3. utility:燃料 / 供电,引用 fuelpower 中任一代表样式
  4. radiator:大型折叠散热面板,引用 radiator

5.6 diagram_layouts 扩展

为布局绑定默认主题:

字段 类型 说明
theme_id uuid 引用 diagram_themes(id),可空;为空时使用基地默认主题

建议约束:

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 继续返回 basequerycomponentsconnectionslayout。新增 layout.presentation

建议结构:

{
  "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 10line_cap 只允许 buttroundsquarerender_params 只允许预定义键,不允许任意 SVG 属性透传。

7. 前端渲染设计

7.1 节点

当前逻辑:

rect.class = node.styleKey
CSS 决定 fill/stroke/stroke-width

目标逻辑:

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 连接

当前逻辑:

connection.type -> connectionClass(type)
CSS 决定颜色和虚实线

目标逻辑:

style = presentation.edgeStyles[connection.type]
line.stroke = style.strokeColor
line.strokeWidth = style.strokeWidth
line.strokeDasharray = style.dashArray
line.strokeLinecap = style.lineCap

连接合并逻辑保留:多个数据库连接如果落在同一对视觉节点之间,并且使用同一种连接类型或同一个视觉样式,可以合并成一条线。

7.3 装饰件

当前逻辑:

if node.decorations.radiator:
  createRadiator(node)

目标逻辑:

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. 根据 itemTypestyleRef 找到对应样式;
  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
  • legendItemsdisplayOrder 排序;
  • radiator 装饰件样式存在;
  • 缺失样式时返回明确配置错误。

9.2 前端源码测试

源码测试应确认:

  • 页面不再硬编码图例文字;
  • 页面不再通过 .core-card.pressurized 等 CSS 类决定业务颜色;
  • 页面读取 presentation.nodeStylespresentation.edgeStylespresentation.decorationStylespresentation.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_stylesdiagram_legend_items
  • 未来扩展锚点仍不在页面显示,只有实际组件在快照时间存在后才渲染。

如果将来需要同时支持“工程总图”“简化拓扑图”“施工阶段图”等多个视图,可以新增不同 diagram_layouts.layout_key,并让 API 支持 layout= 参数。但这不是本轮 B1 的首期目标。

12. 决策摘要

本设计采用 B1:表现层数据库化。

明确放入数据库:

  • 节点位置和尺寸;
  • 节点颜色、描边、圆角;
  • 连接线颜色、线宽、虚实线;
  • 散热器基础视觉参数;
  • 图例项。

明确留在前端:

  • 页面壳、弹窗、工具栏、响应式;
  • hover/focus
  • 文本截断;
  • SVG 创建和交互事件;
  • 安全的已知装饰件 renderer。

这个边界能满足“由后台数据库决定各组件渲染位置、颜色等信息”,同时避免把数据库变成一个难维护的 HTML/CSS 仓库。