| | |
| | | import axios from 'axios'; |
| | | import { ElMessage } from 'element-plus'; |
| | | |
| | | const openLog = false; |
| | | const debug = true; |
| | | |
| | | let ip1 = 'http://47.100.191.150:9029/'; |
| | |
| | | i.interceptors.request.use( |
| | | function (config) { |
| | | // 在发送请求之前做些什么 |
| | | if (import.meta.env.DEV) { |
| | | if (import.meta.env.DEV && openLog) { |
| | | console.log('==>请求开始'); |
| | | console.log(`${config.baseURL}${config.url}`); |
| | | if (config.data) { |
| | |
| | | }, |
| | | function (error) { |
| | | // 对请求错误做些什么 |
| | | if (import.meta.env.DEV) { |
| | | if (import.meta.env.DEV && openLog) { |
| | | console.log('==>请求开始'); |
| | | console.log(error); |
| | | } |
| | |
| | | function (response) { |
| | | // 2xx 范围内的状态码都会触发该函数。 |
| | | // 对响应数据做点什么 |
| | | if (import.meta.env.DEV) { |
| | | if (import.meta.env.DEV && openLog) { |
| | | console.log(response); |
| | | console.log('==>请求结束'); |
| | | } |
| | |
| | | function (error) { |
| | | // 超出 2xx 范围的状态码都会触发该函数。 |
| | | // 对响应错误做点什么 |
| | | if (import.meta.env.DEV) { |
| | | if (import.meta.env.DEV && openLog) { |
| | | console.log(error); |
| | | console.log('==>请求结束'); |
| | | } |
| | |
| | | } |
| | | if (this.lineChart) { |
| | | this.lineChart.setOption(this.option, { notMerge: true }); |
| | | console.log('折线图生成:立即'); |
| | | // console.log('折线图生成:立即'); |
| | | } else { |
| | | this.onChartCreated = () => { |
| | | this.lineChart.setOption(this.option, { notMerge: true }); |
| | |
| | | }, |
| | | mounted() { |
| | | this.lineChart = echarts.init(this.$refs.lineChart); |
| | | setTimeout(() => { |
| | | if (typeof this.onChartCreated === 'function') { |
| | | this.onChartCreated(); |
| | | console.log('折线图生成:滞后'); |
| | | } |
| | | }, 500); |
| | | // setTimeout(() => { |
| | | // if (typeof this.onChartCreated === 'function') { |
| | | // this.onChartCreated(); |
| | | // // console.log('折线图生成:滞后'); |
| | | // } |
| | | // }, 500); |
| | | } |
| | | }; |
| | | </script> |
| | |
| | | const key = this.districtCode + t; |
| | | if (!lableMarkMap.has(key)) { |
| | | this.fetchScene(t).then((res) => { |
| | | const layer = marks.createLabelMarks(sceneIcon(t), res); |
| | | const layer = marks.createLabelMarks(sceneIcon(t), res, false); |
| | | lableMarkMap.set(key, { show: true, layer }); |
| | | }); |
| | | } else { |
| | |
| | | <template> |
| | | <BaseCard size="medium" direction="left"> |
| | | <template #content> |
| | | <el-scrollbar height="calc(98vh - var(--bevel-length-2))"> |
| | | <el-scrollbar height="calc(49vh - var(--bevel-length-2))" always> |
| | | <div v-for="item in seriesList" :key="item.key"> |
| | | <el-row |
| | | v-show="selectFactorType.includes(item.series.key)" |
| | | justify="space-between" |
| | | class="wrap" |
| | | > |
| | | <div class="flex-col"> |
| | | <div class="flex-col m-r-4"> |
| | | <div class="factor-name">{{ item.series.name }}</div> |
| | | <div class="factor-value"> |
| | | {{ item.series.currentData }} |
| | |
| | | layer = undefined; |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | function drawMarks(sceneList) { |
| | |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '11%', |
| | | left: '12%', |
| | | right: '2%', |
| | | top: '7%', |
| | | bottom: '20%' |
| | |
| | | } |
| | | |
| | | /** |
| | | * 高德地图坐标转GPS坐标算法 |
| | | */ |
| | | function gcj02towgs84(lng, lat) { |
| | | // lat = +latlng = +lng |
| | | if (out_of_china(lng, lat)) { |
| | | return [lng, lat]; |
| | | } else { |
| | | let dlat = transformlat(lng - 105.0, lat - 35.0); |
| | | let dlng = transformlng(lng - 105.0, lat - 35.0); |
| | | let radlat = (lat / 180.0) * PI; |
| | | let magic = Math.sin(radlat); |
| | | magic = 1 - ee * magic * magic; |
| | | let sqrtmagic = Math.sqrt(magic); |
| | | dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); |
| | | dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); |
| | | let mglat = Math.round((lat * 2 - lat - dlat) * 1000000) / 1000000; |
| | | let mglng = Math.round((lng * 2 - lng - dlng) * 1000000) / 1000000; |
| | | return [mglng, mglat]; |
| | | } |
| | | } |
| | | |
| | | //从GPS转高德 |
| | | function wgs84_To_Gcj02(lon, lat) { |
| | | if (out_of_china(lon, lat)) { |
| | | return [lon, lat]; |
| | | } else { |
| | | let dLat = transformlat(lon - 105.0, lat - 35.0); |
| | | let dLon = transformlng(lon - 105.0, lat - 35.0); |
| | | let radLat = (lat / 180.0) * PI; |
| | | let magic = Math.sin(radLat); |
| | | magic = 1 - ee * magic * magic; |
| | | let sqrtMagic = Math.sqrt(magic); |
| | | dLat = (dLat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * PI); |
| | | dLon = (dLon * 180.0) / ((a / sqrtMagic) * Math.cos(radLat) * PI); |
| | | let mgLat = lat + dLat; |
| | | let mgLon = lon + dLon; |
| | | return [mgLon, mgLat]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将gps经纬度转换为高德地图经纬度 |
| | | * @param {*} lnglats |
| | | * @param {*} callback |
| | | */ |
| | | function _convertLatlng(index, coor, lnglats, type = 'gps', callback) { |
| | | if (index < coor.length) { |
| | | var path = parse2LngLat(coor[index]); |
| | | // eslint-disable-next-line no-undef |
| | | AMap.convertFrom(path, type, function (status, result) { |
| | | if (result.info === 'ok') { |
| | | lnglats.push.apply(lnglats, result.locations); |
| | | _convertLatlng(index + 1, coor, lnglats, type, callback); |
| | | coor[index].forEach((c) => { |
| | | let r; |
| | | if (type == 'gps') { |
| | | r = wgs84_To_Gcj02(c[0], c[1]); |
| | | } else { |
| | | r = gcj02towgs84(c[0], c[1]); |
| | | } |
| | | lnglats.push({ lng: r[0], lat: r[1] }); |
| | | }); |
| | | _convertLatlng(index + 1, coor, lnglats, type, callback); |
| | | |
| | | // var path = parse2LngLat(coor[index]); |
| | | // // eslint-disable-next-line no-undef |
| | | // AMap.convertFrom(path, type, function (status, result) { |
| | | // if (result.info === 'ok') { |
| | | // lnglats.push.apply(lnglats, result.locations); |
| | | // _convertLatlng(index + 1, coor, lnglats, type, callback); |
| | | // } |
| | | // }); |
| | | } else { |
| | | callback(lnglats); |
| | | } |
| | |
| | | /** |
| | | * 高德地图坐标转GPS坐标算法 |
| | | */ |
| | | gcj02towgs84(lng, lat) { |
| | | // lat = +latlng = +lng |
| | | if (out_of_china(lng, lat)) { |
| | | return [lng, lat]; |
| | | } else { |
| | | let dlat = transformlat(lng - 105.0, lat - 35.0); |
| | | let dlng = transformlng(lng - 105.0, lat - 35.0); |
| | | let radlat = (lat / 180.0) * PI; |
| | | let magic = Math.sin(radlat); |
| | | magic = 1 - ee * magic * magic; |
| | | let sqrtmagic = Math.sqrt(magic); |
| | | dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); |
| | | dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); |
| | | let mglat = Math.round((lat * 2 - lat - dlat) * 1000000) / 1000000; |
| | | let mglng = Math.round((lng * 2 - lng - dlng) * 1000000) / 1000000; |
| | | return [mglng, mglat]; |
| | | } |
| | | }, |
| | | gcj02towgs84, |
| | | |
| | | //从GPS转高德 |
| | | wgs84_To_Gcj02(lon, lat) { |
| | | if (out_of_china(lon, lat)) { |
| | | return [lon, lat]; |
| | | } else { |
| | | let dLat = transformlat(lon - 105.0, lat - 35.0); |
| | | let dLon = transformlng(lon - 105.0, lat - 35.0); |
| | | let radLat = (lat / 180.0) * PI; |
| | | let magic = Math.sin(radLat); |
| | | magic = 1 - ee * magic * magic; |
| | | let sqrtMagic = Math.sqrt(magic); |
| | | dLat = (dLat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * PI); |
| | | dLon = (dLon * 180.0) / ((a / sqrtMagic) * Math.cos(radLat) * PI); |
| | | let mgLat = lat + dLat; |
| | | let mgLon = lon + dLon; |
| | | return [mgLon, mgLat]; |
| | | } |
| | | } |
| | | wgs84_To_Gcj02 |
| | | }; |
| | |
| | | viewMode: '3D', // 地图模式 |
| | | resizeEnable: true, |
| | | center: [121.6039283, 31.25295567], |
| | | zooms: [3, 18], |
| | | zooms: [2, 26], |
| | | zoom: 14 |
| | | }); |
| | | |
| | |
| | | }); |
| | | } |
| | | |
| | | export { createMap, onMapMounted, map, AMap, mouseTool, object3Dlayer, isDragging }; |
| | | export { |
| | | createMap, |
| | | onMapMounted, |
| | | map, |
| | | AMap, |
| | | mouseTool, |
| | | object3Dlayer, |
| | | isDragging |
| | | }; |
| | |
| | | viewMode: '3D', // 地图模式 |
| | | resizeEnable: true, |
| | | center: [121.6039283, 31.25295567], |
| | | zooms: [0, 18], |
| | | zooms: [2, 26], |
| | | zoom: 14 |
| | | }); |
| | | |
| | |
| | | <el-col span="1"> |
| | | <FactorLegend :factor="factorDatas.factor[factorType]"></FactorLegend> |
| | | </el-col> |
| | | <el-col span="1"> |
| | | <SourceTrace v-model:factorType="factorType"></SourceTrace> |
| | | </el-col> |
| | | <el-col span="1"> </el-col> |
| | | </el-row> |
| | | <DashBoard class="dash-board" :factor-datas="factorDatas"></DashBoard> |
| | | <RealTimeTrend |
| | |
| | | :factor-datas="factorDatas" |
| | | :device-type="deviceType" |
| | | ></RealTimeTrend> |
| | | <SourceTrace |
| | | class="source-trace" |
| | | v-model:factorType="factorType" |
| | | ></SourceTrace> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | .dash-board { |
| | | position: absolute; |
| | | left: 0; |
| | | bottom: 2px; |
| | | bottom: 0px; |
| | | } |
| | | .real-time-trend { |
| | | position: absolute; |
| | | right: 0; |
| | | top: 0; |
| | | } |
| | | .source-trace { |
| | | position: absolute; |
| | | right: 0; |
| | | bottom: 0px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <el-row> |
| | | <el-col span="2"> |
| | | <CardButton |
| | | direction="left" |
| | | name="动态溯源" |
| | | @click="() => (show = !show)" |
| | | ></CardButton> |
| | | </el-col> |
| | | <el-col v-show="show" span="10"> |
| | | <BaseCard> |
| | | <template #content> |
| | |
| | | <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-row ref="scrollContentRef"> |
| | | <div |
| | | v-for="(item, index) in streams" |
| | | :key="index" |
| | | class="clue-card" |
| | | > |
| | | <el-row> |
| | | <!-- <el-tag v-if="index == 0" type="danger">最新</el-tag> --> |
| | | <el-text type="primary">{{ |
| | | '线索时间:' + |
| | |
| | | <el-link |
| | | type="primary" |
| | | underline |
| | | @click="item.showMore = !item.showMore" |
| | | @click="showMarksAndPolygon(item)" |
| | | > |
| | | {{ |
| | | (item.showMore ? '收起溯源场景' : '查看溯源场景') + |
| | |
| | | ')' |
| | | }} |
| | | </el-link> |
| | | <el-link |
| | | <!-- <el-link |
| | | type="primary" |
| | | underline |
| | | @click="drawPolygon(item.pollutedArea)" |
| | | > |
| | | 定位异常 |
| | | </el-link> |
| | | </el-link> --> |
| | | </el-row> |
| | | <div v-show="item.showMore" style="width: 320px; height: 140px"> |
| | | <RealTimeLineChart |
| | |
| | | :show-marks="item.showMore" |
| | | :scene-list="item.pollutedSource.sceneList" |
| | | ></SceneTable> |
| | | <el-divider /> |
| | | |
| | | <!-- <el-space gap="4"> |
| | | <el-tag :type="item.status == 1 ? 'danger' : 'info'">{{ |
| | |
| | | ></SceneTable> |
| | | <el-divider /> --> |
| | | </div> |
| | | </div> |
| | | </el-row> |
| | | </el-scrollbar> |
| | | </template> |
| | | </BaseCard> |
| | | </el-col> |
| | | <el-col span="2"> |
| | | <CardButton name="动态溯源" @click="() => (show = !show)"></CardButton> |
| | | </el-col> |
| | | </el-row> |
| | | </template> |
| | |
| | | * 动态溯源信息管理 |
| | | * 通过websocket方式接收后端推送的异常信息并展示 |
| | | */ |
| | | import { reactive, ref, onMounted, onUnmounted, inject } from 'vue'; |
| | | import { reactive, ref, onMounted, onUnmounted, unref } from 'vue'; |
| | | import websocket from '@/api/websocket'; |
| | | import sector from '@/utils/map/sector'; |
| | | import mapUtil from '@/utils/map/util'; |
| | |
| | | |
| | | const emits = defineEmits(['update:factorType']); |
| | | |
| | | const height = `60vh`; |
| | | const height = `48vh`; |
| | | const width = `60vh`; |
| | | |
| | | const show = ref(false); |
| | | const scrollContentRef = ref(); |
| | |
| | | websocket.send(inputVal.value); |
| | | }; |
| | | |
| | | const dealMsg = (data) => { |
| | | let showFirstClueTask; |
| | | function dealMsg(data) { |
| | | const [type, content] = data |
| | | .substring(START_STR.length, data.length - END_STR.length) |
| | | .split(SPLIT_STR); |
| | | |
| | | // 污染线索 PollutedClue |
| | | if (type == '1') { |
| | | const obj = JSON.parse(content); |
| | | obj.showMore = true; |
| | | const obj = reactive(JSON.parse(content)); |
| | | // obj.showMore = true; |
| | | obj.showMore = false; |
| | | console.log('污染线索: ', obj); |
| | | drawPolygon(obj.pollutedArea); |
| | | parseChartData(obj); |
| | | |
| | | if (streams.length == 0) { |
| | | streams.push(obj); |
| | | } else { |
| | |
| | | |
| | | // scrollToBottom(); |
| | | scrollToTop(); |
| | | // drawPolygon(obj.pollutedArea); |
| | | parseChartData(obj); |
| | | |
| | | if (showFirstClueTask) { |
| | | clearTimeout(showFirstClueTask); |
| | | } |
| | | showFirstClueTask = setTimeout(() => { |
| | | showMarksAndPolygon(obj); |
| | | }, 1000); |
| | | } |
| | | // 污染分析结果 AnalysisResult |
| | | else if (type == '2') { |
| | | const obj = JSON.parse(content); |
| | | console.log('污染分析结果: ', obj); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | onMounted(() => { |
| | | websocket.registerReceiveEvent(dealMsg); |
| | |
| | | // } |
| | | }); |
| | | |
| | | // 绘制污染区域 |
| | | let pollutedAreaPolygon; |
| | | function drawPolygon(pollutedArea) { |
| | | if (pollutedAreaPolygon) { |
| | | map.remove(pollutedAreaPolygon); |
| | | function showMarksAndPolygon(item) { |
| | | item.showMore = !item.showMore; |
| | | if (item.showMore) { |
| | | drawPolygon(item); |
| | | } else { |
| | | if (polygonMap.has(item.guid)) { |
| | | map.remove(polygonMap.get(item.guid)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 绘制污染区域(高德地图的多边形对象,通过vue的reactive包装为响应式对象后,无法正确在地图中使用) |
| | | const polygonMap = new Map(); |
| | | function drawPolygon(item) { |
| | | const pollutedArea = item.pollutedArea; |
| | | if (polygonMap.has(item.guid)) { |
| | | map.add(polygonMap.get(item.guid)); |
| | | } else { |
| | | const bounds = pollutedArea.polygon.map((v) => [v.first, v.second]); |
| | | // eslint-disable-next-line no-undef |
| | | pollutedAreaPolygon = new AMap.Polygon({ |
| | | const pollutedAreaPolygon = new AMap.Polygon({ |
| | | map: map, //显示该覆盖物的地图对象 |
| | | strokeWeight: 2, //轮廓线宽度 |
| | | path: bounds, //多边形轮廓线的节点坐标数组 |
| | |
| | | strokeStyle: 'dashed', |
| | | zIndex: 9 |
| | | }); |
| | | map.setFitView(); |
| | | polygonMap.set(item.guid, pollutedAreaPolygon); |
| | | } |
| | | map.setFitView(polygonMap.get(item.guid)); |
| | | } |
| | | |
| | | function parseChartData(obj) { |
| | | console.log('折线图:start'); |
| | | const factorDatas = new FactorDatas(); |
| | | factorDatas.setData(obj.pollutedData.dataVoList, 0, () => { |
| | | obj._chartOptions = factorDataParser.parseData(factorDatas, [ |
| | |
| | | |
| | | .scrollbar { |
| | | min-width: 300px; |
| | | max-width: 60vw; |
| | | /* color: #02ffea; */ |
| | | } |
| | | |
| | | .clue-card { |
| | | padding: 0 4px; |
| | | margin-right: 2px; |
| | | width: 320px; |
| | | border: 1px solid white; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |