From 52a3f616b92c2ac014facdc9e1567f196282b53e Mon Sep 17 00:00:00 2001 From: Nixevol Date: Fri, 26 Jun 2026 09:13:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=9C=8B=E6=9D=BF?= =?UTF-8?q?=E5=B0=8F=E5=8C=BA=E8=AF=A6=E6=83=85=E6=8A=BD=E5=B1=89=EF=BC=88?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E4=B8=A4=E8=A1=8C=E3=80=81=E5=8E=BB=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E6=A0=87=E7=AD=BE=E3=80=81=E6=8C=87=E6=A0=87=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E3=80=81=E5=BB=BA=E8=AE=AE=E5=A4=8D=E5=88=B6=E3=80=81?= =?UTF-8?q?=E5=90=8C=E6=89=87=E5=8C=BA=E5=B0=8F=E5=8C=BA=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/project_context.md | 9 +++ frontend/src/components/CapacityDashboard.vue | 70 ++++++++++++++----- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/docs/project_context.md b/docs/project_context.md index ded9433..699d965 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -962,3 +962,12 @@ CellData 处理日志细化(`app/services/cell_data.py`): - 站型分布、频段标记小区也排除「未知」:`overview` 的 `by_station`、`by_freq` 调用加 `skip_unknown=True`(同制式分布,`WHERE 列 IS NOT NULL AND <> ''`)。 - 矮屏可滚动:`.cap-dashboard` `overflow:hidden` → `overflow-y:auto`(仍固定 `height:calc(100vh-64px)`);`.cap-list` `min-height:0` → `min-height:300px`。视口够高时 flex 填满不滚动;过矮时卡片+图表+清单(≥300px) 总高超出 → 整页出纵向滚动条,可滚到问题小区清单。移除原 `@media(<=1180px)` 里多余的 `height:auto/overflow` 覆盖(统一由主规则处理)。 - 验证:后端 `py_compile` 通过;`frontend` `vue-tsc --noEmit` 通过。 + +## 2026-06-26:看板小区详情抽屉优化 + +- 抽屉标题 CGI 与小区名称上下两行:`.cap-detail-head` 改 `flex-direction:column`。 +- 标签区去掉重复的「频段」tag(5G 制式值与频段常同为如 700M 显得重复),保留 制式/带宽/站型。 +- 指标卡片重排为:物理站 → 扇区 → 上行利用率 → 下行利用率 → 日均流量 → 用户数 → 小区功率 → 是否高负荷(`detailMetrics` 顺序调整)。 +- 优化建议右上角加复制按钮:`copySuggestion()` 复用 `composables/clipboard.writeClipboardText` 复制 `detailSuggestion`,成功/失败 `message` 提示;`.cap-suggest-head` 改 space-between 布局。 +- 同扇区同运营商小区每项加详情图标按钮(`OpenOutline`),点击 `openDetail(sib.id)` 在抽屉内切换到该小区详情;`.cap-sib-item` 改 flex 行(`.cap-sib-body` 占主 + 右侧 `.cap-icon-btn`)。新增通用 `.cap-icon-btn` 样式(复制/详情共用)。 +- 验证:`frontend` `vue-tsc --noEmit` 通过。 diff --git a/frontend/src/components/CapacityDashboard.vue b/frontend/src/components/CapacityDashboard.vue index a171d27..20c8e88 100644 --- a/frontend/src/components/CapacityDashboard.vue +++ b/frontend/src/components/CapacityDashboard.vue @@ -142,7 +142,6 @@ {{ detailValue('制式') }} {{ detailValue('带宽') }} {{ detailValue('站型') }} - {{ detailValue('频段') }}
@@ -153,7 +152,12 @@
-
优化建议
+
+ 优化建议 + +

{{ detailSuggestion }}

@@ -162,17 +166,22 @@
-
- {{ sib.id }} - {{ sib.name }} -
-
- {{ sib.band || '-' }} - {{ sib.freq || '-' }} - 下行 {{ sib.dl }}% - 流量 {{ sib.flow }}GB - {{ sib.problem }} +
+
+ {{ sib.id }} + {{ sib.name }} +
+
+ {{ sib.band || '-' }} + {{ sib.freq || '-' }} + 下行 {{ sib.dl }}% + 流量 {{ sib.flow }}GB + {{ sib.problem }} +
+
@@ -194,8 +203,10 @@ import { BulbOutline, CellularOutline, CloudUploadOutline, + CopyOutline, DownloadOutline, FlashOutline, + OpenOutline, PulseOutline, RefreshOutline, SearchOutline, @@ -203,6 +214,7 @@ import { } from '@vicons/ionicons5'; import { apiGet, downloadGet } from '../api/client'; +import { writeClipboardText } from '../composables/clipboard'; import { themeName } from '../composables/theme'; import { resetPageHeader, setPageHeader, type PageHeaderAction } from '../composables/pageHeader'; import EChart from './EChart.vue'; @@ -552,13 +564,13 @@ const detailMetrics = computed(() => { const pct = (key: string) => { const v = det[key]; return v === '' || v === null || v === undefined ? '-' : `${(Number(v) * 100).toFixed(1)}%`; }; const raw = (key: string) => { const v = det[key]; return v === '' || v === null || v === undefined ? '-' : String(v); }; return [ + { label: '物理站', value: raw('物理站') }, + { label: '扇区', value: raw('扇区') }, { label: d.labels.ul, value: pct(d.ul_field) }, { label: d.labels.dl, value: pct(d.dl_field) }, { label: '日均流量(GB)', value: raw(d.flow_field) }, { label: '用户数', value: raw(d.users_field) }, { label: '小区功率', value: raw('功率') }, - { label: '扇区', value: raw('扇区') }, - { label: '物理站', value: raw('物理站') }, { label: '是否高负荷', value: raw('是否高负荷小区') } ]; }); @@ -628,6 +640,16 @@ async function exportCells() { } } +async function copySuggestion() { + if (!detailSuggestion.value) return; + try { + await writeClipboardText(detailSuggestion.value); + message.success('优化建议已复制'); + } catch { + message.error('复制失败'); + } +} + async function openDetail(id: string) { detailVisible.value = true; loadingDetail.value = true; @@ -891,9 +913,9 @@ onBeforeUnmount(resetPageHeader); .cap-pager-total { font-size: 12px; color: var(--cap-text-muted); } /* 详情抽屉 */ -.cap-detail-head { display: flex; align-items: baseline; gap: 10px; min-width: 0; } -.cap-detail-id { font-weight: 700; font-size: 15px; color: var(--cap-text-strong); } -.cap-detail-name { font-size: 12px; color: var(--cap-text-sub); overflow: hidden; text-overflow: ellipsis; } +.cap-detail-head { display: flex; flex-direction: column; gap: 2px; min-width: 0; } +.cap-detail-id { font-weight: 700; font-size: 15px; color: var(--cap-text-strong); line-height: 1.3; } +.cap-detail-name { font-size: 12px; color: var(--cap-text-sub); line-height: 1.4; } .cap-detail { display: flex; flex-direction: column; gap: 16px; } .cap-detail-tags { display: flex; flex-wrap: wrap; gap: 8px; } .cap-tag { padding: 3px 12px; border-radius: 999px; font-size: 12px; font-weight: 600; } @@ -908,10 +930,20 @@ onBeforeUnmount(resetPageHeader); .cap-metric-label { font-size: 12px; color: var(--cap-text-sub); } .cap-metric-value { margin-top: 4px; font-size: 16px; font-weight: 700; color: var(--cap-text-strong); } .cap-suggest { padding: 12px 14px; border-radius: 12px; background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.32); } -.cap-suggest-head { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d97706; margin-bottom: 6px; } +.cap-suggest-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; } +.cap-suggest-title { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d97706; } .cap-suggest-text { margin: 0; font-size: 13px; line-height: 1.7; color: var(--cap-text); } +.cap-icon-btn { + display: inline-flex; align-items: center; justify-content: center; + width: 28px; height: 28px; flex: 0 0 auto; + border-radius: 8px; border: 1px solid var(--cap-panel-border); + background: var(--cap-soft-bg); color: var(--cap-text-sub); + cursor: pointer; font-size: 15px; transition: all 0.18s ease; +} +.cap-icon-btn:hover { color: #06b6d4; border-color: rgba(56, 189, 248, 0.5); } .cap-sib-list { display: flex; flex-direction: column; gap: 8px; } -.cap-sib-item { padding: 10px 12px; border-radius: 10px; border: 1px solid var(--cap-panel-border); background: var(--cap-soft-bg); } +.cap-sib-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--cap-panel-border); background: var(--cap-soft-bg); } +.cap-sib-body { flex: 1 1 auto; min-width: 0; } .cap-sib-main { display: flex; align-items: baseline; gap: 8px; } .cap-sib-id { font-weight: 600; font-size: 13px; color: var(--cap-text); } .cap-sib-name { font-size: 12px; color: var(--cap-text-sub); overflow: hidden; text-overflow: ellipsis; }