README.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components.d.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/chart/RealTimeLineChart.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/components/map/MapLocate.vue | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/utils/chart/chart-option.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/HomePage.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/sourcetrace/SourceTrace.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/sourcetrace/component/ClueRecordItem.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/sourcetrace/component/PollutedExceptionItem.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/sourcetrace/component/SourceTraceFilter.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
README.md
@@ -134,7 +134,7 @@ â â ââ map â â â ââ BaseMap.vue â â â ââ ConfigManage.vue â â â ââ MapLocation.vue â â â ââ MapLocate.vue â â â ââ MapScene.vue â â â ââ MapToolbox.vue â â ââ MessageBox.vue src/components.d.ts
@@ -65,7 +65,7 @@ GaugeChart: typeof import('./components/chart/GaugeChart.vue')['default'] GridSearch: typeof import('./components/grid/GridSearch.vue')['default'] HistoricalTrajectory: typeof import('./components/animation/HistoricalTrajectory.vue')['default'] MapLocation: typeof import('./components/map/MapLocation.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'] src/components/chart/RealTimeLineChart.vue
@@ -22,9 +22,15 @@ // }; // } }, // æçº¿å¾å±ç¤ºé«åº¦ chartHeight: { type: String, default: '140px' }, // æçº¿å¾Yè½´å»åº¦é´è· yMinInterval: { type: Number, default: 1 } }, data() { @@ -45,7 +51,7 @@ refreshChart() { const { xAxis, series } = this.modelValue; if (!this.option) { this.option = smallLineOption(xAxis, series); this.option = smallLineOption(xAxis, series, this.yMinInterval); } else { this.option.xAxis[0].data = xAxis; this.option.series = series; src/components/map/MapLocate.vue
src/utils/chart/chart-option.js
@@ -121,7 +121,7 @@ } // æçº¿å¾ function smallLineOption(_xAxis, _series) { function smallLineOption(_xAxis, _series, yMinInterval) { var fontSize = fGetChartFontSize(); return { animationEasing: 'elasticOut', @@ -198,7 +198,7 @@ splitLine: { show: false }, minInterval: 1, minInterval: yMinInterval ? yMinInterval : 1, intervel: 1, min: function (value) { return Math.floor(value.min); src/views/HomePage.vue
@@ -7,7 +7,7 @@ <!-- <SatelliteTelemetry></SatelliteTelemetry> --> <!-- <MissionManage></MissionManage> --> <ConfigManage></ConfigManage> <!-- <MapLocation></MapLocation> --> <!-- <MapLocate></MapLocate> --> <SceneSearch></SceneSearch> <MapScene></MapScene> <GridSearch></GridSearch> src/views/sourcetrace/SourceTrace.vue
@@ -18,17 +18,30 @@ <el-col v-show="show" span="10"> <BaseCard> <template #content> <el-checkbox-group v-model="selectedMsgTypes" size="default" :min="1"> <el-space> <el-checkbox value="1">å¼å¸¸åç</el-checkbox> <el-checkbox value="2">污æçº¿ç´¢</el-checkbox> <el-row justify="space-between" align="middle" style="border-bottom: 1px solid white" > <!-- æ°æ®åçç鿡件 --> <SourceTraceFilter v-model:data-slice="selectedMsgTypes" v-model:factor-type="selectedFactorTypes" :factor-options="factorOptions" v-model:scene-type="selectedSceneTypes" :scene-options="sceneOptions" ></SourceTraceFilter> <!-- <el-divider direction="vertical"></el-divider> --> <!-- æ°æ®åçç»è®¡ --> <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-space> </el-checkbox-group> </div> </el-row> <el-scrollbar ref="scrollbarRef" class="scrollbar"> <!-- <div ref="scrollContentRef" style="display: flex; width: fit-content" > --> <TransitionGroup name="list"> <div v-for="item in filterStreams" @@ -40,7 +53,6 @@ ></ClueRecordItem> </div> </TransitionGroup> <!-- </div> --> </el-scrollbar> </template> </BaseCard> @@ -72,6 +84,9 @@ import PollutedExceptionItem from './component/PollutedExceptionItem.vue'; import ClueRecordItem from './component/ClueRecordItem.vue'; import PollutedClueItem from '@/views/sourcetrace/component/PollutedClueItem.vue'; import SourceTraceFilter from '@/views/sourcetrace/component/SourceTraceFilter.vue'; const NO_SCENE = 'no_scene'; const props = defineProps({ factorType: String @@ -89,7 +104,11 @@ const selectedException = ref(); const selectedClue = ref(); const selectedMsgTypes = ref(['1', '2']); const selectedMsgTypes = ref(['1', '2', '3']); const selectedFactorTypes = ref([]); const factorOptions = ref([]); const selectedSceneTypes = ref([]); const sceneOptions = ref([]); function scrollToBottom() { const h1 = scrollContentRef.value.clientHeight + 100; @@ -107,8 +126,57 @@ const streams = reactive([]); const filterStreams = computed(() => { return streams.filter((v) => { return selectedMsgTypes.value.indexOf(v._type) != -1; // å¤ææ¶æ¯ç±»åæ¯å¦éä¸ const b1 = selectedMsgTypes.value.indexOf(v._type) != -1; let b2, b3; switch (v._type) { case '1': case '3': // å¤æçæµå åç±»åæ¯å¦éä¸ b2 = selectedFactorTypes.value.indexOf(v.pollutedData.factorId) != -1; // å¤æåºæ¯ç±»åæ¯å¦éä¸ if (v.pollutedSource.sceneList.length == 0) { b3 = selectedSceneTypes.value.indexOf(NO_SCENE) != -1; } else { b3 = v.pollutedSource.sceneList.findIndex( (v) => selectedSceneTypes.value.indexOf(v.typeId) != -1 ) != -1; } break; case '2': b2 = true; b3 = v.sortedSceneList.findIndex( (v) => selectedSceneTypes.value.indexOf(v.first.typeId) != -1 ) != -1; break; } return b1 && b2 && b3; }); }); // ç»è®¡åç±»åæ¶æ¯çæ°é const countMsg1 = computed(() => { const count = { type1: 0, type2: 0, type3: 0 }; streams.forEach((v) => { switch (v._type) { case '1': count.type1++; break; case '2': count.type2++; break; case '3': count.type3++; break; } }); return count; }); const inputVal = ref(''); @@ -116,27 +184,16 @@ websocket.send(inputVal.value); }; let showFirstClueTask; function dealMsg(data) { const { type, content } = websocketMsgParser.parseMsg(data); const obj = reactive(JSON.parse(content)); obj._type = type; // 污æçº¿ç´¢ PollutedClue if (type == '1') { const obj = reactive(JSON.parse(content)); obj._type = type; // obj.showMore = true; obj.showMore = false; console.log('污æå¼å¸¸åç: ', obj); // if (streams.length == 0) { // streams.push(obj); // } else { // // streams.forEach((s) => { // // showMarksAndPolygon(s); // // }); // // hideAll(); // streams.unshift(obj); // } addNewMsg(obj); show.value = true; @@ -144,20 +201,61 @@ // scrollToTop(); // drawPolygon(obj.pollutedArea); parseChartData(obj); // if (showFirstClueTask) { // clearTimeout(showFirstClueTask); // } // showFirstClueTask = setTimeout(() => { // showMarksAndPolygon(obj); // }, 1000); } else if (type == '2') { const obj = JSON.parse(content); obj._type = type; // const obj = JSON.parse(content); // obj._type = type; console.log('污æçº¿ç´¢ç»æ: ', obj); obj._timestr = timeFormatter(obj.time); // streams.unshift(obj); addNewMsg(obj); } else if (type == '3') { console.log('污ææéåç: ', obj); addNewMsg(obj); parseChartData(obj); } optionsFilte(obj); } // å¯¹æ°æ®è¿è¡çéï¼å æ¬çæµå åååºæ¯ç±»å function optionsFilte(objData) { switch (objData._type) { case '1': case '3': // çéçæµå åç±»å if ( factorOptions.value.findIndex( (v) => v.value == objData.pollutedData.factorId ) == -1 ) { factorOptions.value.push({ label: objData.pollutedData.factorName, value: objData.pollutedData.factorId }); selectedFactorTypes.value.push(objData.pollutedData.factorId); } // çéåºæ¯ç±»å if (objData.pollutedSource.sceneList.length == 0) { // è¥æ²¡ææ¾å°é£é©æºæ¶ï¼å°è¯¥å类设å®ä¸ºnull if (sceneOptions.value.findIndex((v) => v.value == NO_SCENE) == -1) { sceneOptions.value.push({ label: 'æ ', value: NO_SCENE }); selectedSceneTypes.value.push(NO_SCENE); } } else { objData.pollutedSource.sceneList.forEach((s) => { if (sceneOptions.value.findIndex((v) => v.value == s.typeId) == -1) { sceneOptions.value.push({ label: s.type, value: s.typeId }); selectedSceneTypes.value.push(s.typeId); } }); } // case '2': // break; } } @@ -202,6 +300,7 @@ function handleOpen(item) { switch (item._type) { case '1': case '3': if (selectedException.value) { selectedException.value._selected = false; } src/views/sourcetrace/component/ClueRecordItem.vue
@@ -1,35 +1,64 @@ <template> <div :class="'wrapper' + (item._selected ? ' wrapper-select' : '')"> <div v-if="item._type == '1'"> <el-row v-if="item._type == '1'"> <el-col :span="3"> <el-tag :type="noWarn ? 'info' : 'warning'" effect="dark" size="small" >溯æº</el-tag > </el-col> <el-col :span="21"> <el-row justify="space-between"> <el-space> <el-tag v-if="noWarn" type="info" effect="dark" size="small" >å¼å¸¸</el-tag > <el-tag v-else type="warning" effect="dark" size="small">å¼å¸¸</el-tag> <el-text type="primary">{{ <el-text type="primary" size="default"> <el-icon><Timer /></el-icon> {{ item.pollutedData.startTime + ' - ' + item.pollutedData.endTime }}</el-text> }} </el-text> </el-space> <el-link type="primary" @click="emits('open', item)"> 详æ </el-link> </el-row> <el-col :span="24"> <div> <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="primary">{{ item.pollutedData.factorName + formatException(item.pollutedData.exceptionType) + 'ï¼' + formatDistanceType(item.pollutedArea.distanceType) }}</el-text> <el-text :type="noWarn ? 'primary' : 'warning'"> {{ item.pollutedSource.sceneList.length == 0 ? 'æªæ¾å°å¯çæ±¡ææº' : 'æ¾å°' + item.pollutedSource.sceneList.length + '个å¯çæ±¡ææº' ? 'æªæ¾å°é£é©æº' : 'æ¾å°' + item.pollutedSource.sceneList.length + '个é£é©æº' }} </el-text> </el-col> <!-- <el-col :span="2"> </el-col> --> </div> <div v-if="item.pollutedSource.sceneList.length > 0"> <div v-for="s in item.pollutedSource.sceneList" :key="s.guid"> <img style="width: 24px" :src="sceneIcon(s.typeId)" :alt="s.type" /> <el-text type="warning" tag="ins" truncated class="text-link" @click="handleSetCenter(item, s)" > {{ s.name }} </el-text> </div> </div> </el-col> </el-row> <div v-else-if="item._type == '2'"> <el-row justify="space-between"> <el-tag type="danger" effect="dark" size="small">线索</el-tag> @@ -37,10 +66,73 @@ </el-row> <el-text type="danger">{{ item.advice }}</el-text> </div> <el-row v-else-if="item._type == '3'"> <el-col :span="3"> <el-tag type="primary" effect="dark" size="small">æé</el-tag> </el-col> <el-col :span="21"> <el-row justify="space-between"> <el-space> <el-text type="primary" size="default"> <el-icon><Timer /></el-icon> {{ item.pollutedData.startTime + ' - ' + item.pollutedData.endTime }} </el-text> </el-space> <el-link type="primary" @click="emits('open', item)"> 详æ </el-link> </el-row> <div> <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> </div> <div v-if="item.pollutedSource.sceneList.length > 0"> <div v-for="s in item.pollutedSource.sceneList" :key="s.guid"> <img style="width: 24px" :src="sceneIcon(s.typeId)" :alt="s.type" /> <el-text type="warning" tag="ins" truncated class="text-link" @click="handleSetCenter(item, s)" > {{ s.name }} </el-text> </div> </div> </el-col> <!-- <el-row justify="space-between"> <el-space> <el-tag type="primary" effect="dark" size="small">æé</el-tag> <el-text type="primary">{{ item.pollutedData.startTime + ' - ' + item.pollutedData.endTime }}</el-text> </el-space> <el-link type="primary" @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-col> --> </el-row> </div> </template> <script setup> import { computed } from 'vue'; import { sceneTypes, sceneIcon } from '@/constant/scene-types'; import MapUtil from '@/utils/map/util'; const props = defineProps({ item: Object @@ -78,6 +170,34 @@ break; } } function formatFactorName(name) { switch (name) { case 'PM25': return 'PM<sub>2.5</sub>'; // return '<span>PM2.5</span>'; case 'PM10': return 'PM<sub>10</sub>'; case 'NO2': return 'NO<sub>2</sub>'; case 'H2S': return 'H<sub>2</sub>S'; case 'SO2': return 'SO<sub>2</sub>'; case 'O3': return 'O<sub>3</sub>'; case 'VOC': return 'VOC<sub>s</sub>'; default: break; } } function handleSetCenter(item, scene) { MapUtil.setCenter([scene.longitude, scene.latitude], true); emits('open', item); } </script> <style scoped> .wrapper { @@ -93,4 +213,8 @@ .no-warning { color: var(--el-text-color-disabled) !important; } .text-link { width: 90%; cursor: pointer; } </style> src/views/sourcetrace/component/PollutedExceptionItem.vue
@@ -10,34 +10,43 @@ <BaseCard v-if="item" v-show="item.showMore"> <template #content> <el-scrollbar class="clue-card"> <el-row justify="space-between"> <!-- <el-tag v-if="index == 0" type="danger">ææ°</el-tag> --> <el-text type="primary">{{ 'åçæ¶é´ï¼' + item.pollutedData.startTime + ' - ' + item.pollutedData.endTime }}</el-text> <el-row justify="space-between" align="bottom"> <el-text type="warning" size="large"> å ¸ååç </el-text> <el-link type="primary" :underline="true" @click="showMarksAndPolygon(item)" > {{ item.showMore ? 'æ¶èµ·å¼å¸¸' : 'å®ä½å¼å¸¸' }} {{ item.showMore ? 'æ¶èµ·' : 'å®ä½' }} <el-icon size="large"><CircleClose /></el-icon> </el-link> </el-row> <div> <el-text type="primary"> 污æåºåï¼{{ item.pollutedArea.address }} <el-icon><Timer /></el-icon> {{ 'åçæ¶æ®µï¼' + item.pollutedData.startTime + ' - ' + item.pollutedData.endTime }} </el-text> </div> <div> <el-text type="primary"> <el-icon><MapLocation /></el-icon> {{ 'é£é©åºåï¼' + item.pollutedArea.address }} </el-text> </div> <!-- <div> <el-text type="primary"> 溯æºè·ç¦»ï¼{{ formatDistanceType(item.pollutedArea.distanceType) }} </el-text> </div> </div> --> <div> <el-text type="primary"> <el-icon><BellFilled /></el-icon> å¼å¸¸ç±»åï¼{{ item.pollutedData.exception }} </el-text> </div> @@ -92,6 +101,7 @@ :key="index1" :model-value="item1" chart-height="80px" :y-min-interval="20" ></RealTimeLineChart> <!-- </div> --> <div class="border-dashed"> src/views/sourcetrace/component/SourceTraceFilter.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,99 @@ <template> <div> <div> <el-space> <el-text type="primary">æ°æ®åç</el-text> <el-checkbox-group :model-value="dataSlice" @update:model-value="(e) => emits('update:data-slice', e)" size="default" :min="1" > <el-space> <el-checkbox value="1">溯æº</el-checkbox> <el-checkbox value="2">线索</el-checkbox> <el-checkbox value="3">æç¤º</el-checkbox> </el-space> </el-checkbox-group> </el-space> </div> <div> <el-space> <el-text type="primary">çæµå å</el-text> <el-checkbox-group :model-value="factorType" @update:model-value="(e) => emits('update:factor-type', e)" size="default" :min="1" > <el-space> <el-checkbox v-for="item in factorOptions" :value="item.value" :key="item.label" > {{ item.label }} </el-checkbox> </el-space> </el-checkbox-group> </el-space> </div> <div> <el-space> <el-text type="primary">åºæ¯ç±»å</el-text> <el-checkbox-group :model-value="sceneType" @update:model-value="(e) => emits('update:scene-type', e)" size="default" :min="1" > <el-space> <el-checkbox v-for="item in sceneOptions" :value="item.value" :key="item.label" > {{ item.label }} </el-checkbox> </el-space> </el-checkbox-group> </el-space> </div> </div> </template> <script setup> import { ref } from 'vue'; const props = defineProps({ // æ°æ®åçï¼çº¿ç´¢ãæç¤ºãæº¯æº dataSlice: Array, // çæµå å factorType: Array, factorOptions: Array, // åºæ¯ç±»å sceneType: Array, sceneOptions: Array }); const emits = defineEmits([ 'update:data-slice', 'update:factor-type', 'update:scene-type' ]); </script> <style scoped> :deep(.el-checkbox) { --el-checkbox-text-color: white; --main-color: #23dad1; --el-checkbox-checked-text-color: var(--main-color); --el-checkbox-checked-input-border-color: var(--main-color); --el-checkbox-checked-bg-color: var(--main-color); --el-checkbox-input-border-color-hover: var(--main-color); --el-checkbox-disabled-checked-input-fill: var(--main-color); --el-checkbox-disabled-checked-input-border-color: var(--main-color); --el-checkbox-disabled-checked-icon-color: white; margin-right: 6px; /* height: initial; */ } </style>