refactor: 容量看板标题与制式切换/刷新并入框架页头

This commit is contained in:
2026-06-26 09:04:37 +08:00
parent 558aaf0c14
commit cc6ef3bab6
2 changed files with 27 additions and 56 deletions
+7
View File
@@ -948,3 +948,10 @@ CellData 处理日志细化(`app/services/cell_data.py`):
- 默认制式:看板进入默认 `rat='5g'`(原 4g)。 - 默认制式:看板进入默认 `rat='5g'`(原 4g)。
- 修复矮图表数值轴刻度重叠:图表高度仅 `clamp(108px,13vh,160px)`,上行/下行利用率分布 0-10% 桶计数极大(约 1.9w)且带千分位,ECharts 自动放 5-6 条刻度导致 Y 轴标签纵向叠压。新增 `abbrNum()`(≥1万→「x万」、≥1千→「xk」、≥1亿→「x亿」)与 `valueAxis()`(`splitNumber:3` + 缩写 formatter),套用到上/下行直方与制式分布的数值轴;频段标记小区横向柱的数值 x 轴同样加 `splitNumber:3`+缩写。 - 修复矮图表数值轴刻度重叠:图表高度仅 `clamp(108px,13vh,160px)`,上行/下行利用率分布 0-10% 桶计数极大(约 1.9w)且带千分位,ECharts 自动放 5-6 条刻度导致 Y 轴标签纵向叠压。新增 `abbrNum()`(≥1万→「x万」、≥1千→「xk」、≥1亿→「x亿」)与 `valueAxis()`(`splitNumber:3` + 缩写 formatter),套用到上/下行直方与制式分布的数值轴;频段标记小区横向柱的数值 x 轴同样加 `splitNumber:3`+缩写。
- 验证:`frontend` `vue-tsc --noEmit` 通过。 - 验证:`frontend` `vue-tsc --noEmit` 通过。
## 2026-06-26:容量看板标题/操作并入框架页头
- 参照「处理历史」做法,看板不再自绘标题栏:删除 `.cap-topbar`(标题/副标题/制式切换/刷新)。改用 `composables/pageHeader.ts` 的 `setPageHeader`/`resetPageHeader` 把内容注入 AppShell 顶部 `page-header`(与其他页一致,标题取路由 meta「容量看板」)。
- 注入内容:subtitle「高负荷小区分析 · 实时数据」;actions = 4G / 5G 两个按钮(激活=`type:primary, variant:solid`,未激活=`default+outline`,点击 `switchRat`)+「刷新」(icon `RefreshOutline`,`loading/disabled` 绑 `loadingOverview`)。因 `PageHeaderAction.type/variant` 非响应式,用 `watch([rat, ready], applyPageHeader)` 在制式切换/就绪时重建 actions;`onBeforeUnmount` 调 `resetPageHeader` 清理;未就绪(无结果表)时不注入。
- 清理:移除 `ratOptions` 及 `.cap-topbar/.cap-title/.cap-spark/.cap-sub/.cap-controls/.cap-refresh` 样式;`.cap-seg*` 保留(问题清单的筛选分段仍在用)。
- 验证:`frontend` `npm run build`(vue-tsc+vite) 通过。
+20 -56
View File
@@ -15,29 +15,6 @@
<!-- 看板主体 --> <!-- 看板主体 -->
<div v-else class="cap-body"> <div v-else class="cap-body">
<header class="cap-topbar">
<div class="cap-title-wrap">
<span class="cap-spark"></span>
<h2 class="cap-title">容量负荷看板</h2>
<span class="cap-sub">高负荷小区分析 · 实时数据</span>
</div>
<div class="cap-controls">
<div class="cap-seg">
<button
v-for="opt in ratOptions"
:key="opt.value"
type="button"
class="cap-seg-btn"
:class="{ active: rat === opt.value }"
@click="switchRat(opt.value)"
>{{ opt.label }}</button>
</div>
<button class="cap-refresh" type="button" :disabled="loadingOverview" title="刷新" @click="reload">
<n-icon><RefreshOutline /></n-icon>
</button>
</div>
</header>
<div class="cap-overview"> <div class="cap-overview">
<n-spin :show="loadingOverview"> <n-spin :show="loadingOverview">
<!-- 汇总卡片 --> <!-- 汇总卡片 -->
@@ -207,7 +184,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, h, onMounted, reactive, ref, type Component } from 'vue'; import { computed, h, onBeforeUnmount, onMounted, reactive, ref, watch, type Component } from 'vue';
import { NTag, darkTheme, lightTheme, useMessage } from 'naive-ui'; import { NTag, darkTheme, lightTheme, useMessage } from 'naive-ui';
import type { DataTableColumns } from 'naive-ui'; import type { DataTableColumns } from 'naive-ui';
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts';
@@ -227,6 +204,7 @@ import {
import { apiGet, downloadGet } from '../api/client'; import { apiGet, downloadGet } from '../api/client';
import { themeName } from '../composables/theme'; import { themeName } from '../composables/theme';
import { resetPageHeader, setPageHeader, type PageHeaderAction } from '../composables/pageHeader';
import EChart from './EChart.vue'; import EChart from './EChart.vue';
type Rat = '4g' | '5g'; type Rat = '4g' | '5g';
@@ -314,10 +292,6 @@ const chartHeight = 'clamp(108px, 13vh, 160px)';
const loadingStatus = ref(true); const loadingStatus = ref(true);
const ready = ref(false); const ready = ref(false);
const rat = ref<Rat>('5g'); const rat = ref<Rat>('5g');
const ratOptions: { label: string; value: Rat }[] = [
{ label: '4G', value: '4g' },
{ label: '5G', value: '5g' }
];
const loadingOverview = ref(false); const loadingOverview = ref(false);
const overview = ref<Overview | null>(null); const overview = ref<Overview | null>(null);
@@ -668,6 +642,23 @@ async function openDetail(id: string) {
} }
} }
// ---------- 顶部框架页头(标题/制式切换/刷新) ----------
function buildHeaderActions(): PageHeaderAction[] {
return [
{ key: 'rat-4g', label: '4G', type: rat.value === '4g' ? 'primary' : 'default', variant: rat.value === '4g' ? 'solid' : 'outline', onClick: () => switchRat('4g') },
{ key: 'rat-5g', label: '5G', type: rat.value === '5g' ? 'primary' : 'default', variant: rat.value === '5g' ? 'solid' : 'outline', onClick: () => switchRat('5g') },
{ key: 'dashboard-refresh', label: '刷新', icon: RefreshOutline, variant: 'outline', loading: loadingOverview, disabled: loadingOverview, onClick: reload }
];
}
function applyPageHeader() {
if (!ready.value) {
resetPageHeader();
return;
}
setPageHeader({ subtitle: '高负荷小区分析 · 实时数据', actions: buildHeaderActions() });
}
watch([rat, ready], applyPageHeader);
function switchRat(value: Rat) { function switchRat(value: Rat) {
if (rat.value === value) return; if (rat.value === value) return;
rat.value = value; rat.value = value;
@@ -695,6 +686,7 @@ function reload() {
} }
onMounted(loadStatus); onMounted(loadStatus);
onBeforeUnmount(resetPageHeader);
</script> </script>
<style scoped> <style scoped>
@@ -792,24 +784,6 @@ onMounted(loadStatus);
.cap-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; } .cap-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
.cap-topbar {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.cap-title-wrap { display: flex; align-items: center; gap: 12px; }
.cap-spark {
width: 6px; height: 24px; border-radius: 3px;
background: linear-gradient(180deg, #22d3ee, #818cf8);
box-shadow: 0 0 16px rgba(56, 189, 248, 0.7);
}
.cap-title { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: 1px; color: var(--cap-text-strong); }
.cap-sub { font-size: 12px; color: var(--cap-text-muted); }
.cap-controls { display: flex; align-items: center; gap: 12px; }
.cap-seg { .cap-seg {
display: inline-flex; display: inline-flex;
padding: 3px; padding: 3px;
@@ -836,16 +810,6 @@ onMounted(loadStatus);
background: linear-gradient(135deg, #22d3ee, #38bdf8); background: linear-gradient(135deg, #22d3ee, #38bdf8);
box-shadow: 0 0 18px rgba(56, 189, 248, 0.45); box-shadow: 0 0 18px rgba(56, 189, 248, 0.45);
} }
.cap-refresh {
display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 32px; border-radius: 9px;
border: 1px solid var(--cap-control-border);
background: var(--cap-control-bg);
color: var(--cap-text-sub); cursor: pointer; font-size: 16px;
transition: all 0.18s ease;
}
.cap-refresh:hover:not(:disabled) { color: #06b6d4; border-color: rgba(56, 189, 248, 0.5); box-shadow: 0 0 16px rgba(56, 189, 248, 0.3); }
.cap-refresh:disabled { opacity: 0.5; cursor: wait; }
/* 概览区(卡片 + 图表),固定高度,由 n-spin 包裹 */ /* 概览区(卡片 + 图表),固定高度,由 n-spin 包裹 */
.cap-overview { flex: 0 0 auto; } .cap-overview { flex: 0 0 auto; }