Commit Graph

14 Commits

Author SHA1 Message Date
chenlin
caad49998c no message 2025-12-13 20:55:17 +08:00
chenlin
e1373b98cb Merge branch 'dev' of http://120.46.213.136:9528/isoftstone/lc_frontend into dev 2025-12-13 18:14:36 +08:00
chenlin
3e9ac2b357 bug修复 2025-12-13 18:14:32 +08:00
7832cec925 拿掉在线率 2025-12-12 11:49:06 +08:00
968cc98f55 大屏调整 2025-12-12 11:01:19 +08:00
c49b23040c 动环接口调整 2025-12-12 10:09:30 +08:00
chenlin
ad45c913d8 动环接口对接 2025-12-12 09:09:41 +08:00
chenlin
4bca8652d6 动环监控大屏 2025-12-03 15:25:06 +08:00
chenlin
685a02ac01 <template>
<div class="big-screen-container">
    <!-- 标题栏 -->
    <div class="screen-header">
      <h1 class="screen-title">动环监控大屏</h1>
      <div class="screen-datetime">{{ currentDateTime }}</div>
    </div>

    <!-- 主要内容区域 -->
    <div class="screen-content">
      <!-- 第一行:统计卡片 -->
      <div class="stats-row">
        <!-- 总设备数卡片 -->
        <div class="stat-card">
          <div class="card-title">总设备数</div>
          <div class="card-value">{{ totalDevices.toLocaleString() }}</div>
          <div class="card-trend growth">
            <svg class="trend-icon" viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor">
              <path d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"></path>
            </svg>
            <span>较上月增长 {{ totalDevicesGrowth }}%</span>
          </div>
        </div>

        <!-- 在线设备数卡片 -->
        <div class="stat-card">
          <div class="card-title">在线设备数</div>
          <div class="card-value">{{ onlineDevices.toLocaleString() }}</div>
          <div class="card-trend online-rate">
            <span>在线率 {{ onlineRate }}%</span>
          </div>
        </div>
      </div>

      <!-- 第二行:图表 -->
      <div class="charts-row">
        <!-- 告警趋势折线图 -->
        <div class="chart-card">
          <div class="chart-title">当月告警趋势</div>
          <div ref="alarmTrendChartRef" class="chart-container"></div>
        </div>

        <!-- 设备类型分布饼图 -->
        <div class="chart-card">
          <div class="chart-title">告警设备类型分布</div>
          <div ref="deviceTypePieChartRef" class="chart-container"></div>
        </div>
      </div>

      <!-- 第三行:园区信息列表 -->
      <div class="table-row">
        <div class="table-card">
          <div class="table-title">园区信息列表</div>
          <el-table
            :data="parkList"
            stripe
            style="width: 100%"
            class="park-table"
            height="calc(100% - 50px)"
            :header-cell-style="{
              background: 'rgba(78, 155, 248, 0.08)',
              color: '#ffffff',
              borderBottom: '1px solid rgba(78, 155, 248, 0.15)',
              fontWeight: '500',
              padding: '12px 8px'
            }"
            :row-style="{
              background: 'transparent',
              color: '#ffffff'
            }"
            :cell-style="{
              borderBottom: '1px solid rgba(78, 155, 248, 0.08)',
              color: '#ffffff',
              padding: '12px 8px'
            }"
          >
            <el-table-column prop="parkName" label="园区名称" width="180" align="left" />
            <el-table-column prop="totalDevices" label="设备总数" width="100" align="center">
              <template #default="{ row }">
                <span class="device-count">{{ row.totalDevices }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="upsStatus" label="UPS" width="120" align="center">
              <template #default="{ row }">
                <span :class="['status-text', row.upsStatus === '正常' ? 'status-normal' : 'status-alarm']">
                  {{ row.upsStatus }}
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="airConditionStatus" label="精密空调" width="120" align="center">
              <template #default="{ row }">
                <span :class="['status-text', row.airConditionStatus === '正常' ? 'status-normal' : 'status-alarm']">
                  {{ row.airConditionStatus }}
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="temperatureStatus" label="温湿度" width="120" align="center">
              <template #default="{ row }">
                <span :class="['status-text', row.temperatureStatus === '正常' || typeof row.temperatureStatus === 'number' ? 'status-normal' : 'status-alarm']">
                  {{ row.temperatureStatus }}
                </span>
              </template>
            </el-table-column>
            <el-table-column prop="otherStatus" label="其他" width="120" align="center">
              <template #default="{ row }">
                <span :class="['status-text', row.otherStatus === '正常' ? 'status-normal' : 'status-alarm']">
                  {{ row.otherStatus }}
                </span>
              </template>
            </el-table-column>
            <el-table-column label="详情" width="150" align="center">
              <template #default="{ row }">
                <a
                  :class="['detail-link', row.hasAlarm ? 'detail-alarm' : 'detail-normal']"
                  @click="handleViewDetail(row)"
                >
                  <span v-if="row.hasAlarm" class="alarm-badge">异常</span>
                  <span v-else class="normal-badge">正常</span>
                  查看详情
                </a>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';

// 定义园区信息类型
interface ParkInfo {
  key: string;
  parkName: string;
  totalDevices: number;
  upsStatus: string;
  airConditionStatus: string;
  temperatureStatus: string | number;
  otherStatus: string;
  hasAlarm: boolean;
}

// 当前日期时间
const currentDateTime = ref('');

// 统计数据
const totalDevices = ref(1286);
const totalDevicesGrowth = ref(5.2);
const onlineDevices = ref(1254);
const onlineRate = ref(97.5);

// 图表引用
const alarmTrendChartRef = ref<HTMLDivElement>();
const deviceTypePieChartRef = ref<HTMLDivElement>();

// 图表实例
let alarmTrendChart: echarts.ECharts | null = null;
let deviceTypePieChart: echarts.ECharts | null = null;

// 告警趋势数据
const alarmTrendData = ref({
  dates: ['5日', '10日', '15日', '20日', '25日', '30日'],
  values: [8, 4, 20, 16, 21, 9]
});

// 设备类型分布数据
const deviceTypeData = ref([
  { name: 'UPS', value: 35 },
  { name: '精密空调', value: 25 },
  { name: '温湿度检测', value: 20 },
  { name: '其他', value: 20 }
]);

// 园区信息列表数据
const parkList = ref<ParkInfo[]>([
  {
    key: '1',
    parkName: '雄安总部',
    totalDevices: 156,
    upsStatus: '3个告警',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: true
  },
  {
    key: '2',
    parkName: '恒毅大厦',
    totalDevices: 98,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '3',
    parkName: '丰台创新中心',
    totalDevices: 124,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: 2,
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '4',
    parkName: '重庆产业大厦',
    totalDevices: 92,
    upsStatus: '正常',
    airConditionStatus: '2个告警',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: true
  },
  {
    key: '5',
    parkName: '海南园区',
    totalDevices: 78,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '6',
    parkName: '雄安地面站',
    totalDevices: 64,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '7',
    parkName: '铜川地面站',
    totalDevices: 58,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '8',
    parkName: '佳木斯地面站',
    totalDevices: 72,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '9',
    parkName: '库尔勒地面站',
    totalDevices: 68,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: 1,
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '10',
    parkName: '澄迈地面站',
    totalDevices: 56,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  },
  {
    key: '11',
    parkName: '文昌地面站',
    totalDevices: 60,
    upsStatus: '正常',
    airConditionStatus: '正常',
    temperatureStatus: '正常',
    otherStatus: '正常',
    hasAlarm: false
  }
]);

// 更新日期时间
const updateDateTime = () => {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours()).padStart(2, '0');
  const minutes = String(now.getMinutes()).padStart(2, '0');
  const seconds = String(now.getSeconds()).padStart(2, '0');
  currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};

// 初始化告警趋势图表
const initAlarmTrendChart = () => {
  if (!alarmTrendChartRef.value) return;

  alarmTrendChart = echarts.init(alarmTrendChartRef.value);

  const option: EChartsOption = {
    grid: {
      top: '15%',
      left: '8%',
      right: '5%',
      bottom: '12%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      data: alarmTrendData.value.dates,
      axisLine: {
        lineStyle: {
          color: '#3a5165'
        }
      },
      axisLabel: {
        color: '#8b9bb3',
        fontSize: 12
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: '#1e3a52',
          type: 'dashed'
        }
      }
    },
    yAxis: {
      type: 'value',
      min: 0,
      max: 25,
      interval: 5,
      axisLine: {
        show: false
      },
      axisLabel: {
        color: '#8b9bb3',
        fontSize: 12
      },
      splitLine: {
        lineStyle: {
          color: '#1e3a52',
          type: 'dashed'
        }
      }
    },
    series: [
      {
        data: alarmTrendData.value.values,
        type: 'line',
        smooth: true,
        lineStyle: {
          color: '#4e9bf8',
          width: 2
        },
        itemStyle: {
          color: '#4e9bf8',
          borderWidth: 2
        },
        areaStyle: {
          color: {
            type: 'linear',
            x: 0,
            y: 0,
            x2: 0,
            y2: 1,
            colorStops: [
              { offset: 0, color: 'rgba(78, 155, 248, 0.3)' },
              { offset: 1, color: 'rgba(78, 155, 248, 0.05)' }
            ]
          }
        },
        markPoint: {
          symbol: 'circle',
          symbolSize: 8,
          itemStyle: {
            color: '#ff5555',
            borderWidth: 2,
            borderColor: '#ff5555'
          },
          data: [
            { name: '峰值1', coord: [2, 20] }, // 15日的告警峰值
            { name: '峰值2', coord: [4, 21] }  // 25日的告警峰值
          ]
        }
      }
    ]
  };

  alarmTrendChart.setOption(option);
};

// 初始化设备类型分布饼图
const initDeviceTypePieChart = () => {
  if (!deviceTypePieChartRef.value) return;

  deviceTypePieChart = echarts.init(deviceTypePieChartRef.value);

  const total = deviceTypeData.value.reduce((sum, item) => sum + item.value, 0);

  const option: EChartsOption = {
    legend: {
      orient: 'vertical',
      right: '10%',
      top: 'center',
      textStyle: {
        color: '#ffffff',
        fontSize: 13
      },
      formatter: (name: string) => {
        const item = deviceTypeData.value.find(d => d.name === name);
        return `${name} (${item?.value || 0})`;
      }
    },
    series: [
      {
        type: 'pie',
        radius: ['45%', '70%'],
        center: ['35%', '50%'],
        avoidLabelOverlap: false,
        label: {
          show: true,
          position: 'center',
          formatter: () => {
            return `{value|${total}}\n{label|告警}`;
          },
          rich: {
            value: {
              color: '#ffffff',
              fontSize: 28,
              fontWeight: 'bold',
              lineHeight: 40
            },
            label: {
              color: '#8b9bb3',
              fontSize: 14,
              lineHeight: 20
            }
          }
        },
        labelLine: {
          show: false
        },
        data: deviceTypeData.value.map((item, index) => {
          const colors = ['#4e9bf8', '#52c41a', '#fa8c16', '#8c8c8c'];
          return {
            name: item.name,
            value: item.value,
            itemStyle: {
              color: colors[index]
            }
          };
        })
      }
    ]
  };

  deviceTypePieChart.setOption(option);
};

// 查看详情
const handleViewDetail = (record: ParkInfo) => {
  console.log('查看园区详情:', record);
  // TODO: 实现详情页面跳转或弹窗
};

// 定时器
let dateTimeTimer: number | null = null;

// 组件挂载
onMounted(() => {
  // 更新日期时间
  updateDateTime();
  dateTimeTimer = window.setInterval(updateDateTime, 1000);

  // 初始化图表
  setTimeout(() => {
    initAlarmTrendChart();
    initDeviceTypePieChart();
  }, 100);

  // 监听窗口大小变化
  window.addEventListener('resize', handleResize);
});

// 组件卸载
onBeforeUnmount(() => {
  if (dateTimeTimer) {
    clearInterval(dateTimeTimer);
  }
  if (alarmTrendChart) {
    alarmTrendChart.dispose();
  }
  if (deviceTypePieChart) {
    deviceTypePieChart.dispose();
  }
  window.removeEventListener('resize', handleResize);
});

// 处理窗口大小变化
const handleResize = () => {
  alarmTrendChart?.resize();
  deviceTypePieChart?.resize();
};
</script>

<style scoped lang="scss">
.big-screen-container {
  width: 100%;
  min-height: 100vh;
  background: linear-gradient(180deg, #0a1929 0%, #0d1e2f 100%);
  padding: 20px;
  box-sizing: border-box;
  overflow: auto;
}

// 标题栏
.screen-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 0 10px;

  .screen-title {
    font-size: 32px;
    font-weight: bold;
    color: #ffffff;
    margin: 0;
    letter-spacing: 2px;
  }

  .screen-datetime {
    font-size: 18px;
    color: #8b9bb3;
    font-family: 'Courier New', monospace;
  }
}

// 主要内容区域
.screen-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

// 统计卡片行
.stats-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.stat-card {
  background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
  border-radius: 12px;
  padding: 30px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(78, 155, 248, 0.1);

  .card-title {
    font-size: 16px;
    color: #8b9bb3;
    margin-bottom: 15px;
  }

  .card-value {
    font-size: 48px;
    font-weight: bold;
    color: #ffffff;
    margin-bottom: 12px;
    font-family: 'Arial', sans-serif;
  }

  .card-trend {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;

    &.growth {
      color: #52c41a;

      .trend-icon {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
      }
    }

    &.online-rate {
      color: #13c2c2;
    }
  }
}

// 图表行
.charts-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.chart-card {
  background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(78, 155, 248, 0.1);
  height: 350px;

  .chart-title {
    font-size: 16px;
    color: #ffffff;
    margin-bottom: 15px;
    font-weight: 500;
  }

  .chart-container {
    width: 100%;
    height: calc(100% - 40px);
  }
}

// 表格行
.table-row {
  width: 100%;
}

.table-card {
  background: linear-gradient(135deg, #1a2940 0%, #0f1e2d 100%);
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(78, 155, 248, 0.1);
  height: 450px;

  .table-title {
    font-size: 16px;
    color: #ffffff;
    margin-bottom: 15px;
    font-weight: 500;
  }

  :deep(.park-table) {
    background: transparent !important;

    // 表格主体背景
    .el-table__inner-wrapper {
      background: transparent !important;

      &::before {
        display: none;
      }
    }

    .el-table__body-wrapper {
      background: transparent !important;

      // 滚动条样式
      scrollbar-width: thin;
      scrollbar-color: rgba(78, 155, 248, 0.3) transparent;

      &::-webkit-scrollbar {
        width: 6px;
      }

      &::-webkit-scrollbar-thumb {
        background: rgba(78, 155, 248, 0.3);
        border-radius: 3px;
      }

      &::-webkit-scrollbar-track {
        background: transparent;
      }
    }

    .el-table__header-wrapper,
    .el-table__footer-wrapper {
      background: transparent !important;
    }

    // 表格行悬浮效果
    .el-table__row {
      &:hover {
        background: rgba(78, 155, 248, 0.05) !important;
      }
    }

    // 空数据样式
    .el-table__empty-block {
      background: transparent !important;

      .el-table__empty-text {
        color: #8b9bb3;
      }
    }

    .device-count {
      color: #ffffff;
      font-weight: 500;
    }

    .status-text {
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 13px;

      &.status-normal {
        color: #52c41a;
      }

      &.status-alarm {
        color: #ff4d4f;
      }
    }

    .detail-link {
      cursor: pointer;
      font-size: 13px;
      display: inline-flex;
      align-items: center;
      gap: 4px;
      text-decoration: none;

      &.detail-normal {
        color: #52c41a;

        .normal-badge {
          color: #52c41a;
        }
      }

      &.detail-alarm {
        .alarm-badge {
          color: #ff4d4f;
        }
      }

      &:hover {
        opacity: 0.8;
      }
    }
  }
}

// 响应式布局
@media screen and (max-width: 1600px) {
  .screen-header {
    .screen-title {
      font-size: 28px;
    }

    .screen-datetime {
      font-size: 16px;
    }
  }

  .stat-card {
    padding: 25px;

    .card-value {
      font-size: 42px;
    }
  }

  .chart-card {
    height: 320px;
  }

  .table-card {
    height: 400px;
  }
}

@media screen and (max-width: 1280px) {
  .screen-header {
    .screen-title {
      font-size: 26px;
    }

    .screen-datetime {
      font-size: 15px;
    }
  }

  .stat-card {
    padding: 20px;

    .card-title {
      font-size: 14px;
    }

    .card-value {
      font-size: 38px;
    }

    .card-trend {
      font-size: 13px;
    }
  }

  .chart-card {
    height: 300px;
    padding: 15px;

    .chart-title {
      font-size: 14px;
    }
  }

  .table-card {
    height: 380px;
    padding: 15px;

    .table-title {
      font-size: 14px;
    }
  }
}

// 仅在非常小的屏幕(小于1024px)才改为单列
@media screen and (max-width: 1024px) {
  .stats-row,
  .charts-row {
    grid-template-columns: 1fr;
  }

  .chart-card {
    height: 280px;
  }

  .table-card {
    height: 360px;
  }
}
</style>
2025-12-03 15:12:18 +08:00
8215478113 table 2025-12-03 15:06:55 +08:00
chenlin
c2171db699 动环监控大屏 2025-12-03 14:55:57 +08:00
59843de257 feat(screen): 更新区域大屏安全指数获取逻辑并优化界面布局
- 在 mainScreen.vue 和 regionScreen.vue 中新增安全指数独立报表获取逻辑
- 修复 regionScreen.vue 中的模板标签格式和属性换行问题-优化 regionScreen.vue 中的 CSS 背景图片设置和布局结构
- 移除 mainScreen.vue 中未使用的 echarts 日志导入
- 调整多个组件标签的属性排列以提高可读性
-优化 regionScreen.vue 中的数据处理逻辑和查询参数传递方式- 清理 regionScreen.vue 中多余的空行和样式代码
2025-11-06 11:56:15 +08:00
20680032bc feat(screen): 隐藏隐患排查治理标题并调整子标题
- 在 companyScreen.vue 中隐藏“隐患排查治理”标题
- 在 HiddenDangerPanel.vue 中将“分类风险”更改为“隐患等级”
- 在 mainScreenV1.vue 中同步修改标题和子标题显示逻辑
- 在 parkScreen.vue 中隐藏“隐患排查治理”标题
2025-10-31 14:58:49 +08:00
e6e86f2ce0 init 2025-10-17 10:31:13 +08:00