# 蓝湖正式素材替换 · 项目共识与经验留档 来源:`lanhu-cockpit-asset-replacement` 地图全部 20 张子票(8 决策 + 5 规格 + 5 执行 + 盘点/迁移各 1)的讨论与实施沉淀。 性质:**本项目「设计素材接入」类任务的单一经验来源(single source of truth)**。后续同类任务(换设计稿、接新素材、改视觉体系)先读本文,不再逐轮重新发明。 生效范围:城市生命线驾驶舱 `/cockpit` 的 2D UI 素材、图标、位置符号、字体、装饰。 配套文档(各自仍是其领域的单一事实来源): - [UI 设计及图标素材使用共识](icon-usage-consensus.md) —— 图标/切图的目录、命名、染色、归档细则。 - [终稿验收报告](final-acceptance-report.md) —— 本次验收证据与未闭合项。 --- ## 一、总原则(决策类经验) ### 1. 现状优先,默认归视觉 设计稿差异**默认归入素材替换**,不无谓升级为结构决策;仅当差异触及**状态语义 / 信息层级 / 交互契约 / 数据契约**时才升级。 - 信息层级一律**以现有实现为准**;数据内容一律**以实际后端为准**,设计稿数据仅作参考展示。 - 升级必须**另立决策票**并由维护者裁定,不由实现者口头处理或直接按设计稿做。 ### 2. 结构边界词表(可判定) | 属视觉(可改) | 属结构(须升级) | | --- | --- | | 两栏宽、面板高、边距、间距、字号 | 面板数量、面板顺序、列排列、两栏改单栏 | | 图标/颜色/字体/纹理/形状外观 | 状态集合与触发时机、图层显隐、拾取、聚合、状态语义 | | 数据展示格式(时间/千分位/小数位) | 数据字段、单位、口径 | | 视觉反馈层(光效、动效表现) | 相机交互、选择态、定位/拾取/联动契约 | ### 3. 设计稿「没画」≠「该删」 设计稿未覆盖的现有 UI **维持现状**并标记「无设计稿依据」,不因蓝湖没画就删除或臆造样式。 ### 4. 色值只有一个来源 色值只在 `styles/tokens.css` 声明一份;素材若自带彩色即成第二套色源。**实测教训**:素材成品色与项目语义色无一相同(素材 Red `#E80000` vs `--lv1 #ff4d5e`),因此只采用**纯白可着色母版 + 运行时注入状态色**。 ### 5. 临时资源必须可追溯、不得充当正式验收 临时素材必须带标记(manifest `status: temporary`);**验收不得以临时资源作为正式视觉通过**;缺口清单必须列出仍未替换的临时项。 --- ## 二、资产与运行时(工程类经验) ### 1. manifest 是唯一权威入口 组件/地图服务/设备资产层**只消费语义 key**,不直接引用物理路径。条目须含:语义 key、路径、来源、格式/尺寸、发布版本、内容 hash、状态(formal/temporary)、适用范围、核心标记、fallback key。 - 生产默认**拒绝远程资源**;远程例外必须登记来源/域名/安全/缓存/超时/fallback。 - 语义 key 稳定;物理文件名可带 hash。 - **manifest 与资源原子发布**;核心资源组失败整包回滚,普通资源按条目 fallback。 ### 2. 路径必须跟随应用基址 所有资源引用走 `import.meta.env.BASE_URL`(或宿主注入基址),**禁止硬编码 `/cockpit/...`**,以兼容相对 `publicPath` 与 qiankun 注入路径。 ### 3. 单一接缝优于分散实现 本图建立的接缝(后续复用,不要另起): - **统一资产解析层**:manifest 校验 + 语义 key 解析 + fallback + 诊断。 - **位置符号合成计划层**(`domain/markerPlan.js`):母版归属 / 内层图标几何 / 尺寸 / 锚点 / 着色 / 降饱和,纯数据产出,渲染媒介可换。 ### 4. 状态语义不可被素材替换改变 点位状态(六态)与设备状态(五态)是**两张平行表**,色键可同、语义不混;认不出的状态如实落「状态未发布」,**不猜成正常**。颜色不是唯一线索(形状 + 文字并存)。 --- ## 三、踩过的坑(每条都是真实事故) | 坑 | 现象 | 纪律 | | --- | --- | --- | | **manifest 被并发脚本整文件重写** | 另一会话跑 `build-manifest.mjs` 把 `fonts` 块抹掉(26409→15538 字节),字体静默失效 | 脚本重写前**保留未知顶层块**;`build-manifest.mjs` 已加**跨批防覆盖闸门**(未登记 entries 存在即中止) | | **Vite 缓存 `import` 的 manifest 转译结果** | 外部脚本改写 manifest 后页面拿到旧模块,字体静默不生效 | 门禁如实报 `fonts-unavailable` 使其可观察;改完 manifest **必须硬刷新** | | **HMR 模块图陈旧** | HMR 只热更边界组件、不重载其传递依赖的 manifest,浏览器仍跑旧 manifest | 同上;**判定效果前先硬刷新** | | **「字体请求为 0」被误判为正常** | Web 字体排在回退链首位,页面文本必用到;请求为 0 说明字体没生效 | **「字体请求为 0」必须判 FAIL** | | **窄视口响应式缺陷** | `--u` 触底 1px 后顶栏总宽超视口,grid 挤压菜单列 → 按钮文字换行被定高裁成单字 | 响应式回归**必须覆盖窄视口**(此前十轮只在大视口验证) | | **图片盒子被整图拉伸** | 装饰线可见段按百分比映射视口,挪图片盒子永远无效 | 用**裁切窗口**(background-size + position)而非挪盒子 | | **切图撑高 grid 行** | 标题切图作为 grid 项内容把顶栏行撑高,「垂直居中」基准被改 | 定高容器 + 绝对定位让溢出不占布局 | | **`mask-image` 不收层语法** | 四角切角染色自误 | 色值收口时验证 mask 语法 | | **单点验证代替全域验证** | 同一代码在不同视口/环境表现不同(predev 有 54px 页头) | 明确验收环境;**predev 差异不等于产品缺陷** | | **锚点迁移留下假绿** | 断言按旧口径建立,改渲染后仍绿 | 锚点口径变更必须**同步更新断言并以对抗方式确认能失败** | --- ## 四、字体与授权(合规类经验) ### 1. 授权证据确证制 - 内置字体需**书面/电子授权证据**,明确覆盖 Web 嵌入与商用,注明名称与版本。 - **「免费下载」≠「允许 Web 嵌入与商用」**;**系统自带字体只能作回退栈、不得打包**。 - 白名单只放行**确证带开源许可**的字族;名字暗示免费但无证据者**按无证据处理**,除非归档上游许可证文本。 ### 2. 实测教训:整目录不可放行 维护者提供的字体目录 `E:\_Design\字体` **零授权文件**,且含明确 `All rights reserved` 的字库(`Modernia`、`Furore`)与多款公认商用收费字库。**不得以目录为单位放行**——必须逐族取证。 ### 3. 体积与子集 中文开源字体单字重约 15–16 MB;**必须子集化或限定字重档数**,且**必须保留系统回退链**(动态数据可能含子集外汉字,否则出豆腐块)。 ### 4. 能力边界(不可越界) 自动化**只能校验「是否携带许可证据」**,**不声称能判定授权合法性**——授权合法性是法律事实,由证据文件与责任人负责。 ### 5. 缺口分级(仅三类阻断发布) **阻断**:① 核心资源组(品牌标识 / 点位设备状态形状语义 / 状态色语义)不可用;② 视觉会误导业务语义;③ 法规/授权风险。 **允许带回退交付**:其余缺口,条件是登记缺口清单 + 临时资源带标记 + 验收不把临时资源当正式视觉。 --- ## 五、实施与验收(流程类经验) ### 1. 批次按资产类别切,部分可并行 品牌/图标、面板 token、位置符号、字体、装饰五批;**位置符号依赖 token**(外层母版着色),**装饰最后**;品牌/图标、token、字体可并行。 ### 2. 每批进入条件 素材已入库且**过 manifest 校验**(存在、hash、fallback 齐全)+ 该批有**可执行验收清单** + 已用差异判断表分类。 ### 3. 双轨验收 实现者自测(纯逻辑断言 + 宿主 `data-*` 读数 + 结构回归)+ 维护者走查真实 `/cockpit` 并签收。**终稿验收是最后一道闸,不是第一道。** ### 4. 证据单一来源 - 验收证据写进**执行票本身**(走查单 + 截图 + 读数),不另建「批验收记录」第二来源。 - 偏差只写进**票 06 缺口清单**,不另建「偏差清单」。 ### 5. 缺陷分级 P0 阻断(复用上述三类判据)/ P1 高(1280px 不可读、位置符号不可辨、锚点错位)/ P2 中(观感、性能)/ P3 低(非核心装饰,入缺口清单后可发布)。 ### 6. 临时素材关闭判据 核心资源组**全部换正式素材** + 缺口清单**无「临时素材仍在使用」条目**;非核心装饰留临时项时在缺口清单**显式登记「未替换」**。最终以**逐项关闭清单**留痕并由维护者签收。 ### 7. 1280px 是硬承诺 最小 1280×720 下:文字可读、按钮可操作、面板不重叠优先;左右栏保持宽度,面板内**纵向滚动**,支持卷轴式展开/折叠,装饰可缩放/隐藏/降级。 --- ## 六、验收环境事实(本仓现状) | 环境 | 地址/状态 | 说明 | | --- | --- | --- | | 真实 `/cockpit` | `http://127.0.0.1:8000/#/cockpit` | **唯一开发/验收入口**,所有会话统一使用,不另起端口 | | 后端只读接口 | `http://192.168.110.96:8301/api/cockpit/v1/**` | 免鉴权;`dataSource: real` | | qiankun 宿主 | **本仓不存在宿主工程** | 仅被嵌侧适配代码 `src/.fes/plugin-qiankun/`;宿主验收需临时自建(见下) | | predev 预演站 | `npm run pre.dev` | 有 54px 页头,**布局差异不等于产品缺陷** | ### qiankun 宿主验收的已知事实(供后续复用) 自建最小宿主可验证「加载 + 挂载 + 基址注入」: - 被嵌侧 appName 为 **`TvT.js`**(dev HTML 实测);生命周期经 `window.moudleQiankunAppLifeCycles['TvT.js']` 暴露。 - 必须传 `props.url`(如 `/cockpit`),否则被嵌侧不切 memory history、`/cockpit` 不渲染。 - 实测宿主内 `__INJECTED_PUBLIC_PATH_BY_QIANKUN__` = `http://127.0.0.1:8000/`,**基址注入生效**。 - **已知阻塞**:被嵌侧 `src/.fes/plugin-qiankun/micro/runtime.js:24` 解构 `{ router, history }` 并在 `:26` 调 `history.push(...)`,但 FES 4 的 `onRouterCreated` 契约只保证 `router`(同签名的 `plugin-access` 只解构 `{ router }`)→ 宿主内抛 `Cannot read properties of undefined (reading 'push')`,`/cockpit` 落在宿主默认页。该文件 `git status` 无本地改动,属**宿主集成既有问题**,不属素材替换范围。 - 该阻塞若要修,属「宿主集成」独立事项;**素材替换侧的资源路径解析不受影响**(`resolveCesiumBaseUrl()` 对注入基址的三条断言已全绿)。 --- ## 七、可复用的检查清单(新任务照抄) **开工前** - [ ] 差异判断表分类:哪些归视觉、哪些须升级 - [ ] 素材是否入库并过 manifest 校验(存在/hash/fallback) - [ ] 是否有可执行验收清单 **实施中** - [ ] 色值只在 `tokens.css`;无散落物理路径;无死资源 - [ ] 资源走 `BASE_URL`;不硬编码绝对路径 - [ ] 结构回归(四模式 / 16 panelId / 状态词表冻结) **收口前** - [ ] 纯逻辑断言 + 宿主读数 + 真实 `/cockpit` 走查 - [ ] 1280×720 逐面板检查(含窄视口) - [ ] 偏差已登记缺口清单(唯一来源) - [ ] 临时资源状态明确(已替换/已删除/登记未替换) - [ ] 结构口径变更时,旧断言已同步更新且**确认能失败** --- *本文由 `lanhu-cockpit-asset-replacement` 地图收口时提取。后续同类任务请以本文为起点;若与本文冲突,以更新的决策票为准并回写本文。*