Просмотр исходного кода

fix(cockpit): T01 二轮版式修正 —— 面板收放真生效、压矮横栏、一张图铺满背景

- 面板高度:左右栏改 flex 列,面板默认 flex:1 1 0(两块等分),收起时 flex:0 0 auto 缩回标题栏
- 顶栏 74→56、筛选条 46→38 设计像素,间距 8→6,内边距与字号同步下调;两条横栏合计占视口 9.9%
- 主体改为「一张图绝对铺满 + 左右两栏浮于图上」;浮层文字移到两栏之间的中间带居中
- 临时影像底图 public/cockpit/aerial.webp,经 import.meta.env.BASE_URL 取用(兼容相对 base/qiankun)
- 走查新增 A13-panel-collapse-resize 与 A12 铺满几何断言:16/16 通过

第一轮已提交的骨架/视觉改动保持不变;本轮仅版式与背景层。
Kazerin 1 месяц назад
Родитель
Сommit
390bf5dac8

+ 13 - 3
.scratch/cockpit-shell-panel-layout/issues/01-shell-mode-panel-mapping.md

@@ -15,13 +15,23 @@ Category: enhancement
 - [x] `CockpitShell` 公共契约可验证模式到面板集合的映射。
 - [x] 点击面板标题栏可收起/展开;切换一级模式后全部恢复默认展开。
 - [x] 切换一级模式不重置全局筛选。
-- [x] 有模式/面板映射纯逻辑单测和人工走查记录。**零依赖断言脚本通过;浏览器走查 15/15 通过**(A1–A12,含本轮新增的筛选持久化与铺满几何断言)。
+- [x] 有模式/面板映射纯逻辑单测和人工走查记录。**零依赖断言脚本通过;浏览器走查 16/16 通过**(A1–A13,含筛选持久化、铺满几何与面板收放高度三条新断言);**维护者已目测确认通过**。
 
 ## 进度:100%
 
-**已关闭。** 维护者审查后给了三条修正,均已落地并重跑验证。
+**已关闭(第二轮完成)。** 维护者看过首版后提出三条版式修正,已全部落地、重跑走查 16/16,并于二轮目测确认通过。面板高度分配按维护者决定**维持等分**(「先不改」),真数据接入后若某面板长期偏高再评估按内容分配。
 
-**本轮改动(2026-09-16,按《驾驶舱原型图-美化版》对齐骨架与视觉)**
+**第二轮修正(2026-09-16,重开 T01)**
+
+| 提出的问题 | 处理 |
+| --- | --- |
+| ① 面板收放没有实际效果(高度被写死) | 左右栏由 `grid-template-rows: 1fr 1fr` 改为 flex 列;面板默认 `flex: 1 1 0`(两块等分),`.is-collapsed` 为 `flex: 0 0 auto`(缩回标题栏高度)。实测:`[408,408] → 收起 [42,774] → 展开 [408,408]` |
+| ② 顶栏与筛选条占比过高、挤压主体 | 顶栏 74→**56**、筛选条 46→**38** 设计像素;栅格间距 8→6;页面内边距 10/14/14→8/12/12;品牌/页签/时钟/胶囊字号同步下调。实测两条横栏合计 **94px = 视口 9.9%** |
+| ③ 一张图应铺满整个背景 | 主体改为「一张图绝对铺满 + 左右两栏浮于图上」;临时用维护者提供的影像铺底(`public/cockpit/aerial.webp`,经 `import.meta.env.BASE_URL` 取用,兼容相对 base / qiankun)。浮层文字统一移到**两栏之间的中间带**居中,避免被浮栏遮挡 |
+
+新增回归断言 **A13-panel-collapse-resize**:收起一块后它必须缩到标题栏高度、另一块至少长大 1.5 倍、两块合计仍占满栏高、再展开恢复等分。这类「高度写死」问题此前的结构断言查不出来——这正是它第一轮漏过的原因。
+
+**第一轮改动(按《驾驶舱原型图-美化版》对齐骨架与视觉)**
 
 语义(四个一级模式、模式内 4 面板同屏、无子视图、切模式不重建地图)一律不变;改动只在外壳、视觉与全局筛选状态。
 

+ 1 - 1
.scratch/cockpit-shell-panel-layout/map.md

@@ -24,7 +24,7 @@ Labels: wayfinder:map
 - [spec.md](spec.md) — 二级面板改为同屏左右两栏加标题栏展开/收起,替代 `mode/subview` 切换。
 - [决策地图](../web-cockpit-decision-map/map.md) — `mode`/`panelId` 英文词典、三键相机契约、地面与管线视图合成、Cesium 集成方式、无登录页鉴权。
 - [ADR 0003](../../docs/adr/0003-web-page-information-architecture.md) — 页面信息架构与 `mode`/`panelId` 词典。
-- [01: Shell 与模式/面板映射](issues/01-shell-mode-panel-mapping.md) — 已关闭:外壳铺满视口(不锁 16:9)、四模式切换、每模式 4 面板同屏收放、范围/行业接入 filterStore 且切模式不重置;浏览器走查 15/15。
+- [01: Shell 与模式/面板映射](issues/01-shell-mode-panel-mapping.md) — 已关闭(第二轮):外壳铺满视口、每模式 4 面板同屏且收放真正改变高度、范围/行业接入 filterStore 且切模式不重置、一张图用临时影像铺满背景;浏览器走查 16/16 + 维护者目测确认。
 - [26: Dev server 文件监视被 `.scratch` 面板原子写击穿](issues/26-devserver-watch-scratch-crash.md) — 已修复:`.fes.js` 的 dev server watch 排除 `**/.scratch/**`(Vite 默认 ignore 合并不覆盖),3 轮原子写模拟 + src 热更均实测通过。
 
 ## Not yet specified

+ 44 - 6
.scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs

@@ -411,7 +411,7 @@ try {
     const vh = geom.vh
     check(
         'A12-layout-fill',
-        '页面铺满视口:无纵向溢出、顶栏与筛选条矮、中央地图吃掉剩余宽高',
+        '页面铺满视口:两条横栏占用受控,中央一张图铺满整个主体背景',
         !!geom.shell &&
             !!geom.topbar &&
             !!geom.scope &&
@@ -419,16 +419,54 @@ try {
             !!geom.map &&
             Math.abs(geom.shell.h - vh) <= 2 &&
             geom.scrollH <= vh + 2 &&
-            geom.topbar.h <= Math.round(vh * 0.12) &&
-            geom.scope.h >= 28 &&
-            geom.scope.h <= 90 &&
+            geom.topbar.h + geom.scope.h <= Math.round(vh * 0.16) &&
+            geom.scope.h >= 24 &&
             geom.main.bottom >= vh - 24 &&
             geom.main.bottom <= vh &&
-            geom.map.w >= Math.round(geom.vw * 0.4) &&
-            geom.map.h >= Math.round(geom.main.h * 0.8),
+            geom.map.w >= Math.round(geom.vw * 0.85) &&
+            geom.map.h >= Math.round(geom.main.h * 0.95),
         JSON.stringify(geom),
     )
 
+    // A13 面板收放必须真正改变高度:收起的那块缩回标题栏,另一块吃掉腾出的空间。
+    // 这条是针对「面板高度被写死、收放无视觉变化」的回归断言。
+    const readLeftHeights = () =>
+        page.evaluate(() => ({
+            column: Math.round(
+                document.querySelector('.cockpit-shell__column.is-left').getBoundingClientRect().height,
+            ),
+            panels: Array.from(
+                document.querySelectorAll('.cockpit-shell__column.is-left .cockpit-panel'),
+            ).map((p) => Math.round(p.getBoundingClientRect().height)),
+        }))
+
+    const heightBefore = await readLeftHeights()
+    await page.click('.cockpit-shell__column.is-left .cockpit-panel .cockpit-panel__head')
+    await new Promise((r) => setTimeout(r, 300))
+    const heightCollapsed = await readLeftHeights()
+    await page.click('.cockpit-shell__column.is-left .cockpit-panel .cockpit-panel__head')
+    await new Promise((r) => setTimeout(r, 300))
+    const heightRestored = await readLeftHeights()
+
+    const grewEnough = heightCollapsed.panels[1] >= Math.round(heightBefore.panels[1] * 1.5)
+    const firstShrankToHeader = heightCollapsed.panels[0] <= 70
+    const fillsColumn =
+        heightCollapsed.panels[0] + heightCollapsed.panels[1] >= heightCollapsed.column - 24
+    const restored =
+        Math.abs(heightRestored.panels[0] - heightBefore.panels[0]) <= 6 &&
+        Math.abs(heightRestored.panels[1] - heightBefore.panels[1]) <= 6
+
+    check(
+        'A13-panel-collapse-resize',
+        '收起一块面板后它缩回标题栏、另一块吃掉腾出的高度;再展开恢复等分',
+        heightBefore.panels.length === 2 &&
+            firstShrankToHeader &&
+            grewEnough &&
+            fillsColumn &&
+            restored,
+        `before=${JSON.stringify(heightBefore)} collapsed=${JSON.stringify(heightCollapsed)} restored=${JSON.stringify(heightRestored)}`,
+    )
+
     // A11 运行期无错误。
     check('A11-no-page-errors', '走查期间无页面级报错', pageErrors.length === 0, JSON.stringify(pageErrors.slice(0, 5)))
 

BIN
public/cockpit/aerial.webp


+ 80 - 70
src/modules/cockpit/CockpitShell.vue

@@ -2,7 +2,7 @@
   驾驶舱外壳:唯一 /cockpit 路由的全部内容。
 
   骨架与视觉参考《驾驶舱原型图-美化版》(切角面板 / 青色辉光 / 深空蓝底 / 全息地图壳),
-  但**不锁死 16:9**:页面铺满视口,中央一张图吃掉全部剩余宽高(大屏、超宽屏自动拉伸)。
+  但不锁 16:9:页面铺满视口,**中央一张图作为整个主体的背景铺满**,左右两栏浮在图上。
 
   语义按 ADR 0003 / 0007:四个一级模式、模式内 4 个二级面板同屏展示、无子视图切换。
   一级模式只通过 uiStore.mode 切换,不新增路由、不重建外壳与地图节点。
@@ -106,7 +106,15 @@
         </div>
 
         <main class="cockpit-shell__main">
-            <aside class="cockpit-shell__column" aria-label="左栏">
+            <!--
+                中央一张图:铺满整个主体背景。常驻——不放在 v-for 内,也不加 v-if/key,
+                一级模式切换只重渲染左右两栏,本节点保持原实例。
+            -->
+            <section class="cockpit-shell__map" aria-label="中央一张图">
+                <MapPlaceholder :mode-label="modeLabel(mode)" />
+            </section>
+
+            <aside class="cockpit-shell__column is-left" aria-label="左栏">
                 <CockpitPanel
                     v-for="panelId in columns.left"
                     :key="panelId"
@@ -119,15 +127,7 @@
                 </CockpitPanel>
             </aside>
 
-            <!--
-                中央一张图常驻:不放在 v-for 内,也不加 v-if/key。
-                一级模式切换只重渲染左右两栏,本节点保持原实例;尺寸随视口拉伸。
-            -->
-            <section class="cockpit-shell__map" aria-label="中央一张图">
-                <MapPlaceholder :mode-label="modeLabel(mode)" />
-            </section>
-
-            <aside class="cockpit-shell__column" aria-label="右栏">
+            <aside class="cockpit-shell__column is-right" aria-label="右栏">
                 <CockpitPanel
                     v-for="panelId in columns.right"
                     :key="panelId"
@@ -197,7 +197,7 @@ onBeforeUnmount(() => {
 /* ============================================================
    主题令牌(原型图同名变量;子组件通过继承取用)
    --u 是「设计像素」:1920 宽时为 1px,随视口线性放大、封顶 2px(4K 时正好一倍),
-   小于 1920 时不再缩小。这样 4K 大屏不会显得空旷,同时页面仍是铺满而非等比留边。
+   小于 1920 时不再缩小。这样 4K 大屏不显空旷,同时页面仍是铺满而非等比留边。
    ============================================================ */
 .cockpit-shell {
     --u: clamp(1px, 0.0521vw, 2px);
@@ -221,12 +221,13 @@ onBeforeUnmount(() => {
 
     position: relative;
     display: grid;
-    grid-template-rows: calc(74 * var(--u)) calc(46 * var(--u)) minmax(0, 1fr);
-    gap: calc(8 * var(--u));
+    /* 顶栏 / 筛选条 / 主体:两条横栏压矮,把高度让给真正展示信息的主体 */
+    grid-template-rows: calc(52 * var(--u)) calc(34 * var(--u)) minmax(0, 1fr);
+    gap: calc(4 * var(--u));
     width: 100%;
     height: 100vh;
     overflow: hidden;
-    padding: calc(10 * var(--u)) calc(14 * var(--u)) calc(14 * var(--u));
+    padding: calc(8 * var(--u)) calc(12 * var(--u)) calc(12 * var(--u));
     color: var(--text);
     font: calc(13 * var(--u)) / 1.45 var(--font);
 }
@@ -257,7 +258,7 @@ onBeforeUnmount(() => {
     mask-image: radial-gradient(120% 90% at 50% 30%, #000 30%, transparent 88%);
 }
 
-/* 只给真正的三行网格项加定位;不要用 `.cockpit-shell > *`,
+/* 只给真正的网格项加定位;不要用 `.cockpit-shell > *`,
    否则会以同权重覆盖 .cockpit-shell__atmosphere 的 position: fixed。 */
 .cockpit-shell__topbar,
 .cockpit-shell__scope,
@@ -268,16 +269,14 @@ onBeforeUnmount(() => {
 }
 
 /* ============================================================
-   顶栏
+   顶栏(压矮:56 设计像素)
    ============================================================ */
 .cockpit-shell__topbar {
-    position: relative;
     display: grid;
-    grid-template-columns: minmax(280px, 1fr) auto minmax(280px, 1fr);
+    grid-template-columns: minmax(240px, 1fr) auto minmax(240px, 1fr);
     align-items: center;
-    gap: calc(16 * var(--u));
-    height: calc(74 * var(--u));
-    padding: 0 calc(4 * var(--u));
+    gap: calc(12 * var(--u));
+    height: calc(56 * var(--u));
 }
 
 .cockpit-shell__topbar-bg {
@@ -294,7 +293,7 @@ onBeforeUnmount(() => {
 .cockpit-shell__wing {
     position: absolute;
     top: 50%;
-    width: calc(210 * var(--u));
+    width: calc(170 * var(--u));
     height: 2px;
     pointer-events: none;
     background: linear-gradient(90deg, transparent, rgba(79, 216, 255, 0.75));
@@ -313,28 +312,27 @@ onBeforeUnmount(() => {
     box-shadow: 0 0 12px var(--cyan);
 }
 
-.cockpit-shell__wing.is-left { left: calc(50% - 460px); }
+.cockpit-shell__wing.is-left { left: calc(50% - 400px); }
 
 .cockpit-shell__wing.is-right {
-    right: calc(50% - 460px);
+    right: calc(50% - 400px);
     transform: scaleX(-1);
 }
 
 .cockpit-shell__brand {
-    position: relative;
     display: flex;
     align-items: center;
-    gap: calc(12 * var(--u));
+    gap: calc(10 * var(--u));
     min-width: 0;
-    padding-left: calc(8 * var(--u));
+    padding-left: calc(6 * var(--u));
 }
 
 .cockpit-shell__mark {
     display: grid;
     place-items: center;
     flex: none;
-    width: calc(42 * var(--u));
-    height: calc(42 * var(--u));
+    width: calc(32 * var(--u));
+    height: calc(32 * var(--u));
     filter: drop-shadow(0 0 10px rgba(79, 216, 255, 0.6));
 }
 
@@ -349,9 +347,9 @@ onBeforeUnmount(() => {
 
 .cockpit-shell__brand-text strong {
     display: block;
-    font-size: calc(25 * var(--u));
+    font-size: calc(21 * var(--u));
     font-weight: 700;
-    letter-spacing: 0.13em;
+    letter-spacing: 0.12em;
     white-space: nowrap;
     background: linear-gradient(180deg, #ffffff 8%, #bfeaff 52%, #4fb6e8 100%);
     -webkit-background-clip: text;
@@ -362,15 +360,14 @@ onBeforeUnmount(() => {
 
 .cockpit-shell__brand-text em {
     display: block;
-    margin-top: 2px;
+    margin-top: 1px;
     color: var(--muted);
-    font-size: calc(10.5 * var(--u));
+    font-size: calc(9.5 * var(--u));
     font-style: normal;
-    letter-spacing: 0.24em;
+    letter-spacing: 0.2em;
 }
 
 .cockpit-shell__modes {
-    position: relative;
     display: flex;
     gap: calc(4 * var(--u));
     justify-content: center;
@@ -378,16 +375,16 @@ onBeforeUnmount(() => {
 
 .cockpit-shell__mode {
     position: relative;
-    min-height: calc(38 * var(--u));
-    padding: calc(9 * var(--u)) calc(26 * var(--u));
+    min-height: calc(30 * var(--u));
+    padding: calc(6 * var(--u)) calc(20 * var(--u));
     border: 0;
     color: #a9cde4;
     font: inherit;
-    font-size: calc(15 * var(--u));
-    letter-spacing: 0.12em;
+    font-size: calc(13.5 * var(--u));
+    letter-spacing: 0.1em;
     text-shadow: 0 1px 3px rgba(0, 12, 24, 0.9);
     background: linear-gradient(180deg, rgba(20, 66, 108, 0.6), rgba(8, 30, 54, 0.2));
-    clip-path: polygon(11px 0, calc(100% - 11px) 0, 100% 100%, 0 100%);
+    clip-path: polygon(9px 0, calc(100% - 9px) 0, 100% 100%, 0 100%);
     cursor: pointer;
     transition: color 0.18s, background 0.18s, filter 0.18s;
 }
@@ -415,27 +412,26 @@ onBeforeUnmount(() => {
     position: absolute;
     left: 26%;
     right: 26%;
-    bottom: 3px;
+    bottom: 2px;
     height: 2px;
     background: #cdf3ff;
     box-shadow: 0 0 10px #7fe0ff, 0 0 20px #4fd8ff;
 }
 
 .cockpit-shell__top-meta {
-    position: relative;
     display: flex;
     align-items: center;
     justify-content: flex-end;
-    gap: calc(14 * var(--u));
-    padding-right: calc(10 * var(--u));
+    gap: calc(10 * var(--u));
+    padding-right: calc(8 * var(--u));
     color: var(--text-2);
-    font-size: calc(12 * var(--u));
+    font-size: calc(11 * var(--u));
     white-space: nowrap;
 }
 
 .cockpit-shell__clock {
     font-family: var(--num);
-    font-size: calc(15 * var(--u));
+    font-size: calc(13 * var(--u));
     letter-spacing: 0.06em;
     color: #d7f1ff;
     text-shadow: 0 0 12px rgba(79, 216, 255, 0.55);
@@ -444,20 +440,20 @@ onBeforeUnmount(() => {
 .cockpit-shell__meta {
     margin: 0;
     color: var(--muted);
-    font-size: calc(11.5 * var(--u));
+    font-size: calc(10.5 * var(--u));
     letter-spacing: 0.06em;
 }
 
 /* ============================================================
-   全局筛选条
+   全局筛选条(压矮:38 设计像素)
    ============================================================ */
 .cockpit-shell__scope {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    gap: calc(12 * var(--u));
+    gap: calc(10 * var(--u));
     overflow: hidden;
-    padding: 0 calc(12 * var(--u));
+    padding: 0 calc(10 * var(--u));
     border: 1px solid rgba(58, 170, 226, 0.24);
     background: linear-gradient(180deg, rgba(7, 30, 54, 0.72), rgba(4, 15, 28, 0.88));
     box-shadow: inset 0 1px 0 rgba(120, 220, 255, 0.1), var(--shadow);
@@ -467,14 +463,14 @@ onBeforeUnmount(() => {
 .cockpit-shell__scope-right {
     display: flex;
     align-items: center;
-    gap: calc(8 * var(--u));
+    gap: calc(6 * var(--u));
     white-space: nowrap;
 }
 
 .cockpit-shell__scope-label {
     color: var(--muted);
-    font-size: calc(11 * var(--u));
-    letter-spacing: 0.1em;
+    font-size: calc(10 * var(--u));
+    letter-spacing: 0.08em;
 }
 
 .cockpit-shell__chips {
@@ -484,8 +480,8 @@ onBeforeUnmount(() => {
 
 .cockpit-shell__divider {
     width: 1px;
-    height: calc(18 * var(--u));
-    margin: 0 calc(4 * var(--u));
+    height: calc(16 * var(--u));
+    margin: 0 calc(3 * var(--u));
     background: linear-gradient(180deg, transparent, rgba(79, 216, 255, 0.5), transparent);
 }
 
@@ -493,14 +489,15 @@ onBeforeUnmount(() => {
 .cockpit-shell__chip {
     display: inline-flex;
     align-items: center;
-    padding: calc(3 * var(--u)) calc(11 * var(--u));
+    padding: calc(2 * var(--u)) calc(9 * var(--u));
     border: 1px solid rgba(58, 170, 226, 0.32);
     background: rgba(8, 34, 60, 0.6);
     color: #9dc2d9;
     font: inherit;
-    font-size: calc(11.5 * var(--u));
-    letter-spacing: 0.05em;
-    clip-path: polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);
+    font-size: calc(10.5 * var(--u));
+    letter-spacing: 0.04em;
+    white-space: nowrap;
+    clip-path: polygon(4px 0, 100% 0, 100% calc(100% - 4px), calc(100% - 4px) 100%, 0 100%, 0 4px);
     cursor: pointer;
     transition: all 0.16s;
 }
@@ -526,32 +523,45 @@ onBeforeUnmount(() => {
 
 .cockpit-shell__scope-note {
     color: var(--muted);
-    font-size: calc(11 * var(--u));
+    font-size: calc(10 * var(--u));
     letter-spacing: 0.04em;
 }
 
 /* ============================================================
-   主体:左右侧栏 + 中央一张图(铺满,地图吃掉剩余宽高)
+   主体:一张图铺满背景,左右两栏浮在图上
    ============================================================ */
 .cockpit-shell__main {
-    display: grid;
-    grid-template-columns: clamp(300px, calc(322 * var(--u)), 560px) minmax(0, 1fr) clamp(300px, calc(322 * var(--u)), 560px);
-    gap: calc(8 * var(--u));
+    position: relative;
     min-height: 0;
 }
 
+.cockpit-shell__map {
+    position: absolute;
+    inset: 0;
+    z-index: 0;
+}
+
 .cockpit-shell__column {
-    display: grid;
-    grid-template-rows: 1fr 1fr;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    z-index: 2;
+    display: flex;
+    flex-direction: column;
     gap: calc(8 * var(--u));
+    width: clamp(280px, calc(300 * var(--u)), 460px);
     min-height: 0;
+    pointer-events: none;
 }
 
-.cockpit-shell__map {
-    min-height: 0;
-    height: 100%;
+.cockpit-shell__column > * {
+    pointer-events: auto;
 }
 
+.cockpit-shell__column.is-left { left: 0; }
+
+.cockpit-shell__column.is-right { right: 0; }
+
 .cockpit-shell__placeholder {
     margin: 0;
     color: var(--muted);

+ 6 - 0
src/modules/cockpit/components/CockpitPanel.vue

@@ -52,6 +52,8 @@ const emit = defineEmits(['toggle'])
 
     position: relative;
     display: flex;
+    /* 展开时参与分配所在栏的高度:两块等分,收起的那块缩回标题栏高度、其余吃掉全部空间。 */
+    flex: 1 1 0;
     flex-direction: column;
     min-width: 0;
     min-height: 0;
@@ -65,6 +67,10 @@ const emit = defineEmits(['toggle'])
     box-shadow: inset 0 1px 0 rgba(130, 225, 255, 0.14), var(--shadow, 0 18px 44px rgba(0, 0, 0, 0.55));
 }
 
+.cockpit-panel.is-collapsed {
+    flex: 0 0 auto;
+}
+
 .cockpit-panel::before {
     content: '';
     position: absolute;

+ 88 - 90
src/modules/cockpit/components/MapPlaceholder.vue

@@ -1,47 +1,49 @@
 <!--
-  中央一张图占位:全息地图壳(底纹 / 网格 / 扫描线 / 图层 / 分级图例 / 状态行)。
-  视觉参考《驾驶舱原型图-美化版》的 .map 区块;尺寸随视口铺满并随 --u 缩放。
-  数据层与相机控制由后续票接入(底图见票 21/22,相机见票 03)。
+  中央一张图:铺满整个主体背景。
+  当前用临时影像底图(public/cockpit/aerial.webp)+ 全息浮层,先看整体效果;
+  数据层与相机控制由后续票接入(底图/瓦片见票 21/22,相机见票 03,票 02 接入 Cesium)。
 
-  常驻:一级模式切换与面板收展都不得重建本组件(实现票 02 会在此接入 Cesium MapCanvas)。
+  常驻:一级模式切换与面板收展都不得重建本组件。
+
+  浮层文字统一放在**两栏之间的中间带**并居中,避免被左右浮栏压住。
 -->
 <template>
     <div class="map-placeholder">
+        <!-- 临时影像底图(真实 GIS 底图接入后由票 21/22 替换) -->
+        <div class="map-holo__photo" :style="{ backgroundImage: `url(${photoUrl})` }"></div>
+
         <div class="map-holo" aria-hidden="true">
-            <div class="map-holo__base"></div>
             <div class="map-holo__grid"></div>
             <div class="map-holo__scan"></div>
             <div class="map-holo__vignette"></div>
         </div>
 
-        <div class="map-holo__title">
+        <div class="map-holo__top">
             <b>中央“一张图”</b>
-            <span>{{ modeLabel }} · 只读展示 · 管网与设备态势待接入</span>
-        </div>
-
-        <div class="map-holo__layers">
-            <span>管线图层</span>
-            <span>监测点</span>
-            <span>事件定位</span>
-            <span>应急资源</span>
-        </div>
-
-        <!-- 事件四级配色(ADR 0008):1 级红 / 2 级黄 / 3 级蓝 / 4 级绿 -->
-        <div class="map-holo__legend">
-            <span><i class="map-holo__chip c1"></i>1级</span>
-            <span><i class="map-holo__chip c2"></i>2级</span>
-            <span><i class="map-holo__chip c3"></i>3级</span>
-            <span><i class="map-holo__chip c4"></i>4级</span>
+            <div class="map-holo__layers">
+                <span>管线图层</span>
+                <span>监测点</span>
+                <span>事件定位</span>
+                <span>应急资源</span>
+            </div>
         </div>
 
-        <div class="map-holo__stage">
+        <div class="map-holo__bottom">
+            <!-- 事件四级配色(ADR 0008):1 级红 / 2 级黄 / 3 级蓝 / 4 级绿 -->
+            <div class="map-holo__legend">
+                <span><i class="map-holo__chip c1"></i>1级</span>
+                <span><i class="map-holo__chip c2"></i>2级</span>
+                <span><i class="map-holo__chip c3"></i>3级</span>
+                <span><i class="map-holo__chip c4"></i>4级</span>
+            </div>
             <p class="map-placeholder__title">中央一张图占位</p>
-            <p class="map-holo__hint">实现票 02 接入 Cesium 常驻 MapCanvas</p>
-        </div>
-
-        <div class="map-holo__status">
-            <i></i>
-            <span>底图与数据未接入 · 相机控制见票 03 · 图层见票 21/22</span>
+            <p class="map-holo__hint">
+                临时底图 aerial.webp · 票 02 接入 Cesium 常驻 MapCanvas · {{ modeLabel }}
+            </p>
+            <div class="map-holo__status">
+                <i></i>
+                <span>影像为临时占位 · 相机控制见票 03 · 图层见票 21/22</span>
+            </div>
         </div>
     </div>
 </template>
@@ -50,6 +52,10 @@
 defineProps({
     modeLabel: { type: String, default: '' },
 })
+
+// 走 Vite 的 BASE_URL,保证 qiankun / 相对 base 部署下也能取到 public 资源。
+const base = import.meta.env.BASE_URL || '/'
+const photoUrl = `${base}cockpit/aerial.webp`
 </script>
 
 <style scoped>
@@ -68,11 +74,21 @@ defineProps({
     min-height: 0;
     overflow: hidden;
     border: 1px solid var(--panel-line, rgba(58, 170, 226, 0.3));
-    background: linear-gradient(180deg, rgba(4, 20, 38, 0.9), rgba(2, 10, 20, 0.96));
+    background: #04121f;
     box-shadow: inset 0 1px 0 rgba(130, 225, 255, 0.12), var(--shadow, 0 18px 44px rgba(0, 0, 0, 0.55));
 }
 
-/* ---------- 全息底:径向辉光 + 网格 + 扫描线 ---------- */
+/* ---------- 临时影像底图:铺满 ---------- */
+.map-holo__photo {
+    position: absolute;
+    inset: 0;
+    background-position: center center;
+    background-size: cover;
+    background-repeat: no-repeat;
+    filter: saturate(0.85) brightness(0.78);
+}
+
+/* ---------- 全息浮层:网格 + 扫描线 + 暗角 ---------- */
 .map-holo,
 .map-holo > div {
     position: absolute;
@@ -80,27 +96,19 @@ defineProps({
     pointer-events: none;
 }
 
-.map-holo__base {
-    background:
-        radial-gradient(760px 420px at 50% 42%, rgba(20, 92, 150, 0.42), transparent 72%),
-        radial-gradient(900px 520px at 50% 108%, rgba(16, 74, 126, 0.3), transparent 70%),
-        linear-gradient(180deg, #03101f 0%, #04182c 48%, #02080f 100%);
-}
-
 .map-holo__grid {
-    opacity: 0.55;
+    opacity: 0.4;
     background-image:
-        linear-gradient(rgba(70, 170, 230, 0.07) 1px, transparent 1px),
-        linear-gradient(90deg, rgba(70, 170, 230, 0.07) 1px, transparent 1px);
+        linear-gradient(rgba(70, 170, 230, 0.09) 1px, transparent 1px),
+        linear-gradient(90deg, rgba(70, 170, 230, 0.09) 1px, transparent 1px);
     background-size: 58px 58px, 58px 58px;
-    mask-image: radial-gradient(120% 92% at 50% 40%, #000 34%, transparent 90%);
 }
 
 .map-holo__scan {
     height: 22%;
     top: -22%;
     inset: auto 0 auto 0;
-    background: linear-gradient(180deg, transparent 0%, rgba(79, 216, 255, 0.16) 50%, transparent 100%);
+    background: linear-gradient(180deg, transparent 0%, rgba(79, 216, 255, 0.14) 50%, transparent 100%);
     animation: map-scan 7s linear infinite;
 }
 
@@ -112,62 +120,65 @@ defineProps({
 }
 
 .map-holo__vignette {
-    background: radial-gradient(120% 96% at 50% 46%, transparent 52%, rgba(0, 6, 14, 0.72) 100%);
+    background: linear-gradient(180deg, rgba(2, 8, 16, 0.55) 0%, transparent 26%, transparent 68%, rgba(2, 8, 16, 0.6) 100%);
 }
 
-/* ---------- 标题 / 图层 / 图例 / 状态 ---------- */
-.map-holo__title {
+/* ---------- 中间带浮层(左右浮栏之间居中) ---------- */
+.map-holo__top {
     position: absolute;
     z-index: 2;
-    top: calc(14 * var(--u, 1px));
-    left: calc(16 * var(--u, 1px));
+    top: calc(12 * var(--u, 1px));
+    left: 50%;
+    transform: translateX(-50%);
+    text-align: center;
+    pointer-events: none;
 }
 
-.map-holo__title b {
+.map-holo__top b {
     display: block;
     color: #eaf9ff;
     font-size: calc(17 * var(--u, 1px));
     font-weight: 700;
-    letter-spacing: 0.12em;
-    text-shadow: 0 0 14px rgba(79, 216, 255, 0.7);
-}
-
-.map-holo__title span {
-    display: block;
-    margin-top: 3px;
-    color: var(--muted, #6d92ae);
-    font-size: calc(11.5 * var(--u, 1px));
-    letter-spacing: 0.04em;
+    letter-spacing: 0.14em;
+    text-shadow: 0 0 16px rgba(79, 216, 255, 0.85), 0 2px 8px rgba(0, 0, 0, 0.9);
 }
 
 .map-holo__layers {
-    position: absolute;
-    z-index: 2;
-    top: calc(62 * var(--u, 1px));
-    left: calc(16 * var(--u, 1px));
     display: flex;
     gap: calc(5 * var(--u, 1px));
+    justify-content: center;
+    margin-top: calc(6 * var(--u, 1px));
     flex-wrap: wrap;
 }
 
 .map-holo__layers span {
-    padding: calc(3 * var(--u, 1px)) calc(8 * var(--u, 1px));
-    border: 1px solid rgba(58, 170, 226, 0.34);
-    background: rgba(6, 30, 54, 0.7);
+    padding: calc(2 * var(--u, 1px)) calc(8 * var(--u, 1px));
+    border: 1px solid rgba(58, 170, 226, 0.42);
+    background: rgba(4, 20, 38, 0.66);
     color: #bad4e0;
     font-size: calc(10 * var(--u, 1px));
     letter-spacing: 0.04em;
 }
 
-.map-holo__legend {
+.map-holo__bottom {
     position: absolute;
     z-index: 2;
-    top: calc(16 * var(--u, 1px));
-    right: calc(16 * var(--u, 1px));
+    bottom: calc(10 * var(--u, 1px));
+    left: 50%;
+    transform: translateX(-50%);
+    display: grid;
+    gap: calc(4 * var(--u, 1px));
+    justify-items: center;
+    text-align: center;
+    pointer-events: none;
+}
+
+.map-holo__legend {
     display: flex;
     gap: calc(10 * var(--u, 1px));
-    color: var(--muted, #6d92ae);
+    color: #b7cee0;
     font-size: calc(11 * var(--u, 1px));
+    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
 }
 
 .map-holo__legend span {
@@ -187,42 +198,29 @@ defineProps({
 .map-holo__chip.c3 { background: var(--lv3, #4fd8ff); box-shadow: 0 0 8px var(--lv3, #4fd8ff); }
 .map-holo__chip.c4 { background: var(--lv4, #35e39a); box-shadow: 0 0 8px var(--lv4, #35e39a); }
 
-/* 中央占位说明 */
-.map-holo__stage {
-    position: absolute;
-    z-index: 2;
-    inset: 0;
-    display: grid;
-    place-content: center;
-    gap: 6px;
-    text-align: center;
-}
-
 .map-placeholder__title {
     margin: 0;
     color: #eaf9ff;
-    font-size: calc(16 * var(--u, 1px));
+    font-size: calc(13 * var(--u, 1px));
     font-weight: 700;
-    letter-spacing: 0.14em;
-    text-shadow: 0 0 16px rgba(79, 216, 255, 0.6);
+    letter-spacing: 0.16em;
+    text-shadow: 0 0 14px rgba(79, 216, 255, 0.7), 0 2px 8px rgba(0, 0, 0, 0.9);
 }
 
 .map-holo__hint {
     margin: 0;
     color: var(--muted, #6d92ae);
-    font-size: calc(11.5 * var(--u, 1px));
+    font-size: calc(10.5 * var(--u, 1px));
+    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
 }
 
 .map-holo__status {
-    position: absolute;
-    z-index: 2;
-    left: calc(16 * var(--u, 1px));
-    bottom: calc(12 * var(--u, 1px));
     display: flex;
     align-items: center;
     gap: 7px;
     color: var(--muted, #6d92ae);
-    font-size: calc(11 * var(--u, 1px));
+    font-size: calc(10.5 * var(--u, 1px));
+    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
 }
 
 .map-holo__status i {