|
@@ -149,11 +149,33 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="cockpit-shell__scope-right">
|
|
<div class="cockpit-shell__scope-right">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="cockpit-shell__status-summary"
|
|
|
|
|
+ :class="{ 'is-open': statusPanelExpanded }"
|
|
|
|
|
+ :data-status-expanded="statusPanelExpanded ? 'true' : 'false'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="cockpit-shell__status-toggle"
|
|
|
|
|
+ data-status-toggle
|
|
|
|
|
+ :aria-expanded="statusPanelExpanded"
|
|
|
|
|
+ aria-controls="cockpit-status-details"
|
|
|
|
|
+ @click="statusPanelExpanded = !statusPanelExpanded"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="cockpit-shell__status-dot" :class="statusSummaryTone" aria-hidden="true"></i>
|
|
|
|
|
+ <span data-status-summary>{{ statusSummaryText }}</span>
|
|
|
|
|
+ <span class="cockpit-shell__status-chevron" aria-hidden="true">{{ statusPanelExpanded ? '⌃' : '⌄' }}</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <div
|
|
|
|
|
+ id="cockpit-status-details"
|
|
|
|
|
+ v-show="statusPanelExpanded"
|
|
|
|
|
+ class="cockpit-shell__status-details"
|
|
|
|
|
+ data-status-details
|
|
|
|
|
+ >
|
|
|
<!--
|
|
<!--
|
|
|
- 票 14(决策 02 的 Q24/Q25):「加载下一页 / 刷新快照」两个手动按钮移除——
|
|
|
|
|
- 进入系统/筛选变化后地图摘要**自动分批加载到完成**;这里只留右上角一枚
|
|
|
|
|
- 🔄 手动刷新(换新快照、从第 1 页自动重载)。小胶囊的状态点与短文案保留:
|
|
|
|
|
- 它仍是走查断言面(data-map-summary-*),也是人工看得见的装载进度。
|
|
|
|
|
|
|
+ 票 14(决策 02 的 Q24/Q25):地图摘要进入系统/筛选变化后**自动分批加载到完成**。
|
|
|
|
|
+ 它作为状态详情的一行保留 `data-map-summary-*` 断言面;手动刷新按钮移到状态摘要
|
|
|
|
|
+ 右侧独立位置,避免和范围/行业/视图选择争抢横向空间。
|
|
|
-->
|
|
-->
|
|
|
<div
|
|
<div
|
|
|
class="cockpit-shell__map-summary"
|
|
class="cockpit-shell__map-summary"
|
|
@@ -165,18 +187,6 @@
|
|
|
<span class="cockpit-shell__map-summary-text" data-map-summary-label :title="mapSummaryView.detail">
|
|
<span class="cockpit-shell__map-summary-text" data-map-summary-label :title="mapSummaryView.detail">
|
|
|
{{ mapSummaryView.label }}
|
|
{{ mapSummaryView.label }}
|
|
|
</span>
|
|
</span>
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- class="cockpit-shell__chip cockpit-shell__map-next"
|
|
|
|
|
- data-map-summary-action
|
|
|
|
|
- data-map-refresh
|
|
|
|
|
- :disabled="mapStore.busy"
|
|
|
|
|
- aria-label="手动刷新快照"
|
|
|
|
|
- title="换到后端最新快照,并自动从第 1 页加载到完成"
|
|
|
|
|
- @click="refreshMapSnapshot"
|
|
|
|
|
- >
|
|
|
|
|
- 🔄
|
|
|
|
|
- </button>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!--
|
|
<!--
|
|
@@ -225,43 +235,6 @@
|
|
|
<span v-if="areaNotice" class="cockpit-shell__chip cockpit-shell__locate" data-area-notice role="status">
|
|
<span v-if="areaNotice" class="cockpit-shell__chip cockpit-shell__locate" data-area-notice role="status">
|
|
|
{{ areaNotice }}
|
|
{{ areaNotice }}
|
|
|
</span>
|
|
</span>
|
|
|
- <!-- 票 37:区内状态读数(走查断言面;人工也能一眼看出限位生效了)。 -->
|
|
|
|
|
- <span
|
|
|
|
|
- v-if="areaLocked"
|
|
|
|
|
- class="cockpit-shell__chip cockpit-shell__area"
|
|
|
|
|
- :data-area-state="areaState"
|
|
|
|
|
- :data-range-max="Math.round(areaLimits.max)"
|
|
|
|
|
- role="status"
|
|
|
|
|
- >
|
|
|
|
|
- 区内限位 · 最远 {{ Math.round(areaLimits.max) }} m
|
|
|
|
|
- </span>
|
|
|
|
|
-
|
|
|
|
|
- <!--
|
|
|
|
|
- 票 38:自建雾的可观测读数(验收第 7 条要求「是否支持 / 是否可执行 / 当前强度 /
|
|
|
|
|
- 参数 / 失败降级原因」对外可读)。**它不是「遮缝已生效」的证明** ——
|
|
|
|
|
- 文案只说"视距雾/降级",不写"已遮住边缘"。
|
|
|
|
|
- 降级(无深度支持 / 观察透明错位 / 用户暂关)时转成提示色,业务照常可用。
|
|
|
|
|
-
|
|
|
|
|
- 属性名**故意用 `data-fog-state` 而不是 `data-map-fog`**:后者已经挂在
|
|
|
|
|
- 地图宿主(`.map-canvas`)上表示运行期读数。同名会让 `querySelector('[data-map-fog]')`
|
|
|
|
|
- 命中**宿主**(整页文本),断言与走查就会读到错误的元素 —— 本仓在票 07 上
|
|
|
|
|
- (`data-map-blocked-backend` 同名挂两处)已经栽过一次,这里不重复。
|
|
|
|
|
- -->
|
|
|
|
|
- <span
|
|
|
|
|
- v-if="areaLocked || fogReadout.degrade !== 'none' || fogReadout.state !== 'ready'"
|
|
|
|
|
- class="cockpit-shell__chip cockpit-shell__fog"
|
|
|
|
|
- :class="{ 'is-bad': fogReadout.state !== 'ready' }"
|
|
|
|
|
- data-fog-chip
|
|
|
|
|
- :data-fog-state="fogReadout.state"
|
|
|
|
|
- :data-fog-phase="fogReadout.phase"
|
|
|
|
|
- :data-fog-strength="fogReadout.strength"
|
|
|
|
|
- :data-fog-degrade="fogReadout.degrade"
|
|
|
|
|
- :title="fogTitle"
|
|
|
|
|
- role="status"
|
|
|
|
|
- >
|
|
|
|
|
- {{ fogText }}
|
|
|
|
|
- </span>
|
|
|
|
|
-
|
|
|
|
|
<!--
|
|
<!--
|
|
|
票 15:状态推送的连接状态(验收第 6 条「连接/重连/过期/失败可演示」)。
|
|
票 15:状态推送的连接状态(验收第 6 条「连接/重连/过期/失败可演示」)。
|
|
|
data-ws-state 是走查断言面;文案来自运行时服务,外壳不重写。
|
|
data-ws-state 是走查断言面;文案来自运行时服务,外壳不重写。
|
|
@@ -273,7 +246,7 @@
|
|
|
:title="runtimeStore.status.lastError || runtimeStore.statusText"
|
|
:title="runtimeStore.status.lastError || runtimeStore.statusText"
|
|
|
role="status"
|
|
role="status"
|
|
|
>
|
|
>
|
|
|
- 推送 · {{ runtimeStore.statusText }}
|
|
|
|
|
|
|
+ {{ runtimeStore.statusText }}
|
|
|
<span v-if="runtimeStore.orderScanIntervalMs" data-order-scan-delay> · 工单扫描间隔 {{ runtimeStore.orderScanIntervalMs / 1000 }} 秒</span>
|
|
<span v-if="runtimeStore.orderScanIntervalMs" data-order-scan-delay> · 工单扫描间隔 {{ runtimeStore.orderScanIntervalMs / 1000 }} 秒</span>
|
|
|
</span>
|
|
</span>
|
|
|
|
|
|
|
@@ -301,7 +274,21 @@
|
|
|
· {{ overallFreshnessLabel }}
|
|
· {{ overallFreshnessLabel }}
|
|
|
</b>
|
|
</b>
|
|
|
</span>
|
|
</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="cockpit-shell__chip cockpit-shell__refresh-action"
|
|
|
|
|
+ data-map-summary-action
|
|
|
|
|
+ data-map-refresh
|
|
|
|
|
+ :disabled="mapStore.busy"
|
|
|
|
|
+ aria-label="手动刷新快照"
|
|
|
|
|
+ title="换到后端最新快照,并自动从第 1 页加载到完成"
|
|
|
|
|
+ @click="refreshMapSnapshot"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span aria-hidden="true" class="cockpit-shell__refresh-glyph">↻</span>
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<main class="cockpit-shell__main">
|
|
<main class="cockpit-shell__main">
|
|
@@ -506,6 +493,7 @@ const monitoringStore = useMonitoringStore()
|
|
|
const overviewStore = useOverviewStore()
|
|
const overviewStore = useOverviewStore()
|
|
|
const eventStore = useEventStore()
|
|
const eventStore = useEventStore()
|
|
|
const runtimeStore = useRuntimeStore()
|
|
const runtimeStore = useRuntimeStore()
|
|
|
|
|
+const statusPanelExpanded = ref(false)
|
|
|
const { mode, expandedPanelIds } = storeToRefs(uiStore)
|
|
const { mode, expandedPanelIds } = storeToRefs(uiStore)
|
|
|
const { scope, industry, viewMode, pointStatus, areaState } = storeToRefs(filterStore)
|
|
const { scope, industry, viewMode, pointStatus, areaState } = storeToRefs(filterStore)
|
|
|
|
|
|
|
@@ -1031,8 +1019,25 @@ const overallFreshnessLabel = computed(() => overallFreshnessText(overallFreshne
|
|
|
// ---- 地图摘要渐进加载(票 08):顶栏只展示状态与一个动作,分页/快照参数都在适配层 ----
|
|
// ---- 地图摘要渐进加载(票 08):顶栏只展示状态与一个动作,分页/快照参数都在适配层 ----
|
|
|
const mapSummaryView = computed(() => mapStore.view)
|
|
const mapSummaryView = computed(() => mapStore.view)
|
|
|
|
|
|
|
|
|
|
+/** 收起态只留一条最新状态,展开态才显示全部诊断字段。 */
|
|
|
|
|
+const statusSummaryText = computed(() => {
|
|
|
|
|
+ if (overallFreshnessState.value === 'partial') return overallFreshnessLabel.value
|
|
|
|
|
+ if (['empty', 'stale', 'unavailable'].includes(runtimeStore.freshness.view)) return runtimeStore.freshness.text
|
|
|
|
|
+ if (runtimeStore.status.state !== 'open') return runtimeStore.statusText
|
|
|
|
|
+ if (['idle', 'loading', 'empty', 'failed', 'expired'].includes(mapSummaryView.value.phase)) return mapSummaryView.value.label
|
|
|
|
|
+ return `更新 ${runtimeStore.freshness.lastSuccessClock}`
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const statusSummaryTone = computed(() => {
|
|
|
|
|
+ if (overallFreshnessState.value === 'partial') return 'is-warning'
|
|
|
|
|
+ if (['empty', 'stale', 'unavailable'].includes(runtimeStore.freshness.view)) return 'is-warning'
|
|
|
|
|
+ if (runtimeStore.status.state !== 'open') return 'is-warning'
|
|
|
|
|
+ if (['idle', 'loading', 'empty', 'failed', 'expired'].includes(mapSummaryView.value.phase)) return 'is-warning'
|
|
|
|
|
+ return 'is-ok'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 票 14:手动刷新(右上角 🔄,决策 02 的 Q25)=换新快照并**自动从第 1 页加载到完成**
|
|
|
|
|
|
|
+ * 票 14:手动刷新(状态栏最右侧的刷新动作,决策 02 的 Q25)=换新快照并**自动从第 1 页加载到完成**
|
|
|
* (旧「加载下一页 / 刷新快照」两个按钮已移除;自动链归 mapStore 的装载链)。
|
|
* (旧「加载下一页 / 刷新快照」两个按钮已移除;自动链归 mapStore 的装载链)。
|
|
|
*/
|
|
*/
|
|
|
async function refreshMapSnapshot() {
|
|
async function refreshMapSnapshot() {
|
|
@@ -1811,11 +1816,12 @@ onBeforeUnmount(() => {
|
|
|
全局筛选条(压矮:38 设计像素)
|
|
全局筛选条(压矮:38 设计像素)
|
|
|
============================================================ */
|
|
============================================================ */
|
|
|
.cockpit-shell__scope {
|
|
.cockpit-shell__scope {
|
|
|
|
|
+ z-index: 3;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
gap: calc(10 * var(--u));
|
|
gap: calc(10 * var(--u));
|
|
|
- overflow: hidden;
|
|
|
|
|
|
|
+ overflow: visible;
|
|
|
padding: 0 calc(10 * var(--u));
|
|
padding: 0 calc(10 * var(--u));
|
|
|
border: 1px solid rgba(58, 170, 226, 0.24);
|
|
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));
|
|
background: linear-gradient(180deg, rgba(7, 30, 54, 0.72), rgba(4, 15, 28, 0.88));
|
|
@@ -1828,11 +1834,26 @@ onBeforeUnmount(() => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: calc(6 * var(--u));
|
|
gap: calc(6 * var(--u));
|
|
|
white-space: nowrap;
|
|
white-space: nowrap;
|
|
|
- /* 票 08:flex 项默认不许收缩,右块加了「地图摘要」控件后,1280 宽会把筛选条撑溢出
|
|
|
|
|
- (走查实测 +202px)。允许收缩,让摘要文案的 ellipsis 真正生效。 */
|
|
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.cockpit-shell__scope-left {
|
|
|
|
|
+ flex: 1 1 auto;
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+ overflow-y: hidden;
|
|
|
|
|
+ scrollbar-width: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__scope-left::-webkit-scrollbar {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__scope-right {
|
|
|
|
|
+ flex: 0 1 auto;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ max-width: 46%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.cockpit-shell__scope-label {
|
|
.cockpit-shell__scope-label {
|
|
|
color: var(--muted);
|
|
color: var(--muted);
|
|
|
font-size: calc(10 * var(--u));
|
|
font-size: calc(10 * var(--u));
|
|
@@ -1893,21 +1914,112 @@ onBeforeUnmount(() => {
|
|
|
letter-spacing: 0.04em;
|
|
letter-spacing: 0.04em;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 票 08:地图摘要的渐进加载状态 + 一个动作按钮。
|
|
|
|
|
- 票 33:收成小胶囊(无标题、更小字号、更窄上限),不抢一张图。 */
|
|
|
|
|
|
|
+/* 票 08:地图摘要仍是状态详情的一行,收起时由上层摘要按钮替代其横向占位。 */
|
|
|
.cockpit-shell__map-summary {
|
|
.cockpit-shell__map-summary {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: calc(5 * var(--u));
|
|
gap: calc(5 * var(--u));
|
|
|
- padding: calc(1 * var(--u)) calc(6 * var(--u));
|
|
|
|
|
- border: 1px solid rgba(58, 170, 226, 0.22);
|
|
|
|
|
- background: rgba(4, 20, 38, 0.4);
|
|
|
|
|
- /* 空间不足时先压这一块(文案 ellipsis 收缩),不去挤范围/行业/视图按钮。 */
|
|
|
|
|
- flex-shrink: 1;
|
|
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.cockpit-shell__status-summary {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ flex: 0 1 auto;
|
|
|
|
|
+ min-width: min(220px, 28vw);
|
|
|
|
|
+ max-width: min(520px, 42vw);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-toggle {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: calc(6 * var(--u));
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ min-height: calc(28 * var(--u));
|
|
|
|
|
+ padding: calc(3 * var(--u)) calc(8 * var(--u));
|
|
|
|
|
+ border: 1px solid rgba(58, 170, 226, 0.32);
|
|
|
|
|
+ background: rgba(4, 20, 38, 0.72);
|
|
|
|
|
+ color: #b9d8e8;
|
|
|
|
|
+ font: inherit;
|
|
|
|
|
+ font-size: calc(10.5 * var(--u));
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ clip-path: polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-toggle:hover,
|
|
|
|
|
+.cockpit-shell__status-toggle:focus-visible {
|
|
|
|
|
+ border-color: rgba(79, 216, 255, 0.75);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-toggle [data-status-summary] {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-dot {
|
|
|
|
|
+ width: calc(7 * var(--u));
|
|
|
|
|
+ height: calc(7 * var(--u));
|
|
|
|
|
+ flex: none;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: var(--muted, #6d92ae);
|
|
|
|
|
+ box-shadow: 0 0 8px currentColor;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-dot.is-ok {
|
|
|
|
|
+ background: var(--lv4, #35e39a);
|
|
|
|
|
+ color: var(--lv4, #35e39a);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-dot.is-warning {
|
|
|
|
|
+ background: var(--lv2, #ffa63d);
|
|
|
|
|
+ color: var(--lv2, #ffa63d);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-chevron {
|
|
|
|
|
+ color: var(--cyan, #4fd8ff);
|
|
|
|
|
+ font-size: calc(14 * var(--u));
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-details {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ z-index: 20;
|
|
|
|
|
+ top: calc(100% + 5 * var(--u));
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ gap: calc(6 * var(--u));
|
|
|
|
|
+ width: min(560px, 48vw);
|
|
|
|
|
+ max-width: calc(100vw - 24 * var(--u));
|
|
|
|
|
+ max-height: min(62vh, 520px);
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ padding: calc(9 * var(--u));
|
|
|
|
|
+ border: 1px solid rgba(79, 216, 255, 0.42);
|
|
|
|
|
+ background: rgba(3, 17, 32, 0.96);
|
|
|
|
|
+ box-shadow: 0 14px 32px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(130, 230, 255, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__status-details > * {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__refresh-action {
|
|
|
|
|
+ flex: none;
|
|
|
|
|
+ min-width: calc(58 * var(--u));
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: calc(5 * var(--u));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cockpit-shell__refresh-glyph {
|
|
|
|
|
+ font-size: calc(15 * var(--u));
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* 状态点:加载中呼吸、完成转绿、失败/过期转红 —— 颜色语义与小胶囊共存。 */
|
|
/* 状态点:加载中呼吸、完成转绿、失败/过期转红 —— 颜色语义与小胶囊共存。 */
|
|
|
.cockpit-shell__map-summary-dot {
|
|
.cockpit-shell__map-summary-dot {
|
|
|
flex: none;
|
|
flex: none;
|
|
@@ -1954,11 +2066,6 @@ onBeforeUnmount(() => {
|
|
|
color: var(--lv4);
|
|
color: var(--lv4);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.cockpit-shell__map-next:disabled {
|
|
|
|
|
- opacity: 0.5;
|
|
|
|
|
- cursor: default;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/* 票 05:共享分区状态板。横向排列、可换行;空间不够时先压这一块,不去挤筛选按钮。 */
|
|
/* 票 05:共享分区状态板。横向排列、可换行;空间不够时先压这一块,不去挤筛选按钮。 */
|
|
|
.cockpit-shell__sections {
|
|
.cockpit-shell__sections {
|
|
|
display: flex;
|
|
display: flex;
|