wangrong
2026-08-30 8954db4cf4969166961c72569dfa24645276b947
src/modules/report/components/screen-shell.vue
@@ -4,7 +4,7 @@
         <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>
@@ -16,7 +16,7 @@
               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>
@@ -24,17 +24,6 @@
         </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" />
@@ -43,20 +32,6 @@
      <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>
@@ -65,32 +40,46 @@
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() {
@@ -127,6 +116,7 @@
   &.is-fullscreen {
      padding: 12px;
      height: 100vh;
      min-height: 100vh;
      background: #07111f;
@@ -137,6 +127,77 @@
      .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;
            }
         }
      }
   }
@@ -223,18 +284,5 @@
   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>