rhkb.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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="7">
  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-[290px]"></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-[290px]"></div>
  98. </el-card>
  99. </el-col>
  100. <el-col :span="10">
  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="chartContainer" class="h-[290px]"></div>
  108. </el-card>
  109. </el-col>
  110. </el-row>
  111. <!-- 第三行:消息统计行 -->
  112. <el-row :gutter="16" class="mb-1">
  113. <el-col :span="7">
  114. <el-card class="chart-card" shadow="never">
  115. <template #header>
  116. <div class="flex items-center">
  117. <span class="text-base font-medium text-gray-600">近7天物料消耗TOP5</span>
  118. </div>
  119. </template>
  120. <div ref="materialChartRef" class="h-[320px]"></div>
  121. </el-card>
  122. </el-col>
  123. <el-col :span="17">
  124. <el-card class="chart-card" shadow="never">
  125. <template #header>
  126. <div class="flex items-center justify-between">
  127. <span class="text-base font-medium text-gray-600">工单数量情况</span>
  128. </div>
  129. </template>
  130. <div ref="qxRef" class="h-[320px]"></div>
  131. </el-card>
  132. </el-col>
  133. </el-row>
  134. <!-- TODO 第四行:地图 -->
  135. </template>
  136. <script setup lang="ts" name="Index">
  137. import * as echarts from 'echarts/core'
  138. import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
  139. import {
  140. GridComponent,
  141. LegendComponent,
  142. TitleComponent,
  143. ToolboxComponent,
  144. TooltipComponent
  145. } from 'echarts/components'
  146. import { LabelLayout, UniversalTransition } from 'echarts/features'
  147. import { CanvasRenderer } from 'echarts/renderers'
  148. import { useElementSize } from '@vueuse/core'
  149. import {
  150. IotStatisticsDeviceMessageSummaryRespVO,
  151. IotStatisticsSummaryRespVO
  152. } from '@/api/iot/statistics'
  153. import { formatDate } from '@/utils/formatTime'
  154. import { IotStatApi } from '@/api/pms/stat'
  155. import SummaryCard from "@/components/SummaryCard/index.vue";
  156. import {reactive, ref} from "vue";
  157. import {useUserStore} from "@/store/modules/user";
  158. // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
  159. /** IoT 首页 */
  160. defineOptions({ name: 'IotRhStat' })
  161. // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
  162. echarts.use([
  163. TooltipComponent,
  164. LegendComponent,
  165. PieChart,
  166. CanvasRenderer,
  167. LabelLayout,
  168. TitleComponent,
  169. ToolboxComponent,
  170. GridComponent,
  171. LineChart,
  172. UniversalTransition,
  173. GaugeChart,
  174. BarChart
  175. ])
  176. const dateRange = ref<[Date, Date] | null>(null)
  177. const by = ref({
  178. todo: undefined,
  179. finished: undefined
  180. })
  181. const fill = ref({
  182. filledCount: undefined,
  183. unfilledCount: undefined
  184. })
  185. const inspectt = ref({
  186. finished: 0,
  187. todo: 0
  188. })
  189. const queryParams = reactive({
  190. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  191. endTime: Date.now() // 设置默认结束时间为当前时间
  192. })
  193. const backendData = ref([])
  194. const statusChartRef = ref() // 设备数量统计的图表
  195. const materialChartRef = ref() // 设备数量统计的图表
  196. // 基础统计数据
  197. // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
  198. const statsData = ref<IotStatisticsSummaryRespVO>({
  199. productCategoryCount: 0,
  200. productCount: 0,
  201. deviceCount: 0,
  202. deviceMessageCount: 0,
  203. productCategoryTodayCount: 0,
  204. productTodayCount: 0,
  205. deviceTodayCount: 0,
  206. deviceMessageTodayCount: 0,
  207. deviceOnlineCount: 0,
  208. deviceOfflineCount: 0,
  209. deviceInactiveCount: 0,
  210. productCategoryDeviceCounts: {}
  211. })
  212. const device = ref({
  213. total: undefined,
  214. today: undefined
  215. })
  216. const maintain = ref({
  217. total: undefined,
  218. today: undefined
  219. })
  220. const work = ref({
  221. total: undefined,
  222. today: undefined
  223. })
  224. const inspect = ref({
  225. total: undefined,
  226. today: undefined
  227. })
  228. const status = ref({
  229. finished: 0,
  230. todo: 0
  231. })
  232. const todayStatus = ref({
  233. finished: 0,
  234. todo: 0
  235. })
  236. const typeData = ref({})
  237. const materialData = ref({})
  238. const orderSevenData = ref({})
  239. const safe = ref()
  240. /** 获取统计数据 */
  241. const getStats = () => {
  242. initYwcbChart()
  243. // 获取基础统计数据
  244. IotStatApi.getDeviceCount("rh").then((res) => {
  245. device.value = res
  246. })
  247. IotStatApi.getMaintainCount("rh").then((res) => {
  248. maintain.value = res
  249. })
  250. IotStatApi.getMainWorkCount().then((res) => {
  251. work.value = res
  252. })
  253. IotStatApi.getInspectCount().then((res) => {
  254. inspect.value = res
  255. })
  256. IotStatApi.getMaintenanceStatus().then((res) => {
  257. status.value = res
  258. // initCharts()
  259. })
  260. IotStatApi.getMaintenanceTodayStatus().then((res) => {
  261. todayStatus.value = res
  262. initTopChart()
  263. })
  264. IotStatApi.getDeviceStatusCount('rh').then((res) => {
  265. typeData.value = res
  266. initDeviceStatusCharts()
  267. })
  268. IotStatApi.getSafeCount().then((res) => {
  269. safe.value = res
  270. })
  271. IotStatApi.getMaterial().then((res) => {
  272. materialData.value = res
  273. initMaterials()
  274. })
  275. IotStatApi.getOrderSeven('rh').then((res) => {
  276. orderSevenData.value = res
  277. initQxChart();
  278. })
  279. IotStatApi.getMaintenanceStatus("rh").then((res) => {
  280. by.value = res
  281. })
  282. const fillQueryParams = reactive({
  283. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  284. endTime: Date.now(), // 设置默认结束时间为当前时间
  285. createTime: [],
  286. deptId: null, // 选中的部门ID
  287. status: null // 填写状态
  288. })
  289. IotStatApi.getInspectStatuss(fillQueryParams,'rh').then((res) => {
  290. inspectt.value = res
  291. })
  292. fillQueryParams.deptId = useUserStore().getUser.deptId
  293. IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
  294. fill.value = res.totalList[0] || []
  295. })
  296. }
  297. const initMaterials = () => {
  298. echarts.init(materialChartRef.value).setOption({
  299. tooltip: {
  300. trigger: 'item'
  301. },
  302. legend: {
  303. // top: '5%',
  304. // right: '10%',
  305. // align: 'left',
  306. // orient: 'vertical',
  307. // icon: 'circle'
  308. orient: 'horizontal', // 水平排列图例项
  309. bottom: '0%', // 放置在底部
  310. icon: 'circle'
  311. },
  312. series: [
  313. {
  314. name: '',
  315. type: 'pie',
  316. radius: ['50%', '80%'],
  317. avoidLabelOverlap: false,
  318. center: ['50%', '44%'],
  319. label: {
  320. show: false,
  321. position: 'outside'
  322. },
  323. emphasis: {
  324. label: {
  325. show: true,
  326. fontSize: 15,
  327. fontWeight: 'bold'
  328. }
  329. },
  330. labelLine: {
  331. show: false
  332. },
  333. data: materialData.value
  334. }
  335. ]
  336. })
  337. }
  338. /** 初始化图表 */
  339. const initDeviceStatusCharts = () => {
  340. // 设备数量统计
  341. echarts.init(statusChartRef.value).setOption({
  342. tooltip: {
  343. trigger: 'item'
  344. },
  345. legend: {
  346. orient: 'horizontal', // 水平排列图例项
  347. bottom: '0%', // 放置在底部
  348. icon: 'circle'
  349. },
  350. series: [
  351. {
  352. name: '',
  353. type: 'pie',
  354. radius: ['50%', '80%'],
  355. avoidLabelOverlap: false,
  356. center: ['50%', '44%'],
  357. label: {
  358. show: false,
  359. position: 'outside'
  360. },
  361. emphasis: {
  362. label: {
  363. show: true,
  364. fontSize: 15,
  365. fontWeight: 'bold'
  366. }
  367. },
  368. labelLine: {
  369. show: false
  370. },
  371. data: typeData.value
  372. }
  373. ]
  374. })
  375. }
  376. /** 初始化消息统计图表 */
  377. const chartContainer = ref(null)
  378. let chartInstance = null
  379. // 生成过去12个月份的标签 (格式: YYYY-MM)
  380. const generateMonthLabels = () => {
  381. const months = []
  382. const date = new Date()
  383. for (let i = 11; i >= 0; i--) {
  384. const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
  385. const year = tempDate.getFullYear()
  386. const month = String(tempDate.getMonth() + 1).padStart(2, '0')
  387. months.push(`${year}-${month}`)
  388. }
  389. return months
  390. }
  391. // 模拟数据获取
  392. const fetchChartData = async () => {
  393. // 模拟异步请求
  394. return new Promise((resolve) => {
  395. setTimeout(() => {
  396. resolve({
  397. months: ['空压机','增压机','提纯撬'],
  398. repairs: [10, 30, 90 ]
  399. })
  400. }, 300)
  401. })
  402. }
  403. // 初始化图表配置
  404. const initYwcbChart = async () => {
  405. if (!chartContainer.value) return
  406. // 获取数据
  407. const { months, faults, repairs } = await fetchChartData()
  408. // const months = ['空压机','增压机','提纯撬']
  409. // ECharts配置
  410. const option = {
  411. tooltip: {
  412. trigger: 'axis',
  413. axisPointer: {
  414. type: 'shadow'
  415. },
  416. formatter: (params) => {
  417. return `${params[0].axisValue}<br/>
  418. ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
  419. }
  420. },
  421. legend: {
  422. data: ['当日运维成本'],
  423. top: 1
  424. },
  425. grid: {
  426. left: '3%',
  427. right: '4%',
  428. bottom: '1%',
  429. containLabel: true
  430. },
  431. xAxis: {
  432. type: 'category',
  433. data: months,
  434. axisLabel: {
  435. rotate: 45,
  436. margin: 15
  437. }
  438. },
  439. yAxis: {
  440. type: 'value',
  441. axisLabel: {
  442. formatter: (value) => Math.floor(value).toString()
  443. }
  444. },
  445. series: [
  446. {
  447. name: '当日运维成本',
  448. type: 'bar',
  449. itemStyle: {
  450. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  451. { offset: 0, color: '#f69606' },
  452. ])
  453. },
  454. emphasis: {
  455. focus: 'series'
  456. },
  457. data: repairs
  458. }
  459. ]
  460. }
  461. // 初始化图表
  462. chartInstance = echarts.init(chartContainer.value)
  463. chartInstance.setOption(option)
  464. // 窗口缩放监听
  465. window.addEventListener('resize', handleResize)
  466. handleResize()
  467. }
  468. // 自适应调整
  469. const handleResize = () => {
  470. chartInstance?.resize()
  471. }
  472. const topContainer = ref(null)
  473. let topInstance = null
  474. // 响应式容器尺寸
  475. const { width, height } = useElementSize(topContainer)
  476. // 处理数据(排序+限制5条)
  477. const processedData = () => {
  478. const data = IotStatApi.getDeviceTypeCount()
  479. backendData.value = data
  480. return [...backendData.value].sort((a, b) => a.value - b.value)
  481. }
  482. const fetchTop = () => {
  483. IotStatApi.getDeviceTypeCount().then((res) => {
  484. backendData.value = res
  485. })
  486. }
  487. // 初始化图表配置
  488. const getTopOption = () => {
  489. // backendData.value = data
  490. const data = backendData.value.sort((a, b) => a.value - b.value)
  491. return {
  492. tooltip: {
  493. trigger: 'axis',
  494. axisPointer: { type: 'shadow' },
  495. formatter: (params) => {
  496. const item = params[0]
  497. return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
  498. }
  499. },
  500. grid: {
  501. height: '200px',
  502. left: '6%',
  503. right: '6%',
  504. bottom: '18%',
  505. containLabel: true
  506. },
  507. xAxis: {
  508. type: 'value',
  509. axisLabel: {
  510. formatter: (value) => {
  511. if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
  512. return value.toLocaleString()
  513. }
  514. }
  515. },
  516. yAxis: {
  517. type: 'category',
  518. data: data.map((item) => item.category),
  519. axisTick: { show: false },
  520. axisLabel: {}
  521. },
  522. series: [
  523. {
  524. type: 'bar',
  525. data: data.map((item) => item.value),
  526. itemStyle: {
  527. color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
  528. { offset: 0, color: '#83bff6' },
  529. { offset: 0.7, color: '#188df0' },
  530. { offset: 1, color: '#188df0' }
  531. ]),
  532. borderRadius: [0, 8, 8, 0]
  533. },
  534. label: {
  535. show: true,
  536. position: 'right',
  537. formatter: '{@value}',
  538. color: '#333',
  539. fontWeight: 'bold'
  540. },
  541. emphasis: {
  542. itemStyle: {
  543. shadowBlur: 10,
  544. shadowColor: 'rgba(0, 0, 0, 0.5)'
  545. }
  546. }
  547. }
  548. ]
  549. }
  550. }
  551. // 初始化图表
  552. const initTopChart = async () => {
  553. await IotStatApi.getDeviceTypeCount('rh').then((res) => {
  554. backendData.value = res
  555. })
  556. if (!topContainer.value) return
  557. topInstance = echarts.init(topContainer.value)
  558. updateTopChart()
  559. }
  560. // 更新图表
  561. const updateTopChart = () => {
  562. if (!topInstance) return
  563. topInstance.setOption(getTopOption())
  564. }
  565. // 自适应调整
  566. watch([width, height], () => {
  567. topInstance?.resize()
  568. })
  569. // 监听数据变化
  570. watch(
  571. backendData,
  572. () => {
  573. updateTopChart()
  574. },
  575. { deep: true }
  576. )
  577. const activeDom = ref(null)
  578. let activeInstance = null
  579. const activeData = ref([])
  580. const initActiveChart = async () => {
  581. if (!activeDom.value) return
  582. activeData.value = await IotStatApi.getDeptCount()
  583. activeInstance = echarts.init(activeDom.value)
  584. const option = {
  585. tooltip: {
  586. trigger: 'axis',
  587. axisPointer: { type: 'shadow' }, //:ml-citation{ref="1,7" data="citationList"}
  588. formatter: (params) => `
  589. ${params[0].name}<br/>
  590. ${params[0].marker} 总人数: ${params[0].value}<br/>
  591. ${params[1].marker} 活跃人数: ${params[1].value}
  592. `
  593. },
  594. legend: {
  595. data: ['总人数', '活跃人数'],
  596. top: 30
  597. },
  598. grid: {
  599. left: '3%',
  600. right: '4%',
  601. bottom: '3%',
  602. containLabel: true //:ml-citation{ref="2,7" data="citationList"}
  603. },
  604. xAxis: {
  605. type: 'category',
  606. data: activeData.value.map((item) => item.department),
  607. axisLabel: {
  608. interval: 0,
  609. rotate: 0 //:ml-citation{ref="5" data="citationList"}
  610. }
  611. },
  612. yAxis: {
  613. type: 'value',
  614. name: '人数',
  615. splitLine: {
  616. show: true,
  617. lineStyle: { type: 'dashed' }
  618. }
  619. },
  620. series: [
  621. {
  622. name: '总人数',
  623. type: 'bar',
  624. data: activeData.value.map((item) => item.total),
  625. itemStyle: {
  626. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  627. { offset: 0, color: '#5470c6' },
  628. { offset: 1, color: '#83bff6' }
  629. ])
  630. },
  631. barWidth: 30
  632. },
  633. {
  634. name: '活跃人数',
  635. type: 'bar',
  636. data: activeData.value.map((item) => item.active),
  637. itemStyle: {
  638. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  639. { offset: 0, color: '#91cc75' },
  640. { offset: 1, color: '#e6f4d2' }
  641. ])
  642. },
  643. barWidth: 30
  644. }
  645. ]
  646. }
  647. activeInstance.setOption(option)
  648. }
  649. const qxRef = ref(null)
  650. let qxInstance = null
  651. // 生成近12个月份 (包含当年和去年)
  652. const generateMonths = () => {
  653. const months = []
  654. const date = new Date()
  655. date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
  656. for (let i = 0; i < 12; i++) {
  657. date.setMonth(date.getMonth() - 1)
  658. months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
  659. }
  660. return months.reverse()
  661. }
  662. const initQxChart = () => {
  663. if (!qxRef.value) return
  664. qxInstance = echarts.init(qxRef.value)
  665. debugger
  666. const option = {
  667. tooltip: {
  668. trigger: 'axis',
  669. axisPointer: {
  670. type: 'cross',
  671. label: {
  672. backgroundColor: '#6a7985'
  673. }
  674. }
  675. },
  676. legend: {
  677. data: orderSevenData.value.series.map((item) => item.name),
  678. top: 30
  679. },
  680. grid: {
  681. left: '3%',
  682. right: '4%',
  683. bottom: '3%',
  684. containLabel: true
  685. },
  686. xAxis: {
  687. type: 'category',
  688. boundaryGap: false,
  689. data: orderSevenData.value.xAxis,
  690. axisLabel: {
  691. formatter: (value) => value.split('-').join('/') // 显示为 2023/01
  692. }
  693. },
  694. // yAxis: {
  695. // type: 'value',
  696. // axisLabel: {
  697. // formatter: '{value}'
  698. // }
  699. // },
  700. yAxis: [
  701. {
  702. type: 'value',
  703. axisLabel: {
  704. formatter: '{value}'
  705. },
  706. position: 'left' // 左侧 Y 轴
  707. },
  708. {
  709. type: 'value',
  710. axisLabel: {
  711. formatter: '{value}'
  712. },
  713. position: 'right', // 右侧 Y 轴
  714. splitLine: {
  715. show: false // 隐藏右侧 Y 轴的分割线
  716. }
  717. }
  718. ],
  719. // series: orderSevenData.value.series.map((item, index) => ({
  720. // name: item.name,
  721. // type: 'line',
  722. // smooth: true,
  723. // symbol: 'circle',
  724. // symbolSize: 8,
  725. // itemStyle: {
  726. // color: ['#5470c6', '#f1d209', '#e14f0f','#09a134'][index] // 蓝、绿、黄
  727. // },
  728. // areaStyle: {
  729. // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  730. // { offset: 0, color: 'rgba(84,112,198,0.4)' },
  731. // { offset: 1, color: 'rgba(84,112,198,0.1)' }
  732. // ])
  733. // },
  734. // data: item.data
  735. // }))
  736. series: orderSevenData.value.series.map((item, index) => {
  737. // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
  738. const yAxisIndex = index < 2 ? 0 : 1;
  739. return {
  740. name: item.name,
  741. type: 'line',
  742. smooth: true,
  743. symbol: 'circle',
  744. symbolSize: 8,
  745. itemStyle: {
  746. color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
  747. },
  748. areaStyle: {
  749. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  750. { offset: 0, color: 'rgba(84,112,198,0.4)' },
  751. { offset: 1, color: 'rgba(84,112,198,0.1)' }
  752. ])
  753. },
  754. data: item.data,
  755. yAxisIndex: yAxisIndex // 指定使用的 Y 轴
  756. }
  757. })
  758. }
  759. qxInstance.setOption(option)
  760. }
  761. // 响应式调整
  762. const resizeQxChart = () => qxInstance?.resize()
  763. /** 初始化 */
  764. onMounted(() => {
  765. getStats()
  766. // initChart()
  767. // initTopChart()
  768. // initActiveChart()
  769. // initQxChart()
  770. window.addEventListener('resize', resizeQxChart)
  771. // fetchTop()
  772. window.addEventListener('resize', () => topInstance?.resize())
  773. })
  774. onBeforeUnmount(() => {
  775. chartInstance?.dispose()
  776. window.removeEventListener('resize', () => chartInstance?.resize())
  777. topInstance?.dispose()
  778. window.removeEventListener('resize', handleResize)
  779. qxInstance?.dispose()
  780. window.removeEventListener('resize', resizeQxChart)
  781. })
  782. </script>
  783. <style lang="scss" scoped></style>