| | |
| | | <div class="brand"> |
| | | <div class="brand__mark" /> |
| | | <div> |
| | | <div class="brand__title">校园绿色运营驾驶舱</div> |
| | | <div class="brand__title">绿色校园运营驾驶舱</div> |
| | | <div class="brand__sub">{{ current?.audience }}视角 · {{ current?.label }}</div> |
| | | </div> |
| | | </div> |
| | |
| | | class="nav__item" |
| | | :class="{ 'is-active': item.key === currentKey }" |
| | | type="button" |
| | | @click="go(item.path)" |
| | | @click="go(item.key)" |
| | | > |
| | | <span class="nav__index">{{ item.index }}</span> |
| | | <span>{{ item.label }}</span> |
| | |
| | | </nav> |
| | | |
| | | <div class="tools"> |
| | | <el-tag v-if="loadError" type="danger" effect="plain" size="small">{{ loadError }}</el-tag> |
| | | <el-tag v-if="dataMode === 'demo'" type="info" effect="plain" size="small"> |
| | | 演示数据 · 待业务数据接入 |
| | | </el-tag> |
| | | <el-tag v-else-if="dataMode === 'mixed'" type="warning" effect="plain" size="small"> |
| | | 部分演示 · 部分指标待补数 |
| | | </el-tag> |
| | | <el-tag v-else-if="unavailable.length" type="warning" effect="plain" size="small"> |
| | | 部分指标待补数 |
| | | </el-tag> |
| | | <el-tag v-else type="success" effect="plain" size="small">实时数据</el-tag> |
| | | <cl-select-button v-model="timeRange" :options="TIME_RANGE_OPTIONS" small /> |
| | | <el-button :icon="Refresh" circle :loading="loading" @click="reload" /> |
| | | <el-button :icon="isFullscreen ? Close : FullScreen" circle @click="toggleFullscreen" /> |
| | |
| | | |
| | | <section v-loading="loading" class="green-screen__body"> |
| | | <slot /> |
| | | <el-collapse v-if="dataMode === 'mixed' && demoSections.length" class="missing"> |
| | | <el-collapse-item title="当前为演示数据的区块" name="demo"> |
| | | <ul> |
| | | <li v-for="item in demoSections" :key="item.section">{{ item.label }}</li> |
| | | </ul> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | <el-collapse v-if="unavailable.length" class="missing"> |
| | | <el-collapse-item title="当前拿不到或口径待确认的指标" name="1"> |
| | | <ul> |
| | | <li v-for="item in unavailable" :key="item">{{ item }}</li> |
| | | </ul> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </section> |
| | | </div> |
| | | </template> |
| | |
| | | import { computed, onMounted, onUnmounted, ref } from 'vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | import { Close, FullScreen, Refresh } from '@element-plus/icons-vue'; |
| | | import { SCREEN_NAV, TIME_RANGE_OPTIONS, type ScreenKey } from '../data/screen'; |
| | | import { provideScreenTime, useScreenData, useScreenFieldSource } from '../hooks/use-screen'; |
| | | import { |
| | | SCREEN_HOME_PATH, |
| | | SCREEN_NAV, |
| | | TIME_RANGE_OPTIONS, |
| | | resolveScreenKey, |
| | | type ScreenKey |
| | | } from '../data/screen'; |
| | | import { provideScreenTime, useScreenData, useScreenFullscreen } from '../hooks/use-screen'; |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const timeRange = provideScreenTime(); |
| | | const { data, dataMode, loading, loadError, reload } = useScreenData(); |
| | | const { demoSections } = useScreenFieldSource(); |
| | | const { loading, reload } = useScreenData(); |
| | | const { isFullscreen, setFullscreen } = useScreenFullscreen(); |
| | | const rootRef = ref<HTMLElement>(); |
| | | const isFullscreen = ref(false); |
| | | |
| | | const unavailable = computed(() => data.value.unavailable || []); |
| | | |
| | | const currentKey = computed<ScreenKey>(() => { |
| | | const page = String(route.params.page || 'overview') as ScreenKey; |
| | | return SCREEN_NAV.some(e => e.key === page) ? page : 'overview'; |
| | | }); |
| | | const currentKey = computed<ScreenKey>(() => |
| | | resolveScreenKey(route.params.page as string, route.query.page as string) |
| | | ); |
| | | |
| | | const current = computed(() => SCREEN_NAV.find(e => e.key === currentKey.value)); |
| | | |
| | | function go(path: string) { |
| | | if (path !== route.path) router.push(path); |
| | | /** 始终留在首页路径,用 ?page= 切换 pageMap,避免跳到 /overview 丢掉导航壳 */ |
| | | function go(key: ScreenKey) { |
| | | if (key === currentKey.value) return; |
| | | const query = { ...route.query } as Record<string, any>; |
| | | if (key === 'overview') { |
| | | delete query.page; |
| | | } else { |
| | | query.page = key; |
| | | } |
| | | router.replace({ path: SCREEN_HOME_PATH, query }); |
| | | } |
| | | |
| | | function syncFullscreen() { |
| | | isFullscreen.value = document.fullscreenElement === rootRef.value; |
| | | setFullscreen(document.fullscreenElement === rootRef.value); |
| | | // 全屏后触发布局重算,让 echarts / 子页 flex 高度铺满 |
| | | window.setTimeout(() => { |
| | | window.dispatchEvent(new Event('resize')); |
| | | }, 320); |
| | | } |
| | | |
| | | async function toggleFullscreen() { |
| | |
| | | |
| | | &.is-fullscreen { |
| | | padding: 12px; |
| | | height: 100vh; |
| | | min-height: 100vh; |
| | | background: #07111f; |
| | | |
| | |
| | | |
| | | .brand__sub { |
| | | color: #94a3b8; |
| | | } |
| | | |
| | | .green-screen__body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | |
| | | :deep(> *) { |
| | | flex: 1; |
| | | min-height: 0; |
| | | height: 100%; |
| | | } |
| | | |
| | | /* 02/03/04 等子页在全屏下纵向铺满 */ |
| | | :deep(.screen-page) { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | |
| | | .kpi-row, |
| | | .kpi-grid { |
| | | flex-shrink: 0; |
| | | margin-bottom: 0 !important; |
| | | } |
| | | |
| | | .mid-row, |
| | | .el-row:not(.kpi-row) { |
| | | flex: 1; |
| | | min-height: 0; |
| | | margin-bottom: 0 !important; |
| | | display: flex !important; |
| | | |
| | | > .el-col { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | } |
| | | |
| | | .panel-card { |
| | | flex: 1; |
| | | min-height: 0 !important; |
| | | height: 100%; |
| | | } |
| | | |
| | | .panel-card__body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .chart, |
| | | .chart--bar, |
| | | .chart--value, |
| | | .chart--rank, |
| | | .flow, |
| | | .note { |
| | | flex: 1; |
| | | min-height: 0; |
| | | height: 100% !important; |
| | | } |
| | | |
| | | /* 全屏时强制刷新 echarts 容器高度 */ |
| | | .echarts, |
| | | [_echarts_instance_] { |
| | | height: 100% !important; |
| | | width: 100% !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | align-items: center; |
| | | gap: 10px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .missing { |
| | | margin: 8px 0 0; |
| | | border: none; |
| | | |
| | | ul { |
| | | margin: 0; |
| | | padding-left: 18px; |
| | | line-height: 1.8; |
| | | color: var(--el-text-color-regular); |
| | | font-size: 13px; |
| | | } |
| | | } |
| | | </style> |