rykb.vue 26 KB

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