rykb.vue 30 KB

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