riku
2025-07-10 2cffd9c7db5c3191cf172641c800e5a328d6f3af
2025.7.10 修改动态溯源模块
已修改9个文件
已添加2个文件
628 ■■■■ 文件已修改
src/components.d.ts 173 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/index.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/historymode/HistoryMode.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/realtimemode/RealtimeMode.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/SourceTrace.vue 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/ClueRecordItem.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/PollutedClueItem.vue 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/PollutedClueItem_Old.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/PollutedExceptionItem.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/PollutedWarnItem.vue 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sourcetrace/component/SourceTraceFilter.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts
@@ -7,93 +7,94 @@
declare module 'vue' {
  export interface GlobalComponents {
    BaseCard: (typeof import('./components/BaseCard.vue'))['default'];
    BaseMap: (typeof import('./components/map/BaseMap.vue'))['default'];
    CardButton: (typeof import('./components/CardButton.vue'))['default'];
    CardDialog: (typeof import('./components/CardDialog.vue'))['default'];
    'CardDialog copy': (typeof import('./components/CardDialog copy.vue'))['default'];
    CheckButton: (typeof import('./components/common/CheckButton.vue'))['default'];
    ConfigManage: (typeof import('./components/map/ConfigManage.vue'))['default'];
    copy: (typeof import('./components/monitor/WeatherData_Old.vue'))['default'];
    CoreHeader: (typeof import('./components/core/CoreHeader.vue'))['default'];
    CoreMenu: (typeof import('./components/core/CoreMenu.vue'))['default'];
    DataSummary: (typeof import('./components/monitor/DataSummary.vue'))['default'];
    DataTable: (typeof import('./components/monitor/DataTable.vue'))['default'];
    DescriptionsList: (typeof import('./components/list/DescriptionsList.vue'))['default'];
    DescriptionsListItem: (typeof import('./components/list/DescriptionsListItem.vue'))['default'];
    DeviceCreate: (typeof import('./components/device/DeviceCreate.vue'))['default'];
    DeviceManage: (typeof import('./components/device/DeviceManage.vue'))['default'];
    ElButton: (typeof import('element-plus/es'))['ElButton'];
    ElCascader: (typeof import('element-plus/es'))['ElCascader'];
    ElCheckbox: (typeof import('element-plus/es'))['ElCheckbox'];
    ElCheckboxGroup: (typeof import('element-plus/es'))['ElCheckboxGroup'];
    ElCol: (typeof import('element-plus/es'))['ElCol'];
    ElConfigProvider: (typeof import('element-plus/es'))['ElConfigProvider'];
    ElDatePicker: (typeof import('element-plus/es'))['ElDatePicker'];
    ElDialog: (typeof import('element-plus/es'))['ElDialog'];
    ElDivider: (typeof import('element-plus/es'))['ElDivider'];
    ElDropdown: (typeof import('element-plus/es'))['ElDropdown'];
    ElDropdownItem: (typeof import('element-plus/es'))['ElDropdownItem'];
    ElDropdownMenu: (typeof import('element-plus/es'))['ElDropdownMenu'];
    ElForm: (typeof import('element-plus/es'))['ElForm'];
    ElFormItem: (typeof import('element-plus/es'))['ElFormItem'];
    ElIcon: (typeof import('element-plus/es'))['ElIcon'];
    ElInput: (typeof import('element-plus/es'))['ElInput'];
    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'];
    ElRadio: (typeof import('element-plus/es'))['ElRadio'];
    ElRadioGroup: (typeof import('element-plus/es'))['ElRadioGroup'];
    ElRow: (typeof import('element-plus/es'))['ElRow'];
    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'];
    FactorRadio: (typeof import('./components/monitor/FactorRadio.vue'))['default'];
    FactorTrend: (typeof import('./components/monitor/FactorTrend.vue'))['default'];
    GaugeChart: (typeof import('./components/chart/GaugeChart.vue'))['default'];
    GridSearch: (typeof import('./components/grid/GridSearch.vue'))['default'];
    HistoricalTrajectory: (typeof import('./components/animation/HistoricalTrajectory.vue'))['default'];
    MapLocate: (typeof import('./components/map/MapLocate.vue'))['default'];
    MapScene: (typeof import('./components/map/MapScene.vue'))['default'];
    MapToolbox: (typeof import('./components/map/MapToolbox.vue'))['default'];
    MessageBox: (typeof import('./components/MessageBox.vue'))['default'];
    MissionEdit: (typeof import('./components/mission/MissionEdit.vue'))['default'];
    MissionImport: (typeof import('./components/mission/MissionImport.vue'))['default'];
    MissionManage: (typeof import('./components/mission/MissionManage.vue'))['default'];
    OptionDevice: (typeof import('./components/search/OptionDevice.vue'))['default'];
    OptionLocation: (typeof import('./components/search/OptionLocation.vue'))['default'];
    OptionLocation2: (typeof import('./components/search/OptionLocation2.vue'))['default'];
    OptionMission: (typeof import('./components/search/OptionMission.vue'))['default'];
    OptionPollutionDegree: (typeof import('./components/search/OptionPollutionDegree.vue'))['default'];
    OptionTime: (typeof import('./components/search/OptionTime.vue'))['default'];
    OptionType: (typeof import('./components/search/OptionType.vue'))['default'];
    ProgressLineChart: (typeof import('./components/chart/ProgressLineChart.vue'))['default'];
    RealTimeLineChart: (typeof import('./components/chart/RealTimeLineChart.vue'))['default'];
    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'];
    VehicleData: (typeof import('./components/monitor/VehicleData.vue'))['default'];
    WeatherData: (typeof import('./components/monitor/WeatherData.vue'))['default'];
    WeatherData_Old: (typeof import('./components/monitor/WeatherData_Old.vue'))['default'];
    WeatherDataCopy: (typeof import('./components/monitor/WeatherData-copy.vue'))['default'];
    BaseCard: typeof import('./components/BaseCard.vue')['default']
    BaseMap: typeof import('./components/map/BaseMap.vue')['default']
    CardButton: typeof import('./components/CardButton.vue')['default']
    CardDialog: typeof import('./components/CardDialog.vue')['default']
    'CardDialog copy': typeof import('./components/CardDialog copy.vue')['default']
    CheckButton: typeof import('./components/common/CheckButton.vue')['default']
    ConfigManage: typeof import('./components/map/ConfigManage.vue')['default']
    copy: typeof import('./components/CardDialog copy.vue')['default']
    CoreHeader: typeof import('./components/core/CoreHeader.vue')['default']
    CoreMenu: typeof import('./components/core/CoreMenu.vue')['default']
    DataSummary: typeof import('./components/monitor/DataSummary.vue')['default']
    DataTable: typeof import('./components/monitor/DataTable.vue')['default']
    DescriptionsList: typeof import('./components/list/DescriptionsList.vue')['default']
    DescriptionsListItem: typeof import('./components/list/DescriptionsListItem.vue')['default']
    DeviceCreate: typeof import('./components/device/DeviceCreate.vue')['default']
    DeviceManage: typeof import('./components/device/DeviceManage.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
    ElDialog: typeof import('element-plus/es')['ElDialog']
    ElDivider: typeof import('element-plus/es')['ElDivider']
    ElDropdown: typeof import('element-plus/es')['ElDropdown']
    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
    ElForm: typeof import('element-plus/es')['ElForm']
    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']
    ElPopover: typeof import('element-plus/es')['ElPopover']
    ElRadio: typeof import('element-plus/es')['ElRadio']
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
    ElRow: typeof import('element-plus/es')['ElRow']
    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']
    FactorIconText: typeof import('./components/monitor/FactorIconText.vue')['default']
    FactorLegend: typeof import('./components/monitor/FactorLegend.vue')['default']
    FactorRadio: typeof import('./components/monitor/FactorRadio.vue')['default']
    FactorTrend: typeof import('./components/monitor/FactorTrend.vue')['default']
    GaugeChart: typeof import('./components/chart/GaugeChart.vue')['default']
    GridSearch: typeof import('./components/grid/GridSearch.vue')['default']
    HistoricalTrajectory: typeof import('./components/animation/HistoricalTrajectory.vue')['default']
    MapLocate: typeof import('./components/map/MapLocate.vue')['default']
    MapScene: typeof import('./components/map/MapScene.vue')['default']
    MapToolbox: typeof import('./components/map/MapToolbox.vue')['default']
    MessageBox: typeof import('./components/MessageBox.vue')['default']
    MissionEdit: typeof import('./components/mission/MissionEdit.vue')['default']
    MissionImport: typeof import('./components/mission/MissionImport.vue')['default']
    MissionManage: typeof import('./components/mission/MissionManage.vue')['default']
    OptionDevice: typeof import('./components/search/OptionDevice.vue')['default']
    OptionLocation: typeof import('./components/search/OptionLocation.vue')['default']
    OptionLocation2: typeof import('./components/search/OptionLocation2.vue')['default']
    OptionMission: typeof import('./components/search/OptionMission.vue')['default']
    OptionPollutionDegree: typeof import('./components/search/OptionPollutionDegree.vue')['default']
    OptionTime: typeof import('./components/search/OptionTime.vue')['default']
    OptionType: typeof import('./components/search/OptionType.vue')['default']
    ProgressLineChart: typeof import('./components/chart/ProgressLineChart.vue')['default']
    RealTimeLineChart: typeof import('./components/chart/RealTimeLineChart.vue')['default']
    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']
    VehicleData: typeof import('./components/monitor/VehicleData.vue')['default']
    WeatherData: typeof import('./components/monitor/WeatherData.vue')['default']
    WeatherData_Old: typeof import('./components/monitor/WeatherData_Old.vue')['default']
  }
  export interface ComponentCustomProperties {
    vLoading: (typeof import('element-plus/es'))['ElLoadingDirective'];
    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
  }
}
src/styles/index.scss
@@ -2,5 +2,6 @@
@use './elementUI.scss';
body {
  font-family: fantasy;
  // font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-family: 'Times New Roman', Times, serif;
}
src/views/historymode/HistoryMode.vue
@@ -55,7 +55,7 @@
    <SourceTrace
      class="source-trace"
      v-model:factorType="factorType"
      direction="right"
      direction="left"
      mode="history"
      :mission-code="missionCode"
    ></SourceTrace>
src/views/realtimemode/RealtimeMode.vue
@@ -144,7 +144,7 @@
                // startTime: '2024-12-27 08:30:00',
                // startTime: '2024-12-13 16:35:00',
                // startTime: '2024-11-27 11:50:41', // Pm, ä¸­è·ç¦»å·¥åœ°
                // startTime: '2024-08-30 15:28:00', // voc è¿‘距离汽修
                // startTime: '2024-08-30 15:27:00', // voc è¿‘距离汽修
                startTime: '2024-07-23 15:21:30',
                // startTime: '2024-07-23 14:39:00',
                endTime: '2025-01-16 11:51:41',
src/views/sourcetrace/SourceTrace.vue
@@ -1,18 +1,32 @@
<template>
  <el-row>
    <el-col v-if="direction == 'right'" span="2" class="flex-col">
  <el-row
    :style="
      direction == 'left'
        ? 'flex-direction: row;'
        : 'flex-direction: row-reverse'
    "
  >
    <el-col span="2" class="flex-col">
      <el-row justify="end">
        <CardButton
          direction="left"
          :direction="direction"
          name="动态溯源"
          @click="handleDisplayChange"
        ></CardButton>
      </el-row>
      <el-row class="flex-col">
        <PollutedWarnItem
          :item="selectedWarning"
          @showMarksAndPolygon="showMarksAndPolygon"
        ></PollutedWarnItem>
        <PollutedExceptionItem
          :item="selectedException"
          @showMarksAndPolygon="showMarksAndPolygon"
        ></PollutedExceptionItem>
        <PollutedClueItem
          v-model="clueDialog"
          :item="selectedClue"
        ></PollutedClueItem>
      </el-row>
    </el-col>
    <el-col v-show="show" span="10">
@@ -35,9 +49,9 @@
            <!-- æ•°æ®åˆ‡ç‰‡ç»Ÿè®¡ -->
            <div style="border-left: 1px solid white" class="p-l-8">
              <el-space direction="vertical">
                <el-text type="primary">溯源:{{ countMsg1.type1 }}条</el-text>
                <el-text type="primary">线索:{{ countMsg1.type2 }}条</el-text>
                <el-text type="primary">提醒:{{ countMsg1.type3 }}条</el-text>
                <el-text type="info">溯源:{{ countMsg1.type1 }}条</el-text>
                <el-text type="info">线索:{{ countMsg1.type2 }}条</el-text>
                <el-text type="info">提醒:{{ countMsg1.type3 }}条</el-text>
              </el-space>
            </div>
          </el-row>
@@ -57,7 +71,7 @@
        </template>
      </BaseCard>
    </el-col>
    <el-col v-if="direction == 'left'" span="2" class="flex-col">
    <!-- <el-col v-if="direction == 'left'" span="2" class="flex-col">
      <el-row justify="start">
        <CardButton
          direction="right"
@@ -70,12 +84,12 @@
          :item="selectedException"
          @showMarksAndPolygon="showMarksAndPolygon"
        ></PollutedExceptionItem>
      </el-row>
    </el-col>
    <PollutedClueItem
      v-model="clueDialog"
      :item="selectedClue"
    ></PollutedClueItem>
      </el-row>
    </el-col> -->
  </el-row>
</template>
<script setup>
@@ -101,6 +115,7 @@
import ClueRecordItem from './component/ClueRecordItem.vue';
import PollutedClueItem from '@/views/sourcetrace/component/PollutedClueItem.vue';
import SourceTraceFilter from '@/views/sourcetrace/component/SourceTraceFilter.vue';
import PollutedWarnItem from './component/PollutedWarnItem.vue';
const NO_SCENE = 'no_scene';
@@ -110,7 +125,7 @@
const props = defineProps({
  direction: {
    type: String,
    default: 'left'
    default: 'right'
  },
  factorType: String,
  // æ¨¡å¼ï¼Œrealtime:实时模式;history:历史数据模式
@@ -125,12 +140,14 @@
const emits = defineEmits(['update:factorType']);
const show = ref(false);
const clueDialog = ref(false);
const scrollContentRef = ref();
const scrollbarRef = ref();
const selectedWarning = ref();
const selectedException = ref();
const selectedClue = ref();
const clueDialog = ref(false);
const selectedMsgTypes = ref(['1', '2', '3']);
const selectedFactorTypes = ref([]);
const factorOptions = ref([]);
@@ -281,7 +298,7 @@
        // è‹¥æ²¡æœ‰æ‰¾åˆ°é£Žé™©æºæ—¶ï¼Œå°†è¯¥åˆ†ç±»è®¾å®šä¸ºnull
        if (sceneOptions.value.findIndex((v) => v.value == NO_SCENE) == -1) {
          sceneOptions.value.push({
            label: '无',
            label: '未知',
            value: NO_SCENE
          });
          selectedSceneTypes.value.push(NO_SCENE);
@@ -374,21 +391,33 @@
);
function handleOpen(item) {
  // åŽ»é™¤ä¸Šä¸€ä¸ªâ€œæº¯æºâ€å’Œâ€œæé†’â€æ¶ˆæ¯çš„é€‰ä¸­æ ‡å¿—
  if (selectedWarning.value && selectedWarning.value._selected) {
    selectedWarning.value._selected = false;
    showMarksAndPolygon(selectedWarning.value);
  }
  if (selectedException.value && selectedException.value._selected) {
    selectedException.value._selected = false;
    showMarksAndPolygon(selectedException.value);
  }
  if (selectedClue.value && selectedClue.value._selected) {
    selectedClue.value._selected = false;
    clueDialog.value = false;
  }
  switch (item._type) {
    case '1':
    case '3':
      if (selectedException.value) {
        selectedException.value._selected = false;
      }
      // æ˜¾ç¤ºå½“前选中的相关地图标记
      showMarksAndPolygon(item);
      // æ›´æ–°é€‰ä¸­çš„对象
      selectedException.value = item;
      break;
    case '2':
      if (selectedClue.value) {
        selectedClue.value._selected = false;
      }
      selectedClue.value = item;
      clueDialog.value = true;
      break;
    case '3':
      showMarksAndPolygon(item);
      selectedWarning.value = item;
      break;
  }
  item._selected = true;
@@ -412,7 +441,7 @@
  } else {
    if (polygonMap.has(item.guid)) {
      map.remove(polygonMap.get(item.guid));
      selectedException.value._selected = false;
      item._selected = false;
    }
  }
}
@@ -473,7 +502,7 @@
  --el-statistic-content-color: white;
}
:deep(.el-text.el-text--primary) {
:deep(.el-text.el-text--info) {
  --el-text-color: white;
}
src/views/sourcetrace/component/ClueRecordItem.vue
@@ -9,7 +9,7 @@
      <el-col :span="21">
        <el-row justify="space-between">
          <el-space>
            <el-text type="primary" size="default">
            <el-text type="info" size="default">
              <el-icon><Timer /></el-icon>
              {{
                item.pollutedData._startTime +
@@ -18,7 +18,7 @@
              }}
            </el-text>
          </el-space>
          <el-link type="primary" @click="emits('open', item)"> è¯¦æƒ… </el-link>
          <el-link type="info" @click="emits('open', item)"> è¯¦æƒ… </el-link>
        </el-row>
        <div>
          <el-tag
@@ -32,13 +32,13 @@
          >
            <div v-html="formatFactorName(item.pollutedData.factorName)"></div>
          </el-tag>
          <el-text type="primary">
          <el-text type="info">
            {{ item.pollutedData.exception + ',' }}
          </el-text>
          <el-text type="primary">{{
          <el-text type="info">{{
            formatDistanceType(item.pollutedArea.distanceType)
          }}</el-text>
          <el-text :type="noWarn ? 'primary' : 'warning'">
          <el-text :type="noWarn ? 'info' : 'warning'">
            {{
              item.pollutedSource.sceneList.length == 0
                ? '未找到风险源'
@@ -65,11 +65,11 @@
    <div v-else-if="item._type == '2'">
      <el-row justify="space-between">
        <el-tag type="danger" effect="dark" size="small">线索</el-tag>
        <el-link type="primary" @click="emits('open')"> è¯¦æƒ… </el-link>
        <el-link type="info" @click="emits('open')"> è¯¦æƒ… </el-link>
      </el-row>
      <el-space>
        <el-icon color="#F56C6C" :size="40"><WarnTriangleFilled /></el-icon>
        <el-text type="primary">{{ item.advice }}</el-text>
        <el-text type="info">{{ item.advice }}</el-text>
      </el-space>
    </div>
    <el-row v-else-if="item._type == '3'">
@@ -79,7 +79,7 @@
      <el-col :span="21">
        <el-row justify="space-between">
          <el-space>
            <el-text type="primary" size="default">
            <el-text type="info" size="default">
              <el-icon><Timer /></el-icon>
              {{
                item.pollutedData._startTime +
@@ -88,7 +88,7 @@
              }}
            </el-text>
          </el-space>
          <!-- <el-link type="primary" @click="emits('open', item)"> è¯¦æƒ… </el-link> -->
          <el-link type="info" @click="emits('open', item)"> è¯¦æƒ… </el-link>
        </el-row>
        <div>
          <el-tag
@@ -102,7 +102,7 @@
          >
            <div v-html="formatFactorName(item.pollutedData.factorName)"></div>
          </el-tag>
          <el-text type="primary">{{ item.pollutedData.exception }}</el-text>
          <el-text type="info">{{ item.pollutedData.exception }}</el-text>
        </div>
        <div v-if="item.pollutedSource.sceneList.length > 0">
          <div v-for="s in item.pollutedSource.sceneList" :key="s.guid">
@@ -122,18 +122,18 @@
      <!-- <el-row justify="space-between">
        <el-space>
          <el-tag type="primary" effect="dark" size="small">提醒</el-tag>
          <el-text type="primary">{{
          <el-tag type="info" effect="dark" size="small">提醒</el-tag>
          <el-text type="info">{{
            item.pollutedData.startTime + ' - ' + item.pollutedData.endTime
          }}</el-text>
        </el-space>
        <el-link type="primary" @click="emits('open', item)"> è¯¦æƒ… </el-link>
        <el-link type="info" @click="emits('open', item)"> è¯¦æƒ… </el-link>
      </el-row>
      <el-col :span="24">
        <el-tag effect="plain" type="info" size="small" hit round class="m-r-4">
          <div v-html="formatFactorName(item.pollutedData.factorName)"></div>
        </el-tag>
        <el-text type="primary">{{ item.pollutedData.exception }}</el-text>
        <el-text type="info">{{ item.pollutedData.exception }}</el-text>
      </el-col> -->
    </el-row>
  </div>
src/views/sourcetrace/component/PollutedClueItem.vue
@@ -1,5 +1,5 @@
<template>
  <CardDialog
  <!-- <CardDialog
    :model-value="modelValue"
    @update:model-value="handleDialogShow"
    title="污染线索"
@@ -7,22 +7,38 @@
    :modal="false"
    width="400px"
  >
    <template #default>
      <template v-if="item">
    <template #default> -->
  <BaseCard v-if="item" v-show="modelValue">
    <template #content>
      <el-scrollbar class="clue-card">
        <el-row justify="space-between" align="bottom">
          <el-text type="danger" style="font-weight: 600" size="large">
            æ±¡æŸ“线索
          </el-text>
          <el-link
            type="info"
            :underline="true"
            @click="handleDialogShow(!modelValue)"
          >
            {{ modelValue ? '收起' : '打开' }}
            <el-icon size="large"><CircleClose /></el-icon>
          </el-link>
        </el-row>
        <el-row>
          <el-text type="primary" size="small">{{ item._timestr }}</el-text>
          <el-text type="info" size="small">{{ item._timestr }}</el-text>
        </el-row>
        <el-space>
          <el-icon color="#F56C6C" :size="40"><WarnTriangleFilled /></el-icon>
          <el-text type="primary">
          <el-text type="info">
            {{ item.advice }}
          </el-text>
        </el-space>
        <el-row justify="space-between">
          <el-text type="primary" size="small">
          <el-text type="info" size="small">
            æŽ¨èè·¯çº¿æ€»é•¿{{ item.direction.distance }}ç±³
          </el-text>
          <el-link type="primary" size="small" @click="showPolyline">
          <el-link type="info" size="small" @click="showPolyline">
            {{ lineShow ? '收起路线' : '定位路线' }}
          </el-link>
        </el-row>
@@ -32,10 +48,12 @@
        >
          <el-table-column prop="_count" width="42" label="溯源次数" />
        </SceneTable>
      </el-scrollbar>
      </template>
    </template>
  </BaseCard>
  <!-- </template>
    <template #footer> </template>
  </CardDialog>
  </CardDialog> -->
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive, watch } from 'vue';
@@ -62,6 +80,7 @@
watch(
  () => [props.item, props.modelValue],
  (nV, oV) => {
    // çº¿ç´¢ä¿¡æ¯å˜æ›´åŽï¼Œé‡æ–°ç»˜åˆ¶è·¯çº¿å›¾
    if (nV[0] != oV[0]) {
      const polyline = mapLine.drawDirection(
        nV[0].direction.paths.map((v) => [v.first, v.second])
@@ -72,8 +91,11 @@
      }
      lastPolyline = polyline;
      lineShow.value = true;
    } else if (nV[1]) {
      showPolyline(true);
    }
    // æ˜¾ç¤ºéšè—å˜åŒ–时,对应显示或隐藏路线
    else if (nV[1] != oV[1]) {
      // showPolyline(nV[1]);
      handleDialogShow(nV[1]);
    }
  }
);
@@ -132,4 +154,13 @@
:deep(.el-link) {
  --el-link-text-color: #23dad1;
} */
.clue-card {
  padding: 0 4px;
  /* margin-right: 2px; */
  width: 340px;
  height: 360px;
  /* border-right: 1px solid white; */
  border-radius: 2px;
}
</style>
src/views/sourcetrace/component/PollutedClueItem_Old.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
<template>
  <CardDialog
    :model-value="modelValue"
    @update:model-value="handleDialogShow"
    title="污染线索"
    draggable
    :modal="false"
    width="400px"
  >
    <template #default>
      <template v-if="item">
        <el-row>
          <el-text type="info" size="small">{{ item._timestr }}</el-text>
        </el-row>
        <el-space>
          <el-icon color="#F56C6C" :size="40"><WarnTriangleFilled /></el-icon>
          <el-text type="info">
            {{ item.advice }}
          </el-text>
        </el-space>
        <el-row justify="space-between">
          <el-text type="info" size="small">
            æŽ¨èè·¯çº¿æ€»é•¿{{ item.direction.distance }}ç±³
          </el-text>
          <el-link type="info" size="small" @click="showPolyline">
            {{ lineShow ? '收起路线' : '定位路线' }}
          </el-link>
        </el-row>
        <SceneTable
          :show-marks="lineShow"
          :scene-list="formatSceneList(item.sortedSceneList)"
        >
          <el-table-column prop="_count" width="42" label="溯源次数" />
        </SceneTable>
      </template>
    </template>
    <template #footer> </template>
  </CardDialog>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive, watch } from 'vue';
import { sceneTypes, sceneIcon } from '@/constant/scene-types';
import mapLine from '@/utils/map/line';
import mapUtil from '@/utils/map/util';
import marks from '@/utils/map/marks';
const props = defineProps({
  modelValue: Boolean,
  item: Object
});
const lineShow = ref(true);
// ä¸Šä¸€æ¡å¯¼èˆªè·¯çº¿
let lastPolyline = undefined;
// ä¸Šä¸€ä¸ªå¯¼èˆªç›®çš„地
// let lastDestination = undefined;
// let layer = undefined;
const emits = defineEmits(['update:modelValue']);
watch(
  () => [props.item, props.modelValue],
  (nV, oV) => {
    if (nV[0] != oV[0]) {
      const polyline = mapLine.drawDirection(
        nV[0].direction.paths.map((v) => [v.first, v.second])
      );
      // polylineList.unshift(polyline);
      if (lastPolyline) {
        mapUtil.removeViews(lastPolyline);
      }
      lastPolyline = polyline;
      lineShow.value = true;
    } else if (nV[1]) {
      showPolyline(true);
    }
  }
);
function showPolyline(show) {
  if (typeof show === 'boolean') {
    lineShow.value = show;
  } else {
    lineShow.value = !lineShow.value;
  }
  if (lineShow.value && lastPolyline) {
    mapUtil.addViews(lastPolyline);
  } else {
    mapUtil.removeViews(lastPolyline);
  }
}
// function removeLayer() {
//   if (layer != undefined) {
//     mapUtil.removeViews(layer);
//     layer = undefined;
//   }
// }
// function drawMarks(sceneList) {
//   removeLayer();
//   if (sceneList.length != 0) {
//     const icons = [];
//     sceneList.forEach((s) => {
//       icons.push(sceneIcon(s.typeId));
//     });
//     layer = marks.createLabelMarks(icons, sceneList, false);
//   }
// }
function formatSceneList(sceneList) {
  return sceneList.map((v) => {
    return {
      ...v.first,
      _count: v.second
    };
  });
}
function handleDialogShow(value) {
  showPolyline(value);
  emits('update:modelValue', value);
}
</script>
<style scoped>
/* :deep(.el-text.el-text--primary) {
  --el-text-color: white;
}
:deep(.el-link) {
  --el-link-text-color: #23dad1;
} */
</style>
src/views/sourcetrace/component/PollutedExceptionItem.vue
@@ -11,10 +11,12 @@
    <template #content>
      <el-scrollbar class="clue-card">
        <el-row justify="space-between" align="bottom">
          <el-text type="warning" size="large"> å…¸åž‹åˆ‡ç‰‡ </el-text>
          <el-text type="warning" style="font-weight: 600" size="large">
            æº¯æºåˆ‡ç‰‡
          </el-text>
          <el-link
            type="primary"
            type="info"
            :underline="true"
            @click="showMarksAndPolygon(item)"
          >
@@ -23,7 +25,7 @@
          </el-link>
        </el-row>
        <div>
          <el-text type="primary">
          <el-text type="info">
            <el-icon><Timer /></el-icon>
            {{
              '切片时段:' +
@@ -34,18 +36,18 @@
          </el-text>
        </div>
        <div>
          <el-text type="primary">
          <el-text type="info">
            <el-icon><MapLocation /></el-icon>
            {{ '风险区域:' + item.pollutedArea.address }}
          </el-text>
        </div>
        <!-- <div>
          <el-text type="primary">
          <el-text type="info">
            æº¯æºè·ç¦»ï¼š{{ formatDistanceType(item.pollutedArea.distanceType) }}
          </el-text>
        </div> -->
        <div>
          <el-text type="primary">
          <el-text type="info">
            <el-icon><Bell /></el-icon>
            å¼‚常类型:{{ item.pollutedData.exception }}
          </el-text>
@@ -83,7 +85,7 @@
        </el-row>
        <el-row justify="space-between">
          <!-- <el-link
                    type="primary"
                    type="info"
                    underline
                    @click="showMarksAndPolygon(item)"
                  >
@@ -106,7 +108,7 @@
        <!-- </div> -->
        <div class="border-dashed">
          <el-icon color="#ffbc58" size="20"><WarningFilled /></el-icon>
          <el-text type="primary" tag="b">
          <el-text type="info" tag="b">
            {{ item.pollutedSource.conclusion }}
          </el-text>
        </div>
@@ -159,7 +161,7 @@
}
</script>
<style scoped>
:deep(.el-statistic) {
/* :deep(.el-statistic) {
  --el-statistic-title-color: rgb(215, 215, 215);
  --el-statistic-content-color: white;
}
@@ -170,7 +172,7 @@
:deep(.el-link) {
  --el-link-text-color: #23dad1;
}
} */
.scrollbar {
  min-width: 300px;
src/views/sourcetrace/component/PollutedWarnItem.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
<template>
  <BaseCard v-if="item" v-show="item.showMore">
    <template #content>
      <el-scrollbar class="clue-card">
        <el-row justify="space-between" align="bottom">
          <el-text type="primary" style="font-weight: 600" size="large">
            æé†’切片
          </el-text>
          <el-link
            type="info"
            :underline="true"
            @click="showMarksAndPolygon(item)"
          >
            {{ item.showMore ? '收起' : '定位' }}
            <el-icon size="large"><CircleClose /></el-icon>
          </el-link>
        </el-row>
        <div>
          <el-text type="info">
            <el-icon><Timer /></el-icon>
            {{
              '切片时段:' +
              item.pollutedData._startTime +
              ' - ' +
              item.pollutedData._endTime
            }}
          </el-text>
        </div>
        <div>
          <el-text type="info">
            <el-icon><MapLocation /></el-icon>
            {{ '所在区域:' + item.pollutedArea.address }}
          </el-text>
        </div>
        <!-- <div>
          <el-text type="info">
            æº¯æºè·ç¦»ï¼š{{ formatDistanceType(item.pollutedArea.distanceType) }}
          </el-text>
        </div> -->
        <div>
          <el-text type="info">
            <el-icon><Bell /></el-icon>
            æé†’类型:{{ item.pollutedData.exception }}
          </el-text>
        </div>
        <el-row style="border-top: 1px solid white"> </el-row>
        <RealTimeLineChart
          v-for="(item1, index1) in item._chartOptions"
          :key="index1"
          :model-value="item1"
        ></RealTimeLineChart>
      </el-scrollbar>
    </template>
  </BaseCard>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({
  modelValue: Boolean,
  item: Object
});
const emits = defineEmits(['showMarksAndPolygon', 'update:modelValue']);
function showMarksAndPolygon(item) {
  emits('showMarksAndPolygon', item);
}
function formatPercentage(value) {
  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;
  }
}
function formatChangeRate(value) {
  return Math.round(value * 100) / 100 + '';
}
</script>
<style scoped>
/* :deep(.el-statistic) {
  --el-statistic-title-color: rgb(215, 215, 215);
  --el-statistic-content-color: white;
}
:deep(.el-text .el-text--primary) {
  --el-text-color: green;
}
:deep(.el-link) {
  --el-link-text-color: #23dad1;
} */
.scrollbar {
  min-width: 300px;
  max-width: 60vw;
  /* height: 35vh; */
  /* color: #02ffea; */
}
.clue-card {
  padding: 0 4px;
  /* margin-right: 2px; */
  width: 340px;
  height: 240px;
  /* border-right: 1px solid white; */
  border-radius: 2px;
}
.border-dashed {
  /* border: 1px dashed white; */
  display: flex;
  /* align-items: center; */
  border: 1px dashed #ffbc58;
  padding: 0px 1px;
  margin-bottom: 4px;
}
</style>
src/views/sourcetrace/component/SourceTraceFilter.vue
@@ -2,7 +2,7 @@
  <div>
    <div>
      <el-space>
        <el-text type="primary">数据切片</el-text>
        <el-text type="info">数据切片</el-text>
        <el-checkbox-group
          :model-value="dataSlice"
          @update:model-value="(e) => emits('update:data-slice', e)"
@@ -19,7 +19,7 @@
    </div>
    <div>
      <el-space>
        <el-text type="primary">监测因子</el-text>
        <el-text type="info">监测因子</el-text>
        <el-checkbox-group
          :model-value="factorType"
          @update:model-value="(e) => emits('update:factor-type', e)"
@@ -40,7 +40,7 @@
    </div>
    <div>
      <el-space>
        <el-text type="primary">场景类型</el-text>
        <el-text type="info">场景类型</el-text>
        <el-checkbox-group
          :model-value="sceneType"
          @update:model-value="(e) => emits('update:scene-type', e)"