<template>
|
<div ref="rootRef" class="green-screen" :class="{ 'is-fullscreen': isFullscreen }">
|
<header class="green-screen__header">
|
<div class="brand">
|
<div class="brand__mark" />
|
<div>
|
<div class="brand__title">校园绿色运营驾驶舱</div>
|
<div class="brand__sub">{{ current?.audience }}视角 · {{ current?.label }}</div>
|
</div>
|
</div>
|
|
<nav class="nav">
|
<button
|
v-for="item in SCREEN_NAV"
|
:key="item.key"
|
class="nav__item"
|
:class="{ 'is-active': item.key === currentKey }"
|
type="button"
|
@click="go(item.path)"
|
>
|
<span class="nav__index">{{ item.index }}</span>
|
<span>{{ item.label }}</span>
|
</button>
|
</nav>
|
|
<div class="tools">
|
<el-tag v-if="loadError" type="danger" effect="plain" size="small">{{ loadError }}</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" />
|
</div>
|
</header>
|
|
<section v-loading="loading" class="green-screen__body">
|
<slot />
|
<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>
|
|
<script lang="ts" setup>
|
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 } from '../hooks/use-screen';
|
|
const route = useRoute();
|
const router = useRouter();
|
const timeRange = provideScreenTime();
|
const { data, loading, loadError, reload } = useScreenData();
|
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 current = computed(() => SCREEN_NAV.find(e => e.key === currentKey.value));
|
|
function go(path: string) {
|
if (path !== route.path) router.push(path);
|
}
|
|
function syncFullscreen() {
|
isFullscreen.value = document.fullscreenElement === rootRef.value;
|
}
|
|
async function toggleFullscreen() {
|
const el = rootRef.value;
|
if (!el) return;
|
|
if (document.fullscreenElement === el) {
|
await document.exitFullscreen();
|
} else if (document.fullscreenElement) {
|
await document.exitFullscreen();
|
await el.requestFullscreen();
|
} else {
|
await el.requestFullscreen();
|
}
|
}
|
|
onMounted(() => {
|
document.addEventListener('fullscreenchange', syncFullscreen);
|
});
|
|
onUnmounted(() => {
|
document.removeEventListener('fullscreenchange', syncFullscreen);
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.green-screen {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
min-height: calc(100vh - 118px);
|
background: linear-gradient(180deg, var(--el-bg-color-page) 0%, var(--el-fill-color-lighter) 100%);
|
color: var(--el-text-color-primary);
|
|
&.is-fullscreen {
|
padding: 12px;
|
min-height: 100vh;
|
background: #07111f;
|
|
.green-screen__header,
|
.brand__title {
|
color: #e5e7eb;
|
}
|
|
.brand__sub {
|
color: #94a3b8;
|
}
|
}
|
|
&__header {
|
display: flex;
|
align-items: center;
|
gap: 16px;
|
padding: 8px 4px 12px;
|
flex-shrink: 0;
|
}
|
|
&__body {
|
flex: 1;
|
min-height: 0;
|
overflow: auto;
|
padding-bottom: 8px;
|
}
|
}
|
|
.brand {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
min-width: 220px;
|
|
&__mark {
|
width: 12px;
|
height: 36px;
|
border-radius: 6px;
|
background: linear-gradient(180deg, #22c55e, #16a34a);
|
}
|
|
&__title {
|
font-size: 18px;
|
font-weight: 700;
|
letter-spacing: 0.5px;
|
line-height: 1.2;
|
}
|
|
&__sub {
|
margin-top: 2px;
|
font-size: 12px;
|
color: var(--el-text-color-secondary);
|
}
|
}
|
|
.nav {
|
display: flex;
|
flex: 1;
|
gap: 8px;
|
overflow-x: auto;
|
|
&__item {
|
display: flex;
|
align-items: center;
|
gap: 6px;
|
height: 36px;
|
padding: 0 12px;
|
border: 1px solid var(--el-border-color-extra-light);
|
border-radius: 999px;
|
background: var(--el-bg-color);
|
color: var(--el-text-color-regular);
|
white-space: nowrap;
|
cursor: pointer;
|
transition: all 0.2s ease;
|
|
&:hover,
|
&.is-active {
|
border-color: var(--el-color-primary-light-5);
|
color: var(--el-color-primary);
|
background: var(--el-color-primary-light-9);
|
}
|
}
|
|
&__index {
|
font-size: 11px;
|
font-weight: 700;
|
opacity: 0.7;
|
}
|
}
|
|
.tools {
|
display: flex;
|
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>
|