From 8954db4cf4969166961c72569dfa24645276b947 Mon Sep 17 00:00:00 2001
From: wangrong <wangrong@shsening.com>
Date: 星期日, 30 八月 2026 11:48:15 +0800
Subject: [PATCH] 修复首页问题
---
src/modules/report/views/screen/overview.vue | 19
src/modules/report/components/screen-camera-preview.vue | 16
src/modules/report/views/screen/carbon.vue | 21 -
src/modules/monitor/views/cameras.vue | 117 ++++++-
src/modules/report/components/screen-auto-scroll.vue | 142 ++++++++++
src/modules/report/views/screen/participation.vue | 21 -
src/modules/report/views/screen/index.vue | 17
src/modules/report/views/screen/resource.vue | 21 -
src/modules/report/styles/screen-kpi.scss | 19 +
src/modules/report/data/screen.ts | 22 +
src/modules/report/components/kpi-card.vue | 61 ++-
src/modules/report/views/screen/operation.vue | 154 +++++-----
src/modules/report/hooks/use-screen.ts | 51 +++
src/modules/report/components/screen-shell.vue | 156 +++++++---
14 files changed, 576 insertions(+), 261 deletions(-)
diff --git a/src/modules/monitor/views/cameras.vue b/src/modules/monitor/views/cameras.vue
index 19fe750..94372c4 100644
--- a/src/modules/monitor/views/cameras.vue
+++ b/src/modules/monitor/views/cameras.vue
@@ -1,17 +1,17 @@
<template>
<div class="camera-page">
<div class="scroll-wrapper">
- <el-button class="arrow left" @click="scrollLeft" :disabled="canScrollLeft" circle>
+ <el-button class="arrow left" @click="scrollLeftManual" :disabled="canScrollLeft" circle>
<el-icon><ArrowLeft /></el-icon>
</el-button>
- <div class="scroll-viewport" ref="viewportRef">
+ <div class="scroll-viewport" ref="viewportRef" @mouseenter="pauseAuto" @mouseleave="resumeAuto">
<div class="scroll-track" :style="{ transform: `translateX(${-scrollPos}px)` }">
<div v-for="dev in deviceList" :key="dev.id" class="player-card">
<ezuikit-player
v-if="ysToken"
- :key="'p_' + dev.id + '_' + ysToken"
- :container-id="'player_' + dev.id"
+ :key="'p_' + dev.id + '_' + ysToken + '_' + playerRemountKey"
+ :container-id="'player_' + dev.id + '_' + playerRemountKey"
:access-token="ysToken"
:url="`ezopen://open.ys7.com/${dev.iotCode || dev.code}/1.hd.live`"
:pause-after-first-frame="true"
@@ -20,7 +20,7 @@
</div>
</div>
- <el-button class="arrow right" @click="scrollRight" :disabled="canScrollRight" circle>
+ <el-button class="arrow right" @click="scrollRightManual" :disabled="canScrollRight" circle>
<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
@@ -44,42 +44,100 @@
const loading = ref(false);
const scrollPos = ref(0);
const viewportRef = ref<HTMLElement | null>(null);
+/** 鍏ㄥ睆鍒囨崲鍚庨�掑锛屽己鍒堕噸寤鸿悿鐭虫挱鏀惧櫒 */
+const playerRemountKey = ref(0);
-// 姣忔婊氬姩鐨勮窛绂伙紙涓�涓憚鍍忓ご鐨勫搴� + 闂磋窛锛�
+function onScreenFullscreenChange() {
+ playerRemountKey.value += 1;
+}
+
+// 鑷姩婊氬姩鎺у埗
+const AUTO_INTERVAL = 3000; // 姣忔婊氬姩闂撮殧(ms)
+const autoPlay = ref(true); // 鏄惁鑷姩婊氬姩
+let autoTimer: ReturnType<typeof setInterval> | null = null;
+
+// 姣忔婊氬姩鐨勮窛绂�
const scrollStep = computed(() => {
- const cardWidth = 312; // 300px 瀹藉害 + 12px 闂磋窛鐨勪竴鍗�
+ const cardWidth = 300; // 涓� player-card 瀹藉害涓�鑷�
const gap = 12;
return cardWidth + gap;
});
-// 鏄惁鍙互鍚戝乏婊氬姩
-const canScrollLeft = computed(() => scrollPos.value <= 0);
-
-// 鏄惁鍙互鍚戝彸婊氬姩
-const canScrollRight = computed(() => {
- if (!viewportRef.value) return true;
+// 鍙粴鍔ㄧ殑鏈�澶ц窛绂�
+const maxScroll = computed(() => {
+ if (!viewportRef.value) return 0;
const viewportWidth = viewportRef.value.clientWidth;
const trackWidth = deviceList.value.length * scrollStep.value;
- // 澧炲姞 2px 瀹瑰樊
- return scrollPos.value >= trackWidth - viewportWidth - 2;
+ return Math.max(0, trackWidth - viewportWidth);
});
+
+// 鏄惁鍙互鍚戝乏/鍚戝彸婊氬姩锛堟墜鍔ㄧ澶寸敤锛�
+const canScrollLeft = computed(() => scrollPos.value <= 0);
+const canScrollRight = computed(() => scrollPos.value >= maxScroll.value - 2);
+
+// 鑷姩婊氬姩鏂瑰悜: 1 鍚戝彸, -1 鍚戝乏
+const direction = ref(1);
function scrollLeft() {
scrollPos.value = Math.max(0, scrollPos.value - scrollStep.value);
}
function scrollRight() {
- if (!viewportRef.value) return;
- const viewportWidth = viewportRef.value.clientWidth;
- const trackWidth = deviceList.value.length * scrollStep.value;
- const maxScroll = trackWidth - viewportWidth;
-
- // 濡傛灉鍓╀綑璺濈灏忎簬涓�涓闀匡紝鐩存帴婊氬姩鍒板簳閮�
- if (scrollPos.value + scrollStep.value >= maxScroll) {
- scrollPos.value = maxScroll;
+ const max = maxScroll.value;
+ if (scrollPos.value + scrollStep.value >= max) {
+ scrollPos.value = max;
} else {
scrollPos.value += scrollStep.value;
}
+}
+
+// 鎵ц涓�姝ヨ嚜鍔ㄦ粴鍔紙鍒拌竟鐣岃嚜鍔ㄦ崲鍚戯紝鍥炲埌璧风偣寰幆锛�
+function autoTick() {
+ if (deviceList.value.length === 0) return;
+ if (maxScroll.value <= 0) return; // 涓�灞忚涓嬪叏閮紝涓嶆粴鍔�
+
+ scrollPos.value += scrollStep.value * direction.value;
+
+ if (scrollPos.value >= maxScroll.value) {
+ scrollPos.value = maxScroll.value;
+ direction.value = -1; // 鍒版渶鍙筹紝鍥炲ご鍚戝乏
+ } else if (scrollPos.value <= 0) {
+ scrollPos.value = 0;
+ direction.value = 1; // 鍥炲埌璧风偣锛屽惊鐜悜鍙�
+ pauseAuto(); // 鍥炲埌璧风偣鍋滀竴涓嬶紙鍙�夛紝瑙佷笅鏂� startAuto锛�
+ startAuto(); // 绔嬪嵆寮�濮嬫柊涓�杞�
+ }
+}
+
+function startAuto() {
+ stopAuto();
+ if (!autoPlay.value) return;
+ autoTimer = setInterval(autoTick, AUTO_INTERVAL);
+}
+
+function stopAuto() {
+ if (autoTimer) {
+ clearInterval(autoTimer);
+ autoTimer = null;
+ }
+}
+
+// 榧犳爣鎮仠鏆傚仠锛岀Щ寮�缁х画
+function pauseAuto() {
+ stopAuto();
+}
+function resumeAuto() {
+ startAuto();
+}
+
+// 鎵嬪姩鐐瑰嚮绠ご锛氶噸缃嚜鍔ㄦ粴鍔ㄥ畾鏃跺櫒锛岄伩鍏嶄笌鑷姩鑺傚鍐茬獊
+function scrollLeftManual() {
+ scrollLeft();
+ startAuto();
+}
+function scrollRightManual() {
+ scrollRight();
+ startAuto();
}
async function fetchYsToken() {
@@ -98,6 +156,9 @@
const res = await service.basicdata.iot.getCameras();
deviceList.value = (res || []) as any[];
scrollPos.value = 0;
+ direction.value = 1;
+ // 鏁版嵁鍔犺浇瀹屽啀鍚姩鑷姩婊氬姩
+ startAuto();
} catch (e: any) {
console.error('鑾峰彇鎽勫儚澶村垪琛ㄥけ璐�', e?.response?.data?.message || e.message);
deviceList.value = [];
@@ -107,22 +168,26 @@
}
async function refreshDevices() {
+ stopAuto();
await fetchDevices();
}
function handleKeydown(e: KeyboardEvent) {
- if (e.key === 'ArrowLeft') scrollLeft();
- if (e.key === 'ArrowRight') scrollRight();
+ if (e.key === 'ArrowLeft') { scrollLeftManual(); }
+ if (e.key === 'ArrowRight') { scrollRightManual(); }
}
onMounted(async () => {
window.addEventListener('keydown', handleKeydown);
+ window.addEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
await fetchYsToken();
- await fetchDevices();
+ await fetchDevices(); // 鍐呴儴浼氬湪鏁版嵁灏辩华鍚� startAuto
});
onUnmounted(() => {
+ stopAuto();
window.removeEventListener('keydown', handleKeydown);
+ window.removeEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
});
</script>
diff --git a/src/modules/report/components/kpi-card.vue b/src/modules/report/components/kpi-card.vue
index ed1c0ce..151ebe0 100644
--- a/src/modules/report/components/kpi-card.vue
+++ b/src/modules/report/components/kpi-card.vue
@@ -1,13 +1,10 @@
<template>
- <div class="kpi-card" :class="{ 'is-alert': alert, 'is-demo': demo }">
- <div class="kpi-card__label">
- <span>{{ label }}</span>
- <el-tag v-if="demo" class="kpi-card__badge" type="info" effect="plain" size="small">婕旂ず</el-tag>
- </div>
+ <div class="kpi-card" :class="{ 'is-alert': alert }">
+ <div class="kpi-card__label" :title="label">{{ label }}</div>
<div class="kpi-card__value">
<cl-number :value="numeric" :fixed="fixed" type="amount" :suffix="suffix" />
</div>
- <div v-if="hint" class="kpi-card__hint">{{ hint }}</div>
+ <div class="kpi-card__hint" :title="hint || ''">{{ hint || '\u00A0' }}</div>
</div>
</template>
@@ -21,6 +18,7 @@
fixed?: number;
hint?: string;
alert?: boolean;
+ /** @deprecated 棣栭〉涓嶅啀灞曠ず婕旂ず鏍囪瘑锛屼繚鐣� prop 浠ュ厤璋冪敤鏂规姤閿� */
demo?: boolean;
}>();
@@ -29,31 +27,47 @@
<style lang="scss" scoped>
.kpi-card {
- padding: 14px 16px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 112px;
+ padding: 12px 14px;
border-radius: 12px;
border: 1px solid var(--el-border-color-extra-light);
background: var(--el-bg-color);
color: var(--el-text-color-primary);
- min-height: 96px;
+ overflow: hidden;
&__label {
- display: flex;
- align-items: center;
- gap: 6px;
+ flex-shrink: 0;
+ height: 20px;
+ line-height: 20px;
font-size: 13px;
color: var(--el-text-color-secondary);
- }
-
- &__badge {
- transform: scale(0.92);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
&__value {
- margin-top: 8px;
- font-size: 26px;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ min-height: 0;
+ margin: 4px 0;
+ font-size: 24px;
font-weight: 700;
- line-height: 1.2;
+ line-height: 1.15;
color: var(--el-text-color-primary);
+ overflow: hidden;
+
+ :deep(.cl-number) {
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
:deep(.cl-number__suffix) {
margin-left: 4px;
@@ -64,9 +78,14 @@
}
&__hint {
- margin-top: 8px;
+ flex-shrink: 0;
+ height: 18px;
+ line-height: 18px;
font-size: 12px;
color: var(--el-text-color-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
&.is-alert {
@@ -76,10 +95,6 @@
.kpi-card__value {
color: var(--el-color-danger);
}
- }
-
- &.is-demo:not(.is-alert) {
- border-style: dashed;
}
}
</style>
diff --git a/src/modules/report/components/screen-auto-scroll.vue b/src/modules/report/components/screen-auto-scroll.vue
new file mode 100644
index 0000000..0f7d562
--- /dev/null
+++ b/src/modules/report/components/screen-auto-scroll.vue
@@ -0,0 +1,142 @@
+<template>
+ <div class="auto-scroll" @mouseenter="pause" @mouseleave="resume">
+ <div v-if="$slots.head" class="auto-scroll__head">
+ <slot name="head" />
+ </div>
+ <div ref="viewportRef" class="auto-scroll__viewport">
+ <div ref="contentRef" class="auto-scroll__content">
+ <slot />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+ name: 'report-screen-auto-scroll'
+});
+
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
+
+const props = withDefaults(
+ defineProps<{
+ /** 婊氬姩姝ヨ繘鍍忕礌 */
+ step?: number;
+ /** 婊氬姩闂撮殧 ms */
+ interval?: number;
+ /** 渚濊禆鍙樺寲鏃堕噸鏂版娴嬫槸鍚﹂渶瑕佹粴鍔紙濡傚垪琛ㄦ暟鎹級 */
+ watchKey?: string | number;
+ }>(),
+ {
+ step: 1,
+ interval: 40
+ }
+);
+
+const viewportRef = ref<HTMLElement | null>(null);
+const contentRef = ref<HTMLElement | null>(null);
+const paused = ref(false);
+const direction = ref(1);
+let timer: ReturnType<typeof setInterval> | null = null;
+let resizeObserver: ResizeObserver | null = null;
+
+function canScroll() {
+ const el = viewportRef.value;
+ if (!el) return false;
+ return el.scrollHeight > el.clientHeight + 2;
+}
+
+function tick() {
+ const el = viewportRef.value;
+ if (!el || paused.value || !canScroll()) return;
+
+ const max = el.scrollHeight - el.clientHeight;
+ let next = el.scrollTop + props.step * direction.value;
+
+ if (next >= max) {
+ next = max;
+ direction.value = -1;
+ } else if (next <= 0) {
+ next = 0;
+ direction.value = 1;
+ }
+
+ el.scrollTop = next;
+}
+
+function start() {
+ stop();
+ timer = setInterval(tick, props.interval);
+}
+
+function stop() {
+ if (timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+}
+
+function pause() {
+ paused.value = true;
+}
+
+function resume() {
+ paused.value = false;
+}
+
+async function reset() {
+ await nextTick();
+ const el = viewportRef.value;
+ if (el) el.scrollTop = 0;
+ direction.value = 1;
+}
+
+onMounted(() => {
+ start();
+ const el = viewportRef.value;
+ if (el && typeof ResizeObserver !== 'undefined') {
+ resizeObserver = new ResizeObserver(() => {
+ reset();
+ });
+ resizeObserver.observe(el);
+ if (contentRef.value) resizeObserver.observe(contentRef.value);
+ }
+ window.addEventListener('resize', reset);
+ window.addEventListener('report-screen-fullscreen-change', reset);
+});
+
+onUnmounted(() => {
+ stop();
+ resizeObserver?.disconnect();
+ window.removeEventListener('resize', reset);
+ window.removeEventListener('report-screen-fullscreen-change', reset);
+});
+
+watch(
+ () => props.watchKey,
+ () => reset()
+);
+</script>
+
+<style lang="scss" scoped>
+.auto-scroll {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-height: 0;
+
+ &__head {
+ flex-shrink: 0;
+ }
+
+ &__viewport {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
+ }
+
+ &__content {
+ min-height: min-content;
+ }
+}
+</style>
diff --git a/src/modules/report/components/screen-camera-preview.vue b/src/modules/report/components/screen-camera-preview.vue
index 210bc90..41ac8fb 100644
--- a/src/modules/report/components/screen-camera-preview.vue
+++ b/src/modules/report/components/screen-camera-preview.vue
@@ -9,8 +9,8 @@
<div class="screen-cameras__player">
<ezuikit-player
v-if="ysToken"
- :key="'screen_cam_' + dev.id + '_' + ysToken"
- :container-id="'screen_cam_' + dev.id"
+ :key="'screen_cam_' + dev.id + '_' + ysToken + '_' + playerRemountKey"
+ :container-id="'screen_cam_' + dev.id + '_' + playerRemountKey"
:access-token="ysToken"
:url="cameraUrl(dev)"
:pause-after-first-frame="true"
@@ -27,7 +27,7 @@
name: 'report-screen-camera-preview'
});
-import { computed, onMounted, ref } from 'vue';
+import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useCool } from '/@/cool';
import EzuikitPlayer from '../../ezuikit/components/ezuikit-player.vue';
@@ -45,6 +45,11 @@
const previewList = computed(() => deviceList.value.slice(0, maxCount.value));
const { service } = useCool();
+const playerRemountKey = ref(0);
+
+function onScreenFullscreenChange() {
+ playerRemountKey.value += 1;
+}
const deviceList = ref<any[]>([]);
const ysToken = ref('');
@@ -78,9 +83,14 @@
}
onMounted(async () => {
+ window.addEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
await fetchYsToken();
await fetchDevices();
});
+
+onUnmounted(() => {
+ window.removeEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
+});
</script>
<style lang="scss" scoped>
diff --git a/src/modules/report/components/screen-shell.vue b/src/modules/report/components/screen-shell.vue
index e990e92..23d0b88 100644
--- a/src/modules/report/components/screen-shell.vue
+++ b/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="褰撳墠鎷夸笉鍒版垨鍙e緞寰呯‘璁ょ殑鎸囨爣" 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>
diff --git a/src/modules/report/data/screen.ts b/src/modules/report/data/screen.ts
index 658c00a..a8b56f3 100644
--- a/src/modules/report/data/screen.ts
+++ b/src/modules/report/data/screen.ts
@@ -2,14 +2,24 @@
export type ScreenKey = 'overview' | 'resource' | 'carbon' | 'participation' | 'operation';
export type RankMetric = 'weight' | 'people' | 'pointPerCapita' | 'weightPerCapita' | 'carbon';
-export const SCREEN_NAV: { key: ScreenKey; path: string; index: string; label: string; audience: string }[] = [
- { key: 'overview', path: '/report/screen/overview', index: '01', label: '缁胯壊鏍″洯鎬昏', audience: '鏍¢瀵�' },
- { key: 'resource', path: '/report/screen/resource', index: '02', label: '鍨冨溇璧勬簮鍖栧垎鏋�', audience: '鍚庡嫟 / 鐜繚' },
- { key: 'carbon', path: '/report/screen/carbon', index: '03', label: '纰冲噺鎺掍笌缁胯壊璐$尞', audience: '鍙岀⒊ / ESG' },
- { key: 'participation', path: '/report/screen/participation', index: '04', label: '甯堢敓鍙備笌涓庣⒊绉垎', audience: '瀛﹀伐 / 鍥㈠' },
- { key: 'operation', path: '/report/screen/operation', index: '05', label: '鍥炴敹杩愯惀鐩戞帶', audience: '杩愯惀鍏徃' }
+/** 棣栭〉鑿滃崟鍥哄畾涓� /report/screen/index锛屽瓙椤甸�氳繃 ?page= 鍒囨崲锛岄伩鍏嶇寮�棣栭〉鍏ュ彛 */
+export const SCREEN_HOME_PATH = '/report/screen/index';
+
+export const SCREEN_NAV: { key: ScreenKey; index: string; label: string; audience: string }[] = [
+ { key: 'overview', index: '01', label: '缁胯壊鏍″洯鎬昏', audience: '鏍¢瀵�' },
+ { key: 'resource', index: '02', label: '鍨冨溇璧勬簮鍖栧垎鏋�', audience: '鍚庡嫟 / 鐜繚' },
+ { key: 'carbon', index: '03', label: '纰冲噺鎺掍笌缁胯壊璐$尞', audience: '鍙岀⒊ / ESG' },
+ { key: 'participation', index: '04', label: '甯堢敓鍙備笌涓庣⒊绉垎', audience: '瀛﹀伐 / 鍥㈠' },
+ { key: 'operation', index: '05', label: '鍥炴敹杩愯惀鐩戞帶', audience: '杩愯惀鍏徃' }
];
+/** 浠庤矾鐢辫В鏋愬綋鍓嶅ぇ灞忓瓙椤碉紱index / 绌� / 闈炴硶鍊煎潎鍥炶惤鍒� overview */
+export function resolveScreenKey(page?: string | string[] | null, queryPage?: string | string[] | null): ScreenKey {
+ const raw = String(queryPage || page || 'overview');
+ if (raw === 'index') return 'overview';
+ return SCREEN_NAV.some(e => e.key === raw) ? (raw as ScreenKey) : 'overview';
+}
+
export const TIME_RANGE_OPTIONS: { label: string; value: TimeRange }[] = [
{ label: '浠婃棩', value: 'today' },
{ label: '鏈懆', value: 'week' },
diff --git a/src/modules/report/hooks/use-screen.ts b/src/modules/report/hooks/use-screen.ts
index a6621c0..0b7dc34 100644
--- a/src/modules/report/hooks/use-screen.ts
+++ b/src/modules/report/hooks/use-screen.ts
@@ -3,9 +3,7 @@
import { useTheme } from '/#/theme';
import { useCool } from '/@/cool';
import {
- groupDemoFields,
- isDemoField,
- isDemoSection
+ groupDemoFields
} from '../data/screen-field-meta';
import {
initialScreenData,
@@ -22,6 +20,9 @@
const liveFields = ref<string[]>(initial.liveFields);
const loading = ref(false);
const loadError = ref('');
+/** 椹鹃┒鑸辨槸鍚﹀浜庢祻瑙堝櫒鍏ㄥ睆锛涜悿鐭虫挱鏀惧櫒鍦ㄥ叏灞忓垏鎹㈠悗闇� remount 鎵嶈兘鎭㈠鐢婚潰 */
+const screenFullscreen = ref(false);
+const playerRemountKey = ref(0);
export function provideScreenTime() {
return sharedTimeRange;
@@ -29,6 +30,34 @@
export function useScreenTime() {
return sharedTimeRange;
+}
+
+export function useScreenFullscreen() {
+ function bumpPlayerRemount(delay = 280) {
+ window.setTimeout(() => {
+ playerRemountKey.value += 1;
+ window.dispatchEvent(
+ new CustomEvent('report-screen-fullscreen-change', {
+ detail: { remountKey: playerRemountKey.value }
+ })
+ );
+ }, delay);
+ }
+
+ function setFullscreen(active: boolean) {
+ const changed = screenFullscreen.value !== active;
+ screenFullscreen.value = active;
+ if (changed) {
+ bumpPlayerRemount();
+ }
+ }
+
+ return {
+ isFullscreen: screenFullscreen,
+ playerRemountKey,
+ bumpPlayerRemount,
+ setFullscreen
+ };
}
export function useScreenData() {
@@ -73,7 +102,11 @@
applyResolved(resolveScreenData(res, sharedTimeRange.value));
} catch (e: any) {
applyResolved(resolveScreenData(null, sharedTimeRange.value));
- loadError.value = e?.message || '椹鹃┒鑸辨帴鍙h皟鐢ㄥけ璐�';
+ const msg = e?.message || '';
+ // 棣栭〉闈欓粯闄嶇骇鍒版紨绀烘暟鎹紝涓嶅睍绀虹櫥褰�/鏉冮檺绫婚敊璇枃妗�
+ if (!/鐧诲綍澶辨晥|鏃犳潈闄�/.test(msg)) {
+ loadError.value = msg || '椹鹃┒鑸辨帴鍙h皟鐢ㄥけ璐�';
+ }
} finally {
loading.value = false;
}
@@ -91,18 +124,18 @@
return { load, loading, loadError, dataMode };
}
-/** 鍒ゆ柇鍖哄潡/瀛楁鏄惁涓烘紨绀烘暟鎹紙mixed 妯″紡涓嬫寜瀛楁绾у垽瀹氾級 */
+/** 棣栭〉涓嶅睍绀恒�屾紨绀恒�嶆爣璇嗭紝搴曞眰浠嶅彲闈欓粯浣跨敤婕旂ず鏁版嵁濉厖 */
export function useScreenFieldSource() {
const { dataMode, demoFields } = useScreenData();
const demoSections = computed(() => groupDemoFields(demoFields.value));
- function demoSection(section: string) {
- return isDemoSection(section, demoFields.value, dataMode.value);
+ function demoSection(_section: string) {
+ return false;
}
- function demoField(field: string) {
- return isDemoField(field, demoFields.value, dataMode.value);
+ function demoField(_field: string) {
+ return false;
}
return {
diff --git a/src/modules/report/styles/screen-kpi.scss b/src/modules/report/styles/screen-kpi.scss
new file mode 100644
index 0000000..b123be7
--- /dev/null
+++ b/src/modules/report/styles/screen-kpi.scss
@@ -0,0 +1,19 @@
+/* 椹鹃┒鑸� KPI 琛岋細绛夊绛夐珮鍗$墖缃戞牸 */
+.kpi-grid {
+ display: grid;
+ gap: 10px;
+ margin-bottom: 10px;
+ grid-template-columns: repeat(var(--kpi-cols, 6), minmax(0, 1fr));
+
+ @media (max-width: 1400px) {
+ grid-template-columns: repeat(min(var(--kpi-cols, 6), 4), minmax(0, 1fr));
+ }
+
+ @media (max-width: 992px) {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ @media (max-width: 576px) {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/modules/report/views/screen/carbon.vue b/src/modules/report/views/screen/carbon.vue
index 13110a8..8c06102 100644
--- a/src/modules/report/views/screen/carbon.vue
+++ b/src/modules/report/views/screen/carbon.vue
@@ -1,10 +1,8 @@
<template>
<div class="screen-page">
- <el-row :gutter="10" class="kpi-row">
- <el-col v-for="item in kpis" :key="item.label" :lg="4" :sm="12" :xs="24">
- <kpi-card v-bind="item" :demo="isDemoField(item.field)" />
- </el-col>
- </el-row>
+ <div class="kpi-grid" style="--kpi-cols: 6">
+ <kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
+ </div>
<el-row :gutter="10" class="mid-row">
<el-col :lg="14" :xs="24">
@@ -50,7 +48,7 @@
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data, timeRange } = useScreenData();
-const { isDemoField, isDemoSection } = useScreenFieldSource();
+const { isDemoSection } = useScreenFieldSource();
const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
const kpis = computed(() => [
@@ -148,19 +146,12 @@
</script>
<style lang="scss" scoped>
+@import '../../styles/screen-kpi.scss';
+
.screen-page {
- .kpi-row,
.mid-row {
margin-bottom: 10px;
- }
- .kpi-row {
- :deep(.el-col) {
- margin-bottom: 10px;
- }
- }
-
- .mid-row {
:deep(.panel-card) {
min-height: 360px;
}
diff --git a/src/modules/report/views/screen/index.vue b/src/modules/report/views/screen/index.vue
index f56eea8..cd0b453 100644
--- a/src/modules/report/views/screen/index.vue
+++ b/src/modules/report/views/screen/index.vue
@@ -10,20 +10,19 @@
});
import { computed } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
+import { useRoute } from 'vue-router';
import ScreenShell from '../../components/screen-shell.vue';
import Overview from './overview.vue';
import Resource from './resource.vue';
import Carbon from './carbon.vue';
import Participation from './participation.vue';
import Operation from './operation.vue';
-import { SCREEN_NAV, type ScreenKey } from '../../data/screen';
+import { resolveScreenKey, type ScreenKey } from '../../data/screen';
import { useScreenLoader } from '../../hooks/use-screen';
useScreenLoader();
const route = useRoute();
-const router = useRouter();
const pageMap = {
overview: Overview,
@@ -33,12 +32,8 @@
operation: Operation
};
-const currentKey = computed<ScreenKey>(() => {
- const page = String(route.params.page || 'overview') as ScreenKey;
- return SCREEN_NAV.some(e => e.key === page) ? page : 'overview';
-});
-
-if (!route.params.page) {
- router.replace('/report/screen/overview');
-}
+/** 鍋滅暀鍦� /report/screen/index锛岀敤 query.page锛堟垨鍔ㄦ�佽矾鐢� :page锛夊垏鎹㈠瓙椤碉紝涓嶅啀寮哄埗璺宠蛋 */
+const currentKey = computed<ScreenKey>(() =>
+ resolveScreenKey(route.params.page as string, route.query.page as string)
+);
</script>
diff --git a/src/modules/report/views/screen/operation.vue b/src/modules/report/views/screen/operation.vue
index 42c3164..8356869 100644
--- a/src/modules/report/views/screen/operation.vue
+++ b/src/modules/report/views/screen/operation.vue
@@ -1,39 +1,35 @@
<template>
<div class="screen-page">
- <el-row :gutter="10" class="kpi-row">
- <el-col :lg="3" :sm="8" :xs="12">
- <kpi-card label="鍦ㄧ嚎鍒嗘嫞鍛�" :value="data.kpis.sorterOnline"
- :hint="`${data.kpis.sorterOnline} / ${data.kpis.sorterTotal}`"
- :demo="isDemoField('kpis.sorterOnline')" />
- </el-col>
- <el-col :lg="3" :sm="8" :xs="12">
- <kpi-card label="鍦ㄧ嚎杞﹁締" :value="data.kpis.vehicleOnline"
- :hint="`${data.kpis.vehicleOnline} / ${data.kpis.vehicleTotal}`"
- :demo="isDemoField('kpis.vehicleOnline')" />
- </el-col>
- <el-col :lg="3" :sm="8" :xs="12">
- <kpi-card label="鍦ㄧ嚎鍒嗘嫞鐐�" :value="data.kpis.siteOnline"
- :hint="`${data.kpis.siteOnline} / ${data.kpis.siteTotal}锛屾寜绔欑偣鍚敤`"
- :demo="isDemoField('kpis.siteOnline')" />
- </el-col>
- <el-col :lg="3" :sm="8" :xs="12">
- <kpi-card label="浠婃棩鍥炴敹璁㈠崟" :value="data.kpis.todayOrders" :demo="isDemoField('kpis.todayOrders')" />
- </el-col>
- <el-col :lg="4" :sm="8" :xs="12">
- <kpi-card label="浠婃棩棰勭害 / 寰呭鐞�" :value="data.kpis.todayAppointments"
- :hint="`寰呭鐞� ${data.kpis.pendingAppointments}`"
- :demo="isDemoField('kpis.todayAppointments')" />
- </el-col>
- <el-col :lg="4" :sm="8" :xs="12">
- <kpi-card label="寮傚父鍛婅" :value="data.kpis.alerts" alert
- :hint="`鍋ュ悍寮傚父 ${data.kpis.healthAlerts}锛屼笉灞曠ず闅愮浣撳緛`"
- :demo="isDemoField('kpis.alerts')" />
- </el-col>
- <el-col :lg="4" :sm="8" :xs="12">
- <kpi-card label="杞﹁締瀹夊叏浜嬩欢" :value="safetyCount" hint="瓒呴�� / 鎬ュ埞绛夛紝鏉ヨ嚜杞﹁締鏃ユ眹鎬�"
- :demo="isDemoSection('vehicles')" />
- </el-col>
- </el-row>
+ <div class="kpi-grid" style="--kpi-cols: 7">
+ <kpi-card
+ label="鍦ㄧ嚎鍒嗘嫞鍛�"
+ :value="data.kpis.sorterOnline"
+ :hint="`${data.kpis.sorterOnline} / ${data.kpis.sorterTotal}`"
+ />
+ <kpi-card
+ label="鍦ㄧ嚎杞﹁締"
+ :value="data.kpis.vehicleOnline"
+ :hint="`${data.kpis.vehicleOnline} / ${data.kpis.vehicleTotal}`"
+ />
+ <kpi-card
+ label="鍦ㄧ嚎鍒嗘嫞鐐�"
+ :value="data.kpis.siteOnline"
+ :hint="`${data.kpis.siteOnline} / ${data.kpis.siteTotal}锛屾寜绔欑偣鍚敤`"
+ />
+ <kpi-card label="浠婃棩鍥炴敹璁㈠崟" :value="data.kpis.todayOrders" />
+ <kpi-card
+ label="浠婃棩棰勭害 / 寰呭鐞�"
+ :value="data.kpis.todayAppointments"
+ :hint="`寰呭鐞� ${data.kpis.pendingAppointments}`"
+ />
+ <kpi-card
+ label="寮傚父鍛婅"
+ :value="data.kpis.alerts"
+ alert
+ :hint="`鍋ュ悍寮傚父 ${data.kpis.healthAlerts}锛屼笉灞曠ず闅愮浣撳緛`"
+ />
+ <kpi-card label="杞﹁締瀹夊叏浜嬩欢" :value="safetyCount" hint="瓒呴�� / 鎬ュ埞绛夛紝鏉ヨ嚜杞﹁締鏃ユ眹鎬�" />
+ </div>
<el-row :gutter="10" class="mid-row">
<el-col :lg="14" :xs="24">
@@ -58,7 +54,7 @@
</el-col>
</el-row>
- <el-row :gutter="10">
+ <el-row :gutter="10" class="bottom-row">
<el-col :lg="8" :xs="24">
<panel-card title="鍒嗘嫞鍛樼姸鎬�" sub="鏉ヨ嚜鍒嗘嫞鍛樻棩琛紝鍙眹鎬荤姸鎬侊紝涓嶅睍绀轰綋寰佹槑缁�" :demo="isDemoSection('sorterStatus')">
<div class="status-list">
@@ -72,14 +68,16 @@
</el-col>
<el-col :lg="8" :xs="24">
<panel-card title="杞﹁締杩愯" sub="鍏徃棰嗗鐪嬭繍鍔涗笌瀹夊叏锛屼笉杩涙牎棰嗗棣栭〉" :demo="isDemoSection('vehicles')">
- <div class="table table--vehicle">
- <div class="table__head">
- <span>杞︾墝</span>
- <span>閲岀▼km</span>
- <span>鎬犻�熷垎</span>
- <span>瓒呴��</span>
- <span>鐘舵��</span>
- </div>
+ <screen-auto-scroll class="table table--vehicle" :watch-key="vehicleWatchKey">
+ <template #head>
+ <div class="table__head">
+ <span>杞︾墝</span>
+ <span>閲岀▼km</span>
+ <span>鎬犻�熷垎</span>
+ <span>瓒呴��</span>
+ <span>鐘舵��</span>
+ </div>
+ </template>
<div v-for="item in data.vehicles" :key="item.no" class="table__row">
<span>{{ item.no }}</span>
<span>{{ item.km }}</span>
@@ -87,23 +85,25 @@
<span>{{ item.overspeed }}</span>
<span :class="item.status === '鍦ㄧ嚎' ? 'is-on' : 'is-off'">{{ item.status }}</span>
</div>
- </div>
+ </screen-auto-scroll>
</panel-card>
</el-col>
<el-col :lg="8" :xs="24">
<panel-card title="鍒嗘嫞鐐瑰悶鍚�" sub="杩愯惀缁村害鎵嶄笅閽诲埌鐐逛綅" :demo="isDemoSection('sites')">
- <div class="table table--site">
- <div class="table__head">
- <span>鏀堕泦鐐�</span>
- <span>浠婃棩kg</span>
- <span>鍦ㄧ嚎</span>
- </div>
+ <screen-auto-scroll class="table table--site" :watch-key="siteWatchKey">
+ <template #head>
+ <div class="table__head">
+ <span>鏀堕泦鐐�</span>
+ <span>浠婃棩kg</span>
+ <span>鍦ㄧ嚎</span>
+ </div>
+ </template>
<div v-for="item in data.sites" :key="item.name" class="table__row">
<span>{{ item.name }}</span>
<span>{{ item.weight }}</span>
<span :class="item.online ? 'is-on' : 'is-off'">{{ item.online ? '鍦ㄧ嚎' : '绂荤嚎' }}</span>
</div>
- </div>
+ </screen-auto-scroll>
</panel-card>
</el-col>
</el-row>
@@ -118,13 +118,16 @@
import { computed } from 'vue';
import KpiCard from '../../components/kpi-card.vue';
import PanelCard from '../../components/panel-card.vue';
+import ScreenAutoScroll from '../../components/screen-auto-scroll.vue';
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data } = useScreenData();
-const { isDemoField, isDemoSection } = useScreenFieldSource();
+const { isDemoSection } = useScreenFieldSource();
const { mutedColor, splitColor, tooltip } = useChartTheme();
const safetyCount = computed(() => data.value.vehicles.reduce((s, e) => s + e.overspeed, 0));
+const vehicleWatchKey = computed(() => data.value.vehicles.map(e => e.no).join('|'));
+const siteWatchKey = computed(() => data.value.sites.map(e => e.name).join('|'));
function funnelWidth(value: number) {
const max = data.value.appointmentFlow[0]?.value || 1;
@@ -165,17 +168,11 @@
</script>
<style lang="scss" scoped>
-.screen-page {
+@import '../../styles/screen-kpi.scss';
- .kpi-row,
+.screen-page {
.mid-row {
margin-bottom: 10px;
- }
-
- .kpi-row {
- :deep(.el-col) {
- margin-bottom: 10px;
- }
}
.mid-row,
@@ -272,32 +269,47 @@
}
}
- .table {
- padding: 8px 12px 16px;
- font-size: 13px;
+ .bottom-row {
+ :deep(.panel-card) {
+ min-height: 280px;
+ }
- &--vehicle &__head,
- &--vehicle &__row {
+ :deep(.panel-card__body) {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ overflow: hidden;
+ }
+ }
+
+ .table {
+ padding: 8px 12px 12px;
+ font-size: 13px;
+ height: 100%;
+ min-height: 0;
+
+ &--vehicle :deep(.table__head),
+ &--vehicle .table__row {
grid-template-columns: 1.4fr repeat(4, 0.8fr);
}
- &--site &__head,
- &--site &__row {
+ &--site :deep(.table__head),
+ &--site .table__row {
grid-template-columns: 1.6fr 0.8fr 0.6fr;
}
- &__head,
- &__row {
+ :deep(.table__head),
+ .table__row {
display: grid;
gap: 8px;
padding: 10px 4px;
}
- &__head {
+ :deep(.table__head) {
color: var(--el-text-color-secondary);
}
- &__row {
+ .table__row {
border-top: 1px solid var(--el-border-color-extra-light);
}
diff --git a/src/modules/report/views/screen/overview.vue b/src/modules/report/views/screen/overview.vue
index bab6c1d..5227902 100644
--- a/src/modules/report/views/screen/overview.vue
+++ b/src/modules/report/views/screen/overview.vue
@@ -1,10 +1,8 @@
<template>
<div class="screen-page">
- <el-row :gutter="10" class="kpi-row">
- <el-col v-for="item in kpis" :key="item.label" :xl="3" :lg="6" :sm="12" :xs="24">
- <kpi-card v-bind="item" :demo="isDemoField(item.field)" />
- </el-col>
- </el-row>
+ <div class="kpi-grid" style="--kpi-cols: 8">
+ <kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
+ </div>
<el-row :gutter="10" class="charts-row">
<el-col :lg="7" :xs="24">
@@ -56,7 +54,7 @@
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data, timeRange } = useScreenData();
-const { isDemoField, isDemoSection } = useScreenFieldSource();
+const { isDemoSection } = useScreenFieldSource();
const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
const mixMetric = ref<'weight' | 'value' | 'count'>('weight');
@@ -171,17 +169,12 @@
</script>
<style lang="scss" scoped>
+@import '../../styles/screen-kpi.scss';
+
.screen-page {
- .kpi-row,
.charts-row,
.camera-row {
margin-bottom: 10px;
- }
-
- .kpi-row {
- :deep(.el-col) {
- margin-bottom: 10px;
- }
}
.charts-row {
diff --git a/src/modules/report/views/screen/participation.vue b/src/modules/report/views/screen/participation.vue
index 4653c74..aeba5a7 100644
--- a/src/modules/report/views/screen/participation.vue
+++ b/src/modules/report/views/screen/participation.vue
@@ -1,10 +1,8 @@
<template>
<div class="screen-page">
- <el-row :gutter="10" class="kpi-row">
- <el-col v-for="item in kpis" :key="item.label" :lg="4" :sm="12" :xs="24">
- <kpi-card v-bind="item" :demo="isDemoField(item.field)" />
- </el-col>
- </el-row>
+ <div class="kpi-grid" style="--kpi-cols: 6">
+ <kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
+ </div>
<el-row :gutter="10" class="mid-row">
<el-col :lg="14" :xs="24">
@@ -75,7 +73,7 @@
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data } = useScreenData();
-const { isDemoField, isDemoSection } = useScreenFieldSource();
+const { isDemoSection } = useScreenFieldSource();
const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
const trendKind = ref<'people' | 'times'>('people');
@@ -181,19 +179,12 @@
</script>
<style lang="scss" scoped>
+@import '../../styles/screen-kpi.scss';
+
.screen-page {
- .kpi-row,
.mid-row {
margin-bottom: 10px;
- }
- .kpi-row {
- :deep(.el-col) {
- margin-bottom: 10px;
- }
- }
-
- .mid-row {
:deep(.panel-card) {
min-height: 360px;
}
diff --git a/src/modules/report/views/screen/resource.vue b/src/modules/report/views/screen/resource.vue
index 30b020a..61364cd 100644
--- a/src/modules/report/views/screen/resource.vue
+++ b/src/modules/report/views/screen/resource.vue
@@ -1,10 +1,8 @@
<template>
<div class="screen-page">
- <el-row :gutter="10" class="kpi-row">
- <el-col v-for="item in kpis" :key="item.label" :lg="4" :sm="12" :xs="24">
- <kpi-card v-bind="item" :demo="isDemoField(item.field)" />
- </el-col>
- </el-row>
+ <div class="kpi-grid" style="--kpi-cols: 5">
+ <kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
+ </div>
<el-row :gutter="10" class="mid-row">
<el-col :lg="10" :xs="24">
@@ -40,7 +38,7 @@
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data } = useScreenData();
-const { isDemoField, isDemoSection } = useScreenFieldSource();
+const { isDemoSection } = useScreenFieldSource();
const { textColor, mutedColor, splitColor, primary, tooltip } = useChartTheme();
const kpis = computed(() => [
@@ -131,19 +129,12 @@
</script>
<style lang="scss" scoped>
+@import '../../styles/screen-kpi.scss';
+
.screen-page {
- .kpi-row,
.mid-row {
margin-bottom: 10px;
- }
- .kpi-row {
- :deep(.el-col) {
- margin-bottom: 10px;
- }
- }
-
- .mid-row {
:deep(.panel-card) {
min-height: 360px;
}
--
Gitblit v1.9.1