riku
2025-05-30 c2e36c45578e63ad17c5e258c92d62d9ae03dadb
Merge branch 'master' of ssh://114.215.109.124:29418/underway-vue
已修改10个文件
已添加1个文件
462 ■■■■ 文件已修改
src/api/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/websocket.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monitor/FactorRadio.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/scene/SceneTable.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/factor/data.js 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/sector.js 97 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/realtimemode/RealtimeMode.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/SourceTrace.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js
@@ -12,9 +12,10 @@
}
if (debug) {
  ip1 = 'http://192.168.0.110:8084/';
  // ip1 = 'http://locahost:8084/';
  ws = `192.168.0.110:9031`;
  // ip1 = 'http://192.168.0.110:8084/';
  ip1 = 'http://localhost:8084/';
  // ws = `192.168.0.110:9031`;
  ws = `localhost:9031`;
}
const $http = axios.create({
src/api/websocket.js
@@ -18,7 +18,7 @@
  };
  // æ”¶åˆ°æœåŠ¡å™¨å‘é€çš„æ¶ˆæ¯ï¼ševent处理服务器返回的数据
  socket.onmessage = (event) => {
    console.log('receive: ', event.data);
    // console.log('receive: ', event.data);
    onMsgEvents.forEach((e) => {
      if (typeof e === 'function') {
        e(event.data);
src/components.d.ts
@@ -40,6 +40,7 @@
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElPopover: typeof import('element-plus/es')['ElPopover']
@@ -50,11 +51,13 @@
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElSlider: typeof import('element-plus/es')['ElSlider']
    ElSpace: typeof import('element-plus/es')['ElSpace']
    ElStatistic: typeof import('element-plus/es')['ElStatistic']
    ElSwitch: typeof import('element-plus/es')['ElSwitch']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElText: typeof import('element-plus/es')['ElText']
    FactorCheckbox: typeof import('./components/monitor/FactorCheckbox.vue')['default']
    FactorLegend: typeof import('./components/monitor/FactorLegend.vue')['default']
@@ -82,6 +85,7 @@
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
    SceneSearch: typeof import('./components/scene/SceneSearch.vue')['default']
    SceneTable: typeof import('./components/scene/SceneTable.vue')['default']
    SearchBar: typeof import('./components/search/SearchBar.vue')['default']
    SliderBar: typeof import('./components/SliderBar.vue')['default']
    TrajectoryState: typeof import('./components/animation/TrajectoryState.vue')['default']
src/components/monitor/FactorRadio.vue
@@ -17,13 +17,17 @@
export default {
  props: {
    modelValue: {
      type: String,
      default: defaultOptions(TYPE0).value
    },
    deviceType: {
      type: String,
      // type0: è½¦è½½æˆ–无人机; type1:无人船
      default: TYPE0
    }
  },
  emits: ['change'],
  emits: ['change', 'update:modelValue'],
  data() {
    return {
      radio: defaultOptions(TYPE0).value
@@ -38,6 +42,12 @@
    deviceType(nV, oV) {
      if (nV != oV) {
        this.radio = this.options[0].value;
        this.$emit('update:modelValue', this.radio)
      }
    },
    modelValue(nV, oV){
      if (nV != oV) {
        this.radio = nV
      }
    }
  },
@@ -45,6 +55,7 @@
    handleChange(value) {
      const item = this.options.find((v) => v.value == value);
      this.$emit('change', item.value, item);
      this.$emit('update:modelValue', item.value)
      // todo åœ°å›¾3d图像切换展示监测因子
    }
  }
src/components/scene/SceneTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,113 @@
<template>
  <el-table
    :data="sceneList"
    table-layout="fixed"
    size="small"
    height="30vh"
    :show-overflow-tooltip="true"
    border
    row-class-name="t-row"
    cell-class-name="t-cell"
    header-row-class-name="t-header-row"
    header-cell-class-name="t-header-cell"
    @row-click="handleRowClick"
    @filter-change="handleFilterChange"
  >
    <el-table-column type="index" label="#" width="25" />
    <el-table-column
      prop="type"
      label="类型"
      width="56"
      column-key="type"
      :filters="sceneTypeFilter"
      :filter-method="filterHandler"
    />
    <el-table-column prop="name" label="名称" />
    <!-- <el-table-column prop="location" label="地址" /> -->
    <el-table-column
      prop="districtName"
      label="区县"
      align="center"
      width="54"
    />
    <!-- <el-table-column label="管理" width="70" align="center">
      <template #default="{ row }">
        <el-button
          type="primary"
          size="small"
          class="el-button-custom"
          @click="deleteDevice(row)"
          >删除</el-button
        >
      </template>
    </el-table-column> -->
  </el-table>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { sceneTypes, sceneIcon } from '@/constant/scene-types';
import MapUtil from '@/utils/map/util';
import marks from '@/utils/map/marks';
const props = defineProps({
  sceneList: Array
});
let layer = undefined;
let showSceneTypes = ref([]);
const sceneTypeFilter = computed(() => {
  return sceneTypes()
    .filter((v) => {
      return !v.disabled;
    })
    .map((v) => {
      return { text: v.label, value: v.label };
    });
});
const showSceneList = computed(() => {
  if (showSceneTypes.value.length == 0) {
    return props.sceneList;
  } else {
    return props.sceneList.filter((v) => {
      return showSceneTypes.value.indexOf(v.type) != -1;
    });
  }
});
watch(showSceneList, (nV, oV) => {
  if (nV != oV) {
    drawMarks(nV);
  }
});
function drawMarks(sceneList) {
  if (layer != undefined) {
    MapUtil.removeViews(layer);
    layer = undefined;
  }
  if (sceneList.length != 0) {
    const icons = [];
    sceneList.forEach((s) => {
      icons.push(sceneIcon(s.typeId));
    });
    layer = marks.createLabelMarks(icons, sceneList, false);
  }
}
function handleRowClick(row, col, event) {
  MapUtil.setCenter([row.longitude, row.latitude], true);
}
function filterHandler(value, row, column) {
  const property = column['property'];
  return row[property] === value;
}
function handleFilterChange(newFilters) {
  // console.log(newFilters);
  showSceneTypes.value = newFilters['type'];
}
</script>
src/main.js
@@ -4,6 +4,7 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import 'element-plus/theme-chalk/dark/css-vars.css';
import 'element-plus/theme-chalk/src/overlay.scss';
import 'element-plus/theme-chalk/src/message.scss';
import 'element-plus/theme-chalk/src/message-box.scss';
src/utils/factor/data.js
@@ -186,7 +186,7 @@
 * å¼€å¯å®žæ—¶æ•°æ®å¾ªçŽ¯èŽ·å–
 * @param {Object} params
 */
function startLoopFetchRealTimeData(onParam, callback) {
function startLoopFetchRealTimeData(onParam, callback, _interval) {
  // æ—¶é—´é—´éš”
  let interval;
  // æ•°æ®èŽ·å–æ–¹æ³•
@@ -199,6 +199,9 @@
    fetchFun = fetchOriginRealTimeData;
    interval = 10 * 1000;
  }
  if (_interval) {
    interval = _interval
  }
  // å¼€å§‹å¾ªçŽ¯ä»»åŠ¡
  clearFetchingTask();
  fetchingTask = setInterval(() => {
src/utils/map/sector.js
@@ -9,6 +9,9 @@
  // åŠ¨ç”»è½¨è¿¹
  _sectorViewsAna = new Map();
var _ptSector = undefined,
  _ptSectorViews = new Map();
const zoomStyleMapping = {
  14: 0,
  15: 0,
@@ -107,7 +110,14 @@
  });
}
function drawSectorMesh(sDeg, eDeg, lnglat, distance, distance2) {
function drawSectorMesh(
  sDeg,
  eDeg,
  lnglat,
  distance,
  distance2,
  isPollutinTrace
) {
  if (distance == 0 || distance2 == 0) {
    return false;
  }
@@ -143,59 +153,77 @@
    geometry.vertices.push(p0.x, p0.y, 0);
    geometry.vertices.push(l3.x, l3.y, 0);
    geometry.vertices.push(l4.x, l4.y, 0);
    if (!isPollutinTrace) {
      // å†…测扇形颜色
      geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
      geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
      geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
    } else {
      geometry.vertexColors.push(0.25, 0.11, 1, 0.6);
      geometry.vertexColors.push(0.25, 0.11, 1, 0.6);
      geometry.vertexColors.push(0.25, 0.11, 1, 0.6);
    }
    if (!isPollutinTrace) {
    // å¤–侧扇形
    geometry.vertices.push(l3.x, l3.y, 0);
    geometry.vertices.push(l4.x, l4.y, 0);
    geometry.vertices.push(l1.x, l1.y, 0);
    geometry.vertices.push(l2.x, l2.y, 0);
    // console.log(l3.x + ',' + l3.y + ' | ' + l1.x + ',' + l1.y);
    // å†…测扇形颜色
    geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
    geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
    geometry.vertexColors.push(1, 0.11, 0.25, 0.6);
    //外侧扇形颜色
    geometry.vertexColors.push(1, 0.37, 0.07, 0.5);
    geometry.vertexColors.push(1, 0.37, 0.07, 0.5);
    geometry.vertexColors.push(1, 0.37, 0.07, 0.5);
    geometry.vertexColors.push(1, 0.37, 0.07, 0.5);
    }
    // console.log(l3.x + ',' + l3.y + ' | ' + l1.x + ',' + l1.y);
    var index = i * 7;
    if (!isPollutinTrace) {
      const index = i * 7;
    geometry.faces.push(index, index + 1, index + 2);
    geometry.faces.push(index + 3, index + 4, index + 5);
    geometry.faces.push(index + 4, index + 5, index + 6);
    } else {
      const index = i * 3;
      geometry.faces.push(index, index + 1, index + 2);
      // geometry.faces.push(index + 3, index + 4, index + 5);
      // geometry.faces.push(index + 4, index + 5, index + 6);
    }
  }
  object3Dlayer.add(sector);
  _sector = sector;
  isPollutinTrace ? (_ptSector = sector) : (_sector = sector);
  return true;
}
function drawTextMaker(list, list2, distance, distance2) {
function drawTextMaker(list, list2, distance, distance2, isPollutinTrace) {
  const _sectorViewsTmp = isPollutinTrace ? _ptSectorViews : _sectorViews;
  //10分钟扇形
  const a = _sectorViews.get('text10-t');
  if (!isPollutinTrace) {
    const a = _sectorViewsTmp.get('text10-t');
  if (a == undefined) {
    const text10t = textMaker(list[2], '10分钟');
    _sectorViews.set('text10-t', text10t);
      _sectorViewsTmp.set('text10-t', text10t);
    const textM10t = textMaker(list[1], distance + 'm');
    _sectorViews.set('textM10-t', textM10t);
      _sectorViewsTmp.set('textM10-t', textM10t);
    map.add([text10t, textM10t]);
  } else {
    _sectorViews.get('text10-t').setPosition(list[2]);
    _sectorViews.get('textM10-t').setPosition(list[1]);
    _sectorViews.get('textM10-t').setText(distance + 'm');
      _sectorViewsTmp.get('text10-t').setPosition(list[2]);
      _sectorViewsTmp.get('textM10-t').setPosition(list[1]);
      _sectorViewsTmp.get('textM10-t').setText(distance + 'm');
    }
  }
  //5分钟扇形
  const b = _sectorViews.get('text5-t');
  const b = _sectorViewsTmp.get('text5-t');
  if (b == undefined) {
    const text5t = textMaker(list2[1], '5分钟');
    _sectorViews.set('text5-t', text5t);
    _sectorViewsTmp.set('text5-t', text5t);
    const textM5t = textMaker(list2[0], distance2 + 'm');
    _sectorViews.set('textM5-t', textM5t);
    _sectorViewsTmp.set('textM5-t', textM5t);
    map.add([text5t, textM5t]);
  } else {
    _sectorViews.get('text5-t').setPosition(list2[1]);
    _sectorViews.get('textM5-t').setPosition(list2[0]);
    _sectorViews.get('textM5-t').setText(distance2 + 'm');
    _sectorViewsTmp.get('text5-t').setPosition(list2[1]);
    _sectorViewsTmp.get('textM5-t').setPosition(list2[0]);
    _sectorViewsTmp.get('textM5-t').setText(distance2 + 'm');
  }
}
@@ -232,6 +260,19 @@
      _sectorViews.clear();
    }
    this.clearSectorMesh();
  },
  clearSectorPt() {
    var list = [];
    for (const iterator of _ptSectorViews) {
      list.push(iterator[1]);
    }
    if (list.length > 0) {
      map.remove(list);
      _ptSectorViews.clear();
    }
    if (_ptSector) {
      object3Dlayer.remove(_ptSector);
    }
  },
  /**
   * åªæ¸…空扇形
@@ -272,5 +313,17 @@
    if (drawSectorMesh(sDeg, eDeg, lnglat, distance, distance2)) {
      drawTextMaker(list, list2, distance, distance2);
    }
  },
  drawSectorPt(fDatas, i) {
    if (_ptSector) {
      object3Dlayer.remove(_ptSector);
    }
    const { sDeg, eDeg, lnglat, distance, distance2, list, list2 } =
      sectorParams(fDatas, i);
    if (drawSectorMesh(sDeg, eDeg, lnglat, distance, distance2, true)) {
      drawTextMaker(list, list2, distance, distance2, true);
    }
    return { p: lnglat, r: distance };
  }
};
src/views/historymode/HistoryMode.vue
@@ -120,7 +120,7 @@
      if (nValue != oValue && this.status == 0) {
        Layer.clear();
        this.draw();
        this.drawHighlightPollution();
        // this.drawHighlightPollution();
      }
    }
  },
src/views/realtimemode/RealtimeMode.vue
@@ -6,15 +6,18 @@
    <el-row class="m-t-2">
      <FactorRadio
        :device-type="deviceType"
        @change="(e) => (factorType = e)"
        v-model="factorType"
      ></FactorRadio>
    </el-row>
    <el-row class="m-t-2">
      <el-col span="1">
      <FactorLegend
        class="m-t-2"
        :factor="factorDatas.factor[factorType]"
      ></FactorLegend>
      <SourceTrace></SourceTrace>
      </el-col>
      <el-col span="1">
        <SourceTrace v-model:factorType="factorType"></SourceTrace>
      </el-col>
    </el-row>
    <DashBoard class="dash-board" :factor-datas="factorDatas"></DashBoard>
    <RealTimeTrend
@@ -118,8 +121,8 @@
          mode == 'debug'
            ? {
                deviceCode: this.deviceCode,
                startTime: '2025-01-16 11:30:00',
                endTime: '2025-01-16 11:32:00',
                startTime: '2025-01-16 11:34:00',
                endTime: '2025-01-16 11:35:00',
                page,
                perPage: 100
              }
@@ -145,14 +148,15 @@
          return {
            deviceCode: this.deviceCode,
            updateTime: this.latestTime,
            perPage: 10
            perPage: mode == 'debug' ? 1 : 10
          };
        },
        (res) => {
          this.onFetchData(res.data);
          this.onMapData(res.data);
          thirdPartyDataApi.fetchLatestData(this.deviceType, this.deviceCode);
        }
        },
        mode == 'debug' ? 4000 : undefined
      );
    },
    onMapData(dataList) {
@@ -174,14 +178,9 @@
    }
  },
  mounted() {
    // this.fetchRealTimeData();
    // startLoopFetchRealTimeData({
    //   compUser: 'user1',
    //   compPassword: 'User1@jingan',
    //   mn: 'TX105',
    //   dtFrom: '2024-08-07 10:00:00',
    //   dtTo: '2024-08-07 10:00:59'
    // });
    if (mode == 'debug') {
      websocket.send('start');
    }
  },
  unmounted() {
    this.clearFetchingTask();
src/views/sourcetrace/SourceTrace.vue
@@ -1,21 +1,78 @@
<template>
  <el-row>
    <el-col v-show="show" span="10">
  <BaseCard>
    <template #content>
      <div>
          <!-- <div>
        <el-input type="text" v-model="inputVal" />
        <button @click="handleSend">send</button>
        <button @click="handleLink">link</button>
      </div>
      <div>业务状态中控</div>
      <el-scrollbar ref="scrollbarRef" :height="height">
      </div> -->
          <div>动态溯源</div>
          <el-scrollbar ref="scrollbarRef" :height="height" class="scrollbar">
        <div ref="scrollContentRef">
          <div v-for="(item, index) in streams" :key="index">
            <el-text type="primary">[{{ item }}]: </el-text>
                <!-- <el-text type="primary">{{ item.guid }}</el-text> -->
                <el-space gap="4">
                  <el-tag :type="item.status == 1 ? 'danger' : 'info'">{{
                    item._statusStr
                  }}</el-tag>
                  <el-text type="default">{{ item.exception }}</el-text>
                </el-space>
                <el-row gap="4">
                  <el-text type="primary">发生时间:</el-text>
                  <el-text type="primary">{{
                    item.startTime + ' è‡³ '
                  }}</el-text>
                  <el-text type="primary">{{
                    item.status == 1 ? '当前' : item.endTime
                  }}</el-text>
                </el-row>
                <el-row>
                  <el-col :span="6">
                    <el-statistic title="因子" :value="item.factorName" />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic title="均值" :value="item.avg" />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic title="峰值" :value="item.max" />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic title="谷值" :value="item.min" />
                  </el-col>
                </el-row>
                <el-row justify="space-between">
                  <el-link
                    type="primary"
                    @click="item.showMore = !item.showMore"
                  >
                    {{
                      (item.showMore ? '收起溯源场景' : '查看溯源场景') +
                      '(' +
                      item.relatedSceneList.length +
                      ')'
                    }}
                  </el-link>
                  <el-link type="primary" @click="drawSector(item)">
                    æŸ¥çœ‹å¼‚常
                  </el-link>
                </el-row>
                <SceneTable
                  v-show="item.showMore"
                  :scene-list="item.relatedSceneList"
                ></SceneTable>
                <el-divider />
          </div>
        </div>
      </el-scrollbar>
    </template>
  </BaseCard>
    </el-col>
    <el-col span="2">
      <CardButton name="动态溯源" @click="() => (show = !show)"></CardButton>
    </el-col>
  </el-row>
</template>
<script setup>
/**
@@ -24,9 +81,21 @@
 */
import { reactive, ref, onMounted, onUnmounted, inject } from 'vue';
import websocket from '@/api/websocket';
import sector from '@/utils/map/sector';
import mapUtil from '@/utils/map/util';
import { sceneTypes, sceneIcon } from '@/constant/scene-types';
import marks from '@/utils/map/marks';
import { FactorDatas } from '@/model/FactorDatas';
const props = defineProps({
  factorType: String
});
const emits = defineEmits(['update:factorType']);
const height = `60vh`;
const show = ref(false);
const scrollContentRef = ref();
const scrollbarRef = ref();
@@ -34,6 +103,12 @@
  const h1 = scrollContentRef.value.clientHeight + 100;
  setTimeout(() => {
    scrollbarRef.value.setScrollTop(h1);
  }, 100);
}
function scrollToTop() {
  setTimeout(() => {
    scrollbarRef.value.setScrollTop(0);
  }, 100);
}
@@ -50,14 +125,116 @@
 */
const putWorkStream = (data) => {
  const obj = JSON.parse(data);
  console.log('sourcetrace: ', obj);
  obj._statusStr = exceptionStatus(obj.status);
  if (streams.length == 0) {
  streams.push(obj);
  scrollToBottom();
  } else {
    const index = streams.findIndex((v) => {
      return v.guid == obj.guid;
    });
    if (index != -1) {
      const old = streams[index];
      obj.showMore = old.showMore;
      old.relatedSceneList.forEach((s) => {
        const index = obj.relatedSceneList.findIndex((v) => {
          return v.guid == s.guid;
        });
        if (index == -1) {
          obj.relatedSceneList.push(s);
        }
      });
      streams.splice(index, 1, obj);
    } else {
      streams.unshift(obj);
    }
    show.value = true;
  }
  // scrollToBottom();
  scrollToTop();
  const excObj = streams.find((v) => {
    return v.factorId + '' == props.factorType;
  });
  if (excObj) {
    drawSector(excObj);
  }
};
function exceptionStatus(value) {
  switch (value) {
    case 1:
      return '异常发生中';
    case 2:
      return '异常已结束';
    default:
      return '异常已结束';
  }
}
let lastDrawObjGuid;
function drawSector(exceptionObj) {
  emits('update:factorType', exceptionObj.factorId + '');
  setTimeout(() => {
    if (exceptionObj.guid != lastDrawObjGuid) {
      sector.clearSectorPt();
      lastDrawObjGuid = exceptionObj.guid;
    }
    // 1. ç»˜åˆ¶æ–°æ‰‡å½¢åŒºåŸŸ
    const datavo = exceptionObj.midData;
    const factorDatas = new FactorDatas();
    factorDatas.setData([datavo], 0, () => {
      const pr = sector.drawSectorPt(factorDatas, 0);
      // è°ƒæ•´è§†è§’居中显示
      // mapUtil.setCenter(pr.p);
      drawMarks(exceptionObj.relatedSceneList);
    });
  }, 1000);
}
let layer = undefined;
function drawMarks(sceneList) {
  if (layer != undefined) {
    mapUtil.removeViews(layer);
    // layer = undefined;
  }
  if (sceneList.length != 0) {
    const icons = [];
    sceneList.forEach((s) => {
      icons.push(sceneIcon(s.typeId));
    });
    layer = marks.createLabelMarks(icons, sceneList, true);
  }
}
onMounted(() => {
  websocket.registerReceiveEvent(putWorkStream);
});
onUnmounted(() => {
  websocket.removeReceiveEvent(putWorkStream);
  sector.clearSectorPt();
  mapUtil.clearMap();
  // if (layer != undefined) {
  //   mapUtil.removeViews(layer);
  //   layer = undefined;
  // }
});
</script>
<style scoped>
:deep(.el-statistic) {
  --el-statistic-title-color: rgb(215, 215, 215);
  --el-statistic-content-color: white;
}
:deep(.el-text) {
  --el-text-color: white;
}
.scrollbar {
  min-width: 300px;
}
</style>