From 16eb4bd55a4fd61ddd7a171b1a07378c45d1665b Mon Sep 17 00:00:00 2001 From: riku <risaku@163.com> Date: 星期四, 05 六月 2025 13:59:28 +0800 Subject: [PATCH] 动态溯源(待完成) --- src/views/sourcetrace/SourceTrace.vue | 87 +++++++++++------ src/utils/factor/data.js | 17 ++ src/components/device/DeviceManage.vue | 16 +-- src/api/index.js | 8 src/components.d.ts | 2 src/utils/map/line.js | 3 src/views/realtimemode/RealtimeMode.vue | 29 ++++- src/styles/elementUI.scss | 13 ++ src/components/scene/SceneTable.vue | 22 ++-- src/views/sourcetrace/UnderwayAdvice.vue | 83 ++++++++++++++- 10 files changed, 205 insertions(+), 75 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 73e7919..c83bec6 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -13,10 +13,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({ diff --git a/src/components.d.ts b/src/components.d.ts index 75ed0a7..aefd8b5 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -31,6 +31,7 @@ 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'] @@ -48,6 +49,7 @@ 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'] diff --git a/src/components/device/DeviceManage.vue b/src/components/device/DeviceManage.vue index 889e1b8..f7872c8 100644 --- a/src/components/device/DeviceManage.vue +++ b/src/components/device/DeviceManage.vue @@ -7,11 +7,7 @@ > 璁惧绠$悊 </el-button> --> - <CardDialog - :model-value="modelValue" - @changed="handleChange" - title="璧拌埅璁惧绠$悊" - > + <CardDialog v-bind="$attrs" title="璧拌埅璁惧绠$悊"> <el-row class="device-table"> <el-col :span="20"> <el-table @@ -88,9 +84,9 @@ return { loading, fetchData }; }, props: { - modelValue: Boolean + // modelValue: Boolean }, - emits: ['update:modelValue'], + // emits: ['update:modelValue'], data() { return { dialogVisible: false, @@ -105,9 +101,9 @@ } }, methods: { - handleChange(value) { - this.$emit('update:modelValue', value); - }, + // handleChange(value) { + // this.$emit('update:modelValue', value); + // }, deleteDevice(row) { this.onConfirm = () => { this.deviceStore.deleteDevice(row.deviceCode); diff --git a/src/components/scene/SceneTable.vue b/src/components/scene/SceneTable.vue index 3957132..2333209 100644 --- a/src/components/scene/SceneTable.vue +++ b/src/components/scene/SceneTable.vue @@ -49,7 +49,7 @@ </el-table> </template> <script setup> -import { ref, computed, watch } from 'vue'; +import { ref, computed, watch, onUnmounted } from 'vue'; import { sceneTypes, sceneIcon } from '@/constant/scene-types'; import MapUtil from '@/utils/map/util'; import marks from '@/utils/map/marks'; @@ -86,14 +86,15 @@ } }); +onUnmounted(() => { + removeLayer(); +}); + watch(showSceneList, (nV, oV) => { if (nV && props.showMarks) { drawMarks(nV); } else { - if (layer != undefined) { - MapUtil.removeViews(layer); - layer = undefined; - } + removeLayer(); } }); @@ -103,20 +104,21 @@ if (showSceneList.value && nV) { drawMarks(showSceneList.value); } else { - if (layer != undefined) { - MapUtil.removeViews(layer); - layer = undefined; - } + removeLayer(); } }, { immediate: true } ); -function drawMarks(sceneList) { +function removeLayer() { if (layer != undefined) { MapUtil.removeViews(layer); layer = undefined; } +} + +function drawMarks(sceneList) { + removeLayer(); if (sceneList.length != 0) { const icons = []; sceneList.forEach((s) => { diff --git a/src/styles/elementUI.scss b/src/styles/elementUI.scss index 96cfa18..ff482e2 100644 --- a/src/styles/elementUI.scss +++ b/src/styles/elementUI.scss @@ -32,3 +32,16 @@ .el-button-custom:focus-visible { outline: 0px solid var(--el-button-outline-color); } + +// .el-statistic { +// --el-statistic-title-color: rgb(215, 215, 215); +// --el-statistic-content-color: white; +// } + +// .el-text { +// --el-text-color: white; +// } + +// .el-link { +// --el-link-text-color: #23dad1; +// } diff --git a/src/utils/factor/data.js b/src/utils/factor/data.js index 4f47e41..ab85787 100644 --- a/src/utils/factor/data.js +++ b/src/utils/factor/data.js @@ -182,6 +182,7 @@ var fetchingTask; var isFetching; +var pause = false; /** * 寮�鍚疄鏃舵暟鎹惊鐜幏鍙� * @param {Object} params @@ -200,12 +201,12 @@ interval = 10 * 1000; } if (_interval) { - interval = _interval + interval = _interval; } // 寮�濮嬪惊鐜换鍔� clearFetchingTask(); fetchingTask = setInterval(() => { - if (isFetching) { + if (isFetching || pause) { return; } isFetching = true; @@ -237,6 +238,11 @@ }, interval); } +function pauseTask() { + pause = !pause; + return pause; +} + /** * 娓呯悊鍘嗗彶瀹炴椂鏁版嵁鑾峰彇浠诲姟 */ @@ -248,4 +254,9 @@ } } -export { fetchHistoryData, startLoopFetchRealTimeData, clearFetchingTask }; +export { + fetchHistoryData, + startLoopFetchRealTimeData, + clearFetchingTask, + pauseTask +}; diff --git a/src/utils/map/line.js b/src/utils/map/line.js index bb32b78..195e797 100644 --- a/src/utils/map/line.js +++ b/src/utils/map/line.js @@ -22,7 +22,8 @@ function drawDirection(path) { const polyline = newPolyline(path, '#02ffea'); - map.add(polyline) + map.add(polyline); + return polyline; } export default { diff --git a/src/views/realtimemode/RealtimeMode.vue b/src/views/realtimemode/RealtimeMode.vue index 7ebb66b..5691c14 100644 --- a/src/views/realtimemode/RealtimeMode.vue +++ b/src/views/realtimemode/RealtimeMode.vue @@ -5,9 +5,9 @@ <el-button type="primary" class="p-events-auto el-button-custom" - @click="clearFetchingTask" + @click="pauseTask" > - 鍋滄 + {{ pause ? '缁х画' : '鏆傚仠' }} </el-button> </el-row> <el-row class="m-t-2"> @@ -49,7 +49,8 @@ import { fetchHistoryData, startLoopFetchRealTimeData, - clearFetchingTask + clearFetchingTask, + pauseTask } from '@/utils/factor/data'; import thirdPartyDataApi from '@/api/thirdPartyDataApi'; import websocket from '@/api/websocket'; @@ -61,7 +62,13 @@ // const mode = 'product'; export default { - components: { DashBoard, RealTimeTrend, DeviceChange, SourceTrace, UnderwayAdvice }, + components: { + DashBoard, + RealTimeTrend, + DeviceChange, + SourceTrace, + UnderwayAdvice + }, setup() { const { loading, fetchData } = useFetchData(10000); return { loading, fetchData }; @@ -76,7 +83,8 @@ // 鏂拌幏鍙栫殑鐩戞祴鏁版嵁 factorDatas: new FactorDatas(), // 鍏ㄩ儴鐩戞祴鏁版嵁 - allFactorDatas: new FactorDatas() + allFactorDatas: new FactorDatas(), + pause: false }; }, watch: { @@ -127,10 +135,12 @@ mode == 'debug' ? { deviceCode: this.deviceCode, - startTime: '2025-01-16 11:34:00', - endTime: '2025-01-16 11:35:00', + // startTime: '2025-01-16 11:34:00', + // endTime: '2025-01-16 11:35:00', + startTime: '2024-11-27 11:50:41', + endTime: '2025-01-16 11:51:41', page, - perPage: 100 + perPage: 10 } : { deviceCode: this.deviceCode, @@ -145,6 +155,9 @@ }); }); }, + pauseTask() { + this.pause = pauseTask(); + }, clearFetchingTask() { clearFetchingTask(); }, diff --git a/src/views/sourcetrace/SourceTrace.vue b/src/views/sourcetrace/SourceTrace.vue index 0ce544e..725f2d6 100644 --- a/src/views/sourcetrace/SourceTrace.vue +++ b/src/views/sourcetrace/SourceTrace.vue @@ -18,14 +18,17 @@ <!-- <div> <el-text type="primary" size="large" tag="b">鍔ㄦ�佹函婧�</el-text> </div> --> - <el-scrollbar ref="scrollbarRef" :height="height" class="scrollbar"> - <el-row ref="scrollContentRef"> - <div + <el-scrollbar ref="scrollbarRef" class="scrollbar"> + <div + ref="scrollContentRef" + style="display: flex; width: fit-content" + > + <el-scrollbar v-for="(item, index) in streams" :key="index" class="clue-card" > - <el-row> + <el-row justify="space-between"> <!-- <el-tag v-if="index == 0" type="danger">鏈�鏂�</el-tag> --> <el-text type="primary">{{ '绾跨储鏃堕棿锛�' + @@ -33,6 +36,13 @@ ' - ' + item.pollutedData.endTime }}</el-text> + <el-link + type="primary" + :underline="true" + @click="showMarksAndPolygon(item)" + > + {{ item.showMore ? '鏀惰捣寮傚父' : '瀹氫綅寮傚父' }} + </el-link> </el-row> <div> <el-text type="primary"> @@ -46,7 +56,7 @@ }} </el-text> </div> - <el-row> + <el-row style="border-top: 1px solid white"> <el-col :span="6"> <el-statistic title="绐佸彉鍥犲瓙" @@ -86,13 +96,6 @@ '锛�' }} </el-link> --> - <el-link - type="primary" - underline - @click="showMarksAndPolygon(item)" - > - {{ item.showMore ? '鏀惰捣寮傚父' : '瀹氫綅寮傚父' }} - </el-link> </el-row> <div style="width: 320px; height: 140px"> <RealTimeLineChart @@ -100,6 +103,11 @@ :key="index1" :model-value="item1" ></RealTimeLineChart> + </div> + <div class="border-dashed"> + <el-text type="" tag="mark"> + {{ item.pollutedSource.conclusion }} + </el-text> </div> <SceneTable :show-marks="item.showMore" @@ -156,8 +164,8 @@ :scene-list="item.relatedSceneList" ></SceneTable> <el-divider /> --> - </div> - </el-row> + </el-scrollbar> + </div> </el-scrollbar> </template> </BaseCard> @@ -178,9 +186,7 @@ import { map, onMapMounted } from '@/utils/map/index_old'; import { FactorDatas } from '@/model/FactorDatas'; import factorDataParser from '@/utils/chart/factor-data-parser'; -import websocketMsgParser from "@/views/sourcetrace/websocketMsgParser.js"; - - +import websocketMsgParser from '@/views/sourcetrace/websocketMsgParser.js'; const props = defineProps({ factorType: String @@ -188,7 +194,7 @@ const emits = defineEmits(['update:factorType']); -const height = `48vh`; +const height = `30vh`; const width = `60vh`; const show = ref(false); @@ -217,7 +223,7 @@ let showFirstClueTask; function dealMsg(data) { - const {type, content} = websocketMsgParser.parseMsg(data) + const { type, content } = websocketMsgParser.parseMsg(data); // 姹℃煋绾跨储 PollutedClue if (type == '1') { @@ -229,12 +235,13 @@ if (streams.length == 0) { streams.push(obj); } else { - streams.forEach((s) => { - s.showMore = false; - }); + // streams.forEach((s) => { + // showMarksAndPolygon(s); + // }); + hideAll(); streams.unshift(obj); - show.value = true; } + show.value = true; // scrollToBottom(); scrollToTop(); @@ -263,9 +270,20 @@ // } }); +function hideAll() { + streams.forEach((s) => { + if (polygonMap.has(s.guid)) { + s.showMore = false; + map.remove(polygonMap.get(s.guid)); + } + }); +} + function showMarksAndPolygon(item) { item.showMore = !item.showMore; if (item.showMore) { + hideAll(); + item.showMore = true; drawPolygon(item); } else { if (polygonMap.has(item.guid)) { @@ -296,11 +314,11 @@ }); polygonMap.set(item.guid, pollutedAreaPolygon); } - map.setFitView(polygonMap.get(item.guid)); + // map.setFitView(polygonMap.get(item.guid)); } function parseChartData(obj) { - console.log('鎶樼嚎鍥撅細start'); + // console.log('鎶樼嚎鍥撅細start'); const factorDatas = new FactorDatas(); factorDatas.setData(obj.pollutedData.dataVoList, 0, () => { obj._chartOptions = factorDataParser.parseData(factorDatas, [ @@ -310,7 +328,7 @@ value: obj.pollutedData.factorId + '' } ]); - console.log('鎶樼嚎鍥撅細', obj._chartOptions); + // console.log('鎶樼嚎鍥撅細', obj._chartOptions); }); } @@ -435,7 +453,7 @@ --el-statistic-content-color: white; } -:deep(.el-text) { +:deep(.el-text.el-text--primary) { --el-text-color: white; } @@ -446,14 +464,23 @@ .scrollbar { min-width: 300px; max-width: 60vw; + /* height: 35vh; */ /* color: #02ffea; */ } .clue-card { padding: 0 4px; - margin-right: 2px; - width: 320px; - border: 1px solid white; + /* margin-right: 2px; */ + width: 340px; + height: 35vh; + border-right: 1px solid white; border-radius: 2px; } + +.border-dashed { + /* border: 1px dashed white; */ + border: 1px dashed #ffbc58; + padding: 0px 1px; + margin-bottom: 4px; +} </style> diff --git a/src/views/sourcetrace/UnderwayAdvice.vue b/src/views/sourcetrace/UnderwayAdvice.vue index 8c2b68d..183ba28 100644 --- a/src/views/sourcetrace/UnderwayAdvice.vue +++ b/src/views/sourcetrace/UnderwayAdvice.vue @@ -1,29 +1,57 @@ <template> <CardDialog v-model="dialogVisible" - title="璧拌埅璺嚎鎺ㄨ崘" + title="璧拌埅鏅鸿兘鍒嗘瀽" draggable :modal="false" width="400px" > - <template #default> </template> + <template #default> + <template v-if="latestResult"> + <el-row> + <el-text size="small">{{ latestResult._timestr }}</el-text> + </el-row> + <el-space> + <el-icon color="#F56C6C" :size="40"><WarnTriangleFilled /></el-icon> + <el-text> + {{ latestResult.advice }} + </el-text> + </el-space> + <el-row justify="end"> + <el-link type="primary" :underline="true" @click="showPolyline"> + {{ lineShow ? '鏀惰捣璺嚎' : '瀹氫綅璺嚎' }} + </el-link> + <el-text size="small"> + 鎺ㄨ崘璺嚎鎬婚暱{{ latestResult.direction.distance }}绫� + </el-text> + </el-row> + </template> + </template> <template #footer> </template> </CardDialog> </template> <script setup> -import { ref, onMounted, onUnmounted } from 'vue'; +import moment from 'moment'; +import { ref, onMounted, onUnmounted, reactive } from 'vue'; import websocket from '@/api/websocket'; import websocketMsgParser from '@/views/sourcetrace/websocketMsgParser.js'; import mapLine from '@/utils/map/line'; +import mapUtil from '@/utils/map/util'; -const dialogVisible = ref(true); +const dialogVisible = ref(false); +const lineShow = ref(true); +const latestResult = ref(); +let latestPolyline = undefined; +const analysisResultList = reactive([]); +const polylineList = []; onMounted(() => { websocket.registerReceiveEvent(dealMsg); }); onUnmounted(() => { websocket.removeReceiveEvent(dealMsg); + showPolyline(false); }); function dealMsg(data) { @@ -32,13 +60,50 @@ if (type == '2') { const obj = JSON.parse(content); console.log('姹℃煋鍒嗘瀽缁撴灉: ', obj); + obj._timestr = timeFormatter(obj.time); + analysisResultList.unshift(obj); + latestResult.value = obj; - obj.sortedSceneList; - obj.time; - obj.advice; - obj.direction; + // obj.sortedSceneList; + // obj.time; + // obj.advice; + // obj.direction; - mapLine.drawDirection(obj.direction.paths.map((v) => [v.first, v.second])); + const polyline = mapLine.drawDirection( + obj.direction.paths.map((v) => [v.first, v.second]) + ); + polylineList.unshift(polyline); + if (latestPolyline) { + mapUtil.removeViews(latestPolyline); + } + latestPolyline = polyline; + dialogVisible.value = true; } } + +function showPolyline(show) { + if (typeof show === 'boolean') { + lineShow.value = show; + } else { + lineShow.value = !lineShow.value; + } + if (lineShow.value) { + mapUtil.addViews(latestPolyline); + } else { + mapUtil.removeViews(latestPolyline); + } +} + +function timeFormatter(time) { + return moment(time).format('YYYY-MM-DD HH:mm:ss'); +} </script> +<style scoped> +:deep(.el-text) { + --el-text-color: white; +} + +:deep(.el-link) { + --el-link-text-color: #23dad1; +} +</style> -- Gitblit v1.9.3