PolderDetail.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  1. <template>
  2. <div class="polder-detail-content">
  3. <!-- 左侧面板 -->
  4. <div class="left-panel">
  5. <div class="left-panel-3d">
  6. <!-- 圩区水位分析 -->
  7. <m-card :title="selectedPolderDetail === 'konggang' ? '孔巷联圩水位分析' : '圩区水位分析'" class="water-station-card water-station-card-top" :width="398" :height="420">
  8. <div class="water-level-analysis-panel">
  9. <!-- 切换按钮 -->
  10. <div class="water-level-tabs" v-if="selectedPolderDetail === 'konggang'">
  11. <div class="tab-item" :class="{ active: currentWaterLevelTab === 'inner' }" @click="currentWaterLevelTab = 'inner'">
  12. <span>圩内</span>
  13. </div>
  14. <div class="tab-item" :class="{ active: currentWaterLevelTab === 'outer' }" @click="currentWaterLevelTab = 'outer'">
  15. <span>圩外</span>
  16. </div>
  17. </div>
  18. <!-- 水位数据 -->
  19. <div class="water-level-data">
  20. <div class="data-item">
  21. <div class="data-label">水位(m)</div>
  22. <div class="data-value">{{ currentWaterLevelData.level }}</div>
  23. </div>
  24. <div class="data-item">
  25. <div class="data-label">比昨日(m)</div>
  26. <div class="data-value" :class="{ negative: currentWaterLevelData.change < 0 }">{{ currentWaterLevelData.change }}</div>
  27. </div>
  28. <div class="data-item">
  29. <div class="data-label">历史最高(m)</div>
  30. <div class="data-value">{{ currentWaterLevelData.historyMax }}</div>
  31. </div>
  32. <div class="data-item">
  33. <div class="data-label">生态水位(m)</div>
  34. <div class="data-value">{{ currentWaterLevelData.ecological }}</div>
  35. </div>
  36. <div class="data-item">
  37. <div class="data-label">比警戒(m)</div>
  38. <div class="data-value" :class="{ negative: currentWaterLevelData.alertDiff < 0 }">{{ currentWaterLevelData.alertDiff }}</div>
  39. </div>
  40. </div>
  41. <!-- 水位趋势图 -->
  42. <div class="water-level-chart">
  43. <VChart ref="waterLevelChart" :option="currentWaterLevelChartOption" :autoresize="true" style="width: 100%; height: 100%;" />
  44. </div>
  45. </div>
  46. </m-card>
  47. <!-- 圩区工情 -->
  48. <m-card title="圩区工情" class="water-station-card water-station-card-bottom" :width="398" :height="480">
  49. <div class="polder-work-status-panel">
  50. <!-- 泵站启闭情况卡片 -->
  51. <div class="pump-status-cards">
  52. <div class="pump-status-card" v-for="(pump, index) in pumpStatus" :key="index">
  53. <div class="pump-name">{{ pump.name }}</div>
  54. <div class="pump-status" :class="{ 'status-active': pump.status === '运行中', 'status-inactive': pump.status === '停止' }">
  55. {{ pump.status }}
  56. </div>
  57. <div class="pump-flow">{{ pump.flow }}</div>
  58. </div>
  59. </div>
  60. <!-- 开泵流量折线图 -->
  61. <div class="flow-chart-container">
  62. <h4 class="chart-title">开泵流量</h4>
  63. <VChart ref="flowChart" :option="pumpFlowChartOption" :autoresize="true" style="width: 100%; height: 140px;" />
  64. </div>
  65. <!-- 日排水量柱状图 -->
  66. <div class="discharge-chart-container">
  67. <h4 class="chart-title">日排水量</h4>
  68. <VChart ref="dischargeChart" :option="dailyDischargeChartOption" :autoresize="true" style="width: 100%; height: 140px;" />
  69. </div>
  70. </div>
  71. </m-card>
  72. </div>
  73. </div>
  74. <!-- 右侧面板 - 圩区详情 -->
  75. <div class="right-panel">
  76. <div class="right-panel-3d">
  77. <!-- 降雨预报和降雨量 -->
  78. <m-card title="降雨预报" class="water-station-card water-station-card-top" :width="398" :height="450">
  79. <div class="rainfall-forecast-panel">
  80. <!-- 第一行:两列布局(比例6:4) -->
  81. <div class="rainfall-top-row">
  82. <!-- 可纳雨量柱状图 -->
  83. <div class="rainfall-capacity-chart-container">
  84. <h4 class="chart-title">可纳雨量</h4>
  85. <VChart ref="rainfallCapacityChart" :option="rainfallCapacityChartOption" :autoresize="true" style="width: 100%; height: calc(100% - 20px);" />
  86. </div>
  87. <!-- 降雨预报轮播 -->
  88. <div class="forecast-carousel">
  89. <div class="carousel-wrapper" :style="{ transform: `translateX(-${currentForecastIndex * 100}%)` }">
  90. <div class="forecast-card" v-for="(forecast, index) in rainfallForecast" :key="index">
  91. <div class="forecast-date">{{ forecast.date }}</div>
  92. <div class="forecast-icon" :class="forecast.icon"></div>
  93. <div class="forecast-desc">{{ forecast.desc }}</div>
  94. <div class="forecast-temp">{{ forecast.temp }}</div>
  95. </div>
  96. </div>
  97. <div class="carousel-indicators">
  98. <span
  99. v-for="(forecast, index) in rainfallForecast"
  100. :key="index"
  101. class="indicator"
  102. :class="{ active: currentForecastIndex === index }"
  103. @click="currentForecastIndex = index"
  104. ></span>
  105. </div>
  106. </div>
  107. </div>
  108. <!-- 第二行:降雨量柱状图 -->
  109. <div class="rainfall-chart-container">
  110. <h4 class="chart-title">降雨量</h4>
  111. <VChart ref="rainfallChart" :option="polderRainfallChartOption" :autoresize="true" style="width: 100%; height: calc(100% - 20px);" />
  112. </div>
  113. </div>
  114. </m-card>
  115. <!-- 圩区AI内涝识别 -->
  116. <m-card title="圩区AI内涝识别" class="water-station-card water-station-card-bottom" :width="398" :height="480">
  117. <div class="waterlogging-detection-panel">
  118. <!-- 内涝统计 -->
  119. <div class="waterlogging-stats">
  120. <div class="stat-item">
  121. <div class="stat-label">积水点数量</div>
  122. <div class="stat-value">{{ waterloggingStats.count }}/{{ waterloggingStats.total }}</div>
  123. </div>
  124. <div class="stat-item">
  125. <div class="stat-label">是否积水</div>
  126. <div class="stat-value" :class="{ 'status-active': !waterloggingStats.isWaterlogging, 'status-inactive': waterloggingStats.isWaterlogging }">{{ waterloggingStats.isWaterlogging ? '是' : '否' }}</div>
  127. </div>
  128. </div>
  129. <!-- 监控点查看按钮 -->
  130. <div class="monitoring-button-container">
  131. <button class="monitoring-button" @click="showMonitoringPoints = !showMonitoringPoints">
  132. {{ showMonitoringPoints ? '收起监控点' : '查看监控点' }}
  133. </button>
  134. </div>
  135. <!-- 监控点图片 -->
  136. <div class="monitoring-points" v-if="showMonitoringPoints">
  137. <div class="monitoring-point" v-for="(point, index) in monitoringPoints" :key="index">
  138. <div class="point-name">{{ point.name }}</div>
  139. <div class="point-image">
  140. <img :src="point.image" alt="监控点" />
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. </m-card>
  146. </div>
  147. </div>
  148. <!-- 孔巷联圩详情 -->
  149. <template v-if="selectedPolderDetail === 'konggang'">
  150. <!-- 侧边开关控制 -->
  151. <div class="polder-layer-controls">
  152. <div class="toggle-item">
  153. <span class="toggle-text">泵站</span>
  154. <div class="toggle-switch" :class="{ active: showGate }" @click="$emit('update:showGate', !showGate)">
  155. <div class="toggle-knob"></div>
  156. </div>
  157. </div>
  158. <div class="toggle-item">
  159. <span class="toggle-text">水文站</span>
  160. <div class="toggle-switch" :class="{ active: showHydrology }" @click="$emit('update:showHydrology', !showHydrology)">
  161. <div class="toggle-knob"></div>
  162. </div>
  163. </div>
  164. </div>
  165. </template>
  166. </div>
  167. </template>
  168. <script setup>
  169. import { ref, computed } from "vue"
  170. import mCard from "@/components/mCard/index.vue"
  171. import VChart from "vue-echarts"
  172. import * as echarts from "echarts"
  173. // 引入监控点图片
  174. import cityFlood1 from "@/assets/images/城市积水1.png"
  175. import cityFlood2 from "@/assets/images/城市积水2.png"
  176. import cityFlood3 from "@/assets/images/城市积水3.png"
  177. defineProps({
  178. selectedPolderDetail: {
  179. type: String,
  180. default: null
  181. },
  182. showGate: {
  183. type: Boolean,
  184. default: true
  185. },
  186. showHydrology: {
  187. type: Boolean,
  188. default: false
  189. }
  190. })
  191. defineEmits(['update:showGate', 'update:showHydrology'])
  192. // 泵站状态数据
  193. const pumpStatus = ref([
  194. { name: '泵站1', status: '运行中', flow: '8.5 m³/s' },
  195. { name: '泵站2', status: '停止', flow: '0 m³/s' }
  196. ])
  197. // 开泵流量图表配置
  198. const pumpFlowChartOption = ref({
  199. grid: {
  200. left: '1%',
  201. top: '15%',
  202. right: '5%',
  203. bottom: '15%',
  204. containLabel: true
  205. },
  206. tooltip: {
  207. trigger: 'axis',
  208. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  209. borderColor: 'rgba(48, 220, 255, 0.5)',
  210. textStyle: {
  211. color: '#fff'
  212. },
  213. formatter: function(params) {
  214. let result = params[0].name + '<br/>';
  215. params.forEach(item => {
  216. result += item.marker + item.seriesName + ': ' + item.value + ' m³/s<br/>';
  217. });
  218. return result;
  219. }
  220. },
  221. xAxis: {
  222. type: 'category',
  223. data: ['08:00', '10:00', '12:00', '14:00', '16:00', '18:00'],
  224. axisLine: {
  225. lineStyle: {
  226. color: 'rgba(48, 220, 255, 0.6)'
  227. }
  228. },
  229. axisLabel: {
  230. color: 'rgba(255, 255, 255, 0.8)',
  231. fontSize: 9
  232. },
  233. boundaryGap: true
  234. },
  235. yAxis: {
  236. type: 'value',
  237. axisLine: {
  238. lineStyle: {
  239. color: 'rgba(48, 220, 255, 0.6)'
  240. }
  241. },
  242. axisLabel: {
  243. color: 'rgba(255, 255, 255, 0.8)',
  244. fontSize: 9,
  245. formatter: '{value} m³/s'
  246. },
  247. splitLine: {
  248. lineStyle: {
  249. color: 'rgba(48, 220, 255, 0.2)'
  250. }
  251. }
  252. },
  253. tooltip: {
  254. trigger: 'axis',
  255. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  256. borderColor: 'rgba(48, 220, 255, 0.5)',
  257. textStyle: {
  258. color: '#fff'
  259. }
  260. },
  261. series: [
  262. {
  263. name: '流量',
  264. type: 'line',
  265. data: [5.2, 7.8, 8.5, 8.2, 7.9, 8.5],
  266. smooth: true,
  267. symbol: 'circle',
  268. symbolSize: 6,
  269. lineStyle: {
  270. color: '#30dcff',
  271. width: 2,
  272. shadowColor: 'rgba(48, 220, 255, 0.6)',
  273. shadowBlur: 10
  274. },
  275. itemStyle: {
  276. color: '#30dcff',
  277. borderColor: '#fff',
  278. borderWidth: 2
  279. },
  280. areaStyle: {
  281. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  282. {
  283. offset: 0,
  284. color: 'rgba(48, 220, 255, 0.3)'
  285. },
  286. {
  287. offset: 1,
  288. color: 'rgba(48, 220, 255, 0.1)'
  289. }
  290. ])
  291. }
  292. }
  293. ]
  294. })
  295. // 日排水量图表配置
  296. const dailyDischargeChartOption = ref({
  297. grid: {
  298. left: '1%',
  299. top: '15%',
  300. right: '5%',
  301. bottom: '15%',
  302. containLabel: true
  303. },
  304. tooltip: {
  305. trigger: 'axis',
  306. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  307. borderColor: 'rgba(48, 220, 255, 0.5)',
  308. textStyle: {
  309. color: '#fff'
  310. },
  311. formatter: function(params) {
  312. let result = params[0].name + '<br/>';
  313. params.forEach(item => {
  314. result += item.marker + item.seriesName + ': ' + item.value + ' 万m³<br/>';
  315. });
  316. return result;
  317. }
  318. },
  319. xAxis: {
  320. type: 'category',
  321. data: ['1月1日', '1月2日', '1月3日', '1月4日', '1月5日'],
  322. axisLine: {
  323. lineStyle: {
  324. color: 'rgba(48, 220, 255, 0.6)'
  325. }
  326. },
  327. axisLabel: {
  328. color: 'rgba(255, 255, 255, 0.8)',
  329. fontSize: 9,
  330. rotate: 30
  331. },
  332. boundaryGap: true
  333. },
  334. yAxis: {
  335. type: 'value',
  336. axisLine: {
  337. lineStyle: {
  338. color: 'rgba(48, 220, 255, 0.6)'
  339. }
  340. },
  341. axisLabel: {
  342. color: 'rgba(255, 255, 255, 0.8)',
  343. fontSize: 9,
  344. formatter: '{value} 万m³'
  345. },
  346. splitLine: {
  347. lineStyle: {
  348. color: 'rgba(48, 220, 255, 0.2)'
  349. }
  350. }
  351. },
  352. tooltip: {
  353. trigger: 'axis',
  354. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  355. borderColor: 'rgba(48, 220, 255, 0.5)',
  356. textStyle: {
  357. color: '#fff'
  358. }
  359. },
  360. series: [
  361. {
  362. name: '排水量',
  363. type: 'bar',
  364. barWidth: '40%',
  365. data: [12.5, 15.8, 18.2, 16.5, 17.3],
  366. itemStyle: {
  367. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  368. {
  369. offset: 0,
  370. color: 'rgba(48, 220, 255, 0.8)'
  371. },
  372. {
  373. offset: 1,
  374. color: 'rgba(48, 220, 255, 0.3)'
  375. }
  376. ]),
  377. shadowColor: 'rgba(48, 220, 255, 0.6)',
  378. shadowBlur: 10
  379. }
  380. }
  381. ]
  382. })
  383. // 当前选中的水位标签
  384. const currentWaterLevelTab = ref('inner')
  385. // 圩内水位数据
  386. const innerWaterLevelData = {
  387. level: '2.54',
  388. change: '-0.05',
  389. historyMax: '4.06',
  390. ecological: '2.08',
  391. alertDiff: '-0.79',
  392. chartData: [2.60, 2.62, 2.58, 2.56, 2.54],
  393. flowData: [0.4, 0.5, 0.3, 0.4, 0.3]
  394. }
  395. // 圩外水位数据
  396. const outerWaterLevelData = {
  397. level: '2.65',
  398. change: '+0.02',
  399. historyMax: '4.12',
  400. ecological: '2.10',
  401. alertDiff: '-0.68',
  402. chartData: [2.62, 2.63, 2.64, 2.65, 2.65],
  403. flowData: [0.5, 0.6, 0.5, 0.4, 0.5]
  404. }
  405. // 当前水位数据
  406. const currentWaterLevelData = computed(() => {
  407. return currentWaterLevelTab.value === 'inner' ? innerWaterLevelData : outerWaterLevelData
  408. })
  409. // 当前水位图表配置
  410. const currentWaterLevelChartOption = computed(() => {
  411. const data = currentWaterLevelTab.value === 'inner' ? innerWaterLevelData : outerWaterLevelData
  412. return {
  413. grid: {
  414. left: '2%',
  415. top: '15%',
  416. right: '5%',
  417. bottom: '15%',
  418. containLabel: true
  419. },
  420. legend: {
  421. data: ['水位', '流量'],
  422. top: '2%',
  423. textStyle: {
  424. color: 'rgba(255, 255, 255, 0.8)',
  425. fontSize: 10
  426. },
  427. itemWidth: 10,
  428. itemHeight: 10,
  429. selected: {
  430. '水位': true,
  431. '流量': false
  432. },
  433. selectedMode: 'single'
  434. },
  435. xAxis: {
  436. type: 'category',
  437. data: ['02-11 10:00', '02-11 16:00', '02-11 22:00', '02-12 04:00', '02-12 10:00'],
  438. axisLine: {
  439. lineStyle: {
  440. color: 'rgba(48, 220, 255, 0.6)'
  441. }
  442. },
  443. axisLabel: {
  444. color: 'rgba(255, 255, 255, 0.8)',
  445. fontSize: 10
  446. },
  447. boundaryGap: false
  448. },
  449. yAxis: {
  450. type: 'value',
  451. min: 0,
  452. max: 5,
  453. axisLine: {
  454. lineStyle: {
  455. color: 'rgba(48, 220, 255, 0.6)'
  456. }
  457. },
  458. axisLabel: {
  459. color: 'rgba(255, 255, 255, 0.8)',
  460. fontSize: 10
  461. },
  462. splitLine: {
  463. lineStyle: {
  464. color: 'rgba(48, 220, 255, 0.2)'
  465. }
  466. },
  467. axisPointer: {
  468. show: true
  469. },
  470. },
  471. tooltip: {
  472. trigger: 'axis',
  473. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  474. borderColor: 'rgba(48, 220, 255, 0.5)',
  475. textStyle: {
  476. color: '#fff'
  477. },
  478. formatter: function(params) {
  479. let result = params[0].name + '<br/>';
  480. params.forEach(item => {
  481. let unit = item.seriesName === '水位' ? ' m' : ' m³/s';
  482. result += item.marker + item.seriesName + ': ' + item.value + unit + '<br/>';
  483. });
  484. return result;
  485. }
  486. },
  487. series: [
  488. {
  489. name: '水位',
  490. type: 'line',
  491. data: data.chartData,
  492. smooth: true,
  493. symbol: 'circle',
  494. symbolSize: 8,
  495. lineStyle: {
  496. color: '#30dcff',
  497. width: 2,
  498. shadowColor: 'rgba(48, 220, 255, 0.6)',
  499. shadowBlur: 10
  500. },
  501. itemStyle: {
  502. color: '#30dcff',
  503. borderColor: '#fff',
  504. borderWidth: 2
  505. },
  506. areaStyle: {
  507. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  508. {
  509. offset: 0,
  510. color: 'rgba(48, 220, 255, 0.3)'
  511. },
  512. {
  513. offset: 1,
  514. color: 'rgba(48, 220, 255, 0.1)'
  515. }
  516. ])
  517. },
  518. markLine: {
  519. silent: true,
  520. symbol: 'none',
  521. data: [
  522. {
  523. yAxis: 3.32,
  524. label: {
  525. formatter: '警戒水位',
  526. color: '#ffcc00',
  527. position: 'insideMiddleTop',
  528. distance: 5,
  529. fontSize: 10,
  530. align: 'center'
  531. },
  532. lineStyle: {
  533. color: '#ffcc00',
  534. width: 2,
  535. type: 'dashed'
  536. }
  537. },
  538. {
  539. yAxis: 3.82,
  540. label: {
  541. formatter: '保证水位',
  542. color: '#ff0000',
  543. position: 'insideMiddleTop',
  544. distance: 5,
  545. fontSize: 10,
  546. align: 'center'
  547. },
  548. lineStyle: {
  549. color: '#ff0000',
  550. width: 2,
  551. type: 'dashed'
  552. }
  553. }
  554. ]
  555. }
  556. },
  557. {
  558. name: '流量',
  559. type: 'line',
  560. data: data.flowData,
  561. smooth: true,
  562. symbol: 'circle',
  563. symbolSize: 6,
  564. lineStyle: {
  565. color: '#00ff88',
  566. width: 2,
  567. shadowColor: 'rgba(0, 255, 136, 0.6)',
  568. shadowBlur: 10
  569. },
  570. itemStyle: {
  571. color: '#00ff88',
  572. borderColor: '#fff',
  573. borderWidth: 2
  574. },
  575. areaStyle: {
  576. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  577. {
  578. offset: 0,
  579. color: 'rgba(0, 255, 136, 0.3)'
  580. },
  581. {
  582. offset: 1,
  583. color: 'rgba(0, 255, 136, 0.1)'
  584. }
  585. ])
  586. }
  587. }
  588. ]
  589. }
  590. })
  591. // 降雨预报数据
  592. const rainfallForecast = ref([
  593. { date: '今天', icon: 'rainy', desc: '小雨', temp: '4°C' },
  594. { date: '明天', icon: 'cloudy', desc: '多云', temp: '6°C' },
  595. { date: '后天', icon: 'sunny', desc: '晴', temp: '8°C' },
  596. { date: '周四', icon: 'rainy', desc: '中雨', temp: '5°C' }
  597. ])
  598. // 降雨预报轮播状态
  599. const currentForecastIndex = ref(0)
  600. let forecastInterval
  601. // 开始轮播
  602. function startForecastCarousel() {
  603. forecastInterval = setInterval(() => {
  604. currentForecastIndex.value = (currentForecastIndex.value + 1) % rainfallForecast.value.length
  605. }, 3000) // 3秒切换一次
  606. }
  607. // 停止轮播
  608. function stopForecastCarousel() {
  609. if (forecastInterval) {
  610. clearInterval(forecastInterval)
  611. }
  612. }
  613. // 生命周期钩子
  614. import { onMounted, onBeforeUnmount } from 'vue'
  615. onMounted(() => {
  616. startForecastCarousel()
  617. })
  618. onBeforeUnmount(() => {
  619. stopForecastCarousel()
  620. })
  621. // 可纳雨量图表配置
  622. const rainfallCapacityChartOption = ref({
  623. grid: {
  624. left: '8%',
  625. top: '15%',
  626. right: '8%',
  627. bottom: '15%',
  628. containLabel: true
  629. },
  630. tooltip: {
  631. trigger: 'axis',
  632. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  633. borderColor: 'rgba(48, 220, 255, 0.5)',
  634. textStyle: {
  635. color: '#fff'
  636. },
  637. formatter: function(params) {
  638. let result = params[0].name + '<br/>';
  639. params.forEach(item => {
  640. result += item.marker + item.seriesName + ': ' + item.value + ' mm<br/>';
  641. });
  642. return result;
  643. }
  644. },
  645. xAxis: {
  646. type: 'category',
  647. data: ['可纳雨量'],
  648. axisLine: {
  649. show: false
  650. },
  651. axisTick: {
  652. show: false
  653. },
  654. axisLabel: {
  655. color: 'rgba(255, 255, 255, 0.8)',
  656. fontSize: 10
  657. }
  658. },
  659. yAxis: {
  660. type: 'value',
  661. min: 0,
  662. max: 50,
  663. axisLine: {
  664. show: false
  665. },
  666. axisTick: {
  667. show: false
  668. },
  669. axisLabel: {
  670. color: 'rgba(255, 255, 255, 0.8)',
  671. fontSize: 10,
  672. formatter: '{value} mm'
  673. },
  674. splitLine: {
  675. show: false
  676. }
  677. },
  678. series: [
  679. {
  680. name: '可纳雨量',
  681. type: 'bar',
  682. barWidth: 80,
  683. data: [20.16],
  684. itemStyle: {
  685. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  686. {
  687. offset: 0,
  688. color: 'rgba(48, 220, 255, 0.8)'
  689. },
  690. {
  691. offset: 1,
  692. color: 'rgba(48, 220, 255, 0.3)'
  693. }
  694. ]),
  695. shadowColor: 'rgba(48, 220, 255, 0.6)',
  696. shadowBlur: 10,
  697. borderWidth: 1,
  698. borderColor: 'rgba(48, 220, 255, 0.5)'
  699. },
  700. emphasis: {
  701. itemStyle: {
  702. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  703. {
  704. offset: 0,
  705. color: 'rgba(48, 220, 255, 1)'
  706. },
  707. {
  708. offset: 1,
  709. color: 'rgba(48, 220, 255, 0.5)'
  710. }
  711. ]),
  712. shadowColor: 'rgba(48, 220, 255, 0.8)',
  713. shadowBlur: 15
  714. }
  715. },
  716. markLine: {
  717. data: [
  718. {
  719. yAxis: 15.5,
  720. label: {
  721. formatter: '警戒水位可纳雨量',
  722. color: '#ffcc00',
  723. position: 'insideMiddleTop',
  724. fontSize: 10,
  725. align: 'center'
  726. },
  727. lineStyle: {
  728. color: '#ffcc00',
  729. width: 1,
  730. type: 'dashed'
  731. }
  732. },
  733. {
  734. yAxis: 28.3,
  735. label: {
  736. formatter: '堤顶高程可纳雨量',
  737. color: '#ff0000',
  738. position: 'insideMiddleTop',
  739. fontSize: 10,
  740. align: 'center'
  741. },
  742. lineStyle: {
  743. color: '#ff0000',
  744. width: 1,
  745. type: 'dashed'
  746. }
  747. }
  748. ]
  749. }
  750. }
  751. ]
  752. })
  753. // 降雨量图表配置
  754. const polderRainfallChartOption = ref({
  755. grid: {
  756. left: '3%',
  757. top: '15%',
  758. right: '3%',
  759. bottom: '12%',
  760. containLabel: true
  761. },
  762. tooltip: {
  763. trigger: 'axis',
  764. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  765. borderColor: 'rgba(48, 220, 255, 0.5)',
  766. textStyle: {
  767. color: '#fff'
  768. },
  769. formatter: function(params) {
  770. let result = params[0].name + '<br/>';
  771. params.forEach(item => {
  772. result += item.marker + item.seriesName + ': ' + item.value + ' mm<br/>';
  773. });
  774. return result;
  775. }
  776. },
  777. xAxis: {
  778. type: 'category',
  779. data: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
  780. axisLine: {
  781. lineStyle: {
  782. color: 'rgba(48, 220, 255, 0.6)'
  783. }
  784. },
  785. axisLabel: {
  786. color: 'rgba(255, 255, 255, 0.8)',
  787. fontSize: 9
  788. }
  789. },
  790. yAxis: {
  791. type: 'value',
  792. min: 0,
  793. max: 5,
  794. axisLine: {
  795. lineStyle: {
  796. color: 'rgba(48, 220, 255, 0.6)'
  797. }
  798. },
  799. axisLabel: {
  800. color: 'rgba(255, 255, 255, 0.8)',
  801. fontSize: 9,
  802. formatter: '{value} mm'
  803. },
  804. splitLine: {
  805. lineStyle: {
  806. color: 'rgba(48, 220, 255, 0.2)'
  807. }
  808. }
  809. },
  810. tooltip: {
  811. trigger: 'axis',
  812. backgroundColor: 'rgba(0, 20, 40, 0.9)',
  813. borderColor: 'rgba(48, 220, 255, 0.5)',
  814. textStyle: {
  815. color: '#fff'
  816. }
  817. },
  818. series: [
  819. {
  820. name: '降雨量',
  821. type: 'bar',
  822. barWidth: '30%',
  823. data: [0.2, 0.5, 1.2, 0.8, 0.3, 0.1, 0.2, 0.4],
  824. itemStyle: {
  825. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  826. {
  827. offset: 0,
  828. color: 'rgba(48, 220, 255, 0.8)'
  829. },
  830. {
  831. offset: 1,
  832. color: 'rgba(48, 220, 255, 0.3)'
  833. }
  834. ]),
  835. shadowColor: 'rgba(48, 220, 255, 0.6)',
  836. shadowBlur: 10
  837. }
  838. }
  839. ]
  840. })
  841. // 内涝识别数据
  842. const waterloggingStats = ref({
  843. count: 0,
  844. total: 15,
  845. isWaterlogging: false
  846. })
  847. // 监控点数据
  848. const monitoringPoints = ref([
  849. { name: '监控点1', image: cityFlood1 },
  850. { name: '监控点2', image: cityFlood2 },
  851. { name: '监控点3', image: cityFlood3 }
  852. ])
  853. // 显示监控点状态
  854. const showMonitoringPoints = ref(true)
  855. </script>
  856. <style lang="scss" scoped>
  857. .polder-detail-content {
  858. position: relative;
  859. width: 100%;
  860. height: 100%;
  861. }
  862. .left-panel {
  863. position: absolute;
  864. z-index: 4;
  865. width: 398px;
  866. left: 32px;
  867. top: 100px;
  868. bottom: 50px;
  869. perspective: 500px;
  870. perspective-origin: 50% 50%;
  871. }
  872. .left-panel-3d {
  873. position: absolute;
  874. left: 0;
  875. top: 0;
  876. right: 0;
  877. bottom: 0;
  878. display: flex;
  879. flex-direction: column;
  880. gap: 10px;
  881. transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
  882. z-index: 4;
  883. }
  884. .right-panel {
  885. position: absolute;
  886. z-index: 4;
  887. width: 398px;
  888. right: 32px;
  889. top: 100px;
  890. bottom: 50px;
  891. perspective: 500px;
  892. perspective-origin: 50% 50%;
  893. }
  894. .right-panel-3d {
  895. position: absolute;
  896. left: 0;
  897. top: 0;
  898. right: 0;
  899. bottom: 0;
  900. display: flex;
  901. flex-direction: column;
  902. gap: 10px;
  903. transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(-6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
  904. z-index: 4;
  905. }
  906. .water-station-card {
  907. pointer-events: auto;
  908. &.water-station-card-top {
  909. flex-shrink: 0;
  910. }
  911. &.water-station-card-bottom {
  912. flex: 1;
  913. min-height: 200px;
  914. }
  915. }
  916. .water-level-analysis-panel {
  917. height: 100%;
  918. padding: 8px;
  919. box-sizing: border-box;
  920. background: rgba(0, 30, 60, 0.5);
  921. border-radius: 6px;
  922. display: flex;
  923. flex-direction: column;
  924. gap: 6px;
  925. }
  926. .water-level-tabs {
  927. display: flex;
  928. gap: 4px;
  929. padding: 2px;
  930. background: rgba(0, 100, 150, 0.3);
  931. border-radius: 3px;
  932. .tab-item {
  933. flex: 1;
  934. padding: 3px 6px;
  935. text-align: center;
  936. border-radius: 2px;
  937. cursor: pointer;
  938. transition: all 0.3s ease;
  939. background: rgba(0, 50, 100, 0.5);
  940. border: 1px solid rgba(48, 220, 255, 0.3);
  941. span {
  942. font-size: 12px;
  943. color: rgba(255, 255, 255, 0.8);
  944. }
  945. &:hover {
  946. background: rgba(0, 80, 120, 0.6);
  947. border-color: rgba(48, 220, 255, 0.5);
  948. }
  949. &.active {
  950. background: rgba(48, 220, 255, 0.2);
  951. border-color: rgba(48, 220, 255, 0.8);
  952. box-shadow: 0 0 6px rgba(48, 220, 255, 0.3);
  953. span {
  954. color: #30dcff;
  955. font-weight: bold;
  956. }
  957. }
  958. }
  959. }
  960. .water-level-data {
  961. display: grid;
  962. grid-template-columns: repeat(5, 1fr);
  963. gap: 4px;
  964. padding: 6px;
  965. background: rgba(0, 100, 150, 0.3);
  966. border-radius: 4px;
  967. .data-item {
  968. text-align: center;
  969. .data-label {
  970. font-size: 9px;
  971. color: rgba(255, 255, 255, 0.7);
  972. margin-bottom: 1px;
  973. }
  974. .data-value {
  975. font-size: 11px;
  976. font-weight: bold;
  977. color: #30dcff;
  978. font-family: "D-DIN";
  979. &.negative {
  980. color: #ff6b6b;
  981. }
  982. }
  983. }
  984. }
  985. .water-level-chart {
  986. flex: 1;
  987. min-height: 180px;
  988. padding: 0;
  989. display: flex;
  990. :deep(.echarts-container) {
  991. width: 100% !important;
  992. height: 100% !important;
  993. }
  994. }
  995. .polder-work-status-panel {
  996. height: 100%;
  997. padding: 6px;
  998. box-sizing: border-box;
  999. background: rgba(0, 30, 60, 0.5);
  1000. border-radius: 6px;
  1001. display: flex;
  1002. flex-direction: column;
  1003. gap: 6px;
  1004. }
  1005. .pump-status-cards {
  1006. display: grid;
  1007. grid-template-columns: repeat(2, 1fr);
  1008. gap: 8px;
  1009. }
  1010. .pump-status-card {
  1011. background: rgba(0, 100, 150, 0.3);
  1012. border: 1px solid rgba(48, 220, 255, 0.3);
  1013. border-radius: 4px;
  1014. padding: 6px;
  1015. text-align: center;
  1016. .pump-name {
  1017. font-size: 11px;
  1018. color: rgba(255, 255, 255, 0.9);
  1019. margin-bottom: 4px;
  1020. }
  1021. .pump-status {
  1022. font-size: 12px;
  1023. font-weight: bold;
  1024. margin-bottom: 2px;
  1025. &.status-active {
  1026. color: #00ff88;
  1027. text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
  1028. }
  1029. &.status-inactive {
  1030. color: #ff6b6b;
  1031. }
  1032. }
  1033. .pump-flow {
  1034. font-size: 10px;
  1035. color: #30dcff;
  1036. }
  1037. }
  1038. .flow-chart-container,
  1039. .discharge-chart-container {
  1040. flex: 1;
  1041. background: rgba(0, 100, 150, 0.2);
  1042. border: 1px solid rgba(48, 220, 255, 0.3);
  1043. border-radius: 4px;
  1044. padding: 4px;
  1045. min-height: 160px;
  1046. .chart-title {
  1047. font-size: 12px;
  1048. color: #30dcff;
  1049. margin-bottom: 2px;
  1050. text-align: center;
  1051. font-weight: bold;
  1052. }
  1053. :deep(.echarts-container) {
  1054. width: 100% !important;
  1055. height: 100% !important;
  1056. }
  1057. }
  1058. .rainfall-forecast-panel {
  1059. height: 100%;
  1060. padding: 8px;
  1061. box-sizing: border-box;
  1062. background: rgba(0, 30, 60, 0.5);
  1063. border-radius: 6px;
  1064. display: flex;
  1065. flex-direction: column;
  1066. gap: 8px;
  1067. }
  1068. .rainfall-top-row {
  1069. display: flex;
  1070. gap: 8px;
  1071. flex: 1;
  1072. min-height: 200px;
  1073. }
  1074. .empty-container {
  1075. flex: 1;
  1076. background: rgba(0, 100, 150, 0.2);
  1077. border: 1px solid rgba(48, 220, 255, 0.3);
  1078. border-radius: 4px;
  1079. padding: 4px;
  1080. min-height: 100px;
  1081. }
  1082. .forecast-carousel {
  1083. flex: 4;
  1084. position: relative;
  1085. overflow: hidden;
  1086. background: rgba(0, 100, 150, 0.3);
  1087. border: 1px solid rgba(48, 220, 255, 0.3);
  1088. border-radius: 4px;
  1089. padding: 10px;
  1090. .carousel-wrapper {
  1091. display: flex;
  1092. transition: transform 0.5s ease;
  1093. height: 120px;
  1094. .forecast-card {
  1095. flex: 0 0 100%;
  1096. display: flex;
  1097. flex-direction: column;
  1098. align-items: center;
  1099. justify-content: center;
  1100. text-align: center;
  1101. .forecast-date {
  1102. font-size: 12px;
  1103. color: rgba(255, 255, 255, 0.9);
  1104. margin-bottom: 8px;
  1105. }
  1106. .forecast-icon {
  1107. width: 60px;
  1108. height: 60px;
  1109. margin: 0 auto 8px;
  1110. background-size: contain;
  1111. background-repeat: no-repeat;
  1112. background-position: center;
  1113. }
  1114. .forecast-icon.rainy {
  1115. background-image: url('@/assets/images/qixiang/暴雨.png');
  1116. }
  1117. .forecast-icon.cloudy {
  1118. background-image: url('@/assets/images/qixiang/多云.png');
  1119. }
  1120. .forecast-icon.sunny {
  1121. background-image: url('@/assets/images/qixiang/晴天.png');
  1122. }
  1123. .forecast-desc {
  1124. font-size: 14px;
  1125. color: #30dcff;
  1126. margin-bottom: 4px;
  1127. }
  1128. .forecast-temp {
  1129. font-size: 12px;
  1130. color: rgba(255, 255, 255, 0.8);
  1131. }
  1132. }
  1133. }
  1134. .carousel-indicators {
  1135. position: absolute;
  1136. bottom: 5px;
  1137. left: 50%;
  1138. transform: translateX(-50%);
  1139. display: flex;
  1140. gap: 5px;
  1141. .indicator {
  1142. width: 6px;
  1143. height: 6px;
  1144. border-radius: 50%;
  1145. background: rgba(255, 255, 255, 0.3);
  1146. cursor: pointer;
  1147. transition: all 0.3s ease;
  1148. &.active {
  1149. background: #30dcff;
  1150. transform: scale(1.2);
  1151. }
  1152. }
  1153. }
  1154. }
  1155. .rainfall-capacity-chart-container {
  1156. flex: 6;
  1157. background: rgba(0, 100, 150, 0.2);
  1158. border: 1px solid rgba(48, 220, 255, 0.3);
  1159. border-radius: 4px;
  1160. padding: 4px;
  1161. min-height: 120px;
  1162. display: flex;
  1163. flex-direction: column;
  1164. .chart-title {
  1165. font-size: 12px;
  1166. color: #30dcff;
  1167. margin-bottom: 4px;
  1168. text-align: center;
  1169. font-weight: bold;
  1170. flex-shrink: 0;
  1171. }
  1172. :deep(.echarts-container) {
  1173. width: 100% !important;
  1174. height: 90px !important;
  1175. }
  1176. }
  1177. .rainfall-chart-container {
  1178. flex: 1;
  1179. background: rgba(0, 100, 150, 0.2);
  1180. border: 1px solid rgba(48, 220, 255, 0.3);
  1181. border-radius: 4px;
  1182. padding: 4px;
  1183. min-height: 120px;
  1184. display: flex;
  1185. flex-direction: column;
  1186. .chart-title {
  1187. font-size: 12px;
  1188. color: #30dcff;
  1189. margin-bottom: 4px;
  1190. text-align: center;
  1191. font-weight: bold;
  1192. flex-shrink: 0;
  1193. }
  1194. :deep(.echarts-container) {
  1195. width: 100% !important;
  1196. height: 100px !important;
  1197. }
  1198. }
  1199. .waterlogging-detection-panel {
  1200. height: 100%;
  1201. padding: 10px;
  1202. box-sizing: border-box;
  1203. background: rgba(0, 30, 60, 0.5);
  1204. border-radius: 6px;
  1205. display: flex;
  1206. flex-direction: column;
  1207. gap: 10px;
  1208. }
  1209. .waterlogging-stats {
  1210. display: grid;
  1211. grid-template-columns: repeat(2, 1fr);
  1212. gap: 8px;
  1213. }
  1214. .waterlogging-stats .stat-item {
  1215. background: rgba(0, 100, 150, 0.3);
  1216. border: 1px solid rgba(48, 220, 255, 0.3);
  1217. border-radius: 4px;
  1218. padding: 10px;
  1219. text-align: center;
  1220. .stat-label {
  1221. font-size: 12px;
  1222. color: rgba(255, 255, 255, 0.9);
  1223. margin-bottom: 4px;
  1224. }
  1225. .stat-value {
  1226. font-size: 18px;
  1227. font-weight: bold;
  1228. color: #30dcff;
  1229. font-family: "D-DIN";
  1230. &.status-active {
  1231. color: #00ff88;
  1232. text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
  1233. }
  1234. &.status-inactive {
  1235. color: #ff6b6b;
  1236. }
  1237. }
  1238. }
  1239. .monitoring-button-container {
  1240. display: flex;
  1241. justify-content: center;
  1242. margin: 5px 0;
  1243. }
  1244. .monitoring-button {
  1245. background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 191, 255, 0.2) 100%);
  1246. border: 1px solid rgba(48, 220, 255, 0.4);
  1247. border-radius: 4px;
  1248. padding: 6px 12px;
  1249. color: #30dcff;
  1250. font-size: 12px;
  1251. font-weight: bold;
  1252. cursor: pointer;
  1253. transition: all 0.3s ease;
  1254. &:hover {
  1255. background: linear-gradient(135deg, rgba(48, 220, 255, 0.4) 0%, rgba(0, 191, 255, 0.3) 100%);
  1256. border-color: rgba(48, 220, 255, 0.6);
  1257. box-shadow: 0 0 8px rgba(48, 220, 255, 0.3);
  1258. }
  1259. }
  1260. .monitoring-points {
  1261. flex: 1;
  1262. display: flex;
  1263. flex-direction: column;
  1264. gap: 8px;
  1265. overflow-y: auto;
  1266. &::-webkit-scrollbar {
  1267. width: 4px;
  1268. }
  1269. &::-webkit-scrollbar-track {
  1270. background: rgba(0, 0, 0, 0.3);
  1271. }
  1272. &::-webkit-scrollbar-thumb {
  1273. background: rgba(48, 220, 255, 0.4);
  1274. border-radius: 2px;
  1275. }
  1276. }
  1277. .monitoring-point {
  1278. background: rgba(0, 100, 150, 0.3);
  1279. border: 1px solid rgba(48, 220, 255, 0.3);
  1280. border-radius: 4px;
  1281. padding: 8px;
  1282. .point-name {
  1283. font-size: 12px;
  1284. color: #30dcff;
  1285. margin-bottom: 4px;
  1286. font-weight: bold;
  1287. }
  1288. .point-image {
  1289. width: 100%;
  1290. height: 120px;
  1291. border-radius: 4px;
  1292. overflow: hidden;
  1293. border: 1px solid rgba(48, 220, 255, 0.3);
  1294. img {
  1295. width: 100%;
  1296. height: 100%;
  1297. object-fit: cover;
  1298. }
  1299. }
  1300. }
  1301. .polder-layer-controls {
  1302. position: absolute;
  1303. left: 1450px;
  1304. top: 260px;
  1305. display: flex;
  1306. flex-direction: column;
  1307. gap: 15px;
  1308. z-index: 1000;
  1309. pointer-events: auto;
  1310. .toggle-item {
  1311. display: flex;
  1312. align-items: center;
  1313. gap: 10px;
  1314. padding: 8px 12px;
  1315. background: rgba(0, 20, 40, 0.8);
  1316. border-radius: 6px;
  1317. .toggle-text {
  1318. color: #fff;
  1319. font-size: 14px;
  1320. text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
  1321. }
  1322. .toggle-switch {
  1323. width: 44px;
  1324. height: 22px;
  1325. background: rgba(100, 100, 100, 0.5);
  1326. border-radius: 11px;
  1327. cursor: pointer;
  1328. transition: all 0.3s ease;
  1329. position: relative;
  1330. .toggle-knob {
  1331. position: absolute;
  1332. left: 2px;
  1333. top: 2px;
  1334. width: 18px;
  1335. height: 18px;
  1336. background: #a3dcde;
  1337. border-radius: 50%;
  1338. transition: all 0.3s ease;
  1339. }
  1340. &.active {
  1341. background: rgba(48, 220, 255, 0.4);
  1342. box-shadow: 0 0 10px rgba(48, 220, 255, 0.3);
  1343. .toggle-knob {
  1344. left: 24px;
  1345. background: #30dcff;
  1346. box-shadow: 0 0 8px rgba(48, 220, 255, 0.5);
  1347. }
  1348. }
  1349. }
  1350. }
  1351. }
  1352. </style>