rdkb.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <template>
  2. <div class="flex flex-col">
  3. <el-row :gutter="16" class="summary">
  4. <!-- 原有的统计卡片部分保持不变 -->
  5. <el-col v-loading="loading" :sm="3" :xs="12">
  6. <SummaryCard
  7. :value="device.total || 0"
  8. icon="fa-solid:project-diagram"
  9. icon-bg-color="text-blue-500"
  10. icon-color="bg-blue-100"
  11. title="设备数"
  12. />
  13. </el-col>
  14. <el-col v-loading="loading" :sm="3" :xs="12">
  15. <SummaryCard
  16. :value="maintain.total || 0"
  17. icon="fa-solid:list"
  18. icon-bg-color="text-pink-500"
  19. icon-color="bg-blue-100"
  20. title="维修工单"
  21. />
  22. </el-col>
  23. <el-col v-loading="loading" :sm="3" :xs="12">
  24. <SummaryCard
  25. :value="fill.unfilledCount || 0"
  26. icon="fa-solid:times-circle"
  27. icon-bg-color="text-purple-500"
  28. icon-color="bg-purple-100"
  29. title="运行未填写"
  30. />
  31. </el-col>
  32. <el-col v-loading="loading" :sm="3" :xs="12">
  33. <SummaryCard
  34. :value="fill.filledCount || 0"
  35. icon="fa-solid:award"
  36. icon-bg-color="text-purple-500"
  37. icon-color="bg-purple-100"
  38. title="运行已填写"
  39. />
  40. </el-col>
  41. <el-col v-loading="loading" :sm="3" :xs="12">
  42. <SummaryCard
  43. :value="by.todo || 0"
  44. icon="fa-solid:times-circle"
  45. icon-bg-color="text-green-500"
  46. icon-color="bg-green-100"
  47. title="未执行保养"
  48. />
  49. </el-col>
  50. <el-col v-loading="loading" :sm="3" :xs="12">
  51. <SummaryCard
  52. :value="by.finished || 0"
  53. icon="fa-solid:award"
  54. icon-bg-color="text-green-500"
  55. icon-color="bg-green-100"
  56. title="已执行保养"
  57. />
  58. </el-col>
  59. <el-col v-loading="loading" :sm="3" :xs="12">
  60. <SummaryCard
  61. :value="inspect.todo || 0"
  62. icon="fa-solid:times-circle"
  63. icon-bg-color="text-yellow-500"
  64. icon-color="bg-yellow-100"
  65. title="待填写巡检"
  66. />
  67. </el-col>
  68. <el-col v-loading="loading" :sm="3" :xs="12">
  69. <SummaryCard
  70. :value="inspect.finished || 0"
  71. icon="fa-solid:award"
  72. icon-bg-color="text-yellow-500"
  73. icon-color="bg-yellow-100"
  74. title="已填写巡检"
  75. />
  76. </el-col>
  77. <!-- 其他统计卡片... -->
  78. </el-row>
  79. <el-row :gutter="16" class="mb-4">
  80. <!-- 设备状态统计和工单数量情况图表部分保持不变 -->
  81. <el-col :span="7">
  82. <el-card class="chart-card" shadow="never">
  83. <template #header>
  84. <div class="flex items-center">
  85. <span class="text-base font-medium text-gray-600">设备状态统计</span>
  86. </div>
  87. </template>
  88. <div ref="statusChartRef" class="h-[290px]"></div>
  89. </el-card>
  90. </el-col>
  91. <el-col :span="17">
  92. <el-card class="chart-card" shadow="never">
  93. <template #header>
  94. <div class="flex items-center justify-between">
  95. <span class="text-base font-medium text-gray-600">工单数量情况</span>
  96. </div>
  97. </template>
  98. <div ref="qxRef" class="h-[290px]"></div>
  99. </el-card>
  100. </el-col>
  101. </el-row>
  102. <el-row :gutter="16" class="mb-4">
  103. <!-- 备件更换情况部分保持不变 -->
  104. <el-col :span="7">
  105. <el-card class="chart-card" shadow="never">
  106. <template #header>
  107. <div class="flex items-center justify-between">
  108. <span class="text-base font-medium text-gray-600">备件更换情况</span>
  109. </div>
  110. </template>
  111. <!-- 添加两个卡片 -->
  112. <div class="flex justify-between mb-2">
  113. <el-card class="stat-card " >
  114. <div class="flex flex-row justify-evenly">
  115. <div>
  116. <Icon icon="fa-solid:award" size="30" color="blue"/>
  117. </div>
  118. <div class="flex flex-col items-center">
  119. <span class="text-sm text-gray-600">物料消耗数量</span>
  120. <span class="text-lg font-bold">{{ totalMaterialCount }}</span>
  121. </div>
  122. </div>
  123. </el-card>
  124. <el-card class="stat-card">
  125. <div class="flex flex-row justify-evenly">
  126. <div>
  127. <Icon icon="fa-solid:yen-sign" size="30" color="orange"/>
  128. </div>
  129. <div class="flex flex-col items-center">
  130. <span class="text-sm text-gray-600">物料消耗费用</span>
  131. <span class="text-lg font-bold">{{ totalMaterialCost }}</span>
  132. </div>
  133. </div>
  134. </el-card>
  135. </div>
  136. <div ref="sparePartRef" class="h-[330px]"></div>
  137. </el-card>
  138. </el-col>
  139. <el-col :span="17">
  140. <div class="flex flex-col justify-between">
  141. <el-card class="chart-card" shadow="never">
  142. <template #header>
  143. <div class="flex items-center justify-between" >
  144. <span class="text-base font-medium text-gray-600">保养情况</span>
  145. </div>
  146. </template>
  147. <div class="flex mr-3">
  148. <div ref="maintenanceChartRef" class="h-[157px] w-2/5"></div>
  149. <div class="w-3/5 p-4 flex flex-col mt-5">
  150. <div class="flex justify-between">
  151. <div class="flex flex-col items-center">
  152. <Icon icon="fa-solid:list" size="30" color="blue"/>
  153. <p style="font-size: 20px;margin-top: 5px">总工单数</p>
  154. <span style="font-size: 20px">{{ by.finished+by.todo }}</span>
  155. </div>
  156. <div class="flex flex-col items-center">
  157. <Icon icon="fa-solid:check-circle" size="30" color="green"/>
  158. <p style="font-size: 20px;margin-top: 5px">已执行工单数</p>
  159. <span style="font-size: 20px">{{ by.finished }}</span>
  160. </div>
  161. <div class="flex flex-col items-center">
  162. <Icon icon="fa-solid:hourglass-half" size="30" color="orange"/>
  163. <p style="font-size: 20px;margin-top: 5px">待执行工单数 </p>
  164. <span style="font-size: 20px">{{ by.todo }}</span>
  165. </div>
  166. </div>
  167. </div>
  168. </div>
  169. </el-card>
  170. <el-card class="chart-card mt-2" shadow="never">
  171. <template #header>
  172. <div class="flex items-center justify-between">
  173. <span class="text-base font-medium text-gray-600">巡检情况</span>
  174. </div>
  175. </template>
  176. <div class="flex mr-3">
  177. <div ref="inspectChartRef" class="h-[157px] w-2/5"></div>
  178. <div class="w-3/5 p-4 flex flex-col mt-5">
  179. <div class="flex justify-between">
  180. <div class="flex flex-col items-center">
  181. <Icon icon="fa-solid:list" size="30" color="blue"/>
  182. <p style="font-size: 20px;margin-top: 5px">总工单数</p>
  183. <span style="font-size: 20px">{{ inspect.todo+inspect.finished }}</span>
  184. </div>
  185. <div class="flex flex-col items-center">
  186. <Icon icon="fa-solid:check-circle" size="30" color="green"/>
  187. <p style="font-size: 20px;margin-top: 5px">已执行工单数</p>
  188. <span style="font-size: 20px">{{ inspect.finished }}</span>
  189. </div>
  190. <div class="flex flex-col items-center">
  191. <Icon icon="fa-solid:hourglass-half" size="30" color="orange"/>
  192. <p style="font-size: 20px;margin-top: 5px">待执行工单数</p>
  193. <span style="font-size: 20px">{{ inspect.todo }}</span>
  194. </div>
  195. </div>
  196. </div>
  197. </div>
  198. </el-card>
  199. </div>
  200. </el-col>
  201. </el-row>
  202. </div>
  203. </template>
  204. <script lang="ts" setup>
  205. import { MemberSummaryRespVO } from '@/api/mall/statistics/member'
  206. import SummaryCard from '@/components/SummaryCard/index.vue'
  207. import { fenToYuan } from '@/utils'
  208. import * as echarts from 'echarts/core'
  209. import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
  210. import {
  211. GridComponent,
  212. LegendComponent,
  213. TitleComponent,
  214. ToolboxComponent,
  215. TooltipComponent
  216. } from 'echarts/components'
  217. import { LabelLayout, UniversalTransition } from 'echarts/features'
  218. import { CanvasRenderer } from 'echarts/renderers'
  219. import { IotStatApi } from '@/api/pms/stat'
  220. import {ref, onMounted, computed, watch, nextTick, reactive} from 'vue';
  221. import {useUserStore} from "@/store/modules/user";
  222. /** 会员统计 */
  223. defineOptions({ name: 'IotRdStat' })
  224. echarts.use([
  225. TooltipComponent,
  226. LegendComponent,
  227. PieChart,
  228. CanvasRenderer,
  229. LabelLayout,
  230. TitleComponent,
  231. ToolboxComponent,
  232. GridComponent,
  233. LineChart,
  234. UniversalTransition,
  235. GaugeChart,
  236. BarChart
  237. ])
  238. const loading = ref(true) // 加载中
  239. const summary = ref<MemberSummaryRespVO>() // 会员统计数据
  240. const statusChartRef = ref() // 设备数量统计的图表
  241. const qxRef = ref(null)
  242. let qxInstance = null
  243. const sparePartRef = ref(null)
  244. let sparePartInstance = null
  245. const maintenanceChartRef = ref(null)
  246. let maintenanceChartInstance = null
  247. const inspectChartRef = ref(null)
  248. let inspectChartInstance = null
  249. const inspectionChartRef = ref(null)
  250. let inspectionChartInstance = null
  251. const maintenanceChartRef1 = ref(null)
  252. const typeData = ref({})
  253. const orderSevenData = ref({})
  254. const device = ref({
  255. total: undefined,
  256. today: undefined
  257. })
  258. const maintain = ref({
  259. total: undefined,
  260. today: undefined
  261. })
  262. const by = ref({
  263. todo: undefined,
  264. finished: undefined,
  265. })
  266. const fill = ref({
  267. filledCount: undefined,
  268. unfilledCount: undefined
  269. })
  270. const inspect = ref({
  271. finished: 0,
  272. todo: 0
  273. })
  274. const sparePartData = ref({
  275. xAxis: ['扳手', '水杯', '皮带', '螺丝'],
  276. series: [
  277. {
  278. name: '数量',
  279. type: 'bar',
  280. data: [10, 20, 15, 25],
  281. yAxisIndex: 0
  282. },
  283. {
  284. name: '金额',
  285. type: 'line',
  286. data: [100, 200, 150, 250],
  287. yAxisIndex: 1
  288. }
  289. ]
  290. })
  291. // 模拟保养工单数据
  292. const totalMaintenanceOrders = ref(100)
  293. const completedMaintenanceOrders = ref(80)
  294. const pendingMaintenanceOrders = ref(20)
  295. // 模拟巡检工单数据
  296. const totalInspectionOrders = ref(80)
  297. const completedInspectionOrders = ref(60)
  298. // 计算物料消耗数量及费用
  299. const totalMaterialCount = computed(() => {
  300. const quantitySeries = sparePartData.value.series.find(item => item.name === '数量');
  301. return quantitySeries ? quantitySeries.data.reduce((sum, val) => sum + val, 0) : 0;
  302. });
  303. const totalMaterialCost = computed(() => {
  304. const costSeries = sparePartData.value.series.find(item => item.name === '金额');
  305. return costSeries ? costSeries.data.reduce((sum, val) => sum + val, 0) : 0;
  306. });
  307. const getStats = () => {
  308. IotStatApi.getDeviceStatusCount().then((res) => {
  309. typeData.value = res
  310. initDeviceStatusCharts()
  311. })
  312. IotStatApi.getOrderSeven().then((res) => {
  313. orderSevenData.value = res
  314. initQxChart()
  315. })
  316. IotStatApi.getDeviceCount().then((res) => {
  317. device.value = res
  318. })
  319. IotStatApi.getMaintainCount().then((res) => {
  320. maintain.value = res
  321. })
  322. IotStatApi.getMaintenanceStatus().then((res) => {
  323. by.value = res
  324. initMaintenanceChart()
  325. })
  326. const fillQueryParams = reactive({
  327. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  328. endTime: Date.now(), // 设置默认结束时间为当前时间
  329. createTime: [],
  330. deptId: null, // 选中的部门ID
  331. status: null // 填写状态
  332. })
  333. IotStatApi.getInspectStatus(fillQueryParams).then((res) => {
  334. inspect.value = res
  335. initInspectChart()
  336. })
  337. fillQueryParams.deptId = useUserStore().getUser.deptId;
  338. IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
  339. fill.value = res.totalList[0] || [];
  340. })
  341. initSparePartChart()
  342. initInspectionChart()
  343. }
  344. const initQxChart = () => {
  345. if (!qxRef.value) return
  346. qxInstance = echarts.init(qxRef.value)
  347. const option = {
  348. tooltip: {
  349. trigger: 'axis',
  350. axisPointer: {
  351. type: 'cross',
  352. label: {
  353. backgroundColor: '#6a7985'
  354. }
  355. }
  356. },
  357. legend: {
  358. data: orderSevenData.value.series.map((item) => item.name),
  359. top: 30
  360. },
  361. grid: {
  362. left: '3%',
  363. right: '4%',
  364. bottom: '3%',
  365. containLabel: true
  366. },
  367. xAxis: {
  368. type: 'category',
  369. boundaryGap: false,
  370. data: orderSevenData.value.xAxis,
  371. axisLabel: {
  372. formatter: (value) => value.split('-').join('/') // 显示为 2023/01
  373. }
  374. },
  375. yAxis: [
  376. {
  377. type: 'value',
  378. axisLabel: {
  379. formatter: '{value}'
  380. },
  381. position: 'left' // 左侧 Y 轴
  382. },
  383. {
  384. type: 'value',
  385. axisLabel: {
  386. formatter: '{value}'
  387. },
  388. position: 'right', // 右侧 Y 轴
  389. splitLine: {
  390. show: false // 隐藏右侧 Y 轴的分割线
  391. }
  392. }
  393. ],
  394. series: orderSevenData.value.series.map((item, index) => {
  395. const yAxisIndex = index < 2 ? 0 : 1
  396. return {
  397. name: item.name,
  398. type: 'line',
  399. smooth: true,
  400. symbol: 'circle',
  401. symbolSize: 8,
  402. itemStyle: {
  403. color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
  404. },
  405. areaStyle: {
  406. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  407. { offset: 0, color: 'rgba(84,112,198,0.4)' },
  408. { offset: 1, color: 'rgba(84,112,198,0.1)' }
  409. ])
  410. },
  411. data: item.data,
  412. yAxisIndex: yAxisIndex // 指定使用的 Y 轴
  413. }
  414. })
  415. }
  416. qxInstance.setOption(option)
  417. }
  418. const initSparePartChart = () => {
  419. if (!sparePartRef.value) return
  420. sparePartInstance = echarts.init(sparePartRef.value)
  421. const option = {
  422. tooltip: {
  423. trigger: 'axis',
  424. axisPointer: {
  425. type: 'cross',
  426. crossStyle: {
  427. color: '#999'
  428. }
  429. }
  430. },
  431. legend: {
  432. data: ['数量', '金额']
  433. },
  434. grid: {
  435. left: '3%',
  436. right: '4%',
  437. bottom: '3%',
  438. containLabel: true
  439. },
  440. xAxis: {
  441. type: 'category',
  442. data: sparePartData.value.xAxis
  443. },
  444. yAxis: [
  445. {
  446. type: 'value',
  447. name: '数量',
  448. position: 'left',
  449. axisLabel: {
  450. formatter: '{value}'
  451. }
  452. },
  453. {
  454. type: 'value',
  455. name: '金额',
  456. position: 'right',
  457. axisLabel: {
  458. formatter: '{value}'
  459. },
  460. splitLine: {
  461. show: false
  462. }
  463. }
  464. ],
  465. series: sparePartData.value.series.map((item, index) => ({
  466. name: item.name,
  467. type: item.type,
  468. data: item.data,
  469. yAxisIndex: item.yAxisIndex,
  470. itemStyle: {
  471. color: ['#6084fb', '#5aef13'][index]
  472. },
  473. }))
  474. }
  475. sparePartInstance.setOption(option)
  476. }
  477. const initInspectChart = () => {
  478. if (!inspectChartRef.value) return
  479. inspectChartInstance = echarts.init(inspectChartRef.value)
  480. const completionRate = (inspect.value.finished / (inspect.value.finished+inspect.value.todo)) * 100
  481. debugger
  482. const option = {
  483. tooltip: {
  484. trigger: 'item'
  485. },
  486. legend: {
  487. // top: '5%',
  488. // right: '10%',
  489. // align: 'center',
  490. orient: 'horizontal', // 水平排列图例项
  491. bottom: '0%', // 放置在底部
  492. icon: 'circle'
  493. },
  494. series: [
  495. {
  496. type: 'pie',
  497. radius: ['40%', '70%'],
  498. label: {
  499. show: false,
  500. position: 'outside'
  501. },
  502. emphasis: {
  503. label: {
  504. show: true,
  505. fontSize: 15,
  506. fontWeight: 'bold'
  507. }
  508. },
  509. labelLine: {
  510. show: true
  511. },
  512. // itemStyle: {
  513. // color: ['#e4a317', '#5aef13']
  514. // },
  515. data: [
  516. { name:'完成率',value: completionRate.toFixed(2) },
  517. { name:'未完成率',value: 100 - completionRate.toFixed(2) }
  518. ]
  519. }
  520. ]
  521. }
  522. inspectChartInstance.setOption(option)
  523. }
  524. const initMaintenanceChart = () => {
  525. if (!maintenanceChartRef.value) return
  526. maintenanceChartInstance = echarts.init(maintenanceChartRef.value)
  527. const completionRate = (by.value.finished / (by.value.finished+by.value.todo)) * 100
  528. debugger
  529. const option = {
  530. tooltip: {
  531. trigger: 'item'
  532. },
  533. legend: {
  534. // top: '5%',
  535. // right: '10%',
  536. // align: 'center',
  537. orient: 'horizontal', // 水平排列图例项
  538. bottom: '0%', // 放置在底部
  539. icon: 'circle'
  540. },
  541. series: [
  542. {
  543. type: 'pie',
  544. radius: ['40%', '70%'],
  545. label: {
  546. show: false,
  547. position: 'outside'
  548. },
  549. emphasis: {
  550. label: {
  551. show: true,
  552. fontSize: 15,
  553. fontWeight: 'bold'
  554. }
  555. },
  556. labelLine: {
  557. show: true
  558. },
  559. data: [
  560. { name:'完成率',value: completionRate.toFixed(2) },
  561. { name:'未完成率',value: 100 - completionRate.toFixed(2) }
  562. ]
  563. }
  564. ]
  565. }
  566. maintenanceChartInstance.setOption(option)
  567. }
  568. const initInspectionChart = () => {
  569. if (!inspectionChartRef.value) return
  570. inspectionChartInstance = echarts.init(inspectionChartRef.value)
  571. const completionRate = (completedInspectionOrders.value / totalInspectionOrders.value) * 100
  572. const option = {
  573. series: [
  574. {
  575. type: 'pie',
  576. radius: ['50%', '80%'],
  577. data: [
  578. { value: completionRate, name: '完成率' },
  579. { value: 100 - completionRate, name: '未完成率' }
  580. ]
  581. }
  582. ]
  583. }
  584. inspectionChartInstance.setOption(option)
  585. }
  586. /** 初始化图表 */
  587. const initDeviceStatusCharts = () => {
  588. // 设备数量统计
  589. echarts.init(statusChartRef.value).setOption({
  590. tooltip: {
  591. trigger: 'item'
  592. },
  593. legend: {
  594. orient: 'horizontal', // 水平排列图例项
  595. bottom: '0%', // 放置在底部
  596. icon: 'circle'
  597. },
  598. series: [
  599. {
  600. name: '',
  601. type: 'pie',
  602. radius: ['50%', '80%'],
  603. avoidLabelOverlap: false,
  604. center: ['50%', '44%'],
  605. label: {
  606. show: false,
  607. position: 'outside'
  608. },
  609. emphasis: {
  610. label: {
  611. show: true,
  612. fontSize: 15,
  613. fontWeight: 'bold'
  614. }
  615. },
  616. labelLine: {
  617. show: false
  618. },
  619. data: typeData.value
  620. }
  621. ]
  622. })
  623. }
  624. /** 初始化 **/
  625. onMounted(async () => {
  626. loading.value = true
  627. await Promise.all([getStats()])
  628. loading.value = false
  629. })
  630. </script>
  631. <style lang="scss" scoped>
  632. .summary {
  633. .el-col {
  634. margin-bottom: 1rem;
  635. }
  636. }
  637. .stat-card {
  638. width: 48%;
  639. }
  640. .calendar-cell {
  641. position: relative;
  642. height: 100%;
  643. display: flex;
  644. flex-direction: column;
  645. align-items: center;
  646. justify-content: center;
  647. }
  648. .calendar-pie {
  649. width: 100%;
  650. height: 80px;
  651. }
  652. </style>