MapCoordinateBlocked.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <!--
  2. 「无地图位置」清单(票 07)。
  3. 为什么必须有这块可见面:票 07 的验收第 3 条要求「UI 显示『无地图位置』或具体坐标原因」,
  4. 而今天全驾驶舱**没有任何列表界面**(监测点台账归票 18、两级详情归票 10),
  5. 被拦下的对象如果不在这里露面,就只能是「静默隐藏」—— 那正是 CONTEXT.md 在
  6. 「无地图位置」词条里点名禁止的(_Avoid_: 默认坐标点、静默隐藏)。
  7. 所以本组件是**早期可见面**,不是终态列表:它只渲染领域层已经判定好的清单
  8. (`domain/coordinate.js` 的 `blockedEntries`),不自己做任何坐标判断。
  9. 等票 10/18 的真列表建起来,它们消费同一份数据,本组件自然退为地图上的辅助提示。
  10. 两个口径分开说(票 07 的 Q4 裁定):后端全量汇总不随翻页跳动,已加载口径只说这批。
  11. 票 35 的简化(维护者 2026-09-18:「遮挡屏幕的是这一块区域,需要进行简化修改」):
  12. 收起时只留**一行小胶囊**(图标 + 「无地图位置」+ 计数 + 查看原因),
  13. 两条口径与清单属于**展开后的细节**——但仍然常驻 DOM(只是视觉上收起),
  14. 这样票 07 走查读 `data-map-coordinate-backend/-loaded` 文本的断言不受形态影响。
  15. -->
  16. <template>
  17. <div class="map-blocked" :class="{ 'is-compact': !expanded, 'map-blocked--embedded': embedded }" data-map-blocked-panel>
  18. <div class="map-blocked__head">
  19. <CockpitIcon name="points" size="calc(11 * var(--u, 1px))" />
  20. <b>无地图位置</b>
  21. <span class="map-blocked__count" data-map-blocked-count>{{ overview.loaded.blocked }}</span>
  22. <button
  23. type="button"
  24. class="map-blocked__toggle"
  25. :aria-expanded="expanded ? 'true' : 'false'"
  26. data-map-blocked-toggle
  27. @click="expanded = !expanded"
  28. >
  29. {{ expanded ? '收起' : '查看原因' }}
  30. </button>
  31. </div>
  32. <!-- 收起时这两行、以及清单,都由 CSS 隐去(DOM 常驻,保证票 07 的文本断言与可访问性) -->
  33. <div class="map-blocked__detail">
  34. <!-- 后端全量口径(不随翻页跳动)与已加载口径必须分开说,混成一个数就是自相矛盾 -->
  35. <p class="map-blocked__line" data-map-coordinate-backend>{{ overview.backendText }}</p>
  36. <p class="map-blocked__line" data-map-coordinate-loaded>{{ overview.loadedText }}</p>
  37. <ul v-if="expanded" class="map-blocked__list" data-map-blocked-list>
  38. <li v-for="item in overview.blockedEntries" :key="item.key" :data-map-blocked-reason="item.reasonCode">
  39. <span class="map-blocked__name">{{ item.name || item.code || '未命名对象' }}</span>
  40. <span class="map-blocked__code">{{ item.code }}</span>
  41. <!-- 具体原因:命中文案给具体原因,认不出才回「无地图位置」 -->
  42. <span class="map-blocked__reason">{{ item.reasonText }}</span>
  43. </li>
  44. <li v-if="!overview.blockedEntries.length" class="map-blocked__empty">当前已加载范围内没有无地图位置的对象</li>
  45. </ul>
  46. </div>
  47. </div>
  48. </template>
  49. <script setup>
  50. import { computed, ref } from 'vue'
  51. import CockpitIcon from './CockpitIcon.vue'
  52. const props = defineProps({
  53. /** `domain/coordinate.js` 的 `coordinateOverview()` 结果 */
  54. overview: { type: Object, required: true },
  55. /**
  56. * 票 25:并入地图信息集合面板时为 true。
  57. * 原先它是**绝对定位**在地图右下角的独立浮块(会压画面),现在改为跟随面板的常规流。
  58. */
  59. embedded: { type: Boolean, default: false },
  60. })
  61. const expanded = ref(false)
  62. const blockedCount = computed(() => (props.overview && props.overview.loaded ? props.overview.loaded.blocked : 0))
  63. </script>
  64. <style scoped>
  65. .map-blocked,
  66. .map-blocked * {
  67. box-sizing: border-box;
  68. }
  69. .map-blocked {
  70. position: absolute;
  71. z-index: 2;
  72. /* 位置是被左栏逼出来的:中央一张图是**满幅背景**,左右两栏浮在它上面
  73. (`.cockpit-shell__column` z-index 2 > 地图 z-index 0,且地图自成一个层叠上下文),
  74. 所以贴左下的浮块会被 300px 宽的左栏整块盖住。 */
  75. /* 票 35 续:维护者要求挪到**一张图右下角**并继续减少遮挡 ——
  76. 右边界要让开右栏(宽度就是共享 token `--column-w`),否则会被右栏压住看不见;
  77. 底边让开底部状态带(图例已在顶部,底部只剩一行状态)。 */
  78. right: calc(var(--column-w, 300px) + 6 * var(--u, 1px));
  79. /* 让开右下角的状态日志(生产形态在 bottom 8px 处,约 12px 高),浮块落在它上方。 */
  80. bottom: calc(6 * var(--u, 1px));
  81. left: auto;
  82. transform: none;
  83. text-align: right;
  84. /* 票 35:收窄 + 更透明 —— 维护者指出这块最挡屏幕。 */
  85. width: min(280px, 34%);
  86. max-height: 30%;
  87. overflow: hidden auto;
  88. padding: calc(4 * var(--u, 1px)) calc(8 * var(--u, 1px));
  89. border: 1px solid rgba(58, 170, 226, 0.28);
  90. background: rgba(4, 18, 34, 0.72);
  91. color: var(--text-2, #a8cbe2);
  92. font-size: calc(10 * var(--u, 1px));
  93. letter-spacing: 0.03em;
  94. }
  95. /* 票 35:收起形态 = 一行小胶囊(高度只由 head 撑开,宽度贴合内容而不是固定 280px)。 */
  96. .map-blocked.is-compact {
  97. width: auto;
  98. max-width: min(280px, 34%);
  99. padding: calc(3 * var(--u, 1px)) calc(8 * var(--u, 1px));
  100. max-height: none;
  101. overflow: visible;
  102. }
  103. /* 收起时把「查看原因」推向右侧对齐(胶囊贴右下角,视觉上不散)。 */
  104. .map-blocked.is-compact .map-blocked__head {
  105. gap: calc(5 * var(--u, 1px));
  106. }
  107. /* 细节(两条口径 + 清单)只在展开时显示;收起时用 CSS 隐去而**不卸载**,
  108. 保证票 07 走查随时能读到那两个 data-* 的文本。 */
  109. .map-blocked.is-compact .map-blocked__detail {
  110. position: absolute;
  111. width: 1px;
  112. height: 1px;
  113. margin: -1px;
  114. padding: 0;
  115. overflow: hidden;
  116. clip: rect(0 0 0 0);
  117. white-space: nowrap;
  118. border: 0;
  119. }
  120. .map-blocked__head {
  121. display: flex;
  122. align-items: center;
  123. gap: calc(4 * var(--u, 1px));
  124. }
  125. .map-blocked__head b {
  126. color: #e6f5ff;
  127. font-weight: 700;
  128. }
  129. .map-blocked__count {
  130. display: inline-flex;
  131. align-items: center;
  132. padding: 0 calc(5 * var(--u, 1px));
  133. color: var(--lv2, #ffa63d);
  134. font-family: var(--num, monospace);
  135. border: 1px solid rgba(255, 166, 61, 0.5);
  136. }
  137. .map-blocked__toggle {
  138. margin-left: auto;
  139. padding: calc(1 * var(--u, 1px)) calc(5 * var(--u, 1px));
  140. border: 1px solid rgba(58, 170, 226, 0.32);
  141. background: rgba(8, 34, 60, 0.6);
  142. color: #9dc2d9;
  143. font: inherit;
  144. font-size: calc(9.5 * var(--u, 1px));
  145. cursor: pointer;
  146. }
  147. .map-blocked__toggle:hover {
  148. color: #fff;
  149. border-color: rgba(79, 216, 255, 0.6);
  150. }
  151. .map-blocked__line {
  152. margin: calc(3 * var(--u, 1px)) 0 0;
  153. color: var(--muted, #6d92ae);
  154. font-size: calc(9.5 * var(--u, 1px));
  155. line-height: 1.35;
  156. }
  157. .map-blocked__list {
  158. margin: calc(4 * var(--u, 1px)) 0 0;
  159. padding: 0;
  160. list-style: none;
  161. display: grid;
  162. gap: calc(2 * var(--u, 1px));
  163. }
  164. /*
  165. * 名称与编码**都不截断**(维护者反馈过编码被从右侧切掉)。
  166. * 原来名称用 `nowrap + ellipsis`、编码放任被网格挤掉,长编码直接看不见。
  167. * 现在两者都允许换行,网格列用 `minmax(0, …)` 防止 `auto` 列把容器顶破。
  168. */
  169. .map-blocked__list li {
  170. display: grid;
  171. grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  172. gap: 0 calc(4 * var(--u, 1px));
  173. padding-top: calc(2 * var(--u, 1px));
  174. border-top: 1px dashed rgba(58, 170, 226, 0.22);
  175. }
  176. .map-blocked__name {
  177. color: var(--text-2, #a8cbe2);
  178. white-space: normal;
  179. overflow-wrap: anywhere;
  180. }
  181. .map-blocked__code {
  182. color: var(--muted, #6d92ae);
  183. font-family: var(--num, monospace);
  184. white-space: normal;
  185. overflow-wrap: anywhere;
  186. text-align: right;
  187. }
  188. /* 具体原因独占一行,保证读得到(颜色不是唯一线索,文字始终在) */
  189. .map-blocked__reason {
  190. grid-column: 1 / -1;
  191. color: var(--lv2, #ffa63d);
  192. }
  193. .map-blocked__empty {
  194. color: var(--muted, #6d92ae);
  195. }
  196. /*
  197. * 票 25:并入地图信息集合面板后不再是绝对定位浮块 —— 跟随面板常规流,
  198. * 不再压地图,也不再自己算「让开右栏/底带」的位置。
  199. *
  200. * 选择器写成 `.map-blocked.map-blocked--embedded`(而不是单类)是有原因的:
  201. * `.map-blocked.is-compact` 自带 `max-width: min(280px, 34%)`,优先级更高;
  202. * 单类写法压不过它,面板里那栏就会被压到 34% 宽、把「无地图位置」挤成竖排。
  203. */
  204. .map-blocked.map-blocked--embedded,
  205. .map-blocked.map-blocked--embedded.is-compact {
  206. position: static;
  207. right: auto;
  208. bottom: auto;
  209. width: auto;
  210. max-width: 100%;
  211. /* 「容器异常占用」的根因:原样式带 `max-height: 30%`,是给「压在地图上的浮块」设计的。
  212. 嵌进状态面板后那套高度毫无意义,却会让块看起来比内容高一大截。 */
  213. height: auto;
  214. max-height: none;
  215. /* 绝不跟着父容器拉高——只占内容该占的高度。 */
  216. align-self: start;
  217. margin: 0;
  218. text-align: left;
  219. border: 0;
  220. border-top: 1px solid rgba(58, 170, 226, 0.18);
  221. border-radius: 0;
  222. padding: calc(3 * var(--u, 1px)) 0 0;
  223. background: none;
  224. overflow: visible;
  225. }
  226. /* 列表再长也自己滚动,不把外层容器撑高。 */
  227. .map-blocked.map-blocked--embedded .map-blocked__list {
  228. max-height: 40vh;
  229. overflow: auto;
  230. }
  231. /*
  232. * 嵌入态把「名称 / 编码」改成**上下两行**,而不是并排两列。
  233. *
  234. * 原因:并排时编码列是 `auto` 轨道,会撑到 max-content —— 状态面板只有 560px 宽,
  235. * 于是长编码把整行顶出容器,被裁在右边(维护者截图里正是「0500…」「PS00…」这样被切掉)。
  236. * 单列之后编码独占一行并可换行,任何长度都完整可见。
  237. */
  238. .map-blocked.map-blocked--embedded .map-blocked__list li {
  239. grid-template-columns: minmax(0, 1fr);
  240. }
  241. .map-blocked.map-blocked--embedded .map-blocked__code {
  242. text-align: left;
  243. overflow-wrap: anywhere;
  244. word-break: break-all;
  245. }
  246. </style>