Browse Source

feat(big-screen): 点击风控大屏菜单直接全屏展示

- 路由 meta 加 fullscreen: true (router/index.ts)
- layout 检测 fullscreen=true 时只渲染 router-view,隐藏 header 和 sidebar (layout/index.vue)
- 大屏页面顶栏新增'浏览器全屏'(浏览器原生 fullscreen API) 与'退出大屏'按钮
- ESC 键监听用于退出浏览器原生全屏
- Header CSS 改为三栏布局 (时钟 / 按钮组),取消绝对定位避免遮挡
yangjingjing 1 day ago
parent
commit
7cf248f270
3 changed files with 100 additions and 24 deletions
  1. 7 1
      src/layout/index.vue
  2. 1 1
      src/router/index.ts
  3. 92 22
      src/views/risk/bigScreen/index.vue

+ 7 - 1
src/layout/index.vue

@@ -1,5 +1,8 @@
 <template>
-  <div class="risk-layout">
+  <!-- 全屏路由直接占满浏览器视口(不显示 header/sidebar) -->
+  <router-view v-if="isFullscreen" :key="route.fullPath" />
+
+  <div v-else class="risk-layout">
     <!-- 顶部蓝色 banner -->
     <header class="risk-header">
       <div class="header-left">
@@ -90,6 +93,9 @@ const activeMenu = computed(() => {
   return path;
 });
 
+// 全屏模式:路由 meta.fullscreen === true 时,不渲染 layout(隐藏 header/sidebar)
+const isFullscreen = computed(() => Boolean(route.meta?.fullscreen));
+
 // 提取顶级可访问菜单
 // 规则:展示平台首页 + 风控平台(/risk)的子菜单作为顶级项 + 关键框架子菜单
 const menuRoutes = computed(() => {

+ 1 - 1
src/router/index.ts

@@ -336,7 +336,7 @@ export const dynamicRoutes: RouteRecordRaw[] = [
         path: 'bigScreen',
         component: () => import('@/views/risk/bigScreen/index.vue'),
         name: 'RiskBigScreen',
-        meta: { title: '风控大屏', icon: 'data-board', permissions: ['risk:bigScreen:view'] }
+        meta: { title: '风控大屏', icon: 'data-board', permissions: ['risk:bigScreen:view'], fullscreen: true }
       }
     ]
   }

+ 92 - 22
src/views/risk/bigScreen/index.vue

@@ -17,6 +17,16 @@
         <div class="bs-time">{{ currentTime }}</div>
         <div class="bs-date">{{ currentDate }}</div>
       </div>
+      <div class="bs-header-actions">
+        <el-button type="primary" size="small" @click="enterFullscreen">
+          <el-icon><FullScreen /></el-icon>
+          <span style="margin-left: 4px">浏览器全屏</span>
+        </el-button>
+        <el-button type="warning" size="small" @click="exitToPlatform">
+          <el-icon><Back /></el-icon>
+          <span style="margin-left: 4px">退出大屏</span>
+        </el-button>
+      </div>
     </div>
 
     <!-- 顶部: 6 张业务数据卡片 -->
@@ -151,12 +161,15 @@ import { onBeforeUnmount, onMounted, ref } from 'vue';
 import * as echarts from 'echarts';
 import {
   Aim,
+  Back,
   Document,
+  FullScreen,
   Shop,
   Tools,
   User,
   Warning
 } from '@element-plus/icons-vue';
+import { useRouter } from 'vue-router';
 import {
   getBigScreenCards,
   getBigScreenCharts,
@@ -216,6 +229,40 @@ const dotColor = (c?: string): string => {
   return '#1890ff';
 };
 
+// ===== 浏览器原生全屏 + 退出大屏 =====
+const router = useRouter();
+
+function enterFullscreen() {
+  const docEl = document.documentElement;
+  const req =
+    docEl.requestFullscreen ||
+    (docEl as any).webkitRequestFullscreen ||
+    (docEl as any).mozRequestFullScreen ||
+    (docEl as any).msRequestFullscreen;
+  if (req) {
+    const ret = req.call(docEl);
+    if (ret && typeof (ret as any).then === 'function') {
+      (ret as Promise<void>).catch(() => {
+        // 用户拒绝则保持 CSS 全屏(layout 已是 router-view 直接占满)
+      });
+    }
+  }
+}
+
+function exitFullscreen() {
+  const doc: any = document;
+  if (doc.exitFullscreen) doc.exitFullscreen();
+  else if (doc.webkitExitFullscreen) doc.webkitExitFullscreen();
+  else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen();
+  else if (doc.msExitFullscreen) doc.msExitFullscreen();
+}
+
+function exitToPlatform() {
+  // 优先尝试退出浏览器原生全屏,再退回首页
+  exitFullscreen();
+  router.push('/index');
+}
+
 // ===== 加载 =====
 async function loadCards() {
   try {
@@ -452,6 +499,39 @@ function renderComplaintBar(months: string[], counts: number[]) {
 }
 
 // ===== 生命周期 =====
+onBeforeUnmount(() => {
+  if (timer) {
+    clearInterval(timer);
+    timer = undefined;
+  }
+  if (streamTimer) {
+    clearInterval(streamTimer);
+    streamTimer = undefined;
+  }
+  if (resizeHandler) {
+    window.removeEventListener('resize', resizeHandler);
+  }
+  document.removeEventListener('fullscreenchange', onFullscreenChange);
+  document.removeEventListener('keydown', onKeyDown);
+  personnelPieChart?.dispose();
+  accidentPieChart?.dispose();
+  trendLineChart?.dispose();
+  complaintBarChart?.dispose();
+});
+
+// ===== 浏览器全屏监听 + ESC 退出 =====
+function onFullscreenChange() {
+  // ESC 退出全屏时只更新状态,不做其他动作
+  // 监听浏览器原生全屏状态变化,留作扩展点
+}
+
+function onKeyDown(e: KeyboardEvent) {
+  if (e.key === 'Escape') {
+    exitFullscreen();
+  }
+  // F11 由浏览器原生处理
+}
+
 onMounted(() => {
   loadAll();
   tickClock();
@@ -465,6 +545,8 @@ onMounted(() => {
     complaintBarChart?.resize();
   };
   window.addEventListener('resize', resizeHandler);
+  document.addEventListener('fullscreenchange', onFullscreenChange);
+  document.addEventListener('keydown', onKeyDown);
 
   setTimeout(() => {
     personnelPieChart?.resize();
@@ -473,24 +555,6 @@ onMounted(() => {
     complaintBarChart?.resize();
   }, 200);
 });
-
-onBeforeUnmount(() => {
-  if (timer) {
-    clearInterval(timer);
-    timer = undefined;
-  }
-  if (streamTimer) {
-    clearInterval(streamTimer);
-    streamTimer = undefined;
-  }
-  if (resizeHandler) {
-    window.removeEventListener('resize', resizeHandler);
-  }
-  personnelPieChart?.dispose();
-  accidentPieChart?.dispose();
-  trendLineChart?.dispose();
-  complaintBarChart?.dispose();
-});
 </script>
 
 <style scoped lang="scss">
@@ -511,9 +575,9 @@ onBeforeUnmount(() => {
   height: 60px;
   display: flex;
   align-items: center;
-  justify-content: space-between;
   margin-bottom: 16px;
-  padding: 0 8px;
+  padding: 0 16px;
+  position: relative;
 
   .bs-title-wrap {
     flex: 1;
@@ -537,9 +601,9 @@ onBeforeUnmount(() => {
   }
 
   .bs-clock {
-    position: absolute;
-    right: 24px;
+    flex: 0 0 auto;
     text-align: right;
+    margin-right: 16px;
   }
 
   .bs-time {
@@ -554,6 +618,12 @@ onBeforeUnmount(() => {
     color: #aac8ff;
     margin-top: 2px;
   }
+
+  .bs-header-actions {
+    flex: 0 0 auto;
+    display: flex;
+    gap: 8px;
+  }
 }
 
 // ===== 卡片行 =====