simulation.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <div class="realtime-container">
  3. <div ref="chart" class="chart" :style="{height: '400px', width: '100%'}" />
  4. <div class="realtime-foot">
  5. <div class="realtime-foot-title">测流成果</div>
  6. <div class="realtime-foot-actions">
  7. <div class="realtime-foot-action">
  8. <svg-icon icon-class="manual" class-name="realtime-foot-action-icon" @click="handleManualMeasure" />
  9. <div class="realtime-foot-action-label">手动测流</div>
  10. </div>
  11. <div class="realtime-foot-action" v-if="isplay">
  12. <svg-icon icon-class="realtime-stop" class-name="realtime-foot-action-icon" @click="stop" />
  13. <div class="realtime-foot-action-label">终止回放</div>
  14. </div>
  15. <div class="realtime-foot-action" v-else>
  16. <svg-icon icon-class="realtime-play" class-name="realtime-foot-action-icon" @click="play" />
  17. <div class="realtime-foot-action-label">回放</div>
  18. </div>
  19. </div>
  20. <div class="realtime-foot-time">
  21. <div class="realtime-foot-time-label">施测时间:</div>
  22. <div class="realtime-foot-time-value">{{ task.createTime }}</div>
  23. </div>
  24. </div>
  25. <Manual ref="manual" />
  26. </div>
  27. </template>
  28. <script>
  29. import * as echarts from "echarts";
  30. import resize from '@/utils/resize'
  31. import { getConfig } from '@/api/site/site'
  32. import { getSiteSection } from '@/api/site/berthing'
  33. import CarSvg from '@/assets/images/car.svg'
  34. import Manual from './manual';
  35. export default {
  36. components: {
  37. Manual,
  38. },
  39. mixins: [resize],
  40. props: {
  41. siteId: Number | String,
  42. positions: Array,
  43. measureLine: Array,
  44. siteRealTime: Object,
  45. task: Object,
  46. location: Number,
  47. waterlevel: Number,
  48. isplay: Boolean,
  49. },
  50. data() {
  51. return {
  52. sections: [],
  53. config: {},
  54. }
  55. },
  56. mounted() {
  57. this.loadSection();
  58. this.loadSiteConfig();
  59. this.$nextTick(() => {
  60. this.chart = echarts.init(this.$refs.chart, 'macarons');
  61. })
  62. },
  63. beforeDestroy() {
  64. if (this.chart) {
  65. this.chart.dispose()
  66. this.chart = null
  67. }
  68. },
  69. methods: {
  70. play() {
  71. this.$emit('play');
  72. },
  73. stop() {
  74. this.$modal.confirm('是否确认终止回放?').then(() => {
  75. this.$emit('stop');
  76. }).catch(() => {});
  77. },
  78. loadSection() {
  79. getSiteSection(this.siteId).then((res) => {
  80. this.sections = JSON.parse(res.data.positions) || [];
  81. this.setOptions();
  82. })
  83. },
  84. loadSiteConfig() {
  85. getConfig(this.siteId).then((res) => {
  86. this.config = res.data || {};
  87. this.setOptions();
  88. })
  89. },
  90. handleManualMeasure() {
  91. this.$refs.manual.open({ siteId: this.siteId });
  92. },
  93. setOptions() {
  94. if (!this.chart || !this.config || this.sections.length === 0) {
  95. return;
  96. }
  97. const carWidth = 50; // 小车宽度
  98. const carHeight = 32; // 小车高度
  99. const barWidth = 18; // 立柱宽度
  100. const boxWidth = carWidth * 1.2; // 小车充电盒子宽度
  101. const waterlevel = this.waterlevel;
  102. const start = this.config.offset;
  103. const location = this.location < start ? start : this.location;
  104. const { sections, bar1X, bar2X } = this.calcSection();
  105. const x = sections.map(([x]) => x);
  106. const maxX = Math.max(...x);
  107. const minX = Math.min(...x);
  108. const y = sections.map(([,y]) => y);
  109. const maxY = Math.max(...y);
  110. const minY = Math.min(...y);
  111. const disY = maxY - minY;
  112. const barY = maxY + disY * 1.2;
  113. const lineY = maxY + disY * 0.8;
  114. const stopSeries = this.positions.map(({x}) => [x, lineY]);
  115. const stopLineSeries = this.measureLine.map(({pn, wspeed}) => ({xAxis: this.positions[pn - 1].x, name: `${wspeed}m³/h`}));
  116. const options = {
  117. grid: {
  118. top: 0,
  119. left: 0,
  120. right: 0,
  121. bottom: 0,
  122. show: false,
  123. },
  124. xAxis: {
  125. type: 'value',
  126. min: minX,
  127. max: maxX,
  128. show: false,
  129. },
  130. yAxis: {
  131. min: minY - disY * 0.1,
  132. max: maxY + disY * 1.5,
  133. type: 'value',
  134. show: false,
  135. },
  136. series: [
  137. {
  138. data: sections,
  139. type: 'line',
  140. // symbol: 'none',
  141. animation: false,
  142. z: 10,
  143. lineStyle: {
  144. width: 1,
  145. color: '#FF8500',
  146. },
  147. areaStyle: {
  148. opacity: 1,
  149. color: '#ffc27f',
  150. }
  151. },
  152. {
  153. data: [[minX, waterlevel],[maxX, waterlevel]],
  154. type: 'line',
  155. symbol: 'none',
  156. z: 0,
  157. lineStyle: {
  158. width: 0,
  159. },
  160. areaStyle: {
  161. opacity: 1,
  162. color: '#a5cdf7',
  163. }
  164. },
  165. {
  166. data: [[bar1X, lineY],[bar2X, lineY]],
  167. type: 'line',
  168. symbol: 'none',
  169. z: 2,
  170. lineStyle: {
  171. width: 2,
  172. color: '#54606C',
  173. },
  174. },
  175. {
  176. data: stopSeries,
  177. type: 'line',
  178. z: 4,
  179. symbol: 'circle',
  180. symbolSize: (_, params) => {
  181. if (params.dataIndex < this.measureLine.length) {
  182. return 20
  183. } else {
  184. return 10
  185. }
  186. },
  187. lineStyle: {
  188. width: 0,
  189. },
  190. itemStyle: {
  191. color: (params) => {
  192. if (params.dataIndex < this.measureLine.length) {
  193. return '#55DA74'
  194. } else {
  195. return '#FF8500'
  196. }
  197. },
  198. },
  199. markLine: {
  200. data: stopLineSeries,
  201. symbol: 'none',
  202. lineStyle: {
  203. color: '#FF8500'
  204. },
  205. label: {
  206. show: true,
  207. position: 'middle',
  208. formatter: '{b}',
  209. color: '#FF8500',
  210. }
  211. }
  212. },
  213. {
  214. data: [[bar1X, barY],[bar2X, barY]],
  215. type: 'bar',
  216. barWidth: barWidth,
  217. z: 3,
  218. itemStyle: {
  219. color: '#A6B7C7',
  220. }
  221. },
  222. {
  223. data: [[location, lineY]],
  224. type: 'scatter',
  225. symbol: `image://${CarSvg}`,
  226. symbolSize: [carWidth, carHeight],
  227. symbolOffset: [0, -6],
  228. silent: true,
  229. z: 3,
  230. itemStyle: {
  231. opacity: 1
  232. }
  233. },
  234. {
  235. data: [[start, lineY]],
  236. type: 'scatter',
  237. symbol: 'rect',
  238. symbolSize: [boxWidth, 30],
  239. symbolOffset: [0, '-50%'],
  240. silent: true,
  241. z: 4,
  242. itemStyle: {
  243. color: '#778CB2',
  244. opacity: 0.5
  245. }
  246. },
  247. ]
  248. };
  249. this.chart.setOption(options);
  250. },
  251. calcSection() {
  252. const { local, offset } = this.config;
  253. const sections = [...this.sections.map(({x, y}) => [x, y])];
  254. const firstPoint = this.sections[0];
  255. const lastPoint = this.sections[this.sections.length - 1];
  256. let bar1X = 0;
  257. let bar2X = 0;
  258. if (local === 1) {
  259. bar1X = offset
  260. bar2X = lastPoint.x + (firstPoint.x - offset)
  261. if (offset < firstPoint.x) {
  262. sections.unshift([offset, firstPoint.y])
  263. sections.push([bar2X, lastPoint.y])
  264. }
  265. } else {
  266. bar1X = firstPoint.x - (offset - lastPoint.x)
  267. bar2X = offset
  268. if (offset > lastPoint.x) {
  269. sections.unshift([bar1X, firstPoint.y])
  270. sections.push([offset, lastPoint.y])
  271. }
  272. }
  273. const x = sections.map(([x]) => x);
  274. const min = Math.min(...x);
  275. const max = Math.max(...x);
  276. const width = max - min;
  277. const gap = width * 0.05;
  278. sections.unshift([min - gap, firstPoint.y]);
  279. sections.push([max + gap, lastPoint.y]);
  280. return { sections, bar1X, bar2X };
  281. }
  282. },
  283. watch: {
  284. waterlevel() {
  285. this.setOptions();
  286. },
  287. location() {
  288. this.setOptions();
  289. },
  290. measureLine() {
  291. this.setOptions();
  292. },
  293. }
  294. }
  295. </script>
  296. <style scoped>
  297. .realtime-container {
  298. height: 500px;
  299. background: linear-gradient(0, #FFFFFF 57%, #D1E8FF 100%);
  300. border-radius: 4px;
  301. padding: 0 20px;
  302. }
  303. .realtime-foot {
  304. height: 100px;
  305. background: #fff;
  306. display: flex;
  307. align-items: center;
  308. justify-content: space-between;
  309. }
  310. .realtime-foot-title {
  311. font-size: 18px;
  312. font-weight: 600;
  313. color: #1D2738;
  314. }
  315. .realtime-foot-actions {
  316. display: flex;
  317. align-items: center;
  318. }
  319. .realtime-foot-action {
  320. display: flex;
  321. flex-direction: column;
  322. align-items: center;
  323. margin: 0 25px;
  324. cursor: pointer;
  325. }
  326. .realtime-foot-action-icon {
  327. width: 40px;
  328. height: 40px;
  329. }
  330. .realtime-foot-action-label {
  331. font-size: 12px;
  332. line-height: 20px;
  333. color: #54606C;
  334. }
  335. .realtime-foot-time {
  336. display: flex;
  337. align-items: center;
  338. font-size: 14px;
  339. }
  340. .realtime-foot-time-label {
  341. color: #54606C;
  342. }
  343. .realtime-foot-time-value {
  344. color: #1D2738;
  345. }
  346. </style>