|
@@ -0,0 +1,406 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="realtime-container">
|
|
|
|
+ <div ref="chart" class="chart" :style="{height: '400px', width: '100%'}" />
|
|
|
|
+ <div class="realtime-foot">
|
|
|
|
+ <div class="realtime-foot-title">实时动态模拟</div>
|
|
|
|
+ <div class="realtime-foot-actions">
|
|
|
|
+ <div class="realtime-foot-action">
|
|
|
|
+ <svg-icon icon-class="manual" class-name="realtime-foot-action-icon" @click="handleManualMeasure" />
|
|
|
|
+ <div class="realtime-foot-action-label">手动测流</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="realtime-foot-time">
|
|
|
|
+ <div class="realtime-foot-time-label">上次施测时间:</div>
|
|
|
|
+ <div class="realtime-foot-time-value">{{ task.createTime }}</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <Manual ref="manual" />
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+import * as echarts from "echarts";
|
|
|
|
+import resize from '@/utils/resize'
|
|
|
|
+import { getConfig } from '@/api/site/site'
|
|
|
|
+import { getSiteSection } from '@/api/site/berthing'
|
|
|
|
+import { getCarLocation, getWaterLevel, taskAction } from '@/api/analysis/achievement'
|
|
|
|
+import CarSvg from '@/assets/images/car.svg'
|
|
|
|
+import Manual from './manual';
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ mixins: [resize],
|
|
|
|
+ components: {
|
|
|
|
+ Manual,
|
|
|
|
+ },
|
|
|
|
+ props: {
|
|
|
|
+ siteId: Number | String,
|
|
|
|
+ positions: Array,
|
|
|
|
+ measureLine: Array,
|
|
|
|
+ siteRealTime: Object,
|
|
|
|
+ task: Object,
|
|
|
|
+ },
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ sections: [],
|
|
|
|
+ config: {},
|
|
|
|
+ location: 0,
|
|
|
|
+ waterlevel: 0,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ mounted() {
|
|
|
|
+ this.loadSection();
|
|
|
|
+ this.loadSiteConfig();
|
|
|
|
+ this.loadCarLocation();
|
|
|
|
+ this.timer1 = setInterval(() => this.loadCarLocation(), 5e3)
|
|
|
|
+ this.loadWaterLevel();
|
|
|
|
+ this.timer2 = setInterval(() => this.loadWaterLevel(), 5e3)
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
+ this.chart = echarts.init(this.$refs.chart, 'macarons');
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ beforeDestroy() {
|
|
|
|
+ if (this.timer1) clearTimeout(this.timer1);
|
|
|
|
+ if (this.timer2) clearTimeout(this.timer2);
|
|
|
|
+ if (this.chart) {
|
|
|
|
+ this.chart.dispose()
|
|
|
|
+ this.chart = null
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ pause() {
|
|
|
|
+ this.$modal.confirm('是否确认暂停当前任务?').then(() => {
|
|
|
|
+ return taskAction(this.siteId, 1);
|
|
|
|
+ }).then(() => {
|
|
|
|
+ this.$emit('refresh');
|
|
|
|
+ this.$modal.msgSuccess("暂停成功");
|
|
|
|
+ }).catch(() => {});
|
|
|
|
+ },
|
|
|
|
+ play() {
|
|
|
|
+ this.$modal.confirm('是否确认重启当前任务?').then(() => {
|
|
|
|
+ return taskAction(this.siteId, 2);
|
|
|
|
+ }).then(() => {
|
|
|
|
+ this.$emit('refresh');
|
|
|
|
+ this.$modal.msgSuccess("重启成功");
|
|
|
|
+ }).catch(() => {});
|
|
|
|
+ },
|
|
|
|
+ stop() {
|
|
|
|
+ this.$modal.confirm('是否确认终止当前任务?').then(() => {
|
|
|
|
+ return taskAction(this.siteId, 0);
|
|
|
|
+ }).then(() => {
|
|
|
|
+ this.$router.back();
|
|
|
|
+ this.$modal.msgSuccess("终止成功");
|
|
|
|
+ }).catch(() => {});
|
|
|
|
+ },
|
|
|
|
+ loadCarLocation() {
|
|
|
|
+ getCarLocation({ siteId: this.siteId }).then((res) => {
|
|
|
|
+ this.location = res.data?.[0].position || 0;
|
|
|
|
+ this.setOptions();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ loadSection() {
|
|
|
|
+ getSiteSection(this.siteId).then((res) => {
|
|
|
|
+ this.sections = JSON.parse(res.data.positions) || [];
|
|
|
|
+ this.setOptions();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ loadWaterLevel() {
|
|
|
|
+ getWaterLevel(this.siteId).then((res) => {
|
|
|
|
+ this.waterlevel = res.data.waterlevel || 0;
|
|
|
|
+ this.setOptions();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ loadSiteConfig() {
|
|
|
|
+ getConfig(this.siteId).then((res) => {
|
|
|
|
+ this.config = res.data || {};
|
|
|
|
+ this.setOptions();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ setOptions() {
|
|
|
|
+ if (!this.chart || !this.config || this.sections.length === 0) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const carWidth = 50; // 小车宽度
|
|
|
|
+ const carHeight = 32; // 小车高度
|
|
|
|
+ const barWidth = 18; // 立柱宽度
|
|
|
|
+ const boxWidth = carWidth * 1.2; // 小车充电盒子宽度
|
|
|
|
+
|
|
|
|
+ const waterlevel = this.waterlevel;
|
|
|
|
+ const start = this.config.offset;
|
|
|
|
+ const location = this.location;
|
|
|
|
+
|
|
|
|
+ const { sections, bar1X, bar2X } = this.calcSection();
|
|
|
|
+
|
|
|
|
+ const x = sections.map(([x]) => x);
|
|
|
|
+ const maxX = Math.max(...x);
|
|
|
|
+ const minX = Math.min(...x);
|
|
|
|
+
|
|
|
|
+ const y = sections.map(([,y]) => y);
|
|
|
|
+ const maxY = Math.max(...y);
|
|
|
|
+ const minY = Math.min(...y);
|
|
|
|
+ const disY = maxY - minY;
|
|
|
|
+ const barY = maxY + disY * 1.2;
|
|
|
|
+ const lineY = maxY + disY * 0.8;
|
|
|
|
+
|
|
|
|
+ const stopSeries = this.positions.map(({x}) => [x, lineY]);
|
|
|
|
+ const stopLineSeries = this.measureLine.map(({pn, wspeed}) => ({xAxis: this.positions[pn - 1].x, name: `${wspeed}m³/h`}));
|
|
|
|
+
|
|
|
|
+ const options = {
|
|
|
|
+ grid: {
|
|
|
|
+ top: 0,
|
|
|
|
+ left: 0,
|
|
|
|
+ right: 0,
|
|
|
|
+ bottom: 0,
|
|
|
|
+ show: false,
|
|
|
|
+ },
|
|
|
|
+ xAxis: {
|
|
|
|
+ type: 'value',
|
|
|
|
+ min: minX,
|
|
|
|
+ max: maxX,
|
|
|
|
+ show: false,
|
|
|
|
+ },
|
|
|
|
+ yAxis: {
|
|
|
|
+ min: minY - disY * 0.1,
|
|
|
|
+ max: maxY + disY * 1.5,
|
|
|
|
+ type: 'value',
|
|
|
|
+ show: false,
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ trigger: 'item',
|
|
|
|
+ },
|
|
|
|
+ series: [
|
|
|
|
+ {
|
|
|
|
+ data: sections,
|
|
|
|
+ type: 'line',
|
|
|
|
+ // symbol: 'none',
|
|
|
|
+ animation: false,
|
|
|
|
+ z: 10,
|
|
|
|
+ lineStyle: {
|
|
|
|
+ width: 1,
|
|
|
|
+ color: '#FF8500',
|
|
|
|
+ },
|
|
|
|
+ areaStyle: {
|
|
|
|
+ opacity: 1,
|
|
|
|
+ color: '#ffc27f',
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: [[minX, waterlevel],[maxX, waterlevel]],
|
|
|
|
+ type: 'line',
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ z: 0,
|
|
|
|
+ lineStyle: {
|
|
|
|
+ width: 0,
|
|
|
|
+ },
|
|
|
|
+ areaStyle: {
|
|
|
|
+ opacity: 1,
|
|
|
|
+ color: '#a5cdf7',
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: [[bar1X, lineY],[bar2X, lineY]],
|
|
|
|
+ type: 'line',
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ z: 2,
|
|
|
|
+ lineStyle: {
|
|
|
|
+ width: 2,
|
|
|
|
+ color: '#54606C',
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: stopSeries,
|
|
|
|
+ type: 'line',
|
|
|
|
+ z: 4,
|
|
|
|
+ symbol: 'circle',
|
|
|
|
+ symbolSize: (_, params) => {
|
|
|
|
+ if (params.dataIndex < this.measureLine.length) {
|
|
|
|
+ return 20
|
|
|
|
+ } else {
|
|
|
|
+ return 10
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ lineStyle: {
|
|
|
|
+ width: 0,
|
|
|
|
+ },
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: (params) => {
|
|
|
|
+ if (params.dataIndex < this.measureLine.length) {
|
|
|
|
+ return '#55DA74'
|
|
|
|
+ } else {
|
|
|
|
+ return '#FF8500'
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ markLine: {
|
|
|
|
+ data: stopLineSeries,
|
|
|
|
+ symbol: 'none',
|
|
|
|
+ lineStyle: {
|
|
|
|
+ color: '#FF8500'
|
|
|
|
+ },
|
|
|
|
+ label: {
|
|
|
|
+ show: true,
|
|
|
|
+ position: 'middle',
|
|
|
|
+ formatter: '{b}',
|
|
|
|
+ color: '#FF8500',
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ formatter: (params) => {
|
|
|
|
+ return `起点距: ${params.value[0]}`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: [[bar1X, barY],[bar2X, barY]],
|
|
|
|
+ type: 'bar',
|
|
|
|
+ barWidth: barWidth,
|
|
|
|
+ z: 3,
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#A6B7C7',
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: [[location, lineY]],
|
|
|
|
+ type: 'scatter',
|
|
|
|
+ symbol: `image://${CarSvg}`,
|
|
|
|
+ symbolSize: [carWidth, carHeight],
|
|
|
|
+ symbolOffset: [0, -6],
|
|
|
|
+ silent: true,
|
|
|
|
+ z: 3,
|
|
|
|
+ itemStyle: {
|
|
|
|
+ opacity: 1
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ data: [[start, lineY]],
|
|
|
|
+ type: 'scatter',
|
|
|
|
+ symbol: 'rect',
|
|
|
|
+ symbolSize: [boxWidth, 30],
|
|
|
|
+ symbolOffset: [0, '-50%'],
|
|
|
|
+ silent: true,
|
|
|
|
+ z: 4,
|
|
|
|
+ itemStyle: {
|
|
|
|
+ color: '#778CB2',
|
|
|
|
+ opacity: 0.5
|
|
|
|
+ },
|
|
|
|
+ tooltip: {
|
|
|
|
+ show: false,
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ ]
|
|
|
|
+ };
|
|
|
|
+ this.chart.setOption(options);
|
|
|
|
+ },
|
|
|
|
+ calcSection() {
|
|
|
|
+ const { local, offset } = this.config;
|
|
|
|
+ const sections = [...this.sections.map(({x, y}) => [x, y])];
|
|
|
|
+ const firstPoint = this.sections[0];
|
|
|
|
+ const lastPoint = this.sections[this.sections.length - 1];
|
|
|
|
+ let bar1X = 0;
|
|
|
|
+ let bar2X = 0;
|
|
|
|
+
|
|
|
|
+ if (local === 1) {
|
|
|
|
+ bar1X = offset
|
|
|
|
+ bar2X = lastPoint.x + (firstPoint.x - offset)
|
|
|
|
+ if (offset < firstPoint.x) {
|
|
|
|
+ sections.unshift([offset, firstPoint.y])
|
|
|
|
+ sections.push([bar2X, lastPoint.y])
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ bar1X = firstPoint.x - (offset - lastPoint.x)
|
|
|
|
+ bar2X = offset
|
|
|
|
+ if (offset > lastPoint.x) {
|
|
|
|
+ sections.unshift([bar1X, firstPoint.y])
|
|
|
|
+ sections.push([offset, lastPoint.y])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const x = sections.map(([x]) => x);
|
|
|
|
+ const min = Math.min(...x);
|
|
|
|
+ const max = Math.max(...x);
|
|
|
|
+
|
|
|
|
+ const width = max - min;
|
|
|
|
+ const gap = width * 0.05;
|
|
|
|
+
|
|
|
|
+ sections.unshift([min - gap, firstPoint.y]);
|
|
|
|
+ sections.push([max + gap, lastPoint.y]);
|
|
|
|
+
|
|
|
|
+ return { sections, bar1X, bar2X };
|
|
|
|
+ },
|
|
|
|
+ handleManualMeasure() {
|
|
|
|
+ this.$refs.manual.open({ siteId: this.siteId });
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.realtime-container {
|
|
|
|
+ height: 500px;
|
|
|
|
+ background: linear-gradient(0, #FFFFFF 57%, #D1E8FF 100%);
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ padding: 0 20px;
|
|
|
|
+}
|
|
|
|
+.realtime-foot {
|
|
|
|
+ height: 100px;
|
|
|
|
+ background: #fff;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-title {
|
|
|
|
+ font-size: 18px;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ color: #1D2738;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-actions {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-action {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin: 0 25px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-action-icon {
|
|
|
|
+ width: 40px;
|
|
|
|
+ height: 40px;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-action-label {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ line-height: 20px;
|
|
|
|
+ color: #54606C;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-time {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-time-label {
|
|
|
|
+ color: #54606C;
|
|
|
|
+}
|
|
|
|
+.realtime-foot-time-value {
|
|
|
|
+ color: #1D2738;
|
|
|
|
+}
|
|
|
|
+</style>
|