| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- <!--
- 「无地图位置」清单(票 07)。
- 为什么必须有这块可见面:票 07 的验收第 3 条要求「UI 显示『无地图位置』或具体坐标原因」,
- 而今天全驾驶舱**没有任何列表界面**(监测点台账归票 18、两级详情归票 10),
- 被拦下的对象如果不在这里露面,就只能是「静默隐藏」—— 那正是 CONTEXT.md 在
- 「无地图位置」词条里点名禁止的(_Avoid_: 默认坐标点、静默隐藏)。
- 所以本组件是**早期可见面**,不是终态列表:它只渲染领域层已经判定好的清单
- (`domain/coordinate.js` 的 `blockedEntries`),不自己做任何坐标判断。
- 等票 10/18 的真列表建起来,它们消费同一份数据,本组件自然退为地图上的辅助提示。
- 两个口径分开说(票 07 的 Q4 裁定):后端全量汇总不随翻页跳动,已加载口径只说这批。
- 票 35 的简化(维护者 2026-09-18:「遮挡屏幕的是这一块区域,需要进行简化修改」):
- 收起时只留**一行小胶囊**(图标 + 「无地图位置」+ 计数 + 查看原因),
- 两条口径与清单属于**展开后的细节**——但仍然常驻 DOM(只是视觉上收起),
- 这样票 07 走查读 `data-map-coordinate-backend/-loaded` 文本的断言不受形态影响。
- -->
- <template>
- <div class="map-blocked" :class="{ 'is-compact': !expanded, 'map-blocked--embedded': embedded }" data-map-blocked-panel>
- <div class="map-blocked__head">
- <CockpitIcon name="points" size="calc(11 * var(--u, 1px))" />
- <b>无地图位置</b>
- <span class="map-blocked__count" data-map-blocked-count>{{ overview.loaded.blocked }}</span>
- <button
- type="button"
- class="map-blocked__toggle"
- :aria-expanded="expanded ? 'true' : 'false'"
- data-map-blocked-toggle
- @click="expanded = !expanded"
- >
- {{ expanded ? '收起' : '查看原因' }}
- </button>
- </div>
- <!-- 收起时这两行、以及清单,都由 CSS 隐去(DOM 常驻,保证票 07 的文本断言与可访问性) -->
- <div class="map-blocked__detail">
- <!-- 后端全量口径(不随翻页跳动)与已加载口径必须分开说,混成一个数就是自相矛盾 -->
- <p class="map-blocked__line" data-map-coordinate-backend>{{ overview.backendText }}</p>
- <p class="map-blocked__line" data-map-coordinate-loaded>{{ overview.loadedText }}</p>
- <ul v-if="expanded" class="map-blocked__list" data-map-blocked-list>
- <li v-for="item in overview.blockedEntries" :key="item.key" :data-map-blocked-reason="item.reasonCode">
- <span class="map-blocked__name">{{ item.name || item.code || '未命名对象' }}</span>
- <span class="map-blocked__code">{{ item.code }}</span>
- <!-- 具体原因:命中文案给具体原因,认不出才回「无地图位置」 -->
- <span class="map-blocked__reason">{{ item.reasonText }}</span>
- </li>
- <li v-if="!overview.blockedEntries.length" class="map-blocked__empty">当前已加载范围内没有无地图位置的对象</li>
- </ul>
- </div>
- </div>
- </template>
- <script setup>
- import { computed, ref } from 'vue'
- import CockpitIcon from './CockpitIcon.vue'
- const props = defineProps({
- /** `domain/coordinate.js` 的 `coordinateOverview()` 结果 */
- overview: { type: Object, required: true },
- /**
- * 票 25:并入地图信息集合面板时为 true。
- * 原先它是**绝对定位**在地图右下角的独立浮块(会压画面),现在改为跟随面板的常规流。
- */
- embedded: { type: Boolean, default: false },
- })
- const expanded = ref(false)
- const blockedCount = computed(() => (props.overview && props.overview.loaded ? props.overview.loaded.blocked : 0))
- </script>
- <style scoped>
- .map-blocked,
- .map-blocked * {
- box-sizing: border-box;
- }
- .map-blocked {
- position: absolute;
- z-index: 2;
- /* 位置是被左栏逼出来的:中央一张图是**满幅背景**,左右两栏浮在它上面
- (`.cockpit-shell__column` z-index 2 > 地图 z-index 0,且地图自成一个层叠上下文),
- 所以贴左下的浮块会被 300px 宽的左栏整块盖住。 */
- /* 票 35 续:维护者要求挪到**一张图右下角**并继续减少遮挡 ——
- 右边界要让开右栏(宽度就是共享 token `--column-w`),否则会被右栏压住看不见;
- 底边让开底部状态带(图例已在顶部,底部只剩一行状态)。 */
- right: calc(var(--column-w, 300px) + 6 * var(--u, 1px));
- /* 让开右下角的状态日志(生产形态在 bottom 8px 处,约 12px 高),浮块落在它上方。 */
- bottom: calc(6 * var(--u, 1px));
- left: auto;
- transform: none;
- text-align: right;
- /* 票 35:收窄 + 更透明 —— 维护者指出这块最挡屏幕。 */
- width: min(280px, 34%);
- max-height: 30%;
- overflow: hidden auto;
- padding: calc(4 * var(--u, 1px)) calc(8 * var(--u, 1px));
- border: 1px solid rgba(58, 170, 226, 0.28);
- background: rgba(4, 18, 34, 0.72);
- color: var(--text-2, #a8cbe2);
- font-size: calc(10 * var(--u, 1px));
- letter-spacing: 0.03em;
- }
- /* 票 35:收起形态 = 一行小胶囊(高度只由 head 撑开,宽度贴合内容而不是固定 280px)。 */
- .map-blocked.is-compact {
- width: auto;
- max-width: min(280px, 34%);
- padding: calc(3 * var(--u, 1px)) calc(8 * var(--u, 1px));
- max-height: none;
- overflow: visible;
- }
- /* 收起时把「查看原因」推向右侧对齐(胶囊贴右下角,视觉上不散)。 */
- .map-blocked.is-compact .map-blocked__head {
- gap: calc(5 * var(--u, 1px));
- }
- /* 细节(两条口径 + 清单)只在展开时显示;收起时用 CSS 隐去而**不卸载**,
- 保证票 07 走查随时能读到那两个 data-* 的文本。 */
- .map-blocked.is-compact .map-blocked__detail {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- overflow: hidden;
- clip: rect(0 0 0 0);
- white-space: nowrap;
- border: 0;
- }
- .map-blocked__head {
- display: flex;
- align-items: center;
- gap: calc(4 * var(--u, 1px));
- }
- .map-blocked__head b {
- color: #e6f5ff;
- font-weight: 700;
- }
- .map-blocked__count {
- display: inline-flex;
- align-items: center;
- padding: 0 calc(5 * var(--u, 1px));
- color: var(--lv2, #ffa63d);
- font-family: var(--num, monospace);
- border: 1px solid rgba(255, 166, 61, 0.5);
- }
- .map-blocked__toggle {
- margin-left: auto;
- padding: calc(1 * var(--u, 1px)) calc(5 * var(--u, 1px));
- border: 1px solid rgba(58, 170, 226, 0.32);
- background: rgba(8, 34, 60, 0.6);
- color: #9dc2d9;
- font: inherit;
- font-size: calc(9.5 * var(--u, 1px));
- cursor: pointer;
- }
- .map-blocked__toggle:hover {
- color: #fff;
- border-color: rgba(79, 216, 255, 0.6);
- }
- .map-blocked__line {
- margin: calc(3 * var(--u, 1px)) 0 0;
- color: var(--muted, #6d92ae);
- font-size: calc(9.5 * var(--u, 1px));
- line-height: 1.35;
- }
- .map-blocked__list {
- margin: calc(4 * var(--u, 1px)) 0 0;
- padding: 0;
- list-style: none;
- display: grid;
- gap: calc(2 * var(--u, 1px));
- }
- /*
- * 名称与编码**都不截断**(维护者反馈过编码被从右侧切掉)。
- * 原来名称用 `nowrap + ellipsis`、编码放任被网格挤掉,长编码直接看不见。
- * 现在两者都允许换行,网格列用 `minmax(0, …)` 防止 `auto` 列把容器顶破。
- */
- .map-blocked__list li {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(0, auto);
- gap: 0 calc(4 * var(--u, 1px));
- padding-top: calc(2 * var(--u, 1px));
- border-top: 1px dashed rgba(58, 170, 226, 0.22);
- }
- .map-blocked__name {
- color: var(--text-2, #a8cbe2);
- white-space: normal;
- overflow-wrap: anywhere;
- }
- .map-blocked__code {
- color: var(--muted, #6d92ae);
- font-family: var(--num, monospace);
- white-space: normal;
- overflow-wrap: anywhere;
- text-align: right;
- }
- /* 具体原因独占一行,保证读得到(颜色不是唯一线索,文字始终在) */
- .map-blocked__reason {
- grid-column: 1 / -1;
- color: var(--lv2, #ffa63d);
- }
- .map-blocked__empty {
- color: var(--muted, #6d92ae);
- }
- /*
- * 票 25:并入地图信息集合面板后不再是绝对定位浮块 —— 跟随面板常规流,
- * 不再压地图,也不再自己算「让开右栏/底带」的位置。
- *
- * 选择器写成 `.map-blocked.map-blocked--embedded`(而不是单类)是有原因的:
- * `.map-blocked.is-compact` 自带 `max-width: min(280px, 34%)`,优先级更高;
- * 单类写法压不过它,面板里那栏就会被压到 34% 宽、把「无地图位置」挤成竖排。
- */
- .map-blocked.map-blocked--embedded,
- .map-blocked.map-blocked--embedded.is-compact {
- position: static;
- right: auto;
- bottom: auto;
- width: auto;
- max-width: 100%;
- /* 「容器异常占用」的根因:原样式带 `max-height: 30%`,是给「压在地图上的浮块」设计的。
- 嵌进状态面板后那套高度毫无意义,却会让块看起来比内容高一大截。 */
- height: auto;
- max-height: none;
- /* 绝不跟着父容器拉高——只占内容该占的高度。 */
- align-self: start;
- margin: 0;
- text-align: left;
- border: 0;
- border-top: 1px solid rgba(58, 170, 226, 0.18);
- border-radius: 0;
- padding: calc(3 * var(--u, 1px)) 0 0;
- background: none;
- overflow: visible;
- }
- /* 列表再长也自己滚动,不把外层容器撑高。 */
- .map-blocked.map-blocked--embedded .map-blocked__list {
- max-height: 40vh;
- overflow: auto;
- }
- /*
- * 嵌入态把「名称 / 编码」改成**上下两行**,而不是并排两列。
- *
- * 原因:并排时编码列是 `auto` 轨道,会撑到 max-content —— 状态面板只有 560px 宽,
- * 于是长编码把整行顶出容器,被裁在右边(维护者截图里正是「0500…」「PS00…」这样被切掉)。
- * 单列之后编码独占一行并可换行,任何长度都完整可见。
- */
- .map-blocked.map-blocked--embedded .map-blocked__list li {
- grid-template-columns: minmax(0, 1fr);
- }
- .map-blocked.map-blocked--embedded .map-blocked__code {
- text-align: left;
- overflow-wrap: anywhere;
- word-break: break-all;
- }
- </style>
|