rykb.vue 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. <template>
  2. <div class="page-container">
  3. <el-row :gutter="16" class="summary">
  4. <!-- 原有的统计卡片部分保持不变 -->
  5. <el-col :sm="3" :xs="12">
  6. <SummaryCard
  7. :value="device.total || 0"
  8. icon="fa-solid:project-diagram"
  9. icon-bg-color="text-blue-500"
  10. icon-color="bg-blue-100"
  11. title="设备数"
  12. />
  13. </el-col>
  14. <el-col :sm="3" :xs="12">
  15. <SummaryCard
  16. :value="maintain.total || 0"
  17. icon="fa-solid:list"
  18. icon-bg-color="text-pink-500"
  19. icon-color="bg-blue-100"
  20. title="维修工单"
  21. />
  22. </el-col>
  23. <el-col :sm="3" :xs="12">
  24. <SummaryCard
  25. :value="fill.unfilledCount || 0"
  26. icon="fa-solid:times-circle"
  27. icon-bg-color="text-purple-500"
  28. icon-color="bg-purple-100"
  29. title="运行未填写"
  30. />
  31. </el-col>
  32. <el-col :sm="3" :xs="12">
  33. <SummaryCard
  34. :value="fill.filledCount || 0"
  35. icon="fa-solid:award"
  36. icon-bg-color="text-purple-500"
  37. icon-color="bg-purple-100"
  38. title="运行已填写"
  39. />
  40. </el-col>
  41. <el-col :sm="3" :xs="12">
  42. <SummaryCard
  43. :value="by.todo || 0"
  44. icon="fa-solid:times-circle"
  45. icon-bg-color="text-green-500"
  46. icon-color="bg-green-100"
  47. title="未执行保养"
  48. />
  49. </el-col>
  50. <el-col :sm="3" :xs="12">
  51. <SummaryCard
  52. :value="by.finished || 0"
  53. icon="fa-solid:award"
  54. icon-bg-color="text-green-500"
  55. icon-color="bg-green-100"
  56. title="已执行保养"
  57. />
  58. </el-col>
  59. <el-col :sm="3" :xs="12">
  60. <SummaryCard
  61. :value="inspectt.todo || 0"
  62. icon="fa-solid:times-circle"
  63. icon-bg-color="text-yellow-500"
  64. icon-color="bg-yellow-100"
  65. title="待填写巡检"
  66. />
  67. </el-col>
  68. <el-col :sm="3" :xs="12">
  69. <SummaryCard
  70. :value="inspectt.finished || 0"
  71. icon="fa-solid:award"
  72. icon-bg-color="text-yellow-500"
  73. icon-color="bg-yellow-100"
  74. title="已填写巡检"
  75. />
  76. </el-col>
  77. <!-- 其他统计卡片... -->
  78. </el-row>
  79. <!-- 第二行:图表行 -->
  80. <el-row :gutter="16" class="mb-4 mt-3">
  81. <el-col :span="6">
  82. <el-card class="chart-card" shadow="never">
  83. <template #header>
  84. <div class="flex items-center">
  85. <span class="text-base font-medium" style="color: #b6c8da">设备状态统计</span>
  86. </div>
  87. </template>
  88. <div ref="statusChartRef" class="h-[320px]"></div>
  89. </el-card>
  90. </el-col>
  91. <el-col :span="7">
  92. <el-card class="chart-card" shadow="never">
  93. <template #header>
  94. <div class="flex items-center">
  95. <span class="text-base font-medium" style="color: #b6c8da">项目部设备数量统计</span>
  96. </div>
  97. </template>
  98. <div ref="topContainer" class="h-[320px]"></div>
  99. </el-card>
  100. </el-col>
  101. <el-col :span="11">
  102. <el-card class="chart-card" shadow="never">
  103. <template #header>
  104. <div class="flex items-center justify-between">
  105. <span class="text-base font-medium" style="color: #b6c8da">工单数量情况</span>
  106. </div>
  107. </template>
  108. <div ref="qxRef" class="h-[320px]"></div>
  109. </el-card>
  110. </el-col>
  111. </el-row>
  112. <!-- 新增:修井完成情况图表 -->
  113. <el-row :gutter="16" class="mb-4">
  114. <el-col :span="8">
  115. <div class="flex flex-col justify-between">
  116. <el-card class="safety-days-card chart-card h-[230px]" shadow="never">
  117. <template #header>
  118. <div class="flex items-center">
  119. <span class="text-base font-medium" style="color: #b6c8da">安全生产天数</span>
  120. </div>
  121. </template>
  122. <div class="safety-days-content">
  123. <div class="days-number">{{ 165 }}</div>
  124. <div class="days-label">天</div>
  125. <div class="safety-desc">截至当前连续安全生产天数</div>
  126. </div>
  127. </el-card>
  128. <el-card class="chart-card mt-1" shadow="never">
  129. <template #header>
  130. <div class="flex items-center">
  131. <span class="text-base font-medium" style="color: #b6c8da">钻井完成情况</span>
  132. </div>
  133. </template>
  134. <div ref="drillingWellChartRef" class="h-[170px]"></div>
  135. </el-card>
  136. </div>
  137. </el-col>
  138. <el-col :span="8">
  139. <el-card class="chart-card" shadow="never">
  140. <template #header>
  141. <div class="flex items-center">
  142. <span class="text-base font-medium" style="color: #b6c8da">钻井工作量情况</span>
  143. </div>
  144. </template>
  145. <div ref="drillingWorkloadChartRef" class="h-[405px]"></div>
  146. </el-card>
  147. </el-col>
  148. <el-col :span="8">
  149. <el-card class="chart-card" shadow="never">
  150. <template #header>
  151. <div class="flex items-center">
  152. <span class="text-base font-medium" style="color: #b6c8da">修井工作量情况</span>
  153. </div>
  154. </template>
  155. <div ref="repairWorkloadChartRef" class="h-[405px]"></div>
  156. </el-card>
  157. </el-col>
  158. </el-row>
  159. </div>
  160. </template>
  161. <script setup lang="ts" name="Index">
  162. import * as echarts from 'echarts/core'
  163. import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
  164. import {
  165. GridComponent,
  166. LegendComponent,
  167. TitleComponent,
  168. ToolboxComponent,
  169. TooltipComponent
  170. } from 'echarts/components'
  171. import { LabelLayout, UniversalTransition } from 'echarts/features'
  172. import { CanvasRenderer } from 'echarts/renderers'
  173. import { useElementSize } from '@vueuse/core'
  174. import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
  175. import { IotStatApi } from '@/api/pms/stat'
  176. import SummaryCard from '@/components/SummaryCard/index.vue'
  177. import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
  178. // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
  179. /** IoT 首页 */
  180. defineOptions({ name: 'IotRyStat' })
  181. // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
  182. echarts.use([
  183. TooltipComponent,
  184. LegendComponent,
  185. PieChart,
  186. CanvasRenderer,
  187. LabelLayout,
  188. TitleComponent,
  189. ToolboxComponent,
  190. GridComponent,
  191. LineChart,
  192. UniversalTransition,
  193. GaugeChart,
  194. BarChart
  195. ])
  196. const dateRange = ref<[Date, Date] | null>(null)
  197. const by = ref({
  198. todo: undefined,
  199. finished: undefined
  200. })
  201. const fill = ref({
  202. filledCount: undefined,
  203. unfilledCount: undefined
  204. })
  205. const inspectt = ref({
  206. finished: 0,
  207. todo: 0
  208. })
  209. const queryParams = reactive({
  210. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  211. endTime: Date.now() // 设置默认结束时间为当前时间
  212. })
  213. const backendData = ref([])
  214. const statusChartRef = ref() // 设备数量统计的图表
  215. // const materialChartRef = ref() // 设备数量统计的图表
  216. const drillingWorkloadChartRef = ref()
  217. const repairWorkloadChartRef = ref()
  218. // 基础统计数据
  219. // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
  220. const statsData = ref<IotStatisticsSummaryRespVO>({
  221. productCategoryCount: 0,
  222. productCount: 0,
  223. deviceCount: 0,
  224. deviceMessageCount: 0,
  225. productCategoryTodayCount: 0,
  226. productTodayCount: 0,
  227. deviceTodayCount: 0,
  228. deviceMessageTodayCount: 0,
  229. deviceOnlineCount: 0,
  230. deviceOfflineCount: 0,
  231. deviceInactiveCount: 0,
  232. productCategoryDeviceCounts: {}
  233. })
  234. const device = ref({
  235. total: undefined,
  236. today: undefined
  237. })
  238. const maintain = ref({
  239. total: undefined,
  240. today: undefined
  241. })
  242. const work = ref({
  243. total: undefined,
  244. today: undefined
  245. })
  246. const inspect = ref({
  247. total: undefined,
  248. today: undefined
  249. })
  250. const status = ref({
  251. finished: 0,
  252. todo: 0
  253. })
  254. const todayStatus = ref({
  255. finished: 0,
  256. todo: 0
  257. })
  258. const typeData = ref({})
  259. const materialData = ref({})
  260. const orderSevenData = ref({})
  261. const safe = ref()
  262. // 新增:修井完成情况数据
  263. const repairWellData = ref({
  264. xAxis: ['小修10队', '小修8队', '小修9队'],
  265. series: [
  266. { name: '日累完成井数', data: [10, 15, 20] },
  267. { name: '月累完成井数', data: [50, 60, 70] },
  268. { name: '年累完成井数', data: [200, 220, 250] }
  269. ]
  270. })
  271. // 新增:钻井完成情况数据
  272. const drillingWellData = ref({
  273. xAxis: ['50010队', '四川宝石带压作业队'],
  274. series: [
  275. // { name: '日累完成井数', data: [5, 8] },
  276. { name: '月累完成井数', data: [40, 30] },
  277. { name: '年累完成井数', data: [180, 150] }
  278. ]
  279. })
  280. const drillingWorkloadData = ref({
  281. xAxis: ['SCP项目部', '伊拉克项目部', '陕西项目部'],
  282. series: [
  283. { name: '日累进尺', data: [150, 100, 200] },
  284. { name: '月累进尺', data: [1000, 1200, 1500] },
  285. { name: '年累进尺', data: [2000, 5000, 10000] }
  286. ]
  287. })
  288. // 新增:修井工作量情况数据
  289. const repairWorkloadData = ref({
  290. xAxis: ['SCP项目部', '伊拉克项目部', '陕西项目部'],
  291. series: [
  292. // { name: '日累进尺', data: [120,80, 160] },
  293. { name: '月完井数', data: [1300, 800, 1000] },
  294. { name: '年完井数', data: [4000, 5000, 7000] }
  295. ]
  296. })
  297. const repairWellChartRef = ref()
  298. const drillingWellChartRef = ref()
  299. /** 获取统计数据 */
  300. const getStats = () => {
  301. // 获取基础统计数据
  302. IotStatApi.getDeviceCount('ry').then((res) => {
  303. device.value = res
  304. })
  305. IotStatApi.getMaintainCount('ry').then((res) => {
  306. maintain.value = res
  307. })
  308. IotStatApi.getMainWorkCount().then((res) => {
  309. work.value = res
  310. })
  311. IotStatApi.getInspectCount().then((res) => {
  312. inspect.value = res
  313. })
  314. IotStatApi.getMaintenanceStatus('ry').then((res) => {
  315. status.value = res
  316. // initCharts()
  317. })
  318. // IotStatApi.getMaintenanceTodayStatus().then((res) => {
  319. // todayStatus.value = res
  320. // initTopChart()
  321. // })
  322. IotStatApi.getDeviceStatusCount('ry').then((res) => {
  323. typeData.value = res
  324. initDeviceStatusCharts()
  325. })
  326. IotStatApi.getSafeCount().then((res) => {
  327. safe.value = res
  328. })
  329. // IotStatApi.getMaterial().then((res) => {
  330. // materialData.value = res
  331. // initMaterials()
  332. // })
  333. IotStatApi.getOrderSeven('ry').then((res) => {
  334. orderSevenData.value = res
  335. initQxChart()
  336. })
  337. IotStatApi.getMaintenanceStatus().then((res) => {
  338. by.value = res
  339. })
  340. const fillQueryParams = reactive({
  341. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  342. endTime: Date.now(), // 设置默认结束时间为当前时间
  343. createTime: [],
  344. deptId: null, // 选中的部门ID
  345. status: null // 填写状态
  346. })
  347. IotStatApi.getInspectStatuss(fillQueryParams, 'ry').then((res) => {
  348. inspectt.value = res
  349. })
  350. fillQueryParams.deptId = '158'
  351. IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
  352. fill.value = res.totalList[0] || []
  353. })
  354. IotStatApi.getProject('ry').then((res) => {
  355. typeData.value = res;
  356. initProjectCharts()
  357. })
  358. }
  359. const initDrillingWorkloadChart = () => {
  360. if (!drillingWorkloadChartRef.value) return
  361. const chart = echarts.init(drillingWorkloadChartRef.value)
  362. const option = {
  363. tooltip: {
  364. trigger: 'axis',
  365. axisPointer: {
  366. type: 'shadow'
  367. }
  368. },
  369. legend: {
  370. data: drillingWorkloadData.value.series.map((item) => item.name),
  371. textStyle: {
  372. color: '#B6C8DA'
  373. }
  374. },
  375. grid: {
  376. left: '3%',
  377. right: '4%',
  378. bottom: '3%',
  379. containLabel: true
  380. },
  381. xAxis: {
  382. type: 'category',
  383. data: drillingWorkloadData.value.xAxis,
  384. axisLabel: {
  385. color: '#B6C8DA'
  386. },
  387. axisLine: {
  388. lineStyle: {
  389. color: '#B6C8DA'
  390. }
  391. }
  392. },
  393. yAxis: {
  394. type: 'value',
  395. name: '进尺(米)',
  396. axisLabel: {
  397. color: '#B6C8DA'
  398. },
  399. splitLine: {
  400. show: true, // 显示水平网格线(默认显示)
  401. lineStyle: {
  402. // 水平网格线颜色(可设置为纯色或带透明度的颜色)
  403. color: '#457794', // 浅灰色半透明
  404. // 可选:设置线条类型(实线/虚线/点线)
  405. type: 'dashed'
  406. }
  407. },
  408. axisLine: {
  409. lineStyle: {
  410. color: '#B6C8DA'
  411. }
  412. }
  413. },
  414. series: drillingWorkloadData.value.series.map((item) => ({
  415. name: item.name,
  416. type: 'bar',
  417. data: item.data
  418. }))
  419. }
  420. chart.setOption(option)
  421. }
  422. // 新增:初始化修井工作量情况图表
  423. const initRepairWorkloadChart = () => {
  424. if (!repairWorkloadChartRef.value) return
  425. const chart = echarts.init(repairWorkloadChartRef.value)
  426. const option = {
  427. tooltip: {
  428. trigger: 'axis',
  429. axisPointer: {
  430. type: 'shadow'
  431. }
  432. },
  433. legend: {
  434. data: repairWorkloadData.value.series.map((item) => item.name),
  435. textStyle: {
  436. color: '#B6C8DA'
  437. }
  438. },
  439. grid: {
  440. left: '3%',
  441. right: '4%',
  442. bottom: '3%',
  443. containLabel: true
  444. },
  445. xAxis: {
  446. type: 'category',
  447. data: repairWorkloadData.value.xAxis,
  448. axisLabel: {
  449. color: '#B6C8DA'
  450. },
  451. axisLine: {
  452. lineStyle: {
  453. color: '#B6C8DA'
  454. }
  455. }
  456. },
  457. yAxis: {
  458. type: 'value',
  459. name: '进尺(米)',
  460. axisLabel: {
  461. color: '#B6C8DA'
  462. },
  463. splitLine: {
  464. show: true, // 显示水平网格线(默认显示)
  465. lineStyle: {
  466. // 水平网格线颜色(可设置为纯色或带透明度的颜色)
  467. color: '#457794', // 浅灰色半透明
  468. // 可选:设置线条类型(实线/虚线/点线)
  469. type: 'dashed'
  470. }
  471. },
  472. axisLine: {
  473. lineStyle: {
  474. color: '#B6C8DA'
  475. }
  476. }
  477. },
  478. series: repairWorkloadData.value.series.map((item) => ({
  479. name: item.name,
  480. type: 'bar',
  481. data: item.data
  482. }))
  483. }
  484. chart.setOption(option)
  485. }
  486. /** 初始化图表 */
  487. const initDeviceStatusCharts = () => {
  488. // 设备数量统计
  489. echarts.init(statusChartRef.value).setOption({
  490. tooltip: {
  491. trigger: 'item'
  492. },
  493. legend: {
  494. orient: 'horizontal', // 水平排列图例项
  495. bottom: '0%', // 放置在底部
  496. icon: 'circle',
  497. textStyle: {
  498. color: '#B6C8DA'
  499. }
  500. },
  501. series: [
  502. {
  503. name: '',
  504. type: 'pie',
  505. radius: ['50%', '80%'],
  506. avoidLabelOverlap: false,
  507. center: ['50%', '46%'],
  508. label: {
  509. show: false,
  510. position: 'outside',
  511. color: 'white'
  512. },
  513. emphasis: {
  514. label: {
  515. show: true,
  516. fontSize: 15,
  517. fontWeight: 'bold'
  518. }
  519. },
  520. labelLine: {
  521. show: false
  522. },
  523. data: typeData.value
  524. }
  525. ]
  526. })
  527. }
  528. // 自适应调整
  529. const handleResize = () => {
  530. chartInstance?.resize()
  531. }
  532. const topContainer = ref(null)
  533. let topInstance = null
  534. // 响应式容器尺寸
  535. const { width, height } = useElementSize(topContainer)
  536. const initProjectCharts = () => {
  537. const chart = echarts.init(topContainer.value);
  538. chart.setOption({
  539. tooltip: {
  540. trigger: 'item'
  541. },
  542. legend: {
  543. // top: '5%',
  544. // right: '10%',
  545. // align: 'center',
  546. orient: 'horizontal', // 水平排列图例项
  547. bottom: '0%', // 放置在底部
  548. icon: 'circle',
  549. textStyle: {
  550. color:'#B6C8DA'
  551. }
  552. },
  553. series: [
  554. {
  555. name: '',
  556. type: 'pie',
  557. radius: ['0%', '70%'],
  558. avoidLabelOverlap: false,
  559. center: ['50%', '45%'],
  560. label: {
  561. show: false,
  562. position: 'outside',
  563. color:'#B6C8DA'
  564. },
  565. emphasis: {
  566. label: {
  567. show: true,
  568. fontSize: 15,
  569. fontWeight: 'bold'
  570. }
  571. },
  572. labelLine: {
  573. show: true
  574. },
  575. data: typeData.value
  576. }
  577. ]})
  578. }
  579. // 自适应调整
  580. watch([width, height], () => {
  581. topInstance?.resize()
  582. })
  583. const activeDom = ref(null)
  584. let activeInstance = null
  585. const activeData = ref([])
  586. const initActiveChart = async () => {
  587. if (!activeDom.value) return
  588. activeData.value = await IotStatApi.getDeptCount()
  589. activeInstance = echarts.init(activeDom.value)
  590. const option = {
  591. tooltip: {
  592. trigger: 'axis',
  593. axisPointer: { type: 'shadow' },
  594. formatter: (params) => `
  595. ${params[0].name}<br/>
  596. ${params[0].marker} 总人数: ${params[0].value}<br/>
  597. ${params[1].marker} 活跃人数: ${params[1].value}
  598. `
  599. },
  600. legend: {
  601. data: ['总人数', '活跃人数'],
  602. top: 30
  603. },
  604. grid: {
  605. left: '3%',
  606. right: '4%',
  607. bottom: '3%',
  608. containLabel: true
  609. },
  610. xAxis: {
  611. type: 'category',
  612. data: activeData.value.map((item) => item.department),
  613. axisLabel: {
  614. interval: 0,
  615. rotate: 0
  616. }
  617. },
  618. yAxis: {
  619. type: 'value',
  620. name: '人数',
  621. splitLine: {
  622. show: true,
  623. lineStyle: { type: 'dashed' }
  624. }
  625. },
  626. series: [
  627. {
  628. name: '总人数',
  629. type: 'bar',
  630. data: activeData.value.map((item) => item.total),
  631. itemStyle: {
  632. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  633. { offset: 0, color: '#5470c6' },
  634. { offset: 1, color: '#83bff6' }
  635. ])
  636. },
  637. barWidth: 30
  638. },
  639. {
  640. name: '活跃人数',
  641. type: 'bar',
  642. data: activeData.value.map((item) => item.active),
  643. itemStyle: {
  644. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  645. { offset: 0, color: '#91cc75' },
  646. { offset: 1, color: '#e6f4d2' }
  647. ])
  648. },
  649. barWidth: 30
  650. }
  651. ]
  652. }
  653. activeInstance.setOption(option)
  654. }
  655. const qxRef = ref(null)
  656. let qxInstance = null
  657. // 生成近12个月份 (包含当年和去年)
  658. const generateMonths = () => {
  659. const months = []
  660. const date = new Date()
  661. date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
  662. for (let i = 0; i < 12; i++) {
  663. date.setMonth(date.getMonth() - 1)
  664. months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
  665. }
  666. return months.reverse()
  667. }
  668. const initQxChart = () => {
  669. if (!qxRef.value) return
  670. qxInstance = echarts.init(qxRef.value)
  671. debugger
  672. const option = {
  673. tooltip: {
  674. trigger: 'axis',
  675. axisPointer: {
  676. type: 'cross',
  677. label: {
  678. backgroundColor: '#6a7985'
  679. }
  680. }
  681. },
  682. legend: {
  683. data: orderSevenData.value.series.map((item) => item.name),
  684. top: 30,
  685. textStyle: {
  686. color: '#B6C8DA'
  687. }
  688. },
  689. grid: {
  690. left: '3%',
  691. right: '4%',
  692. bottom: '3%',
  693. containLabel: true
  694. },
  695. xAxis: {
  696. type: 'category',
  697. boundaryGap: false,
  698. data: orderSevenData.value.xAxis,
  699. axisLabel: {
  700. color: '#B6C8DA',
  701. formatter: (value) => value.split('-').join('/') // 显示为 2023/01
  702. },
  703. axisLine: {
  704. lineStyle: {
  705. color: '#B6C8DA'
  706. }
  707. }
  708. },
  709. yAxis: [
  710. {
  711. type: 'value',
  712. axisLabel: {
  713. color: '#B6C8DA',
  714. formatter: '{value}'
  715. },
  716. splitLine: {
  717. show: true, // 显示水平网格线(默认显示)
  718. lineStyle: {
  719. // 水平网格线颜色(可设置为纯色或带透明度的颜色)
  720. color: '#457794', // 浅灰色半透明
  721. // 可选:设置线条类型(实线/虚线/点线)
  722. type: 'dashed'
  723. }
  724. },
  725. axisLine: {
  726. lineStyle: {
  727. color: '#B6C8DA'
  728. }
  729. },
  730. position: 'left' // 左侧 Y 轴
  731. },
  732. {
  733. type: 'value',
  734. axisLabel: {
  735. formatter: '{value}'
  736. },
  737. axisLine: {
  738. lineStyle: {
  739. color: '#B6C8DA'
  740. }
  741. },
  742. position: 'right', // 右侧 Y 轴
  743. splitLine: {
  744. show: false // 隐藏右侧 Y 轴的分割线
  745. }
  746. }
  747. ],
  748. series: orderSevenData.value.series.map((item, index) => {
  749. // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
  750. const yAxisIndex = index < 2 ? 0 : 1
  751. return {
  752. name: item.name,
  753. type: 'line',
  754. smooth: true,
  755. symbol: 'circle',
  756. symbolSize: 8,
  757. itemStyle: {
  758. color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
  759. },
  760. areaStyle: {
  761. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  762. { offset: 0, color: 'rgba(84,112,198,0.4)' },
  763. { offset: 1, color: 'rgba(84,112,198,0.1)' }
  764. ])
  765. },
  766. data: item.data,
  767. yAxisIndex: yAxisIndex // 指定使用的 Y 轴
  768. }
  769. })
  770. }
  771. qxInstance.setOption(option)
  772. }
  773. // 响应式调整
  774. const resizeQxChart = () => qxInstance?.resize()
  775. // 新增:初始化修井完成情况图表
  776. const initRepairWellChart = () => {
  777. if (!repairWellChartRef.value) return
  778. const option = {
  779. tooltip: {
  780. trigger: 'axis',
  781. axisPointer: {
  782. type: 'shadow'
  783. }
  784. },
  785. legend: {
  786. data: repairWellData.value.series.map((item) => item.name),
  787. top: 10,
  788. textStyle: {
  789. color: '#B6C8DA'
  790. }
  791. },
  792. grid: {
  793. left: '3%',
  794. right: '4%',
  795. bottom: '3%',
  796. containLabel: true
  797. },
  798. xAxis: {
  799. type: 'category',
  800. data: repairWellData.value.xAxis,
  801. axisLabel: {
  802. color: '#B6C8DA'
  803. },
  804. axisLine: {
  805. lineStyle: {
  806. color: '#B6C8DA'
  807. }
  808. }
  809. },
  810. yAxis: {
  811. type: 'value',
  812. name: '完成井数(口)',
  813. axisLabel: {
  814. color: '#B6C8DA',
  815. formatter: '{value}'
  816. },
  817. splitLine: {
  818. show: true, // 显示水平网格线(默认显示)
  819. lineStyle: {
  820. // 水平网格线颜色(可设置为纯色或带透明度的颜色)
  821. color: '#457794', // 浅灰色半透明
  822. // 可选:设置线条类型(实线/虚线/点线)
  823. type: 'dashed'
  824. }
  825. },
  826. axisLine: {
  827. lineStyle: {
  828. color: '#B6C8DA'
  829. }
  830. }
  831. },
  832. series: repairWellData.value.series.map((item) => ({
  833. name: item.name,
  834. type: 'bar',
  835. data: item.data
  836. }))
  837. }
  838. const chart = echarts.init(repairWellChartRef.value)
  839. chart.setOption(option)
  840. window.addEventListener('resize', () => chart.resize())
  841. }
  842. // 新增:初始化钻井完成情况图表
  843. const initDrillingWellChart = () => {
  844. if (!drillingWellChartRef.value) return
  845. const option = {
  846. tooltip: {
  847. trigger: 'axis',
  848. axisPointer: {
  849. type: 'shadow'
  850. }
  851. },
  852. legend: {
  853. data: drillingWellData.value.series.map((item) => item.name),
  854. top: 10,
  855. textStyle: {
  856. color: '#B6C8DA'
  857. }
  858. },
  859. grid: {
  860. left: '3%',
  861. right: '4%',
  862. bottom: '3%',
  863. containLabel: true
  864. },
  865. xAxis: {
  866. type: 'category',
  867. data: drillingWellData.value.xAxis,
  868. axisLabel: {
  869. color: '#B6C8DA',
  870. formatter: (value) => value.split('-').join('/') // 显示为 2023/01
  871. },
  872. axisLine: {
  873. lineStyle: {
  874. color: '#B6C8DA'
  875. }
  876. }
  877. },
  878. yAxis: {
  879. type: 'value',
  880. name: '完成井数(口)',
  881. axisLabel: {
  882. color: '#B6C8DA',
  883. formatter: '{value}'
  884. },
  885. splitLine: {
  886. show: true, // 显示水平网格线(默认显示)
  887. lineStyle: {
  888. // 水平网格线颜色(可设置为纯色或带透明度的颜色)
  889. color: '#457794', // 浅灰色半透明
  890. // 可选:设置线条类型(实线/虚线/点线)
  891. type: 'dashed'
  892. }
  893. },
  894. axisLine: {
  895. lineStyle: {
  896. color: '#B6C8DA'
  897. }
  898. }
  899. },
  900. series: drillingWellData.value.series.map((item) => ({
  901. name: item.name,
  902. type: 'bar',
  903. data: item.data
  904. }))
  905. }
  906. const chart = echarts.init(drillingWellChartRef.value)
  907. chart.setOption(option)
  908. window.addEventListener('resize', () => chart.resize())
  909. }
  910. /** 初始化 */
  911. onMounted(() => {
  912. getStats()
  913. // initChart()
  914. // initTopChart()
  915. // initActiveChart()
  916. // initQxChart()
  917. window.addEventListener('resize', resizeQxChart)
  918. // fetchTop()
  919. window.addEventListener('resize', () => topInstance?.resize())
  920. initRepairWellChart()
  921. initDrillingWellChart()
  922. initDrillingWorkloadChart()
  923. initRepairWorkloadChart()
  924. })
  925. onBeforeUnmount(() => {
  926. chartInstance?.dispose()
  927. window.removeEventListener('resize', () => chartInstance?.resize())
  928. topInstance?.dispose()
  929. window.removeEventListener('resize', handleResize)
  930. qxInstance?.dispose()
  931. window.removeEventListener('resize', resizeQxChart)
  932. echarts.getInstanceByDom(repairWellChartRef.value)?.dispose()
  933. window.removeEventListener('resize', () =>
  934. echarts.getInstanceByDom(repairWellChartRef.value)?.resize()
  935. )
  936. echarts.getInstanceByDom(drillingWellChartRef.value)?.dispose()
  937. window.removeEventListener('resize', () =>
  938. echarts.getInstanceByDom(drillingWellChartRef.value)?.resize()
  939. )
  940. })
  941. </script>
  942. <style lang="scss" scoped>
  943. .page-container {
  944. background-color: #3a6fa3;
  945. min-height: 100vh;
  946. padding: 20px;
  947. }
  948. .summary {
  949. margin-bottom: 20px;
  950. }
  951. ::v-deep .chart-card {
  952. background-color: rgba(0, 0, 0, 0.3);
  953. border-radius: 8px;
  954. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
  955. transition: all 0.3s ease;
  956. border: none;
  957. &:hover {
  958. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  959. }
  960. }
  961. .safety-days-card {
  962. .safety-days-content {
  963. display: flex;
  964. flex-direction: column;
  965. align-items: center;
  966. justify-content: center;
  967. height: 150px;
  968. position: relative;
  969. .days-number {
  970. font-size: 58px;
  971. font-weight: bold;
  972. color: darkorange;
  973. line-height: 1;
  974. transition: all 0.3s ease;
  975. }
  976. .days-number:hover {
  977. transform: scale(1.05);
  978. }
  979. .days-label {
  980. font-size: 20px;
  981. color: white;
  982. margin-top: 8px;
  983. }
  984. .safety-desc {
  985. font-size: 14px;
  986. color: #999;
  987. position: absolute;
  988. bottom: 10px;
  989. text-align: center;
  990. width: 90%;
  991. }
  992. }
  993. }
  994. @media (max-width: 768px) {
  995. .page-container {
  996. padding: 10px;
  997. }
  998. }
  999. ::v-deep .el-card__header {
  1000. border-bottom: none !important;
  1001. padding-bottom: 0;
  1002. }
  1003. </style>