riku
2025-06-03 06eeb9b59644971d93e6dd9207ac447864e527b9
动态溯源(待完成)
已修改7个文件
已添加1个文件
303 ■■■■ 文件已修改
src/api/index.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chart/RealTimeLineChart.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/scene/SceneTable.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/chart/factor-data-parser.js 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/3dLayer.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/realtimemode/RealtimeMode.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/SourceTrace.vue 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js
@@ -12,10 +12,10 @@
}
if (debug) {
  // ip1 = 'http://192.168.0.110:8084/';
  ip1 = 'http://localhost:8084/';
  // ws = `192.168.0.110:9031`;
  ws = `localhost: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/components.d.ts
@@ -39,6 +39,7 @@
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    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']
@@ -49,12 +50,14 @@
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    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']
src/components/chart/RealTimeLineChart.vue
@@ -44,6 +44,7 @@
      }
      if (this.lineChart) {
        this.lineChart.setOption(this.option, { notMerge: true });
        console.log('折线图生成:立即');
      } else {
        this.onChartCreated = () => {
          this.lineChart.setOption(this.option, { notMerge: true });
@@ -53,9 +54,12 @@
  },
  mounted() {
    this.lineChart = echarts.init(this.$refs.lineChart);
    setTimeout(() => {
    if (typeof this.onChartCreated === 'function') {
      this.onChartCreated();
        console.log('折线图生成:滞后');
    }
    }, 500);
  }
};
</script>
src/components/scene/SceneTable.vue
@@ -3,7 +3,6 @@
    :data="sceneList"
    table-layout="fixed"
    size="small"
    height="30vh"
    :show-overflow-tooltip="true"
    border
    row-class-name="t-row"
@@ -13,7 +12,7 @@
    @row-click="handleRowClick"
    @filter-change="handleFilterChange"
  >
    <el-table-column type="index" label="#" width="25" />
    <!-- <el-table-column type="index" label="#" width="25" /> -->
    <el-table-column
      prop="type"
      label="类型"
@@ -23,13 +22,19 @@
      :filter-method="filterHandler"
    />
    <el-table-column prop="name" label="名称" />
    <!-- <el-table-column prop="location" label="地址" /> -->
    <el-table-column
    <el-table-column prop="location" label="地址" />
    <el-table-column label="临近站点" width="65">
      <template #default="{ row }">
        <div>{{ row.closestStation.name }}</div>
        <div>{{ parseInt(row.length) + 'ç±³' }}</div>
      </template>
    </el-table-column>
    <!-- <el-table-column
      prop="districtName"
      label="区县"
      align="center"
      width="54"
    />
    /> -->
    <!-- <el-table-column label="管理" width="70" align="center">
      <template #default="{ row }">
        <el-button
@@ -50,7 +55,11 @@
import marks from '@/utils/map/marks';
const props = defineProps({
  sceneList: Array
  sceneList: Array,
  showMarks: {
    type: Boolean,
    default: true
  }
});
let layer = undefined;
@@ -78,11 +87,30 @@
});
watch(showSceneList, (nV, oV) => {
  if (nV != oV) {
  if (nV && props.showMarks) {
    drawMarks(nV);
  } else {
    if (layer != undefined) {
      MapUtil.removeViews(layer);
      layer = undefined;
    }
  }
});
watch(
  () => props.showMarks,
  (nV, oV) => {
    if (showSceneList.value && nV) {
      drawMarks(showSceneList.value);
    } else {
      if (layer != undefined) {
        MapUtil.removeViews(layer);
        layer = undefined;
      }
    }
  }
);
function drawMarks(sceneList) {
  if (layer != undefined) {
    MapUtil.removeViews(layer);
src/utils/chart/factor-data-parser.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
/**
 * å°†ç›‘测数据格式化为e-charts图表数据
 */
import { factorName } from '@/constant/factor-name';
function refreshX(factorDatas) {
  const newTimes = factorDatas.times.map((v) => {
    return v.split(' ')[1];
  });
  return newTimes;
}
function refreshY(factorDatas) {
  const allSeries = new Map();
  for (const key in factorDatas.factor) {
    if (Object.hasOwnProperty.call(factorDatas.factor, key)) {
      const e = factorDatas.factor[key];
      if (!allSeries.has(key)) {
        allSeries.set(key, {
          key: key,
          name: factorName[e.factorName],
          label: e.factorName,
          type: 'line',
          data: [],
          showAllSymbol: true,
          animationDelay: function (idx) {
            return idx * 10;
          }
        });
      }
      if (e.datas.length == 0) {
        continue;
      }
      const series = allSeries.get(key);
      // æ’入新数据
      const newSeries = e.datas.map((v) => v.factorData);
      series.data = series.data.concat(newSeries);
      // è®¡ç®—数据范围
      const { min, max } = dataRange(series.data);
      series.min = min;
      series.max = max;
      // è®°å½•最新数据
      series.currentData =
        Math.round(series.data[series.data.length - 1] * 10) / 10;
    }
  }
  return allSeries;
}
function dataRange(dataList) {
  let min, max;
  dataList.forEach((e) => {
    if (!min || min > e) {
      min = e;
    }
    if (!max || max < e) {
      max = e;
    }
  });
  return { min, max };
}
function toList(xAxis, allSeries, selectedFactors) {
  const list = [];
  selectedFactors.forEach((t) => {
    if (allSeries.has(t.value)) {
      list.push({
        xAxis: xAxis,
        series: allSeries.get(t.value)
      });
    }
  });
  return list;
}
export default {
  parseData(factorDatas, selectedFactors) {
    const xAxis = refreshX(factorDatas);
    const allSeries = refreshY(factorDatas);
    return toList(xAxis, allSeries, selectedFactors);
  }
};
src/utils/map/3dLayer.js
@@ -116,12 +116,20 @@
  var geometry = cylinder.geometry;
  const scale = _getScale(_minH, _maxH);
  var minH = heights[0];
  var maxH = heights[0];
  for (let i = 0; i < heights.length; i++) {
    const h = heights[i];
    minH = Math.min(minH, h);
    maxH = Math.max(maxH, h);
  }
  const scale = _getScale(minH, maxH);
  for (let i = 0; i < coors.length; i++) {
    var r = lnglats_GD[i];
    var lastP = lnglats_GD[i - 1];
    var p = coors[i];
    var h = (heights[i] - _minH) * scale + _minHeight;
    var h = (heights[i] - minH) * scale + _minHeight;
    if (heights[i] == -1) {
      h = -1;
    }
@@ -277,17 +285,17 @@
    map.off('zoomend', onMapZoom);
    // 2.计算绘图高度的边界值
    if (merge != true) {
      var minH = _minH < 0 ? heights[0] : _minH;
      var maxH = _maxH < 0 ? heights[0] : _maxH;
      for (let i = 0; i < heights.length; i++) {
        const h = heights[i];
        minH = Math.min(minH, h);
        maxH = Math.max(maxH, h);
      }
      _minH = minH;
      _maxH = maxH;
    }
    // if (merge != true) {
    //   var minH = _minH < 0 ? heights[0] : _minH;
    //   var maxH = _maxH < 0 ? heights[0] : _maxH;
    //   for (let i = 0; i < heights.length; i++) {
    //     const h = heights[i];
    //     minH = Math.min(minH, h);
    //     maxH = Math.max(maxH, h);
    //   }
    //   _minH = minH;
    //   _maxH = maxH;
    // }
    // 5.绘制3D图形
    this.drawMesh(fDatas, factor, merge);
src/views/realtimemode/RealtimeMode.vue
@@ -2,18 +2,20 @@
  <div class="p-events-none m-t-2">
    <el-row justify="center" align="middle" class="top-wrap">
      <DeviceChange @change="onDeviceChange"></DeviceChange>
      <el-button
        type="primary"
        class="p-events-auto el-button-custom"
        @click="clearFetchingTask"
      >
        åœæ­¢
      </el-button>
    </el-row>
    <el-row class="m-t-2">
      <FactorRadio
        :device-type="deviceType"
        v-model="factorType"
      ></FactorRadio>
      <FactorRadio :device-type="deviceType" v-model="factorType"></FactorRadio>
    </el-row>
    <el-row class="m-t-2">
      <el-col span="1">
        <FactorLegend
          :factor="factorDatas.factor[factorType]"
        ></FactorLegend>
        <FactorLegend :factor="factorDatas.factor[factorType]"></FactorLegend>
      </el-col>
      <el-col span="1">
        <SourceTrace v-model:factorType="factorType"></SourceTrace>
src/views/sourcetrace/SourceTrace.vue
@@ -8,35 +8,64 @@
        <button @click="handleSend">send</button>
        <button @click="handleLink">link</button>
      </div> -->
          <div>动态溯源</div>
          <!-- <div>
            <el-text type="primary" size="large" tag="b">动态溯源</el-text>
          </div> -->
          <el-scrollbar ref="scrollbarRef" :height="height" class="scrollbar">
            <div ref="scrollContentRef">
              <div v-for="(item, index) in streams" :key="index">
                <el-row gap="4">
                  <el-text type="primary">发生时间:</el-text>
                  <!-- <el-tag v-if="index == 0" type="danger">最新</el-tag> -->
                  <el-text type="primary">{{
                    item.pollutedData.startTime + ' è‡³ '
                  }}</el-text>
                  <el-text type="primary">{{
                    '线索时间:' +
                    item.pollutedData.startTime +
                    ' - ' +
                    item.pollutedData.endTime
                  }}</el-text>
                </el-row>
                <div>污染区域:{{ item.pollutedArea.address }}</div>
                <div>污染距离:{{ item.pollutedArea.distanceType }}</div>
                <div>
                  <el-text type="primary">
                    æ±¡æŸ“区域:{{ item.pollutedArea.address }}
                  </el-text>
                </div>
                <div>
                  <el-text type="primary">
                    æº¯æºè·ç¦»ï¼š{{
                      formatDistanceType(item.pollutedArea.distanceType)
                    }}
                  </el-text>
                </div>
                <el-row>
                  <el-col :span="6">
                    <el-statistic title="因子" :value="item.pollutedData.factorName" />
                    <el-statistic
                      title="突变因子"
                      :value="item.pollutedData.factorName"
                    />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic title="变化幅度" :value="formatPercentage(item.pollutedData.avgPer)" />
                    <el-statistic
                      title="变化幅度"
                      :value="formatPercentage(item.pollutedData.avgPer)"
                    />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic title="发生次数" :value="item.pollutedData.times" />
                    <el-statistic
                      title="发生次数"
                      :value="item.pollutedData.times"
                    />
                  </el-col>
                  <el-col :span="6">
                    <el-statistic
                      title="平均风速"
                      :value="item.pollutedData.windSpeed"
                      suffix="m/s"
                    />
                  </el-col>
                </el-row>
                <el-row justify="space-between">
                  <el-link
                    type="primary"
                    underline
                    @click="item.showMore = !item.showMore"
                  >
                    {{
@@ -46,12 +75,24 @@
                      ')'
                    }}
                  </el-link>
                  <el-link type="primary" @click="drawPolygon(item.pollutedArea)">
                    æŸ¥çœ‹å¼‚常
                  <el-link
                    type="primary"
                    underline
                    @click="drawPolygon(item.pollutedArea)"
                  >
                    å®šä½å¼‚常
                  </el-link>
                </el-row>
                <div v-show="item.showMore" style="width: 320px; height: 140px">
                  <RealTimeLineChart
                    v-for="(item1, index1) in item._chartOptions"
                    :key="index1"
                    :model-value="item1"
                  ></RealTimeLineChart>
                </div>
                <SceneTable
                  v-show="item.showMore"
                  :show-marks="item.showMore"
                  :scene-list="item.pollutedSource.sceneList"
                ></SceneTable>
                <el-divider />
@@ -130,6 +171,7 @@
import marks from '@/utils/map/marks';
import { map, onMapMounted } from '@/utils/map/index_old';
import { FactorDatas } from '@/model/FactorDatas';
import factorDataParser from '@/utils/chart/factor-data-parser';
const START_STR = '##';
const SPLIT_STR = '&&';
@@ -175,18 +217,22 @@
  // æ±¡æŸ“线索 PollutedClue
  if (type == '1') {
    const obj = JSON.parse(content);
    obj.showMore = true;
    console.log('污染线索: ', obj);
    drawPolygon(obj.pollutedArea);
    parseChartData(obj);
    if (streams.length == 0) {
      streams.push(obj);
    } else {
      streams.forEach((s) => {
        s.showMore = false;
      });
      streams.unshift(obj);
      show.value = true;
    }
    // scrollToBottom();
    scrollToTop();
    drawPolygon(obj.pollutedArea);
  }
  // æ±¡æŸ“分析结果 AnalysisResult
  else if (type == '2') {
@@ -222,15 +268,46 @@
    path: bounds, //多边形轮廓线的节点坐标数组
    fillOpacity: 0, //多边形填充透明度
    fillColor: '#CCF3FF', //多边形填充颜色
    // strokeColor: '#ffffff' //线条颜色
    strokeColor: '#0552f7', //线条颜色
    strokeColor: '#02ffea', //线条颜色
    // strokeColor: '#0552f7', //线条颜色
    strokeStyle: 'dashed',
    zIndex: 9
  });
  map.setFitView();
}
function parseChartData(obj) {
  const factorDatas = new FactorDatas();
  factorDatas.setData(obj.pollutedData.dataVoList, 0, () => {
    obj._chartOptions = factorDataParser.parseData(factorDatas, [
      {
        label: obj.pollutedData.factorName,
        name: obj.pollutedData.factorName,
        value: obj.pollutedData.factorId + ''
      }
    ]);
    console.log('折线图:', obj._chartOptions);
  });
}
function formatPercentage(value) {
  return Math.round(value * 100) + '%'
  return Math.round(value * 100) + '%';
}
function formatDistanceType(value) {
  switch (value) {
    case 'TYPE1':
      return '50ç±³';
    case 'TYPE2':
      return '50ç±³ - 500ç±³';
    case 'TYPE3':
      return '50ç±³ - 1公里';
    case 'TYPE4':
      return '50ç±³ - 2公里';
    default:
      break;
  }
}
/******************************************************************************************************************** */
@@ -338,7 +415,12 @@
  --el-text-color: white;
}
:deep(.el-link) {
  --el-link-text-color: #23dad1;
}
.scrollbar {
  min-width: 300px;
  /* color: #02ffea; */
}
</style>