CockpitPanel.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <!--
  2. 二级面板外框:切角科技面板 + 可点击收放的标题栏。
  3. 视觉参考《驾驶舱原型图-美化版》的 .panel / .panel-head;
  4. 尺寸随外壳的 --u 设计像素缩放(大屏不留空,小屏不缩小)。
  5. 标题栏是 button,其文本即按钮的可访问名(标记符号 aria-hidden,不参与命名)。
  6. -->
  7. <template>
  8. <section
  9. class="cockpit-panel"
  10. :class="{ 'is-collapsed': !expanded }"
  11. :style="{ '--panel-grow': effectiveGrow, '--panel-basis': basis || '0%' }"
  12. :data-panel-id="panelId"
  13. :data-panel-grow="Math.round(effectiveGrow * 10) / 10"
  14. :data-panel-measured="measuredGrow ? 'yes' : 'no'"
  15. >
  16. <div class="cockpit-panel__heading">
  17. <button
  18. type="button"
  19. class="cockpit-panel__head"
  20. :aria-expanded="expanded"
  21. @click="emit('toggle')"
  22. >
  23. <span class="cockpit-panel__title">
  24. <!-- 批次 1:面板图标按 panelId 取自蓝湖正式切图;纯装饰,aria-hidden 不参与命名。 -->
  25. <CockpitIcon v-if="iconName" :name="iconName" :size="panelIconSize" class="cockpit-panel__icon" />
  26. {{ title }}
  27. </span>
  28. <small class="cockpit-panel__meta" :class="{ 'is-end': metaAlign === 'end' }">{{ meta }}</small>
  29. <span class="cockpit-panel__mark" aria-hidden="true">{{ expanded ? '−' : '+' }}</span>
  30. </button>
  31. <SectionStateTag v-if="section" class="cockpit-panel__state" :name="panelId" :label="title" :section="section" />
  32. </div>
  33. <!-- v-show 而非 v-if:收起再展开不应重建面板内容或触发重新取数。 -->
  34. <div v-show="expanded" class="cockpit-panel__body">
  35. <!--
  36. 内容包裹层(2026-10-23 维护者第 1 条:面板高度自适应作为**基本规则**):
  37. 这一层的高度=内容自然高度(不被拉伸),量它就知道"这块面板要多少空间",
  38. 再按它给 flex 权重。量 body 本身不行 —— body 会被 flex 拉伸,
  39. scrollHeight 会跟着分配高度变,权重会自我强化。
  40. -->
  41. <div ref="contentRef" class="cockpit-panel__content">
  42. <slot />
  43. </div>
  44. </div>
  45. </section>
  46. </template>
  47. <script setup>
  48. import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
  49. import CockpitIcon from './CockpitIcon.vue'
  50. import SectionStateTag from './SectionStateTag.vue'
  51. import { panelIcon } from '../domain/panelCatalog.js'
  52. const props = defineProps({
  53. title: { type: String, required: true },
  54. meta: { type: String, default: '' },
  55. /**
  56. * 标题右侧小字的对齐:`''`(缺省,左对齐,跟随标题)/ `'end'`(贴右)。
  57. *
  58. * 为什么是**局部 prop** 而不是改全局:overview 四块的右上角小字是规格 §6.1 的
  59. * 「右上角」要求(设备规模·类型·片区分布 等),而其它模式的面板 meta 一直是
  60. * 「标题右侧的数据提要」,两条语义不同 —— 全局改会把 12 块面板一起挪位。
  61. */
  62. metaAlign: { type: String, default: '' },
  63. section: { type: Object, default: null },
  64. expanded: { type: Boolean, default: true },
  65. /**
  66. * 该面板在所在栏里的**高度权重**(2026-10-23 维护者:面板高度按内容自适应)。
  67. *
  68. * 取值由外壳按「这块面板大概有多少内容」给:默认 1(两块等分),内容密的给 >1、
  69. * 内容简的给 <1。它是相对权重不是一个尺寸 —— flex-grow 本来就是「剩下多少按权重分」,
  70. * 所以换屏幕高度不用重算。缺省 1 表示"不关心,按等分"。
  71. */
  72. grow: { type: Number, default: 0 },
  73. /**
  74. * 这块面板是否**以表格/清单为主体**(外壳按「有没有 list 块」算好传进来)。
  75. *
  76. * 为什么要传:自适应权重按内容自然高度量,表格行数上百时高度虚高,
  77. * 会把同列另一块挤成一条缝。渲染层仍不认识面板身份 —— 它只认这个布尔标志。
  78. */
  79. tableHeavy: { type: Boolean, default: false },
  80. /**
  81. * 这块面板在**所在栏**里的高度份额(如 `"38.50%"`),由外壳按同栏各块权重归一算出。
  82. *
  83. * 为什么由外壳给:份额是同栏几块之间的相对分配,单块看不到兄弟、纯 CSS 也算不出权重和。
  84. * 它落到 `flex-basis` 上 —— 下限用 basis 表达而不是 `min-height`,
  85. * 因为百分比 `min-height` 会把整栏先顶满、让权重分配几乎失效(见样式里的说明)。
  86. */
  87. basis: { type: String, default: '' },
  88. /**
  89. * 面板的稳定英文键(`domain/panelCatalog.js` 的 panelId)。
  90. * 走查与断言读它来认面板 —— 不能从面板体内的文案反推:
  91. * 面板体可能是占位、也可能是真实内容,**断言不该依赖某一种**。
  92. */
  93. panelId: { type: String, default: '' },
  94. })
  95. const emit = defineEmits(['toggle'])
  96. /**
  97. * 面板高度自适应(维护者 2026-10-23 第 1 条:**基本规则**,按内容设权重)。
  98. *
  99. * 做法:量内容包裹层的自然高度,把它作为 flex-grow 的权重 —— flex 分配剩余空间时
  100. * 本来就是「按权重分」,于是"内容多的面板拿到更多高度"是**测量结果**而不是手填常量。
  101. *
  102. * 为什么不量 body:body 被 flex 拉伸,scrollHeight 会跟着分配高度走,
  103. * 权重自我强化(分得多 → 量得大 → 分得更多)。内容层是 auto 高度,量它才稳。
  104. *
  105. * 兜底:还没量到(首帧/SSR)时用外壳给的 grow;两者都没有就回 1(等分)。
  106. * 量到 0(空面板)时也用 1,避免除零式塌陷。
  107. */
  108. const contentRef = ref(null)
  109. const measuredGrow = ref(0)
  110. let resizeObserver = null
  111. /**
  112. * 表格类面板的权重打折(维护者 2026-10-23 反馈第 3 条)。
  113. *
  114. * 为什么需要:自适应权重按内容自然高度量,而**表格/清单**(预警处置的「当前预警」「历史分析」)
  115. * 每行高度固定、行数却可能上百 —— 量出来的高度能是旁边面板的十几倍,于是同列的另一块
  116. * 被挤到只剩标题栏。维护者原话:「带有表格的面板权重会变得非常高…需要保留一个下限比例,
  117. * 至少能够显示面板的大部分区域」。
  118. *
  119. * 两件事同时做:
  120. * 1. **打折**:表格面板的权重乘 `TABLE_GROW_DISCOUNT`(内容多不等于该占那么多版面);
  121. * 2. **下限**:同一栏里,权重不得低于该栏最大权重的 `MIN_GROW_RATIO` ——
  122. * 保证被挤压的那块至少还剩这一比例的高度,看得见大部分内容。
  123. *
  124. * 判据是**有没有表格块**(`block.kind === 'list'`),不是面板 id —
  125. * 渲染层不认识面板身份这条纪律不变;面板身份由外壳算好传进来(`tableHeavy`)。
  126. */
  127. const TABLE_GROW_DISCOUNT = 0.35
  128. /** 同栏内任一面板的权重不得低于最大权重的这个比例(即至少分到约 1/3 的高度)。 */
  129. const MIN_GROW_RATIO = 0.34
  130. function measureContent() {
  131. const element = contentRef.value
  132. if (!element) return
  133. // scrollHeight 取内容自然高度(本层不设 overflow,等价于 offsetHeight 的稳定版)。
  134. const height = element.scrollHeight || element.offsetHeight || 0
  135. if (height <= 0) {
  136. measuredGrow.value = 0
  137. return
  138. }
  139. // 表格面板打折后再交给外壳做同栏归一(下限在那里统一施加,见 `normalizeGrow`)。
  140. measuredGrow.value = props.tableHeavy ? height * TABLE_GROW_DISCOUNT : height
  141. }
  142. onMounted(() => {
  143. measureContent()
  144. // 内容变化(取数到达、筛选变化、宽窄重排、展开/收起)都要重新量 ——
  145. // 监听**内容层自己**的尺寸:收起时 v-show 置 display:none,量到 0 就回落
  146. // `props.grow`(此时权重本就不参与分配,因为 is-collapsed 已 flex:0 0 auto);
  147. // 再展开时 display 变化会触发 ResizeObserver,重新拿到真实高度。
  148. if (typeof ResizeObserver === 'function' && contentRef.value) {
  149. resizeObserver = new ResizeObserver(measureContent)
  150. resizeObserver.observe(contentRef.value)
  151. }
  152. })
  153. onBeforeUnmount(() => {
  154. if (resizeObserver) resizeObserver.disconnect()
  155. resizeObserver = null
  156. })
  157. /** 实际生效的权重:测量值优先,其次外壳声明,最后 1。 */
  158. const effectiveGrow = computed(() => measuredGrow.value || props.grow || 1)
  159. /** 面板图标语义名;无对应图标时为空串 → 不渲染图标,不抛错。 */
  160. const iconName = computed(() => panelIcon(props.panelId))
  161. /** 与 15.5u 的标题字号配套的图标边长(走查反馈偏小,由 14 上调)。 */
  162. const panelIconSize = 'calc(17 * var(--u, 1px))'
  163. </script>
  164. <style scoped>
  165. /* 组件内自带的盒模型重置(不依赖全局 `*` 规则)。 */
  166. .cockpit-panel,
  167. .cockpit-panel *,
  168. .cockpit-panel *::before,
  169. .cockpit-panel *::after {
  170. box-sizing: border-box;
  171. }
  172. /* 切角描边:四角各一段青色 L 形,与原型图同一套 SVG(内联为自定义属性)。
  173. 批次 5(票 18)收口:母版只承载几何(stroke 白色,mask 只取 alpha),
  174. 颜色统一经 mask + background-color 取 --panel-line-hi —— 此前四个 data-URI
  175. 各自内联主蓝色值属主题色散落(规格 03 判据:几何可代码化,色值必须走 token)。 */
  176. .cockpit-panel {
  177. --corner-tl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M0.8 16 V3.3 A2.5 2.5 0 0 1 3.3 0.8 H16' fill='none' stroke='%23fff' stroke-width='1.7'/%3E%3C/svg%3E");
  178. --corner-tr: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M0 0.8 H12.7 A2.5 2.5 0 0 1 15.2 3.3 V16' fill='none' stroke='%23fff' stroke-width='1.7'/%3E%3C/svg%3E");
  179. --corner-bl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M0.8 0 V12.7 A2.5 2.5 0 0 0 3.3 15.2 H16' fill='none' stroke='%23fff' stroke-width='1.7'/%3E%3C/svg%3E");
  180. --corner-br: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M0 15.2 H12.7 A2.5 2.5 0 0 0 15.2 12.7 V0' fill='none' stroke='%23fff' stroke-width='1.7'/%3E%3C/svg%3E");
  181. position: relative;
  182. display: flex;
  183. /* 展开时参与分配所在栏的高度。
  184. 2026-10-23(维护者):**不再硬等分**,改为按内容高度加权 —— `--panel-grow` 由外壳
  185. 按该面板内容的估算高度给出默认比例(内容多的拿更多),没给时退回 1:1。
  186. 至少保留 `min-height`,否则内容少的那块会被压到只剩标题栏。 */
  187. /* 高度分配(维护者 2026-10-23 反馈第 3 条:表格面板不能把同栏挤没)。
  188. **下限必须用 flex-basis 表达,不能用 min-height**:
  189. 百分比 `min-height` 在 flex 列里相对父容器高度解析,两块各占 34%/42% 的 min
  190. 会先把整栏顶满(合计 76%),剩下 24% 才按 grow 分 —— 于是"打折"几乎失效,
  191. 而内容少的那块被顶到 34% 后又被撑出大片空白。
  192. 改成 basis = 权重份额:`calc(权重 ÷ 权重和 × 100%)` 在 flex 里就是"先按份额预分",
  193. 剩余空间再按 grow 微调,打折与下限同时成立。
  194. 纯 CSS 算不出同栏权重和,所以外壳把份额算好写成 `--panel-basis`(见 columnBasis)。 */
  195. flex: var(--panel-grow, 1) 1 var(--panel-basis, 0%);
  196. /* 硬护栏:任何情况下至少露出标题栏 + 一段内容(防止极端内容把另一块压成一条线)。 */
  197. min-height: calc(72 * var(--u, 1px));
  198. flex-direction: column;
  199. min-width: 0;
  200. min-height: 0;
  201. border: 1px solid var(--panel-line, rgba(1, 171, 234, 0.35));
  202. background: linear-gradient(
  203. 180deg,
  204. var(--panel-top, rgba(2, 9, 27, 0.28)) 0%,
  205. var(--panel-mid, rgba(0, 53, 156, 0.2)) 26%,
  206. var(--panel-bot, rgba(0, 15, 52, 0.38)) 100%
  207. );
  208. /* 2026-10-22 维护者要求磨砂稍微增强:12px → 16px(面板调透后保住文字可读性)。 */
  209. backdrop-filter: blur(16px) saturate(115%);
  210. -webkit-backdrop-filter: blur(16px) saturate(115%);
  211. box-shadow: inset 0 1px 0 rgba(1, 171, 234, 0.14), var(--shadow, 0 18px 44px rgba(0, 0, 0, 0.55));
  212. }
  213. .cockpit-panel.is-collapsed {
  214. flex: 0 0 auto;
  215. /* 收起态只占标题栏高度,下限不适用。 */
  216. min-height: 0;
  217. flex-basis: auto;
  218. }
  219. /* 卡片密度(2026-10-23):面板内边距与块间距压矮一档,好让四块内容在一屏内看完、
  220. 不依赖滚动条。压缩的是**呼吸感**,不是字号 —— 读数与标签一档不降。 */
  221. .cockpit-panel__body {
  222. --panel-density-gap: calc(2 * var(--u, 1px));
  223. }
  224. .cockpit-panel::before {
  225. content: '';
  226. position: absolute;
  227. inset: -1px;
  228. z-index: 3;
  229. pointer-events: none;
  230. /* 批次 5:四角几何白母版经 mask 染 --panel-line-hi(色值单一来源);几何仍代码化。
  231. 用 mask 简写(不是 mask-image):position/size/repeat 层语法只属于简写,
  232. 写进 mask-image 会被浏览器整条丢弃(四角消失)。同 CockpitIcon 的用法。 */
  233. background-color: var(--panel-line-hi, #01abea);
  234. -webkit-mask:
  235. var(--corner-tl) left top / 15px 15px no-repeat,
  236. var(--corner-tr) right top / 15px 15px no-repeat,
  237. var(--corner-bl) left bottom / 15px 15px no-repeat,
  238. var(--corner-br) right bottom / 15px 15px no-repeat;
  239. mask:
  240. var(--corner-tl) left top / 15px 15px no-repeat,
  241. var(--corner-tr) right top / 15px 15px no-repeat,
  242. var(--corner-bl) left bottom / 15px 15px no-repeat,
  243. var(--corner-br) right bottom / 15px 15px no-repeat;
  244. filter: drop-shadow(0 0 4px rgba(1, 171, 234, 0.5));
  245. }
  246. .cockpit-panel__head {
  247. position: relative;
  248. z-index: 2;
  249. display: flex;
  250. align-items: center;
  251. gap: calc(10 * var(--u, 1px));
  252. width: 100%;
  253. min-height: calc(40 * var(--u, 1px));
  254. padding: 0 calc(12 * var(--u, 1px)) 0 calc(8 * var(--u, 1px));
  255. border: 0;
  256. /* 标题栏**自己不画背景、也不画线**(票 11 二轮真正修好这条的改动)。
  257. 根因不是"两条线",而是**背景带在 button 右端就结束了**:状态光点是 button 的
  258. 兄弟节点,head 的渐变背景(… 42%, transparent 78%)到 78% 就透明,
  259. 于是从状态光点往左出现一条明显的"带子断口" —— 肉眼看就是标题与状态光点被分割。
  260. 所以背景与分隔线都搬到 .cockpit-panel__heading(横贯 head + state 整条)。 */
  261. background: transparent;
  262. font: inherit;
  263. text-align: left;
  264. cursor: pointer;
  265. }
  266. /* hover 反馈画在 heading 上(不是 head):head 只覆盖按钮那一段,
  267. 在 head 上画高亮会在状态光点左侧又切一刀 —— 与要修的那个 bug 是同一个形状。 */
  268. .cockpit-panel__head:focus-visible {
  269. outline: 1px solid var(--cyan, #01abea);
  270. outline-offset: -2px;
  271. }
  272. .cockpit-panel.is-collapsed .cockpit-panel__heading::after {
  273. display: none;
  274. }
  275. /* 标题:左侧一段青色竖条 + 辉光,与原型 .panel-head h2 一致 */
  276. .cockpit-panel__title {
  277. position: relative;
  278. flex: none;
  279. /* 批次 1:标题改为 inline-flex 以容纳面板图标(竖条仍是 ::before 绝对定位,不受影响)。 */
  280. display: inline-flex;
  281. align-items: center;
  282. gap: calc(5 * var(--u, 1px));
  283. padding-left: calc(10 * var(--u, 1px));
  284. color: var(--text);
  285. /* 2026-10-12:走查反馈偏小,由 14.5 上调到 15.5,与 tokens 的 --fs-title 同步。 */
  286. font-size: var(--fs-title, 15.5px);
  287. font-weight: 700;
  288. letter-spacing: 0.1em;
  289. white-space: nowrap;
  290. text-shadow: 0 0 12px rgba(1, 171, 234, 0.6);
  291. }
  292. /* 面板图标:单独一档弱辉光,避免继承标题的强发光把小图标糊成一团。 */
  293. .cockpit-panel__icon {
  294. flex: none;
  295. text-shadow: none;
  296. filter: drop-shadow(0 0 4px rgba(1, 171, 234, 0.45));
  297. }
  298. .cockpit-panel__title::before {
  299. content: '';
  300. position: absolute;
  301. left: 0;
  302. top: 50%;
  303. width: calc(4 * var(--u, 1px));
  304. height: calc(15 * var(--u, 1px));
  305. transform: translateY(-50%);
  306. background: linear-gradient(180deg, var(--cyan-hi), var(--cyan-d, #009de9));
  307. box-shadow: 0 0 10px var(--cyan, #01abea);
  308. clip-path: polygon(0 0, 100% 3px, 100% 100%, 0 calc(100% - 3px));
  309. }
  310. .cockpit-panel__meta {
  311. flex: 1 1 auto;
  312. min-width: 0;
  313. overflow: hidden;
  314. color: var(--muted, #5680b0);
  315. font-size: var(--fs-caption, 11.5px);
  316. letter-spacing: 0.04em;
  317. white-space: nowrap;
  318. text-overflow: ellipsis;
  319. }
  320. .cockpit-panel__meta.is-end {
  321. /* 规格 §6.1:overview 四块的小字是"右上角",贴右显示;仍与折叠符号留出间距。 */
  322. padding-right: calc(6 * var(--u, 1px));
  323. text-align: right;
  324. }
  325. .cockpit-panel__meta::before {
  326. content: '▸ ';
  327. color: rgba(1, 171, 234, 0.7);
  328. }
  329. .cockpit-panel__mark {
  330. flex: none;
  331. width: calc(16 * var(--u, 1px));
  332. color: var(--cyan, #01abea);
  333. font-size: var(--fs-title, 15.5px);
  334. line-height: 1;
  335. text-align: center;
  336. text-shadow: 0 0 10px rgba(1, 171, 234, 0.7);
  337. }
  338. .cockpit-panel__body {
  339. position: relative;
  340. z-index: 1;
  341. flex: 1 1 auto;
  342. min-height: 0;
  343. /* 2026-10-23 压矮:10u→7u 上下、11u→10u 左右(四块要在一屏内看完)。 */
  344. padding: calc(7 * var(--u, 1px)) calc(10 * var(--u, 1px));
  345. /* 2026-10-13 维护者要求:严格只竖向滚动,横向滚动条不出现。
  346. 横向裁切风险由内容侧防:面板内所有网格列都有 minmax(0,·),长词/编码走
  347. overflow-wrap(见 .panel-row dd 与 .panel-item 系列的 min-width:0)。 */
  348. overflow-y: auto;
  349. overflow-x: hidden;
  350. }
  351. /* 内容包裹层:高度=内容自然高度(不被 flex 拉伸),供 ResizeObserver 测量。
  352. 量它就是量"这块面板要多少空间",权重由测量值给(见 script 里的 measureContent)。 */
  353. .cockpit-panel__content {
  354. display: block;
  355. min-width: 0;
  356. }
  357. /* 标题行容器:**唯一**画背景带与分隔线的地方(head 自身透明)。
  358. 背景带必须覆盖 head + 状态光点**整条**,否则在状态光点左侧会出现断口 ——
  359. 那正是维护者两次指出的"标题与右侧状态光点之间存在明显分割"。 */
  360. .cockpit-panel__heading {
  361. position: relative;
  362. display: flex;
  363. align-items: center;
  364. flex: none;
  365. min-width: 0;
  366. border-bottom: 1px solid transparent;
  367. background:
  368. linear-gradient(
  369. 90deg,
  370. var(--panel-mid, rgba(0, 53, 156, 0.2)) 0%,
  371. var(--panel-bot, rgba(0, 15, 52, 0.38)) 46%,
  372. rgba(0, 15, 52, 0.28) 100%
  373. ),
  374. linear-gradient(180deg, rgba(1, 171, 234, 0.1), transparent);
  375. }
  376. /* hover 高亮同样横贯 head + 状态光点整条(与背景带的覆盖范围一致)。 */
  377. .cockpit-panel__heading:hover {
  378. background:
  379. linear-gradient(90deg, rgba(1, 171, 234, 0.32) 0%, rgba(0, 15, 52, 0.36) 46%, rgba(0, 15, 52, 0.3) 100%),
  380. linear-gradient(180deg, rgba(1, 171, 234, 0.14), transparent);
  381. }
  382. .cockpit-panel__heading::after {
  383. content: '';
  384. position: absolute;
  385. left: 0;
  386. right: 0;
  387. bottom: -1px;
  388. height: 1px;
  389. /* 一条完整的分割线:从标题左侧竖条起(6%)一直拉到面板右缘,
  390. **不在状态光点之前收淡** —— 提前收淡正是"看起来被切开"的另一半原因。 */
  391. background: linear-gradient(
  392. 90deg,
  393. transparent 0%,
  394. var(--panel-line-hi, #01abea) 6%,
  395. rgba(1, 171, 234, 0.85) 60%,
  396. rgba(1, 171, 234, 0.62) 100%
  397. );
  398. opacity: 0.65;
  399. pointer-events: none;
  400. }
  401. .cockpit-panel__heading .cockpit-panel__head {
  402. flex: 1;
  403. min-width: 0;
  404. width: auto;
  405. border-bottom: 0;
  406. }
  407. /* 状态光点:与标题栏共用同一条背景带(见 .cockpit-panel__heading)。
  408. 右侧内边距与 head 的右内边距一致,让背景带两侧留白对称 ——
  409. 之前它靠 margin-right 顶开,导致背景带在它左侧断开。 */
  410. .cockpit-panel__state {
  411. margin-right: calc(12 * var(--u, 1px));
  412. position: relative;
  413. z-index: 2;
  414. }
  415. </style>