inspect.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <template>
  2. <!-- 第一行:统计卡片行 -->
  3. <el-row :gutter="16" class="mb-4">
  4. <el-col :span="6">
  5. <el-card class="stat-card" shadow="never">
  6. <div class="flex flex-col">
  7. <div class="flex justify-between items-center text-gray-400">
  8. <span>昨日工单数量</span>
  9. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  10. </div>
  11. <el-divider />
  12. <div class="flex justify-between items-center mb-1">
  13. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  14. >总数量</span
  15. >
  16. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  17. >未完成</span
  18. >
  19. </div>
  20. <div class="flex justify-between items-center mb-1">
  21. <span class="text-3xl font-bold text-gray-700">
  22. {{ day.total }}
  23. </span>
  24. <span class="text-3xl font-bold text-gray-700">
  25. {{ day.todo }}
  26. </span>
  27. </div>
  28. <!-- <el-divider class="my-2" />-->
  29. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  30. <!-- <span>今日新增</span>-->
  31. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  32. <!-- </div>-->
  33. </div>
  34. </el-card>
  35. </el-col>
  36. <el-col :span="6">
  37. <el-card class="stat-card" shadow="never">
  38. <div class="flex flex-col">
  39. <div class="flex justify-between items-center text-gray-400">
  40. <span>近一周工单数量</span>
  41. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  42. </div>
  43. <el-divider />
  44. <div class="flex justify-between items-center mb-1">
  45. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  46. >总数量</span
  47. >
  48. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  49. >未完成</span
  50. >
  51. </div>
  52. <div class="flex justify-between items-center mb-1">
  53. <span class="text-3xl font-bold text-gray-700">
  54. {{ week.total }}
  55. </span>
  56. <span class="text-3xl font-bold text-gray-700">
  57. {{ week.todo }}
  58. </span>
  59. </div>
  60. <!-- <el-divider class="my-2" />-->
  61. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  62. <!-- <span>今日新增</span>-->
  63. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  64. <!-- </div>-->
  65. </div>
  66. </el-card>
  67. </el-col>
  68. <el-col :span="6">
  69. <el-card class="stat-card" shadow="never">
  70. <div class="flex flex-col">
  71. <div class="flex justify-between items-center text-gray-400">
  72. <span>近一月工单数量</span>
  73. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  74. </div>
  75. <el-divider />
  76. <div class="flex justify-between items-center mb-1">
  77. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  78. >总数量</span
  79. >
  80. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  81. >未完成</span
  82. >
  83. </div>
  84. <div class="flex justify-between items-center mb-1">
  85. <span class="text-3xl font-bold text-gray-700">
  86. {{ month.total }}
  87. </span>
  88. <span class="text-3xl font-bold text-gray-700">
  89. {{ month.todo }}
  90. </span>
  91. </div>
  92. <!-- <el-divider class="my-2" />-->
  93. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  94. <!-- <span>今日新增</span>-->
  95. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  96. <!-- </div>-->
  97. </div>
  98. </el-card>
  99. </el-col>
  100. <el-col :span="6">
  101. <el-card class="stat-card" shadow="never">
  102. <div class="flex flex-col">
  103. <div class="flex justify-between items-center text-gray-400">
  104. <span>工单数量</span>
  105. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  106. </div>
  107. <el-divider />
  108. <div class="flex justify-between items-center mb-1">
  109. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  110. >总数量</span
  111. >
  112. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  113. >未完成</span
  114. >
  115. </div>
  116. <div class="flex justify-between items-center mb-1">
  117. <span class="text-3xl font-bold text-gray-700">
  118. {{ total.total }}
  119. </span>
  120. <span class="text-3xl font-bold text-gray-700">
  121. {{ total.todo }}
  122. </span>
  123. </div>
  124. <!-- <el-divider class="my-2" />-->
  125. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  126. <!-- <span>今日新增</span>-->
  127. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  128. <!-- </div>-->
  129. </div>
  130. </el-card>
  131. </el-col>
  132. </el-row>
  133. <!-- 第二行:图表行 -->
  134. <el-row :gutter="16" class="mb-4">
  135. <el-col :span="12">
  136. <el-card class="chart-card" shadow="never">
  137. <template #header>
  138. <div class="flex items-center">
  139. <span class="text-base font-medium text-gray-600">设备状态统计</span>
  140. </div>
  141. </template>
  142. <el-row class="h-[220px]">
  143. <el-col :span="12" class="flex flex-col items-center">
  144. <div ref="writeTodayChartRef" class="h-[160px] w-full"></div>
  145. <div class="text-center mt-2">
  146. <span class="text-sm text-gray-600">未填写</span>
  147. </div>
  148. </el-col>
  149. <el-col :span="12" class="flex flex-col items-center">
  150. <div ref="finishedTodayChartRef" class="h-[160px] w-full"></div>
  151. <div class="text-center mt-2">
  152. <span class="text-sm text-gray-600">已填写</span>
  153. </div>
  154. </el-col>
  155. </el-row>
  156. </el-card>
  157. </el-col>
  158. <el-col :span="12">
  159. <el-card class="chart-card" shadow="never">
  160. <template #header>
  161. <div class="flex items-center">
  162. <span class="text-base font-medium text-gray-600">工单状态统计</span>
  163. </div>
  164. </template>
  165. <el-row class="h-[220px]">
  166. <el-col :span="12" class="flex flex-col items-center">
  167. <div ref="writeChartRef" class="h-[160px] w-full"></div>
  168. <div class="text-center mt-2">
  169. <span class="text-sm text-gray-600">待执行</span>
  170. </div>
  171. </el-col>
  172. <el-col :span="12" class="flex flex-col items-center">
  173. <div ref="finishedChartRef" class="h-[160px] w-full"></div>
  174. <div class="text-center mt-2">
  175. <span class="text-sm text-gray-600">已执行</span>
  176. </div>
  177. </el-col>
  178. </el-row>
  179. </el-card>
  180. </el-col>
  181. </el-row>
  182. <!-- 第三行:消息统计行 -->
  183. <el-row>
  184. <el-col :span="24">
  185. <el-card class="chart-card" shadow="never">
  186. <template #header>
  187. <div class="flex items-center justify-between">
  188. <span class="text-base font-medium text-gray-600">近一年数量统计</span>
  189. </div>
  190. </template>
  191. <div ref="chartContainer" class="h-[300px]"></div>
  192. </el-card>
  193. </el-col>
  194. </el-row>
  195. <!-- TODO 第四行:地图 -->
  196. </template>
  197. <script setup lang="ts" name="Index">
  198. import * as echarts from 'echarts/core'
  199. import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
  200. import {
  201. GridComponent,
  202. LegendComponent,
  203. TitleComponent,
  204. ToolboxComponent,
  205. TooltipComponent
  206. } from 'echarts/components'
  207. import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
  208. import { LabelLayout, UniversalTransition } from 'echarts/features'
  209. import { CanvasRenderer } from 'echarts/renderers'
  210. import {
  211. IotStatisticsDeviceMessageSummaryRespVO,
  212. IotStatisticsSummaryRespVO
  213. } from '@/api/iot/statistics'
  214. const { currentRoute, push } = useRouter()
  215. import { formatDate } from '@/utils/formatTime'
  216. import { IotStatApi } from '@/api/pms/stat'
  217. // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
  218. /** IoT 首页 */
  219. defineOptions({ name: 'IoTHome' })
  220. // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
  221. echarts.use([
  222. TooltipComponent,
  223. LegendComponent,
  224. PieChart,
  225. CanvasRenderer,
  226. LabelLayout,
  227. TitleComponent,
  228. ToolboxComponent,
  229. GridComponent,
  230. LineChart,
  231. UniversalTransition,
  232. GaugeChart,
  233. BarChart
  234. ])
  235. const timeRange = ref('7d') // 修改默认选择为近一周
  236. const dateRange = ref<[Date, Date] | null>(null)
  237. const queryParams = reactive({
  238. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  239. endTime: Date.now() // 设置默认结束时间为当前时间
  240. })
  241. // const deviceCountChartRef = ref() // 设备数量统计的图表
  242. const reportingChartRef = ref() // 在线设备统计的图表
  243. const dealFinishedChartRef = ref() // 离线设备统计的图表
  244. const transOrderChartRef = ref() // 待激活设备统计的图表
  245. const orderFinishChartRef = ref()
  246. const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
  247. const writeChartRef = ref() // 上下行消息量统计的图表
  248. const finishedChartRef = ref() // 上下行消息量统计的图表
  249. const writeTodayChartRef = ref() // 上下行消息量统计的图表
  250. const finishedTodayChartRef = ref() // 上下行消息量统计的图表
  251. // 基础统计数据
  252. // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
  253. const statsData = ref<IotStatisticsSummaryRespVO>({
  254. productCategoryCount: 0,
  255. productCount: 0,
  256. deviceCount: 0,
  257. deviceMessageCount: 0,
  258. productCategoryTodayCount: 0,
  259. productTodayCount: 0,
  260. deviceTodayCount: 0,
  261. deviceMessageTodayCount: 0,
  262. deviceOnlineCount: 0,
  263. deviceOfflineCount: 0,
  264. deviceInactiveCount: 0,
  265. productCategoryDeviceCounts: {}
  266. })
  267. const day = ref({
  268. total: undefined,
  269. todo: undefined
  270. })
  271. const week = ref({
  272. total: undefined,
  273. todo: undefined
  274. })
  275. const month = ref({
  276. total: undefined,
  277. todo: undefined
  278. })
  279. const total = ref({
  280. total: undefined,
  281. todo: undefined
  282. })
  283. const status = ref({
  284. finished: 0,
  285. todo: 0
  286. })
  287. const todayStatus = ref({
  288. finished: 0,
  289. todo: 0
  290. })
  291. // 消息统计数据
  292. const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
  293. upstreamCounts: {},
  294. downstreamCounts: {}
  295. })
  296. /** 处理快捷时间范围选择 */
  297. const handleTimeRangeChange = (timeRange: string) => {
  298. const now = Date.now()
  299. let startTime: number
  300. // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
  301. switch (timeRange) {
  302. case '1h':
  303. startTime = now - 60 * 60 * 1000
  304. break
  305. case '24h':
  306. startTime = now - 24 * 60 * 60 * 1000
  307. break
  308. case '7d':
  309. startTime = now - 7 * 24 * 60 * 60 * 1000
  310. break
  311. default:
  312. return
  313. }
  314. // 清空日期选择器
  315. dateRange.value = null
  316. // 更新查询参数
  317. queryParams.startTime = startTime
  318. queryParams.endTime = now
  319. // 重新获取数据
  320. getStats()
  321. }
  322. /** 处理自定义日期范围选择 */
  323. const handleDateRangeChange = (value: [Date, Date] | null) => {
  324. if (value) {
  325. // 清空快捷选项
  326. timeRange.value = ''
  327. // 更新查询参数
  328. queryParams.startTime = value[0].getTime()
  329. queryParams.endTime = value[1].getTime()
  330. // 重新获取数据
  331. getStats()
  332. }
  333. }
  334. /** 获取统计数据 */
  335. const getStats = async () => {
  336. // 获取基础统计数据
  337. IotStatApi.getInspectDay().then((res) => {
  338. day.value = res
  339. })
  340. IotStatApi.getInspectWeek().then((res) => {
  341. week.value = res
  342. })
  343. IotStatApi.getInspectMonth().then((res) => {
  344. month.value = res
  345. })
  346. IotStatApi.getInspectTotal().then((res) => {
  347. total.value = res
  348. })
  349. IotStatApi.getInspectStatus().then((res) => {
  350. status.value = res
  351. initCharts()
  352. })
  353. IotStatApi.getInspectDeviceStatus().then((res) => {
  354. todayStatus.value = res
  355. initCharts()
  356. })
  357. // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
  358. //
  359. // // 获取消息统计数据
  360. // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
  361. // 初始化图表
  362. }
  363. /** 初始化图表 */
  364. const initCharts = () => {
  365. //待执行
  366. initGaugeChart(
  367. writeTodayChartRef.value,
  368. todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
  369. '#05b',
  370. '设备待执行'
  371. )
  372. //已执行
  373. initGaugeChart(
  374. finishedTodayChartRef.value,
  375. todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
  376. '#f50',
  377. '设备已执行'
  378. )
  379. // 工单待执行
  380. initGaugeChart(
  381. writeChartRef.value,
  382. status.value.todo === undefined ? 0 : status.value.todo,
  383. '#05b',
  384. '工单待执行'
  385. )
  386. //工单已执行
  387. initGaugeChart(
  388. finishedChartRef.value,
  389. status.value.finished === undefined ? 0 : status.value.finished,
  390. '#f50',
  391. '工单已执行'
  392. )
  393. // 消息量统计
  394. //initMessageChart()
  395. }
  396. /** 初始化仪表盘图表 */
  397. const initGaugeChart = (el: any, value: number, color: string, type: string) => {
  398. const chart = echarts.init(el);
  399. chart.setOption({
  400. series: [
  401. {
  402. type: 'gauge',
  403. startAngle: 360,
  404. endAngle: 0,
  405. min: 0,
  406. max: value, // 使用设备总数作为最大值
  407. progress: {
  408. show: true,
  409. width: 22,
  410. itemStyle: {
  411. color: color
  412. }
  413. },
  414. axisLine: {
  415. lineStyle: {
  416. width: 22,
  417. color: [[1, '#E5E7EB']]
  418. }
  419. },
  420. axisTick: { show: false },
  421. splitLine: { show: false },
  422. axisLabel: { show: false },
  423. pointer: { show: false },
  424. anchor: { show: false },
  425. title: { show: false },
  426. detail: {
  427. valueAnimation: true,
  428. fontSize: 24,
  429. fontWeight: 'bold',
  430. fontFamily: 'Inter, sans-serif',
  431. color: color,
  432. offsetCenter: [0, '0'],
  433. formatter: (value: number) => {
  434. return `${value} `
  435. }
  436. },
  437. data: [{ value: value, type: type }]
  438. }
  439. ]
  440. });
  441. // 添加点击事件监听器
  442. chart.on('click', (params) => {
  443. console.log('点击的数据值为:', params.value);
  444. console.log('点击的数据类型为:', params.data.type);
  445. let status = '';
  446. if (params.data.type === '工单待执行') {
  447. status = 'todo'
  448. push({ name: 'IotInspectOrdere', params: { status} })
  449. } else if (params.data.type === '工单已执行') {
  450. status = 'finished'
  451. push({name: 'IotInspectOrdere', params: { status} })
  452. } else if (params.data.type==='设备待执行') {
  453. status = 'todo'
  454. push({name:'IotInspectOrderDetailStat', params:{status}})
  455. } else if (params.data.type==='设备已执行'){
  456. status = 'finished'
  457. push({name:'IotInspectOrderDetailStat', params:{status}})
  458. }
  459. });
  460. }
  461. /** 初始化消息统计图表 */
  462. const initMessageChart = () => {
  463. // 获取所有时间戳并排序
  464. // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
  465. const timestamps = Array.from(
  466. new Set([
  467. ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
  468. ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
  469. ])
  470. ).sort((a, b) => a - b) // 确保时间戳从小到大排序
  471. // 准备数据
  472. const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
  473. const upData = timestamps.map((ts) => {
  474. const item = messageStats.value.upstreamCounts.find(
  475. (count) => Number(Object.keys(count)[0]) === ts
  476. )
  477. return item ? Object.values(item)[0] : 0
  478. })
  479. const downData = timestamps.map((ts) => {
  480. const item = messageStats.value.downstreamCounts.find(
  481. (count) => Number(Object.keys(count)[0]) === ts
  482. )
  483. return item ? Object.values(item)[0] : 0
  484. })
  485. // 配置图表
  486. echarts.init(deviceMessageCountChartRef.value).setOption({
  487. tooltip: {
  488. trigger: 'axis',
  489. backgroundColor: 'rgba(255, 255, 255, 0.9)',
  490. borderColor: '#E5E7EB',
  491. textStyle: {
  492. color: '#374151'
  493. }
  494. },
  495. legend: {
  496. data: ['上行消息量', '下行消息量'],
  497. textStyle: {
  498. color: '#374151',
  499. fontWeight: 500
  500. }
  501. },
  502. grid: {
  503. left: '3%',
  504. right: '4%',
  505. bottom: '3%',
  506. containLabel: true
  507. },
  508. xAxis: {
  509. type: 'category',
  510. boundaryGap: false,
  511. data: xdata,
  512. axisLine: {
  513. lineStyle: {
  514. color: '#E5E7EB'
  515. }
  516. },
  517. axisLabel: {
  518. color: '#6B7280'
  519. }
  520. },
  521. yAxis: {
  522. type: 'value',
  523. axisLine: {
  524. lineStyle: {
  525. color: '#E5E7EB'
  526. }
  527. },
  528. axisLabel: {
  529. color: '#6B7280'
  530. },
  531. splitLine: {
  532. lineStyle: {
  533. color: '#F3F4F6'
  534. }
  535. }
  536. },
  537. series: [
  538. {
  539. name: '上行消息量',
  540. type: 'line',
  541. smooth: true, // 添加平滑曲线
  542. data: upData,
  543. itemStyle: {
  544. color: '#3B82F6'
  545. },
  546. lineStyle: {
  547. width: 2
  548. },
  549. areaStyle: {
  550. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  551. { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
  552. { offset: 1, color: 'rgba(59, 130, 246, 0)' }
  553. ])
  554. }
  555. },
  556. {
  557. name: '下行消息量',
  558. type: 'line',
  559. smooth: true, // 添加平滑曲线
  560. data: downData,
  561. itemStyle: {
  562. color: '#10B981'
  563. },
  564. lineStyle: {
  565. width: 2
  566. },
  567. areaStyle: {
  568. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  569. { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
  570. { offset: 1, color: 'rgba(16, 185, 129, 0)' }
  571. ])
  572. }
  573. }
  574. ]
  575. })
  576. }
  577. const chartContainer = ref(null)
  578. let chartInstance = null
  579. // 生成过去12个月份的标签 (格式: YYYY-MM)
  580. const generateMonthLabels = () => {
  581. const months = []
  582. const date = new Date()
  583. for (let i = 11; i >= 0; i--) {
  584. const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
  585. const year = tempDate.getFullYear()
  586. const month = String(tempDate.getMonth() + 1).padStart(2, '0')
  587. months.push(`${year}-${month}`)
  588. }
  589. return months
  590. }
  591. // 模拟数据获取
  592. const fetchChartData = async () => {
  593. // 模拟异步请求
  594. return new Promise((resolve) => {
  595. setTimeout(() => {
  596. resolve({
  597. months: generateMonthLabels(),
  598. faults: [20,30,100,40,20,50,70,80,60,90,100,100],
  599. repairs: [10,30,90,30,10,20,60,50,22,34,70,85],
  600. })
  601. }, 300)
  602. })
  603. }
  604. // 初始化图表配置
  605. const initChart = async () => {
  606. if (!chartContainer.value) return
  607. // 获取数据
  608. const { months, faults, repairs } = await fetchChartData()
  609. // ECharts配置
  610. const option = {
  611. tooltip: {
  612. trigger: 'axis',
  613. axisPointer: {
  614. type: 'shadow'
  615. },
  616. formatter: (params) => {
  617. return `${params[0].axisValue}<br/>
  618. ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
  619. }
  620. },
  621. legend: {
  622. data: ['巡检工单数量'],
  623. top: 25
  624. },
  625. grid: {
  626. left: '3%',
  627. right: '4%',
  628. bottom: '3%',
  629. containLabel: true
  630. },
  631. xAxis: {
  632. type: 'category',
  633. data: months,
  634. axisLabel: {
  635. rotate: 45,
  636. margin: 15
  637. }
  638. },
  639. yAxis: {
  640. type: 'value',
  641. axisLabel: {
  642. formatter: (value) => Math.floor(value).toString()
  643. }
  644. },
  645. series: [
  646. {
  647. name: '巡检工单数量',
  648. type: 'bar',
  649. itemStyle: {
  650. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  651. { offset: 0, color: '#2196df' },
  652. { offset: 1, color: '#2196df' }
  653. ])
  654. },
  655. emphasis: {
  656. focus: 'series'
  657. },
  658. data: repairs
  659. }
  660. ]
  661. }
  662. // 初始化图表
  663. chartInstance = echarts.init(chartContainer.value)
  664. chartInstance.setOption(option)
  665. // 窗口缩放监听
  666. window.addEventListener('resize', handleResize)
  667. handleResize()
  668. }
  669. // 自适应调整
  670. const handleResize = () => {
  671. chartInstance?.resize()
  672. }
  673. /** 初始化 */
  674. onMounted(() => {
  675. getStats()
  676. initChart()
  677. })
  678. onUnmounted(() => {
  679. chartInstance?.dispose()
  680. window.removeEventListener('resize', handleResize)
  681. })
  682. </script>
  683. <style lang="scss" scoped>
  684. </style>