独墅湖高教创新区危化品智慧管控平台(新危化品)
马宇豪
2025-04-24 235a109b3b461c7acbd1d7bb4f7e920075de2b9e
修改大屏
已修改7个文件
已添加1个文件
718 ■■■■■ 文件已修改
index.html 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hazardousChemicals/bigScreen/components/leftTop.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hazardousChemicals/bigScreen/components/midBottom.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hazardousChemicals/bigScreen/components/midTop.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hazardousChemicals/bigScreen/components/midTop2.vue 490 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hazardousChemicals/bigScreen/index.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -211,6 +211,7 @@
    </div>
  </div>
  <script type="module" src="/src/main.js"></script>
  <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=SHPNPo8VSiNTeyuec9nEbMmJXTS2StiW"></script>
</body>
</html>
package.json
@@ -39,7 +39,6 @@
    "tinymce": "^5.10.2",
    "video.js": "^8.12.0",
    "vue": "3.2.45",
    "vue-baidu-map-3x": "^1.0.35",
    "vue-cropper": "1.0.3",
    "vue-qr": "^4.0.9",
    "vue-quill-editor": "^3.0.6",
src/router/index.js
@@ -53,6 +53,11 @@
    hidden: true
  },
  {
    path: '/bigMap',
    component: () => import('@/views/hazardousChemicals/bigScreen/index.vue'),
    hidden: true
  },
  {
    path: '/warehouseManage',
    component: Layout,
    redirect: '/warehouseManage',
src/views/hazardousChemicals/bigScreen/components/leftTop.vue
@@ -64,7 +64,7 @@
    if (res.code == 200) {
      data.companyList = res.data.list
      if(data.queryParams.companyId == null){
        data.queryParams.companyId = data.companyList[0].id
        data.queryParams.companyId = data.companyList[data.companyList.length - 1].id
      }
    } else {
      ElMessage.warning(res.message)
src/views/hazardousChemicals/bigScreen/components/midBottom.vue
@@ -1,19 +1,99 @@
<template>
  <div class="charts-container">
    <div id="preWarning"></div>
    <div class="table-wrapper">
      <table class="scrollable-table">
        <thead>
        <tr>
          <th>预警信息</th>
          <th>预警时间</th>
        </tr>
        </thead>
      </table>
      <div class="scroll-viewport" ref="viewport">
        <div class="scroll-content" :style="contentStyle">
          <table class="scrollable-table">
            <tbody>
            <tr v-for="(item,index) in warningData" :key="item.id">
              <td>{{ item.warningInfo }}</td>
              <td>{{ item.warningTime }}</td>
            </tr>
            <tr v-for="(item,index) in loopData" :key="`loop-${item.id}`">
              <td>{{ item.warningInfo }}</td>
              <td>{{ item.warningTime }}</td>
            </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import * as echarts from 'echarts';
import {onMounted} from "vue";
import {computed, onBeforeUnmount, onMounted, ref} from "vue";
import {getCompanyMessage, getDailywarningCount} from "@/api/monitor/screenCharts";
import {ElMessage} from "element-plus";
const warningData = [
  {warningInfo: '超期预警',warningTime: '2025-04-24 13:18:41' },
  {warningInfo: '超期预警',warningTime: '2025-04-23 13:00:21' },
  {warningInfo: '超期预警',warningTime: '2025-04-20 10:11:34' },
  {warningInfo: '超期预警',warningTime: '2025-04-18 09:28:51' },
  {warningInfo: '超期预警',warningTime: '2025-04-16 08:18:21' },
  {warningInfo: '超期预警',warningTime: '2025-04-15 05:12:21' },
  {warningInfo: '超期预警',warningTime: '2025-04-14 04:11:41' }
]
// 配置参数
const visibleRows = 5 // 显示的行数
const scrollSpeed = 0.5 // 每次滚动的像素数
const rowHeight = 36 // 行高,与CSS一致
const viewport = ref(null)
const scrollPosition = ref(0)
let animationFrame = null
onMounted(()=>{
  getList()
// 设置视口高度
  if (viewport.value) {
      viewport.value.style.height = `${visibleRows * rowHeight}px`
  }
  // 延迟启动滚动,确保初始渲染完成
  setTimeout(() => {
    scrollAnimation()
  }, 100)
})
onBeforeUnmount(() => {
  if (animationFrame) {
    cancelAnimationFrame(animationFrame)
  }
})
// 复制前几行数据用于循环
const loopData = computed(() => {
  return warningData.slice(0, visibleRows)
})
// 内容区域样式
const contentStyle = computed(() => {
  return {
    transform: `translateY(-${scrollPosition.value}px)`
  }
})
// 滚动动画
const scrollAnimation = () => {
  const totalHeight = warningData.length * rowHeight
  const loopHeight = loopData.value.length * rowHeight
  // 更新滚动位置
  scrollPosition.value += scrollSpeed
  // 当滚动到循环数据部分时,重置位置实现无缝衔接
  if (scrollPosition.value >= totalHeight) {
    scrollPosition.value -= totalHeight
  }
  animationFrame = requestAnimationFrame(scrollAnimation)
}
const getList = async () => {
  const res = await getDailywarningCount()
@@ -105,10 +185,66 @@
.charts-container{
  width: 100%;
  height: 100%;
  display: flex;
}
#preWarning{
  width: 100%;
  flex: 2;
  height: 100%;
}
.table-wrapper {
  position: relative;
  flex: 1;
  height: 100%;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 2px;
  overflow: hidden;
  .scrollable-table {
    width: 100%;
    border-collapse: collapse;
    th,td {
      padding: 12px 15px;
      color: #fff;
      text-align: left;
      border-bottom: 1px solid rgba(255,255,255,.1);
      height: 36px; /* 与rowHeight一致 */
      box-sizing: border-box;
      font-size: 12px;
      font-weight: normal;
    }
    th {
      position: sticky;
      top: 0;
      z-index: 10; /* 确保表头在内容之上 */
    }
    tr{
      background: rgb(6,38,87);
      &:nth-of-type(2n){
        background: rgb(19,72,127);
      }
    }
    thead tr{
      background: rgba(0,0,0,0);
    }
  }
  .scroll-viewport {
    position: relative;
    overflow: hidden;
    .scroll-content {
      will-change: transform; /* 优化性能 */
    }
    .danger {
      color: #ff2f2f;
      animation: blink 1s infinite;
    }
    .warning {
      color: yellow;
      animation: blink 1s infinite;
    }
  }
}
</style>
src/views/hazardousChemicals/bigScreen/components/midTop.vue
@@ -8,13 +8,31 @@
            v-model="companyType"
            filterable
            placeholder="请选择企业类型"
            style="width: 100%"
            style="flex: 1"
            remote
            remote-show-suffix
            :remote-method="getList"
        >
          <el-option
              v-for="item in typeList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
          />
        </el-select>
        <el-select
            clearable
            :teleported="false"
            v-model="warningType"
            filterable
            placeholder="请选择风险等级"
            style="flex: 1"
            remote
            remote-show-suffix
            :remote-method="getList"
        >
          <el-option
              v-for="item in warningList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
@@ -94,6 +112,7 @@
})
const companyType = ref('')
const warningType = ref('')
const typeList = [
  {
    id: 0,
@@ -108,13 +127,30 @@
    name: '中试类'
  }
]
const warningList = [
  {
    id: 1,
    name: '红'
  },
  {
    id: 2,
    name: '橙'
  },
  {
    id: 3,
    name: '黄'
  },
  {
    id: 4,
    name: '蓝'
  }
]
const getList = async () => {
  const res = await getCompanyMessage(companyType.value)
  if(res.code == 200){
    if(res.data && Array.isArray(res.data) && res.data.length>0){
      companyData.value = res.data
      console.log(companyData.value,555)
      const mapData = companyData.value.map(i=>{
        return {
          name: i.companyName + '(' + i.warningCount + ')',
@@ -293,6 +329,8 @@
.filter{
  width: 300px;
  margin-top: 50px;
  display: flex;
  align-items: center;
}
:deep(.el-input__wrapper){
  height: 28px;
src/views/hazardousChemicals/bigScreen/components/midTop2.vue
对比新文件
@@ -0,0 +1,490 @@
<template>
  <div class="charts-container">
    <div class="container-left">
      <div class="filter">
        <el-select
            clearable
            :teleported="false"
            v-model="companyType"
            filterable
            placeholder="请选择企业类型"
            style="flex: 1"
            remote
            remote-show-suffix
            :remote-method="getList"
        >
          <el-option
              v-for="item in typeList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
          />
        </el-select>
        <el-select
            clearable
            :teleported="false"
            v-model="warningType"
            filterable
            placeholder="请选择风险等级"
            style="flex: 1"
            remote
            remote-show-suffix
            :remote-method="getList"
        >
          <el-option
              v-for="item in warningList"
              :key="item.id"
              :label="item.name"
              :value="item.id"
          />
        </el-select>
      </div>
      <div class="table-wrapper">
        <table class="scrollable-table">
          <thead>
          <tr>
            <th>序号</th>
            <th>企业名称</th>
            <th>危化品仓库</th>
            <th>预警信息</th>
          </tr>
          </thead>
        </table>
        <div class="scroll-viewport" ref="viewport">
          <div class="scroll-content" :style="contentStyle">
            <table class="scrollable-table">
              <tbody>
              <tr v-for="(item,index) in companyData" :key="item.id">
                <td>{{ index + 1 }}</td>
                <td>{{ item.companyName }}</td>
                <td>{{ item.warehouseCount }}</td>
                <td>{{ item.warningCount }}</td>
              </tr>
              <tr v-for="(item,index) in loopData" :key="`loop-${item.id}`">
                <td>{{ index + 1 }}</td>
                <td>{{ item.companyName }}</td>
                <td>{{ item.warehouseCount }}</td>
                <td>{{ item.warningCount }}</td>
              </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
    <div id="areaMap"></div>
  </div>
</template>
<script setup>
import * as echarts from 'echarts';
import {onMounted, onBeforeUnmount, ref, computed, reactive} from "vue";
import SUZHOU from './map.json'
import {getAvoidList} from "@/api/hazardousChemicals/avoid";
import {ElMessage} from "element-plus";
import {getCompanyMessage} from "@/api/monitor/screenCharts";
// 表格数据
const companyData = ref([])
// 配置参数
const visibleRows = 8 // 显示的行数
const scrollSpeed = 0.5 // 每次滚动的像素数
const rowHeight = 40 // 行高,与CSS一致
const viewport = ref(null)
const scrollPosition = ref(0)
let animationFrame = null
onMounted(()=>{
  getList()
  // 设置视口高度
  if (viewport.value) {
    viewport.value.style.height = `${visibleRows * rowHeight}px`
  }
  // 延迟启动滚动,确保初始渲染完成
  setTimeout(() => {
    scrollAnimation()
  }, 100)
  initMap()
})
onBeforeUnmount(() => {
  if (animationFrame) {
    cancelAnimationFrame(animationFrame)
  }
})
const companyType = ref('')
const warningType = ref('')
const typeList = [
  {
    id: 0,
    name: '研发类'
  },
  {
    id: 1,
    name: '生产类'
  },
  {
    id: 2,
    name: '中试类'
  }
]
const warningList = [
  {
    id: 1,
    name: '红'
  },
  {
    id: 2,
    name: '橙'
  },
  {
    id: 3,
    name: '黄'
  },
  {
    id: 4,
    name: '蓝'
  }
]
const initMap=()=>{
  var map = new BMapGL.Map("areaMap");
  map.setMapType(BMAP_NORMAL_MAP);
  var point = new BMapGL.Point(120.752833, 31.333439);
  map.centerAndZoom(point, 12);
  map.enableScrollWheelZoom(true);
  map.setTilt(40);
  map.setMapStyleV2({
    styleId: 'c66f44df4e55ce8f0fa90205997df335'
  });
  // var bd = new BMapGL.Boundary();
  // bd.get('苏州市工业园区', function (rs) {
  //   // console.log('外轮廓:', rs.boundaries[0])
  //   // console.log('内镂空:', rs.boundaries[1])
  //   var hole = new BMapGL.Polygon(rs.boundaries, {
  //     fillColor: 'blue',
  //     fillOpacity: 0.2
  //   });
  //   map.addOverlay(hole);
  // });
  var bd1 = new BMapGL.Boundary();
  bd1.get('苏州市工业园区', function (rs) {
    let count = rs.boundaries.length;
    for (let i = 0; i < count; i++) {
      let path = [];
      let str = rs.boundaries[i].replace(' ', '');
      let points = str.split(';');
      for (let j = 0; j < points.length; j++) {
        let lng = points[j].split(',')[0];
        let lat = points[j].split(',')[1];
        path.push(new BMapGL.Point(lng, lat));
      }
      let prism = new BMapGL.Prism(path, 200, {
        topFillColor: '#5679ea',
        topFillOpacity: 0.6,
        sideFillColor: '#5679ea',
        sideFillOpacity: 0.9
      });
      map.addOverlay(prism);
    }
  });
  // var circle = new BMapGL.Circle(new BMapGL.Point(120.742833,31.333439),1000,{strokeColor:"blue", strokeWeight:2, strokeOpacity:0.5});
  // map.addOverlay(circle);
  var point = new BMapGL.Point(120.742833,31.333439);
  var content = '企业一(10)';
  var label = new BMapGL.Label(content, {
    position: point,
    offset: new BMapGL.Size(-20, -10)
  })
  map.addOverlay(label);
  label.setStyle({
    color: '#11FEEE',
    fontSize: '14px',
    border: 'none',
    backgroundColor: '#293075'
  })
  label.setPosition(point)
}
const getList = async () => {
  const res = await getCompanyMessage(companyType.value)
  if(res.code == 200){
    if(res.data && Array.isArray(res.data) && res.data.length>0){
      companyData.value = res.data
      const mapData = companyData.value.map(i=>{
        return {
          name: i.companyName + '(' + i.warningCount + ')',
          value: [i.longitude,i.latitude]
        }
      })
      // initChart(mapData)
    }
  }else{
    ElMessage.warning(res.message)
  }
}
// 复制前几行数据用于循环
const loopData = computed(() => {
  return companyData.value.slice(0, visibleRows)
})
// 内容区域样式
const contentStyle = computed(() => {
  return {
    transform: `translateY(-${scrollPosition.value}px)`
  }
})
// 滚动动画
const scrollAnimation = () => {
  const totalHeight = companyData.value.length * rowHeight
  const loopHeight = loopData.value.length * rowHeight
  // 更新滚动位置
  scrollPosition.value += scrollSpeed
  // 当滚动到循环数据部分时,重置位置实现无缝衔接
  if (scrollPosition.value >= totalHeight) {
    scrollPosition.value -= totalHeight
  }
  animationFrame = requestAnimationFrame(scrollAnimation)
}
const initChart =(mapData)=>{
  //获取echart对象
  let dom = document.getElementById('areaMap')
  if (dom) {
    //初始化
    let myEchart = echarts.init(dom)
    //注册地图
    echarts.registerMap('苏州市', SUZHOU)
    let option = {
      geo: {
        map: '苏州市',
        aspectScale: 0.8,
        layoutCenter: ['50%', '50%'], //地图位置
        layoutSize: '75%',
        itemStyle: {
          normal: {
            shadowColor: '#000',
            shadowOffsetX: 0,
            shadowOffsetY: 40,
            opacity: 0.1
          },
          emphasis: {
            areaColor: '#fff'
          }
        }
      },
      tooltip: {
        trigger: 'item',
        backgroundColor: 'rgba(166, 200, 76, 0.82)',
        borderColor: '#FFFFCC',
        showDelay: 0,
        hideDelay: 0,
        enterable: true,
        transitionDuration: 0,
        extraCssText: 'z-index:100',
        formatter: function (params, ticket, callback) {
          console.log('params', params.value);
          //根据业务自己拓展要显示的内容
          var res = ''
          var name = params.name
          var value = params.value[params.seriesIndex + 1] || params.value
          res = "<span style='color:#fff;'>" + name + '</span><br/>数据:' + value
          return res
        }
      },
      series: [
        {
          tooltip: {
            trigger: 'item',
          },
          name: '苏州市数据',
          type: 'map',
          map: '苏州市', // 自定义扩展图表类型
          label: { // 文字
            show: true,
            color: '#fff',
            fontSize: 10
          },
          itemStyle: { // 地图样式
            shadowBlur: 10,
            shadowColor: '#000',
            areaColor: 'rgb(1,95,176)',  //区域颜色
            normal: {
              areaColor: 'rgb(1,95,176)',
              borderColor: '#02CDE6',
              borderWidth: 1
            },
            emphasis: {
              areaColor: 'rgb(3,26,65)',
              label: {
                color: '#fff'
              }
            }
          },
        },
        // 区域散点图
        {
          type: 'effectScatter',
          coordinateSystem: 'geo',
          symbolSize: 10,
          rippleEffect: {
            period: 3,
            scale: 10,
            brushType: 'fill'
          },
          label: {
            normal: {
              show: true,
              position: 'right',
              formatter: '{b}',
              color: 'yellow',
              fontSize: 12
            }
          },
          data: mapData,
          itemStyle: {
            //坐标点颜色
            normal: {
              show: true,
              color: 'skyblue',
              shadowBlur: 20,
              shadowColor: '#fff'
            },
            emphasis: {
              areaColor: '#fff'
            }
          }
        }
      ],
    }
    myEchart.setOption(option);
    window.addEventListener('resize', function () {
      myEchart.resize();
    });
  }
}
</script>
<style lang="postcss" scoped>
.charts-container{
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-start;
  gap: 10px
}
.container-left{
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 300px;
}
.filter{
  width: 300px;
  margin-top: 50px;
  display: flex;
  align-items: center;
}
:deep(.el-input__wrapper){
  height: 28px;
  box-shadow: none;
  border: 1px solid #11FEEE;
  background: rgba(6,24,88,.6);
  color: #fff;
}
:deep(.el-input__inner){
  color: #02CDE6;
}
:deep(.el-input .el-select__caret){
  color: #02CDE6
}
:deep(.el-popper.is-light){
  background: rgb(8,44,97);
  .el-select-dropdown__item{
    color: #fff;
    &:hover{
      background: #015fb0;
    }
  }
}
.table-wrapper {
  position: relative;
  width: 300px;
  margin-top: 10px;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 2px;
  overflow: hidden;
  .scrollable-table {
    width: 100%;
    border-collapse: collapse;
    th,td {
      padding: 12px 15px;
      color: #fff;
      text-align: left;
      border-bottom: 1px solid rgba(255,255,255,.1);
      height: 40px; /* 与rowHeight一致 */
      box-sizing: border-box;
      font-size: 12px;
      font-weight: normal;
    }
    th {
      position: sticky;
      top: 0;
      z-index: 10; /* 确保表头在内容之上 */
    }
    tr{
      background: rgb(6,38,87);
      &:nth-of-type(2n){
        background: rgb(19,72,127);
      }
    }
    thead tr{
      background: rgba(0,0,0,0);
    }
  }
  .scroll-viewport {
    position: relative;
    overflow: hidden;
    .scroll-content {
      will-change: transform; /* 优化性能 */
    }
    .danger {
      color: #ff2f2f;
      animation: blink 1s infinite;
    }
    .warning {
      color: yellow;
      animation: blink 1s infinite;
    }
  }
}
@keyframes blink {
  0% { opacity: 1; }
  50% { opacity: 0.8; }
  100% { opacity: 1; }
}
  #areaMap{
    flex: 1;
    height: 100%;
  }
:deep(.BMap_cpyCtrl) {
  display: none !important;
}
:deep(.anchorBL) {
  display: none !important;
}
</style>
src/views/hazardousChemicals/bigScreen/index.vue
@@ -9,10 +9,12 @@
        <h1 class="dashboard-title">独墅湖科教创新区危化品智慧管控平台</h1>
      </div>
      <div class="header-right">
<!--        <div class="weather-info">-->
        <div class="weather-info">
<!--          <span class="weather-icon">☀</span>-->
<!--          <span class="weather-text">晴 26°C</span>-->
<!--        </div>-->
          <div class="exit-btn" v-show="!isFull" @click="toFull">全屏</div>
          <div class="exit-btn" v-show="isFull" @click="exitFull">退出全屏</div>
        </div>
      </div>
    </header>
@@ -84,12 +86,16 @@
import LeftBottom from "@/views/hazardousChemicals/bigScreen/components/leftBottom";
import MidBottom from "@/views/hazardousChemicals/bigScreen/components/midBottom";
import RightTop from "@/views/hazardousChemicals/bigScreen/components/rightTop";
import MidTop from "@/views/hazardousChemicals/bigScreen/components/midTop";
import MidTop from "@/views/hazardousChemicals/bigScreen/components/midTop2";
import RightBottom from "@/views/hazardousChemicals/bigScreen/components/rightBottom";
import {useRoute, useRouter} from "vue-router";
const router = useRouter()
const route = useRoute()
const currentTime = ref('');
const currentDate = ref('');
let timer = ref(null);
const isFull = ref(false)
const updateTime = () => {
  const now = new Date();
  currentTime.value = now.toLocaleTimeString();
@@ -106,6 +112,11 @@
  window.onresize = () => {
    setRem();/* 改变窗口大小时重新设置 rem */
  }
  if(route.path == '/bigScreen'){
    isFull.value = false
  }else{
    isFull.value = true
  }
  updateTime();
  timer.value = setInterval(updateTime, 1000);
});
@@ -113,12 +124,20 @@
onBeforeUnmount(() => {
  if (timer.value) clearInterval(timer.value);
});
const toFull = ()=>{
  router.push('/bigMap')
}
const exitFull = ()=>{
  router.push('/bigScreen')
}
</script>
<style lang="postcss">
.data-dashboard {
  width: 100%;
  height: calc(100vh - 70px);
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: #031A41;
@@ -175,7 +194,11 @@
        background: rgba(255, 255, 255, 0.1);
        padding: 10px 20px;
        border-radius: 30px;
        .exit-btn{
          cursor: pointer;
          color: #02CDE6;
          font-size: 14px;
        }
        .weather-icon {
          font-size: 16px;
          margin-right: 10px;