Răsfoiți Sursa

看板调整

Zimo 1 săptămână în urmă
părinte
comite
7a510bb616
36 a modificat fișierele cu 9847 adăugiri și 4136 ștergeri
  1. 6 6
      src/api/pms/stat/index.ts
  2. BIN
      src/assets/font/YouSheBiaoTiHei.ttf
  3. BIN
      src/assets/kb/bg.png
  4. BIN
      src/assets/kb/header.png
  5. 23 0
      src/styles/kb-dialog.scss
  6. 567 0
      src/styles/kb.scss
  7. 170 0
      src/utils/kb.ts
  8. 66 1350
      src/views/Home/Index.vue
  9. 1371 0
      src/views/Home/backup/Index-a.vue
  10. 246 0
      src/views/Home/kb/dayfinish.vue
  11. 134 0
      src/views/Home/kb/hdeviceStatus.vue
  12. 179 0
      src/views/Home/kb/hdeviceType.vue
  13. 160 0
      src/views/Home/kb/hmttr.vue
  14. 212 0
      src/views/Home/kb/horderTrend.vue
  15. 149 0
      src/views/Home/kb/hsafe.vue
  16. 274 0
      src/views/Home/kb/hsummary.vue
  17. 1354 0
      src/views/pms/stat/backup/rhkb-a.vue
  18. 1471 0
      src/views/pms/stat/backup/rykb-a.vue
  19. 70 1332
      src/views/pms/stat/rhkb.vue
  20. 191 0
      src/views/pms/stat/rhkb/deviceList.vue
  21. 134 0
      src/views/pms/stat/rhkb/deviceStatus.vue
  22. 179 0
      src/views/pms/stat/rhkb/deviceType.vue
  23. 207 0
      src/views/pms/stat/rhkb/historyGas.vue
  24. 187 0
      src/views/pms/stat/rhkb/operation.vue
  25. 212 0
      src/views/pms/stat/rhkb/orderTrend.vue
  26. 274 0
      src/views/pms/stat/rhkb/rhsummary.vue
  27. 218 0
      src/views/pms/stat/rhkb/todayGas.vue
  28. 68 1448
      src/views/pms/stat/rykb.vue
  29. 190 0
      src/views/pms/stat/rykb/rydeviceList.vue
  30. 134 0
      src/views/pms/stat/rykb/rydeviceStatus.vue
  31. 212 0
      src/views/pms/stat/rykb/ryorderTrend.vue
  32. 310 0
      src/views/pms/stat/rykb/rysummary.vue
  33. 162 0
      src/views/pms/stat/rykb/safeday.vue
  34. 224 0
      src/views/pms/stat/rykb/xjwork.vue
  35. 225 0
      src/views/pms/stat/rykb/zjfinish.vue
  36. 268 0
      src/views/pms/stat/rykb/zjwork.vue

+ 6 - 6
src/api/pms/stat/index.ts

@@ -15,7 +15,7 @@ export const IotStatApi = {
   getMainDay: async () => {
     return await request.get({ url: `/rq/stat/main/day` })
   },
-  getOrderSeven: async (params: any) => {
+  getOrderSeven: async (params?: any) => {
     return await request.get({ url: `/rq/stat/rh/order/` + params })
   },
   getRepairRigWork: async (params: any) => {
@@ -84,7 +84,7 @@ export const IotStatApi = {
   getMaintenanceTotal: async () => {
     return await request.get({ url: `/rq/stat/maintenance/total` })
   },
-  getMaintenanceStatus: async (params: any) => {
+  getMaintenanceStatus: async (params?: any) => {
     return await request.get({ url: `/rq/stat/maintenance/status/` + params })
   },
   getRhZql: async (params: any) => {
@@ -120,7 +120,7 @@ export const IotStatApi = {
     })
   },
 
-  getDeviceCount: async (params: any) => {
+  getDeviceCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/device/count/` + params })
   },
   getAbnormalDevice: async (params: any) => {
@@ -147,7 +147,7 @@ export const IotStatApi = {
   getRdTeamRate: async (params: any) => {
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
   },
-  getMaintainCount: async (params: any) => {
+  getMaintainCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/maintain/count/` + params })
   },
   getMainWorkCount: async () => {
@@ -156,11 +156,11 @@ export const IotStatApi = {
   getInspectCount: async () => {
     return await request.get({ url: `/rq/stat/home/inspect/count` })
   },
-  getDeviceStatusCount: async (params: any) => {
+  getDeviceStatusCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/device/status/` + params })
   },
 
-  getDeviceTypeCount: async (params: any) => {
+  getDeviceTypeCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/device/type/` + params })
   },
   getDeptCount: async () => {

BIN
src/assets/font/YouSheBiaoTiHei.ttf


BIN
src/assets/kb/bg.png


BIN
src/assets/kb/header.png


+ 23 - 0
src/styles/kb-dialog.scss

@@ -0,0 +1,23 @@
+.device-list-dialog .el-dialog {
+  overflow: hidden;
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(235 243 255 / 94%) 100%);
+  border: 1px solid rgb(255 255 255 / 70%);
+  border-radius: 20px;
+  box-shadow: 0 18px 36px rgb(46 90 164 / 16%);
+}
+
+.device-list-dialog .el-dialog__header {
+  padding: 18px 24px 14px;
+  margin-right: 0;
+  border-bottom: 1px solid rgb(31 91 184 / 8%);
+}
+
+.device-list-dialog .el-dialog__header .el-dialog__title {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 24px;
+  color: #03409b;
+}
+
+.device-list-dialog .el-dialog__body {
+  padding: 18px 24px 24px;
+}

+ 567 - 0
src/styles/kb.scss

@@ -0,0 +1,567 @@
+@font-face {
+  font-family: YouSheBiaoTiHei;
+  font-style: normal;
+  font-weight: normal;
+  src: url('@/assets/font/YouSheBiaoTiHei.ttf') format('truetype');
+  font-display: swap;
+}
+
+.bg {
+  --fvs-theme-color-1-2: #f5f9ff;
+
+  background: url('@/assets/kb/bg.png') center center / 100% 100% no-repeat;
+  background-color: #fff;
+}
+
+.header {
+  height: 52px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 32px;
+  color: var(--fvs-theme-color-1-2);
+  text-align: center;
+  background: url('@/assets/kb/header.png') center center / 100% 100% no-repeat;
+}
+
+.panel {
+  background: linear-gradient(180deg, rgb(235 243 255 / 76%) 0%, rgb(221 233 251 / 58%) 100%);
+  border: 1px solid rgb(255 255 255 / 58%);
+  border-radius: 20px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 72%),
+    0 18px 36px rgb(46 90 164 / 12%);
+  backdrop-filter: blur(12px);
+}
+
+.panel-title {
+  position: relative;
+  display: flex;
+  padding: 0 16px 0 36px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 24px;
+  letter-spacing: 1px;
+  color: #03409b;
+  align-items: center;
+
+  &::after {
+    position: absolute;
+    right: 10px;
+    bottom: 0;
+    left: 10px;
+    height: 2px;
+    background: linear-gradient(
+      to right,
+      rgb(3 64 155 / 15%) 0%,
+      rgb(3 64 155 / 15%) calc(100% - 80px),
+      #03409b calc(100% - 80px),
+      #03409b 100%
+    );
+    content: '';
+  }
+
+  &::before {
+    position: absolute;
+    right: 10px;
+    bottom: 0;
+    z-index: 1;
+    width: 80px;
+    height: 4px;
+    background: #03409b;
+    border-radius: 2px 2px 0 0;
+    content: '';
+  }
+
+  .icon-decorator {
+    position: absolute;
+    left: 14px;
+    display: flex;
+    align-items: center;
+    gap: 3px;
+
+    span {
+      width: 4px;
+      height: 18px;
+      background: #03409b;
+      border-radius: 2px;
+
+      &:last-child {
+        height: 12px;
+        opacity: 0.5;
+      }
+    }
+  }
+}
+
+.summary-card {
+  position: relative;
+  cursor: default;
+  background: linear-gradient(180deg, rgb(255 255 255 / 52%) 0%, rgb(213 227 249 / 38%) 100%);
+  border: 1px solid rgb(255 255 255 / 74%);
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 6px 14px rgb(63 103 171 / 7%);
+  transition:
+    transform 0.28s ease,
+    box-shadow 0.28s ease,
+    border-color 0.28s ease,
+    background 0.28s ease;
+  isolation: isolate;
+}
+
+.summary-card::before {
+  position: absolute;
+  z-index: 0;
+  pointer-events: none;
+  background: linear-gradient(
+    135deg,
+    rgb(255 255 255 / 0%) 0%,
+    rgb(255 255 255 / 0%) 58%,
+    rgb(255 255 255 / 18%) 100%
+  );
+  content: '';
+  inset: 0;
+}
+
+.summary-card::after {
+  position: absolute;
+  right: -16px;
+  bottom: -28px;
+  z-index: 0;
+  width: 84px;
+  height: 84px;
+  pointer-events: none;
+  background: radial-gradient(circle, var(--card-glow) 0%, rgb(255 255 255 / 0%) 70%);
+  content: '';
+  opacity: 0.86;
+  transition:
+    transform 0.28s ease,
+    opacity 0.28s ease;
+}
+
+.summary-card:hover {
+  background: linear-gradient(180deg, rgb(255 255 255 / 72%) 0%, rgb(219 233 255 / 46%) 100%);
+  border-color: rgb(255 255 255 / 90%);
+  transform: translateY(-3px);
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 85%),
+    0 10px 18px rgb(41 89 164 / 13%);
+}
+
+.summary-card:hover::after {
+  opacity: 1;
+  transform: scale(1.08);
+}
+
+.summary-card__shine {
+  position: absolute;
+  top: 0;
+  left: -130%;
+  z-index: 1;
+  width: 60%;
+  height: 100%;
+  pointer-events: none;
+  background: linear-gradient(
+    90deg,
+    rgb(255 255 255 / 0%) 0%,
+    rgb(255 255 255 / 18%) 45%,
+    rgb(255 255 255 / 45%) 50%,
+    rgb(255 255 255 / 18%) 55%,
+    rgb(255 255 255 / 0%) 100%
+  );
+  transform: skewX(-22deg);
+}
+
+.summary-card:hover .summary-card__shine {
+  left: 150%;
+  transition: left 0.8s ease;
+}
+
+.summary-card__icon {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  width: 48px;
+  height: 48px;
+  background: linear-gradient(180deg, rgb(255 255 255 / 96%) 0%, rgb(242 247 255 / 92%) 100%);
+  border: 1px solid rgb(255 255 255 / 88%);
+  border-radius: 12px;
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 95%),
+    0 6px 12px rgb(152 181 230 / 15%);
+  flex-shrink: 0;
+  align-items: center;
+  justify-content: center;
+  transition:
+    transform 0.28s ease,
+    box-shadow 0.28s ease;
+}
+
+.summary-card__icon::after {
+  position: absolute;
+  right: -3px;
+  bottom: -3px;
+  width: 15px;
+  height: 15px;
+  background: radial-gradient(
+    circle at 30% 30%,
+    rgb(255 255 255 / 96%) 0%,
+    var(--card-glow) 58%,
+    rgb(255 255 255 / 0%) 75%
+  );
+  border-radius: 999px;
+  content: '';
+  opacity: 0.95;
+}
+
+.summary-card:hover .summary-card__icon {
+  transform: translateY(-2px) scale(1.03);
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 95%),
+    0 8px 14px rgb(120 158 221 / 20%);
+}
+
+.summary-card__icon-glyph {
+  position: relative;
+  z-index: 1;
+  font-size: 25px;
+  color: var(--card-accent);
+  transition:
+    transform 0.28s ease,
+    filter 0.28s ease;
+}
+
+.summary-card:hover .summary-card__icon-glyph {
+  filter: drop-shadow(0 4px 8px rgb(0 0 0 / 8%));
+  transform: scale(1.08);
+}
+
+.summary-card__body {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  min-height: 48px;
+  min-width: 0;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.summary-card__label {
+  overflow: hidden;
+  font-size: 14px;
+  font-weight: 600;
+  color: #24364f;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  transition:
+    transform 0.28s ease,
+    color 0.28s ease;
+}
+
+.summary-card__value {
+  margin-top: 4px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 19px;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: #1f5bb8;
+  transition:
+    transform 0.28s ease,
+    text-shadow 0.28s ease;
+}
+
+.summary-card:hover .summary-card__label {
+  color: #173b72;
+  transform: translateX(1px);
+}
+
+.summary-card:hover .summary-card__value {
+  text-shadow: 0 4px 10px rgb(31 91 184 / 16%);
+  transform: translateX(1px);
+}
+
+.summary-card__corner {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 1;
+  width: 36px;
+  height: 36px;
+  pointer-events: none;
+  background: linear-gradient(135deg, rgb(255 255 255 / 30%) 0%, rgb(255 255 255 / 0%) 70%);
+  opacity: 0.85;
+  clip-path: polygon(100% 0, 0 0, 100% 100%);
+}
+
+.summary-card {
+  animation: card-in 0.5s ease both;
+}
+
+.summary-card:nth-child(1) {
+  animation-delay: 0.05s;
+}
+
+.summary-card:nth-child(2) {
+  animation-delay: 0.1s;
+}
+
+.summary-card:nth-child(3) {
+  animation-delay: 0.15s;
+}
+
+.summary-card:nth-child(4) {
+  animation-delay: 0.2s;
+}
+
+.summary-card:nth-child(5) {
+  animation-delay: 0.25s;
+}
+
+.summary-card:nth-child(6) {
+  animation-delay: 0.3s;
+}
+
+.summary-card:nth-child(7) {
+  animation-delay: 0.35s;
+}
+
+.summary-card:nth-child(8) {
+  animation-delay: 0.4s;
+}
+
+.summary-card:nth-child(9) {
+  animation-delay: 0.4s;
+}
+
+.summary-card:nth-child(10) {
+  animation-delay: 0.4s;
+}
+
+@keyframes card-in {
+  from {
+    opacity: 0;
+    transform: translateY(12px);
+  }
+
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.summary-card__placeholder {
+  display: inline-block;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 19px;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: rgb(31 91 184 / 45%);
+}
+
+.device-list-table,
+.team-list-table {
+  --el-table-border-color: rgb(31 91 184 / 12%);
+  --el-table-border: 1px solid rgb(31 91 184 / 12%);
+  --el-table-header-bg-color: rgb(255 255 255 / 82%);
+  --el-table-tr-bg-color: transparent;
+  --el-table-bg-color: transparent;
+  --el-table-row-hover-bg-color: rgb(31 91 184 / 6%);
+  --el-fill-color-lighter: rgb(255 255 255 / 55%);
+
+  background: transparent;
+
+  :deep(.el-table__inner-wrapper::before) {
+    display: none;
+  }
+
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-family: YouSheBiaoTiHei, sans-serif;
+    font-size: 20px;
+    font-weight: 500;
+    letter-spacing: 0.5px;
+    color: #03409b;
+    background: linear-gradient(180deg, rgb(255 255 255 / 92%) 0%, rgb(231 240 255 / 82%) 100%);
+    border-bottom: 1px solid rgb(31 91 184 / 16%);
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    padding: 8px 0;
+    font-family: YouSheBiaoTiHei, sans-serif;
+    font-size: 16px;
+    color: #24364f;
+    background: rgb(255 255 255 / 16%);
+    border-bottom: 1px solid rgb(31 91 184 / 8%);
+  }
+
+  :deep(.el-table__body tr:nth-child(2n) td.el-table__cell) {
+    background: rgb(255 255 255 / 28%);
+  }
+
+  :deep(.el-table__row:hover > td.el-table__cell) {
+    background: rgb(31 91 184 / 8%) !important;
+  }
+
+  :deep(.el-scrollbar__bar.is-vertical) {
+    width: 8px;
+  }
+
+  :deep(.el-scrollbar__bar.is-horizontal) {
+    height: 8px;
+  }
+
+  :deep(.el-scrollbar__thumb) {
+    background: rgb(31 91 184 / 26%);
+    border-radius: 999px;
+  }
+}
+
+.device-list-table {
+  :deep(.el-table__row) {
+    cursor: pointer;
+  }
+}
+
+.kb-stage-card {
+  --panel-delay: 0s;
+  --panel-glow: rgb(31 91 184 / 12%);
+
+  position: relative;
+  opacity: 0;
+  transform: translateY(22px) scale(0.985);
+  animation: kb-panel-enter 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards;
+  animation-delay: var(--panel-delay);
+  isolation: isolate;
+  will-change: transform, opacity;
+}
+
+.kb-stage-card::before {
+  position: absolute;
+  z-index: 0;
+  pointer-events: none;
+  background: linear-gradient(
+    112deg,
+    rgb(255 255 255 / 0%) 0%,
+    rgb(255 255 255 / 0%) 34%,
+    rgb(255 255 255 / 26%) 48%,
+    rgb(255 255 255 / 8%) 56%,
+    rgb(255 255 255 / 0%) 68%,
+    rgb(255 255 255 / 0%) 100%
+  );
+  border-radius: 20px;
+  content: '';
+  opacity: 0;
+  transform: translateX(-140%) skewX(-22deg);
+  animation: kb-panel-sheen 1.05s ease-out forwards;
+  animation-delay: calc(var(--panel-delay) + 0.18s);
+  inset: 0;
+}
+
+.kb-stage-card::after {
+  position: absolute;
+  right: -24px;
+  bottom: -30px;
+  z-index: 0;
+  width: 120px;
+  height: 120px;
+  pointer-events: none;
+  background: radial-gradient(circle, var(--panel-glow) 0%, rgb(255 255 255 / 0%) 72%);
+  content: '';
+  opacity: 0.88;
+  transition:
+    transform 0.32s ease,
+    opacity 0.32s ease;
+}
+
+.kb-stage-card:hover {
+  transform: translateY(-4px) scale(1.005);
+  transition:
+    transform 0.28s ease,
+    box-shadow 0.28s ease;
+}
+
+.kb-stage-card:hover::after {
+  opacity: 1;
+  transform: scale(1.08);
+}
+
+.kb-stage-card--list {
+  --panel-glow: rgb(45 124 248 / 14%);
+}
+
+.kb-stage-card--1 {
+  --panel-delay: 0.02s;
+}
+
+.kb-stage-card--2 {
+  --panel-delay: 0.08s;
+}
+
+.kb-stage-card--3 {
+  --panel-delay: 0.14s;
+}
+
+.kb-stage-card--4 {
+  --panel-delay: 0.2s;
+}
+
+.kb-stage-card--5 {
+  --panel-delay: 0.26s;
+}
+
+.kb-stage-card--6 {
+  --panel-delay: 0.32s;
+}
+
+.kb-stage-card--7 {
+  --panel-delay: 0.38s;
+}
+
+.kb-stage-card--8 {
+  --panel-delay: 0.46s;
+}
+
+@keyframes kb-panel-enter {
+  0% {
+    opacity: 0;
+    filter: blur(4px);
+    transform: translateY(22px) scale(0.985);
+  }
+
+  100% {
+    opacity: 1;
+    filter: blur(0);
+    transform: translateY(0) scale(1);
+  }
+}
+
+@keyframes kb-panel-sheen {
+  0% {
+    opacity: 0;
+    transform: translateX(-140%) skewX(-22deg);
+  }
+
+  18% {
+    opacity: 0.85;
+  }
+
+  100% {
+    opacity: 0;
+    transform: translateX(165%) skewX(-22deg);
+  }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .kb-stage-card,
+  .kb-stage-card::before,
+  .kb-stage-card::after {
+    animation: none;
+    transition: none;
+  }
+
+  .kb-stage-card {
+    opacity: 1;
+    transform: none;
+  }
+
+  .kb-stage-card:hover {
+    transform: none;
+  }
+}

+ 170 - 0
src/utils/kb.ts

@@ -0,0 +1,170 @@
+import { LegendOption, TooltipOption } from 'echarts/types/dist/shared'
+
+export const DESIGN_WIDTH = 1920
+export const DESIGN_HEIGHT = 1080
+
+export interface SummaryCardConfig<CardKey> {
+  key: CardKey
+  title: string
+  icon: string
+  accent: string
+  glow: string
+}
+
+export interface CardStateItem {
+  value: number
+  loading: boolean
+}
+
+export interface ChartItem {
+  name: string
+  value: number
+}
+
+export type SeriesItem = {
+  name: string
+  data: number[]
+}
+
+export type ChartData = {
+  xAxis: string[]
+  series: SeriesItem[]
+}
+
+export const FONT_FAMILY = 'YouSheBiaoTiHei, sans-serif'
+export const CHART_RENDERER = 'svg'
+export const ANIMATION = {
+  animationDuration: 500,
+  animationEasing: 'cubicOut' as const
+}
+
+export const THEME = {
+  text: {
+    primary: '#24364f',
+    regular: '#7e93b7',
+    strong: '#1f5bb8',
+    black: '#3c3c3c'
+  },
+  tooltip: {
+    cssText: `
+      background: linear-gradient(180deg, rgba(255,255,255,0.96) 0%, rgba(235,243,255,0.96) 100%);
+      border: 1px solid rgba(255,255,255,0.95);
+      border-radius: 8px;
+      box-shadow: 0 8px 20px rgba(31, 91, 184, 0.16);
+      overflow: hidden;
+      backdrop-filter: blur(12px);
+      -webkit-backdrop-filter: blur(12px);
+    `
+  },
+  grid: {
+    left: 16,
+    right: 16,
+    top: 16,
+    bottom: 16,
+    containLabel: true
+  },
+  split: 'rgba(31, 91, 184, 0.08)',
+  color: {
+    blue: {
+      line: '#1f5bb8',
+      light: '#8ec5ff',
+      mid: '#5e9cff',
+      strong: '#2d7cf8',
+      glow: 'rgba(45, 124, 248, 0.34)',
+      bg: 'rgba(31, 91, 184, 0.08)',
+      shadow: 'rgba(31, 91, 184, 0.16)'
+    },
+    orange: {
+      line: '#f08c2e',
+      light: '#ffd8a8',
+      mid: '#ffb454',
+      strong: '#ff8b3d',
+      glow: 'rgba(255, 139, 61, 0.34)',
+      bg: 'rgba(240, 140, 46, 0.08)',
+      shadow: 'rgba(240, 140, 46, 0.18)'
+    },
+    green: {
+      line: '#2e9a5b',
+      light: '#a8e0c8',
+      mid: '#82c793',
+      strong: '#2fb990',
+      glow: 'rgba(46, 154, 91, 0.34)',
+      bg: 'rgba(34, 197, 94, 0.08)',
+      shadow: 'rgba(34, 197, 94, 0.18)'
+    },
+    red: {
+      line: '#ff4d4f',
+      light: '#fdd5d5',
+      mid: '#ff9999',
+      strong: '#ff6666',
+      glow: 'rgba(255, 77, 79, 0.34)',
+      bg: 'rgba(255, 77, 79, 0.08)',
+      shadow: 'rgba(255, 77, 79, 0.18)'
+    }
+  }
+}
+
+export function createTooltip(extra: Partial<TooltipOption> = {}): any {
+  return {
+    textStyle: {
+      color: THEME.text.black,
+      fontSize: 14,
+      fontFamily: FONT_FAMILY
+    },
+    extraCssText: THEME.tooltip.cssText,
+    ...extra
+  }
+}
+
+export function createLegend(extra: Partial<LegendOption> = {}, data: string[] = []): any {
+  return {
+    data,
+    textStyle: {
+      color: THEME.text.regular,
+      fontSize: 13,
+      fontFamily: FONT_FAMILY
+    },
+    ...extra
+  }
+}
+
+export function formatDateLabel(value: string) {
+  if (!value) return value
+  const parts = value.split('-')
+  if (parts.length === 3) {
+    return `${parts[1]}-${parts[2]}`
+  }
+  return value
+}
+
+export function formatSeriesName(name: string) {
+  return name.split('~~en**')[0] || name
+}
+
+export function formatMonthLabel(value: string) {
+  if (!value) return value
+  const [year, month] = value.split('-')
+  if (!year || !month) return value
+  return `${year.slice(-2)}年${month}月`
+}
+
+export function formatGasAxisValue(value: number) {
+  if (value >= 100000000) {
+    return `${(value / 100000000).toFixed(1)}亿`
+  }
+  if (value >= 10000) {
+    return `${(value / 10000).toFixed(0)}万`
+  }
+  return `${value}`
+}
+
+export function formatGasLabelValue(value: number) {
+  if (!value) return ''
+  if (value >= 100000000) {
+    return `${(value / 100000000).toFixed(2)}亿`
+  }
+  if (value >= 10000) {
+    return `${(value / 10000).toFixed(1)}万`
+  }
+  return `${value}`
+}

+ 66 - 1350
src/views/Home/Index.vue

@@ -1,1370 +1,86 @@
-<template>
-  <!-- 第一行:统计卡片行 -->
-  <div class="page-container">
-    <el-row :gutter="16" class="summary">
-      <!-- 原有的统计卡片部分保持不变 -->
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="device.total || 0"
-          icon="fa-solid:project-diagram"
-          icon-bg-color="text-blue-500"
-          icon-color="bg-blue-100"
-          :title="t('stat.deviceCount')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="maintain.total || 0"
-          icon="fa-solid:list"
-          icon-bg-color="text-pink-500"
-          icon-color="bg-blue-100"
-          :title="t('stat.maintenanceOrder')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="fill.unfilledCount || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-purple-500"
-          icon-color="bg-purple-100"
-          :title="t('stat.operationNotFilled')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="fill.filledCount || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-purple-500"
-          icon-color="bg-purple-100"
-          :title="t('stat.operationFilled')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="by.todo || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-green-500"
-          icon-color="bg-green-100"
-          :title="t('stat.notMaintained')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="by.finished || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-green-500"
-          icon-color="bg-green-100"
-          :title="t('stat.maintained')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectt.todo || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.notInspected')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectt.finished || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.inspected')"
-        />
-      </el-col>
-      <!-- 其他统计卡片... -->
-    </el-row>
-    <!--  <el-row :gutter="16" class="mb-4">-->
-    <!--    <el-col :span="6">-->
-    <!--      <el-card class="stat-card" shadow="never">-->
-    <!--        <div class="flex flex-col">-->
-    <!--          <div class="flex justify-between items-center mb-1">-->
-    <!--            <span class="text-gray-500 text-base font-medium">设备数量</span>-->
-    <!--            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
-    <!--          </div>-->
-    <!--          <span class="text-3xl font-bold text-gray-700">-->
-    <!--            {{ device.total }}-->
-    <!--          </span>-->
-    <!--          <el-divider class="my-2" />-->
-    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
-    <!--            <span>今日新增</span>-->
-    <!--            <span class="text-green-500">+{{ device.today }}</span>-->
-    <!--          </div>-->
-    <!--        </div>-->
-    <!--      </el-card>-->
-    <!--    </el-col>-->
-    <!--    <el-col :span="6">-->
-    <!--      <el-card class="stat-card" shadow="never">-->
-    <!--        <div class="flex flex-col">-->
-    <!--          <div class="flex justify-between items-center mb-1">-->
-    <!--            <span class="text-gray-500 text-base font-medium">维修工单数量</span>-->
-    <!--            <Icon icon="ep:box" class="text-[32px] text-orange-400" />-->
-    <!--          </div>-->
-    <!--          <span class="text-3xl font-bold text-gray-700">{{ maintain.total }}</span>-->
-    <!--          <el-divider class="my-2" />-->
-    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
-    <!--            <span>今日新增</span>-->
-    <!--            <span class="text-green-500">+{{ maintain.today }}</span>-->
-    <!--          </div>-->
-    <!--        </div>-->
-    <!--      </el-card>-->
-    <!--    </el-col>-->
-    <!--    <el-col :span="6">-->
-    <!--      <el-card class="stat-card" shadow="never">-->
-    <!--        <div class="flex flex-col">-->
-    <!--          <div class="flex justify-between items-center mb-1">-->
-    <!--            <span class="text-gray-500 text-base font-medium">保养工单数量</span>-->
-    <!--            <Icon icon="ep:cpu" class="text-[32px] text-purple-400" />-->
-    <!--          </div>-->
-    <!--          <span class="text-3xl font-bold text-gray-700">{{ work.total }}</span>-->
-    <!--          <el-divider class="my-2" />-->
-    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
-    <!--            <span>今日新增</span>-->
-    <!--            <span class="text-green-500">+{{ work.today }}</span>-->
-    <!--          </div>-->
-    <!--        </div>-->
-    <!--      </el-card>-->
-    <!--    </el-col>-->
-    <!--    <el-col :span="6">-->
-    <!--      <el-card class="stat-card" shadow="never">-->
-    <!--        <div class="flex flex-col">-->
-    <!--          <div class="flex justify-between items-center mb-1">-->
-    <!--            <span class="text-gray-500 text-base font-medium">巡检工单数量</span>-->
-    <!--            <Icon icon="ep:camera" class="text-[32px] text-teal-400" />-->
-    <!--          </div>-->
-    <!--          <span class="text-3xl font-bold text-gray-700">-->
-    <!--            {{ inspect.total }}-->
-    <!--          </span>-->
-    <!--          <el-divider class="my-2" />-->
-    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
-    <!--            <span>今日新增</span>-->
-    <!--            <span class="text-green-500">+{{ inspect.today }}</span>-->
-    <!--          </div>-->
-    <!--        </div>-->
-    <!--      </el-card>-->
-    <!--    </el-col>-->
-    <!--  </el-row>-->
-
-    <!-- 第二行:图表行 -->
-    <el-row :gutter="16" class="mb-4">
-      <el-col :span="6">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center justify-between">
-              <span class="text-base font-medium" style="color: #b6c8da">{{ t('stat.mttr') }}</span>
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.materialsUnderInventory')
-              }}</span>
-            </div>
-          </template>
-          <div class="flex flex-col h-[250px]">
-            <!--          <div class="flex justify-between items-center text-gray-400">-->
-            <!--            <span>MTTR</span>-->
-
-            <!--          </div>-->
-            <!--          <el-divider />-->
-            <div class="flex justify-between items-center mb-1 mt-15">
-              <!--            <span class="text-gray-500 text-base font-medium">平均解决时间</span>-->
-              <!--            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
-              <span class="text-5xl font-bold text-gray-700" style="color: lightseagreen">
-                {{ mttr + 'h' }}
-              </span>
-              <span class="text-5xl font-bold text-gray-700" style="color: indianred">
-                {{ safe }}
-              </span>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :span="9">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.deviceStatus')
-              }}</span>
-            </div>
-          </template>
-          <div ref="statusChartRef" class="h-[250px]"></div>
-        </el-card>
-      </el-col>
-      <el-col :span="9">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.deviceClassifyTop5')
-              }}</span>
-            </div>
-          </template>
-          <div ref="topContainer" class="h-[250px]"></div>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <!-- 第三行:消息统计行 -->
-    <el-row :gutter="16" class="mb-4">
-      <el-col :span="8">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center justify-between">
-              <span class="text-base font-medium" style="color: #b6c8da">日报完成率</span>
-              <div>
-                <el-date-picker
-                  v-model="completeRateParams.createTime"
-                  value-format="YYYY-MM-DD HH:mm:ss"
-                  type="daterange"
-                  start-placeholder="开始日期"
-                  end-placeholder="结束日期"
-                  :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-                  class="!w-200px"
-                  @change="handleDateChange"
-                  size="small"
-                />
-              </div>
-            </div>
-          </template>
-          <!-- <div ref="activeDom" class="h-[320px]"></div> -->
-          <Echart :options="myoption" :height="320" />
-        </el-card>
-      </el-col>
-      <el-col :span="16">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center justify-between">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.orderCount')
-              }}</span>
-            </div>
-          </template>
-          <div ref="qxRef" class="h-[320px]"></div>
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script setup lang="ts" name="Index">
-import * as echarts from 'echarts/core'
-import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
-import {
-  GridComponent,
-  LegendComponent,
-  TitleComponent,
-  ToolboxComponent,
-  TooltipComponent
-} from 'echarts/components'
-import { LabelLayout, UniversalTransition } from 'echarts/features'
-import { CanvasRenderer } from 'echarts/renderers'
-import { useElementSize } from '@vueuse/core'
-import {
-  IotStatisticsDeviceMessageSummaryRespVO,
-  IotStatisticsSummaryRespVO
-} from '@/api/iot/statistics'
+<script lang="ts" setup>
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
+import hsummary from './kb/hsummary.vue'
+import hmttr from './kb/hmttr.vue'
+import hdeviceType from './kb/hdeviceType.vue'
+import dayfinish from './kb/dayfinish.vue'
+import hsafe from './kb/hsafe.vue'
+import hdeviceStatus from './kb/hdeviceStatus.vue'
+import horderTrend from './kb/horderTrend.vue'
 
-import { IotStatApi } from '@/api/pms/stat'
-import SummaryCard from '@/components/SummaryCard/index.vue'
-import { reactive, ref } from 'vue'
-import { useLocaleStore } from '@/store/modules/locale'
-import Echart from '@/components/Echart/src/Echart.vue'
+const company = ref('首页')
 
-// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
 
-/** IoT 首页 */
-defineOptions({ name: 'IoTHome' })
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
 
-// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
-echarts.use([
-  TooltipComponent,
-  LegendComponent,
-  PieChart,
-  CanvasRenderer,
-  LabelLayout,
-  TitleComponent,
-  ToolboxComponent,
-  GridComponent,
-  LineChart,
-  UniversalTransition,
-  GaugeChart,
-  BarChart
-])
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
 
-const myoption = ref<any>({
-  xAxis: {
-    type: 'category',
-    data: ['四川瑞都', '瑞恒兴域', '瑞鹰国际'],
-    // 文字颜色
-    axisLabel: {
-      color: '#fff'
-    }
-  },
+const targetAreaStyle = computed(() => ({
+  width: `${DESIGN_WIDTH}px`,
+  height: `${DESIGN_HEIGHT}px`,
+  transform: `scale(${scale.value})`,
+  transformOrigin: '0 0'
+}))
 
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    },
-    formatter: function (params: any) {
-      const p = params && params[0] ? params[0] : params
-      return `${p.name}: ${p.data}%`
-    }
-  },
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
 
-  legend: {
-    top: '5%',
-    right: '10%',
-    align: 'left',
-    orient: 'vertical',
-    icon: 'circle',
-    textStyle: {
-      color: 'white'
-    }
-  },
-  yAxis: {
-    type: 'value',
-    axisLabel: {
-      color: '#fff',
-      formatter: '{value}%'
-    }
-  },
-  series: [
-    {
-      data: [],
-      type: 'bar'
-    }
-  ]
-})
-const completeRateParams = reactive({
-  createTime: []
-})
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
 
-// 计算近一周时间
-const end = new Date()
-const start = new Date()
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
 
-const now = new Date()
-// 构造:今年 1 月 1 日 00:00:00
-const firstDay = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
-// 转时间戳(毫秒),如需秒级时间戳,加 .getTime() / 1000
-
-start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
-
-// 格式化日期为后端需要的格式
-const formatDate = (date): string => {
-  const year = date.getFullYear()
-  const month = String(date.getMonth() + 1).padStart(2, '0')
-  const day = String(date.getDate()).padStart(2, '0')
-  const hours = String(date.getHours()).padStart(2, '0')
-  const minutes = String(date.getMinutes()).padStart(2, '0')
-  const seconds = String(date.getSeconds()).padStart(2, '0')
-  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
-}
-const initMyoption = async () => {
-  // 只在初始化时设置默认值,不覆盖已有值
-  if (!completeRateParams.createTime || completeRateParams.createTime.length === 0) {
-    const start = new Date()
-    start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
-
-    const end = new Date()
-
-    // 将开始时间设置为当天的 00:00:00
-    const startTime = new Date(start)
-    startTime.setHours(0, 0, 0, 0)
-
-    // 将结束时间设置为当天的 23:59:59
-    const endTime = new Date(end)
-    endTime.setHours(23, 59, 59, 999)
-
-    // 格式化为 YYYY-MM-DD HH:mm:ss
-    const formatDate = (date) => {
-      const year = date.getFullYear()
-      const month = String(date.getMonth() + 1).padStart(2, '0')
-      const day = String(date.getDate()).padStart(2, '0')
-      const hours = String(date.getHours()).padStart(2, '0')
-      const minutes = String(date.getMinutes()).padStart(2, '0')
-      const seconds = String(date.getSeconds()).padStart(2, '0')
-      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
-    }
-
-    completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
-  }
-
-  const res = await IotStatApi.getCompleteRate(completeRateParams)
-
-  const list = []
-  res.forEach((item) => {
-    // 后端返回 rate 为小数(如 0.85),这里转换为整数百分比(如 85)以便图表显示
-    if (item.department === '瑞恒兴域') {
-      list.push(Math.trunc(Number(item['瑞恒兴域']) * 100))
-    } else {
-      list.push(Math.trunc(Number(item.rate) * 100))
-    }
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
   })
-
-  myoption.value.series[0].data = list
-}
-
-const handleDateChange = (val) => {
-  if (!val || val.length !== 2) return
-
-  const [start, end] = val
-
-  // 将开始时间设置为当天的 00:00:00
-  const startTime = new Date(start)
-  startTime.setHours(0, 0, 0, 0)
-
-  // 将结束时间设置为当天的 23:59:59
-  const endTime = new Date(end)
-  endTime.setHours(23, 59, 59, 999)
-
-  // 格式化为 YYYY-MM-DD HH:mm:ss
-  const formatDate = (date) => {
-    const year = date.getFullYear()
-    const month = String(date.getMonth() + 1).padStart(2, '0')
-    const day = String(date.getDate()).padStart(2, '0')
-    const hours = String(date.getHours()).padStart(2, '0')
-    const minutes = String(date.getMinutes()).padStart(2, '0')
-    const seconds = String(date.getSeconds()).padStart(2, '0')
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
-  }
-
-  completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
-  initMyoption()
 }
 
-const timeRange = ref('7d') // 修改默认选择为近一周
-const dateRange = ref<[Date, Date] | null>(null)
-
-const queryParams = reactive({
-  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-  endTime: Date.now() // 设置默认结束时间为当前时间
-})
-const backendData = ref([])
-const statusChartRef = ref() // 设备数量统计的图表
-const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
-const { t } = useI18n() // 国际化
-// 基础统计数据
-// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
-const statsData = ref<IotStatisticsSummaryRespVO>({
-  productCategoryCount: 0,
-  productCount: 0,
-  deviceCount: 0,
-  deviceMessageCount: 0,
-  productCategoryTodayCount: 0,
-  productTodayCount: 0,
-  deviceTodayCount: 0,
-  deviceMessageTodayCount: 0,
-  deviceOnlineCount: 0,
-  deviceOfflineCount: 0,
-  deviceInactiveCount: 0,
-  productCategoryDeviceCounts: {}
-})
-const orderSevenData = ref({})
-const by = ref({
-  todo: undefined,
-  finished: undefined
-})
-const fill = ref({
-  filledCount: undefined,
-  unfilledCount: undefined
-})
-const inspectt = ref({
-  finished: 0,
-  todo: 0
-})
-const device = ref({
-  total: undefined,
-  today: undefined
-})
-const maintain = ref({
-  total: undefined,
-  today: undefined
-})
-const work = ref({
-  total: undefined,
-  today: undefined
-})
-const inspect = ref({
-  total: undefined,
-  today: undefined
-})
-
-const status = ref({
-  finished: 0,
-  todo: 0
-})
-const todayStatus = ref({
-  finished: 0,
-  todo: 0
-})
-const typeData = ref({})
-// 消息统计数据
-const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
-  upstreamCounts: {},
-  downstreamCounts: {}
-})
-const mttr = ref(0)
-const safe = ref()
-/** 获取统计数据 */
-const getStats = async () => {
-  // 获取基础统计数据
-  IotStatApi.getDeviceCount().then((res) => {
-    device.value = res
-  })
-  IotStatApi.getMaintainCount().then((res) => {
-    maintain.value = res
-  })
-  IotStatApi.getMainWorkCount().then((res) => {
-    work.value = res
-  })
-  IotStatApi.getInspectCount().then((res) => {
-    inspect.value = res
-  })
-  IotStatApi.getMaintenanceStatus().then((res) => {
-    status.value = res
-    initCharts()
-  })
-  IotStatApi.getMaintenanceTodayStatus().then((res) => {
-    todayStatus.value = res
-    initCharts()
-  })
-  IotStatApi.getDeviceStatusCount().then((res) => {
-    typeData.value = res
-    initCharts()
-  })
-  IotStatApi.getSafeCount().then((res) => {
-    safe.value = res
-  })
-  IotStatApi.getMttr().then((res) => {
-    mttr.value = res
-  })
-  IotStatApi.getOrderSeven('156').then((res) => {
-    orderSevenData.value = res
-    initQxChart()
-  })
-
-  IotStatApi.getMaintenanceStatus().then((res) => {
-    by.value = res
-  })
-  const fillQueryParams = reactive({
-    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-    endTime: Date.now(), // 设置默认结束时间为当前时间
-    createTime: [],
-    deptId: null, // 选中的部门ID
-    status: null // 填写状态
-  })
-  IotStatApi.getInspectStatuss(fillQueryParams, '156').then((res) => {
-    inspectt.value = res
-  })
-  fillQueryParams.deptId = '156'
-  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
-    fill.value = res.totalList[0] || []
-  })
-}
-
-/** 初始化图表 */
-const initCharts = () => {
-  // 设备数量统计
-  echarts.init(statusChartRef.value).setOption({
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      top: '5%',
-      right: '10%',
-      align: 'left',
-      orient: 'vertical',
-      icon: 'circle',
-      textStyle: {
-        color: 'white'
-      }
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['50%', '80%'],
-        avoidLabelOverlap: false,
-        center: ['30%', '50%'],
-        label: {
-          show: false,
-          position: 'outside',
-          color: 'white'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 20,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: false
-        },
-        data: typeData.value
-      }
-    ]
-  })
-  //待执行
-  // initGaugeChart(
-  //   writeTodayChartRef.value,
-  //   todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
-  //   '#05b'
-  // )
-  // //已执行
-  // initGaugeChart(
-  //   finishedTodayChartRef.value,
-  //   todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
-  //   '#f50'
-  // )
-  // // 待执行
-  // initGaugeChart(
-  //   writeChartRef.value,
-  //   status.value.todo === undefined ? 0 : status.value.todo,
-  //   '#05b'
-  // )
-  // //已执行
-  // initGaugeChart(
-  //   finishedChartRef.value,
-  //   status.value.finished === undefined ? 0 : status.value.finished,
-  //   '#f50'
-  // )
-  // 消息量统计
-  //initMessageChart()
-}
-
-/** 初始化仪表盘图表 */
-const initGaugeChart = (el: any, value: number, color: string) => {
-  echarts.init(el).setOption({
-    series: [
-      {
-        type: 'gauge',
-        startAngle: 360,
-        endAngle: 0,
-        min: 0,
-        max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
-        progress: {
-          show: true,
-          width: 12,
-          itemStyle: {
-            color: color
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            width: 12,
-            color: [[1, '#E5E7EB']]
-          }
-        },
-        axisTick: { show: false },
-        splitLine: { show: false },
-        axisLabel: { show: false },
-        pointer: { show: false },
-        anchor: { show: false },
-        title: { show: false },
-        detail: {
-          valueAnimation: true,
-          fontSize: 24,
-          fontWeight: 'bold',
-          fontFamily: 'Inter, sans-serif',
-          color: color,
-          offsetCenter: [0, '0'],
-          formatter: (value: number) => {
-            return `${value} `
-          }
-        },
-        data: [{ value: value }]
-      }
-    ]
-  })
-}
-
-/** 初始化消息统计图表 */
-const initMessageChart = () => {
-  // 获取所有时间戳并排序
-  // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
-  const timestamps = Array.from(
-    new Set([
-      ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
-      ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
-    ])
-  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
-
-  // 准备数据
-  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
-  const upData = timestamps.map((ts) => {
-    const item = messageStats.value.upstreamCounts.find(
-      (count) => Number(Object.keys(count)[0]) === ts
-    )
-    return item ? Object.values(item)[0] : 0
-  })
-  const downData = timestamps.map((ts) => {
-    const item = messageStats.value.downstreamCounts.find(
-      (count) => Number(Object.keys(count)[0]) === ts
-    )
-    return item ? Object.values(item)[0] : 0
-  })
-
-  // 配置图表
-  echarts.init(deviceMessageCountChartRef.value).setOption({
-    tooltip: {
-      trigger: 'axis',
-      backgroundColor: 'rgba(255, 255, 255, 0.9)',
-      borderColor: '#E5E7EB',
-      textStyle: {
-        color: '#374151'
-      }
-    },
-    legend: {
-      data: ['上行消息量', '下行消息量'],
-      textStyle: {
-        color: '#374151',
-        fontWeight: 500
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: xdata,
-      axisLine: {
-        lineStyle: {
-          color: '#E5E7EB'
-        }
-      },
-      axisLabel: {
-        color: '#6B7280'
-      }
-    },
-    yAxis: {
-      type: 'value',
-      axisLine: {
-        lineStyle: {
-          color: '#E5E7EB'
-        }
-      },
-      axisLabel: {
-        color: '#6B7280'
-      },
-      splitLine: {
-        lineStyle: {
-          color: '#F3F4F6'
-        }
-      }
-    },
-    series: [
-      {
-        name: '上行消息量',
-        type: 'line',
-        smooth: true, // 添加平滑曲线
-        data: upData,
-        itemStyle: {
-          color: '#3B82F6'
-        },
-        lineStyle: {
-          width: 2
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
-            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
-          ])
-        }
-      },
-      {
-        name: '下行消息量',
-        type: 'line',
-        smooth: true, // 添加平滑曲线
-        data: downData,
-        itemStyle: {
-          color: '#10B981'
-        },
-        lineStyle: {
-          width: 2
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
-            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
-          ])
-        }
-      }
-    ]
-  })
-}
-
-const chartContainer = ref(null)
-let chartInstance = null
-
-// 生成过去12个月份的标签 (格式: YYYY-MM)
-const generateMonthLabels = () => {
-  const months = []
-  const date = new Date()
-  for (let i = 11; i >= 0; i--) {
-    const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
-    const year = tempDate.getFullYear()
-    const month = String(tempDate.getMonth() + 1).padStart(2, '0')
-    months.push(`${year}-${month}`)
-  }
-  return months
-}
-
-// 模拟数据获取
-const fetchChartData = async () => {
-  // 模拟异步请求
-  return new Promise((resolve) => {
-    setTimeout(() => {
-      resolve({
-        months: generateMonthLabels(),
-        faults: [20, 30, 100, 40, 20, 50, 70, 80, 60, 90, 100, 100],
-        repairs: [10, 30, 90, 30, 10, 20, 60, 50, 22, 34, 70, 85]
-      })
-    }, 300)
-  })
-}
-
-// 初始化图表配置
-const initChart = async () => {
-  if (!chartContainer.value) return
-
-  // 获取数据
-  const { months, faults, repairs } = await fetchChartData()
-
-  // ECharts配置
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      },
-      formatter: (params) => {
-        return `${params[0].axisValue}<br/>
-                ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
-      }
-    },
-    legend: {
-      data: ['保养工单数量'],
-      top: 25
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: months,
-      axisLabel: {
-        rotate: 45,
-        margin: 15
-      }
-    },
-    yAxis: {
-      type: 'value',
-      axisLabel: {
-        formatter: (value) => Math.floor(value).toString()
-      }
-    },
-    series: [
-      {
-        name: '保养工单数量',
-        type: 'bar',
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#2196df' },
-            { offset: 1, color: '#2196df' }
-          ])
-        },
-        emphasis: {
-          focus: 'series'
-        },
-        data: repairs
-      }
-    ]
-  }
-
-  // 初始化图表
-  chartInstance = echarts.init(chartContainer.value)
-  chartInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleResize)
-  handleResize()
-}
-
-// 自适应调整
-const handleResize = () => {
-  chartInstance?.resize()
-}
-
-const topContainer = ref(null)
-let topInstance = null
-// 响应式容器尺寸
-const { width, height } = useElementSize(topContainer)
-// 处理数据(排序+限制5条)
-const processedData = () => {
-  const data = IotStatApi.getDeviceTypeCount()
-  backendData.value = data
-  return [...backendData.value].sort((a, b) => a.value - b.value)
-}
-
-const fetchTop = () => {
-  IotStatApi.getDeviceTypeCount().then((res) => {
-    backendData.value = res
-  })
-}
-// 初始化图表配置
-const getTopOption = () => {
-  // backendData.value = data
-  const data = backendData.value.sort((a, b) => a.value - b.value)
-  return {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'shadow' },
-      formatter: (params) => {
-        const item = params[0]
-        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
-      }
-    },
-    grid: {
-      height: '200px',
-      left: '6%',
-      right: '6%',
-      bottom: '5%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'value',
-      axisLabel: {
-        color: 'white',
-        formatter: (value) => {
-          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
-          return value.toLocaleString()
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#B6C8DA', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      }
-    },
-    yAxis: {
-      type: 'category',
-      data: data.map((item) => item.category),
-      axisTick: { show: false },
-      axisLabel: { color: '#B6C8DA' },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      }
-    },
-    series: [
-      {
-        type: 'bar',
-        data: data.map((item) => item.value),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
-            { offset: 0, color: '#83bff6' },
-            { offset: 0.7, color: '#188df0' },
-            { offset: 1, color: '#188df0' }
-          ]),
-          borderRadius: [0, 8, 8, 0]
-        },
-        label: {
-          show: true,
-          position: 'right',
-          formatter: '{@value}',
-          color: 'white',
-          fontWeight: 'bold'
-        },
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowColor: 'rgba(0, 0, 0, 0.5)'
-          }
-        }
-      }
-    ]
-  }
-}
-
-// 初始化图表
-const initTopChart = async () => {
-  await IotStatApi.getDeviceTypeCount('yf').then((res) => {
-    backendData.value = res
-  })
-  if (!topContainer.value) return
-  topInstance = echarts.init(topContainer.value)
-  updateTopChart()
-}
-
-// 更新图表
-const updateTopChart = () => {
-  if (!topInstance) return
-  topInstance.setOption(getTopOption())
-}
-
-// 自适应调整
-watch([width, height], () => {
-  topInstance?.resize()
-})
-
-// 监听数据变化
-watch(
-  backendData,
-  () => {
-    updateTopChart()
-  },
-  { deep: true }
-)
-
-const activeDom = ref(null)
-let activeInstance = null
-
-// 模拟后端数据结构
-// const activeData = ref([
-//   { department: '瑞恒兴域', total: 356, active: 278 },
-//   { department: '瑞鹰国际', total: 284, active: 192 },
-//   { department: '四川瑞都', total: 432, active: 325 },
-//   { department: '运营中心', total: 157, active: 89 }
-// ])
-const activeData = ref([])
-const initActiveChart = async (total: any, active: any, people: any) => {
-  if (!activeDom.value) return
-  activeData.value = await IotStatApi.getDeptCount()
-  activeInstance = echarts.init(activeDom.value)
-
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'shadow' }, //:ml-citation{ref="1,7" data="citationList"}
-      formatter: (params) => `
-        ${params[0].name}<br/>
-        ${params[0].marker} ${total}: ${params[0].value}<br/>
-        ${params[1].marker} ${active}: ${params[1].value}
-      `
-    },
-    legend: {
-      data: [total, active],
-      top: 30,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true //:ml-citation{ref="2,7" data="citationList"}
-    },
-    xAxis: {
-      type: 'category',
-      data: activeData.value.map((item) => item.department),
-      axisLabel: {
-        color: '#B6C8DA',
-        interval: 0,
-        rotate: 0 //:ml-citation{ref="5" data="citationList"}
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: people,
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    series: [
-      {
-        name: total,
-        type: 'bar',
-        data: activeData.value.map((item) => item.total),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#5470c6' },
-            { offset: 1, color: '#83bff6' }
-          ])
-        },
-        barWidth: 30
-      },
-      {
-        name: active,
-        type: 'bar',
-        data: activeData.value.map((item) => item.active),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#91cc75' },
-            { offset: 1, color: '#e6f4d2' }
-          ])
-        },
-        barWidth: 30
-      }
-    ]
-  }
-
-  activeInstance.setOption(option)
-}
-
-const qxRef = ref(null)
-let qxInstance = null
-
-// 生成近12个月份 (包含当年和去年)
-const generateMonths = () => {
-  const months = []
-  const date = new Date()
-  date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
-
-  for (let i = 0; i < 12; i++) {
-    date.setMonth(date.getMonth() - 1)
-    months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
-  }
-  return months.reverse()
-}
-
-const initQxChart = () => {
-  if (!qxRef.value) return
-  qxInstance = echarts.init(qxRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'cross',
-        label: {
-          backgroundColor: '#6a7985'
-        }
-      }
-    },
-    legend: {
-      data: orderSevenData.value.series.map((item) => item.name),
-      top: 30,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: orderSevenData.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: [
-      {
-        type: 'value',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'left' // 左侧 Y 轴
-      },
-      {
-        type: 'value',
-        axisLabel: {
-          formatter: '{value}'
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'right', // 右侧 Y 轴
-        splitLine: {
-          show: false // 隐藏右侧 Y 轴的分割线
-        }
-      }
-    ],
-    series: orderSevenData.value.series.map((item, index) => {
-      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
-      const yAxisIndex = index < 2 ? 0 : 1
-      return {
-        name: item.name,
-        type: 'line',
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 8,
-        itemStyle: {
-          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(84,112,198,0.4)' },
-            { offset: 1, color: 'rgba(84,112,198,0.1)' }
-          ])
-        },
-        data: item.data,
-        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
-      }
-    })
-  }
-
-  qxInstance.setOption(option)
-}
-
-// 响应式调整
-const resizeQxChart = () => qxInstance?.resize()
-/** 初始化 */
 onMounted(() => {
-  getStats()
-  // initChart()
-  initTopChart()
-  const localeStore = useLocaleStore()
-  const lang = localeStore.getCurrentLocale.lang
-  if (lang === 'zh-CN') {
-    initActiveChart('总人数', '活跃人数', '人数')
-  } else if (lang === 'en') {
-    initActiveChart('totalPeople', 'activePeople', 'count')
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
   }
-  initMyoption()
 
-  // initQxChart()
-  window.addEventListener('resize', resizeQxChart)
-  // fetchTop()
-  window.addEventListener('resize', () => topInstance?.resize())
+  window.addEventListener('resize', updateScale)
 })
-onBeforeUnmount(() => {
-  chartInstance?.dispose()
-  window.removeEventListener('resize', () => chartInstance?.resize())
-  topInstance?.dispose()
-  window.removeEventListener('resize', handleResize)
-  qxInstance?.dispose()
-  window.removeEventListener('resize', resizeQxChart)
+
+onUnmounted(() => {
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
 })
 </script>
-
+<template>
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="bg" :style="targetAreaStyle">
+        <header class="header">{{ company }}</header>
+        <div class="mt-3 px-5">
+          <hsummary class="kb-stage-card kb-stage-card--1" />
+          <div class="w-full h-96 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
+            <hmttr class="kb-stage-card kb-stage-card--2" />
+
+            <hdeviceType class="row-span-2 kb-stage-card kb-stage-card--4" />
+            <dayfinish class="row-span-2 kb-stage-card kb-stage-card--5" />
+            <hsafe class="kb-stage-card kb-stage-card--3" />
+          </div>
+          <div class="w-full h-107 grid grid-cols-2 gap-3 mt-3">
+            <hdeviceStatus class="kb-stage-card kb-stage-card--6" />
+            <horderTrend class="kb-stage-card kb-stage-card--7" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
 <style lang="scss" scoped>
-.page-container {
-  background-color: #3a6fa3;
-  min-height: 90vh;
-  padding: 20px;
-}
-
-.summary {
-  margin-bottom: 20px;
-}
-
-::v-deep .chart-card {
-  background-color: rgba(0, 0, 0, 0.3);
-  border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
-  transition: all 0.3s ease;
-  border: none;
-
-  &:hover {
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
-  }
-}
-
-.safety-days-card {
-  .safety-days-content {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 150px;
-    position: relative;
-
-    .days-number {
-      font-size: 58px;
-      font-weight: bold;
-      color: darkorange;
-      line-height: 1;
-      transition: all 0.3s ease;
-    }
-
-    .days-number:hover {
-      transform: scale(1.05);
-    }
-
-    .days-label {
-      font-size: 20px;
-      color: white;
-      margin-top: 8px;
-    }
-
-    .safety-desc {
-      font-size: 14px;
-      color: #999;
-      position: absolute;
-      bottom: 10px;
-      text-align: center;
-      width: 90%;
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .page-container {
-    padding: 10px;
-  }
-}
-::v-deep .el-card__header {
-  border-bottom: none !important;
-  padding-bottom: 0;
-}
+@import url('@/styles/kb.scss');
 </style>

+ 1371 - 0
src/views/Home/backup/Index-a.vue

@@ -0,0 +1,1371 @@
+<template>
+  <!-- 第一行:统计卡片行 -->
+  <div class="page-container">
+    <el-row :gutter="16" class="summary">
+      <!-- 原有的统计卡片部分保持不变 -->
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="device.total || 0"
+          icon="fa-solid:project-diagram"
+          icon-bg-color="text-blue-500"
+          icon-color="bg-blue-100"
+          :title="t('stat.deviceCount')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="maintain.total || 0"
+          icon="fa-solid:list"
+          icon-bg-color="text-pink-500"
+          icon-color="bg-blue-100"
+          :title="t('stat.maintenanceOrder')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="fill.unfilledCount || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          :title="t('stat.operationNotFilled')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="fill.filledCount || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          :title="t('stat.operationFilled')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="by.todo || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          :title="t('stat.notMaintained')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="by.finished || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          :title="t('stat.maintained')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectt.todo || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.notInspected')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectt.finished || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.inspected')"
+        />
+      </el-col>
+      <!-- 其他统计卡片... -->
+    </el-row>
+    <!--  <el-row :gutter="16" class="mb-4">-->
+    <!--    <el-col :span="6">-->
+    <!--      <el-card class="stat-card" shadow="never">-->
+    <!--        <div class="flex flex-col">-->
+    <!--          <div class="flex justify-between items-center mb-1">-->
+    <!--            <span class="text-gray-500 text-base font-medium">设备数量</span>-->
+    <!--            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
+    <!--          </div>-->
+    <!--          <span class="text-3xl font-bold text-gray-700">-->
+    <!--            {{ device.total }}-->
+    <!--          </span>-->
+    <!--          <el-divider class="my-2" />-->
+    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+    <!--            <span>今日新增</span>-->
+    <!--            <span class="text-green-500">+{{ device.today }}</span>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </el-col>-->
+    <!--    <el-col :span="6">-->
+    <!--      <el-card class="stat-card" shadow="never">-->
+    <!--        <div class="flex flex-col">-->
+    <!--          <div class="flex justify-between items-center mb-1">-->
+    <!--            <span class="text-gray-500 text-base font-medium">维修工单数量</span>-->
+    <!--            <Icon icon="ep:box" class="text-[32px] text-orange-400" />-->
+    <!--          </div>-->
+    <!--          <span class="text-3xl font-bold text-gray-700">{{ maintain.total }}</span>-->
+    <!--          <el-divider class="my-2" />-->
+    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+    <!--            <span>今日新增</span>-->
+    <!--            <span class="text-green-500">+{{ maintain.today }}</span>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </el-col>-->
+    <!--    <el-col :span="6">-->
+    <!--      <el-card class="stat-card" shadow="never">-->
+    <!--        <div class="flex flex-col">-->
+    <!--          <div class="flex justify-between items-center mb-1">-->
+    <!--            <span class="text-gray-500 text-base font-medium">保养工单数量</span>-->
+    <!--            <Icon icon="ep:cpu" class="text-[32px] text-purple-400" />-->
+    <!--          </div>-->
+    <!--          <span class="text-3xl font-bold text-gray-700">{{ work.total }}</span>-->
+    <!--          <el-divider class="my-2" />-->
+    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+    <!--            <span>今日新增</span>-->
+    <!--            <span class="text-green-500">+{{ work.today }}</span>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </el-col>-->
+    <!--    <el-col :span="6">-->
+    <!--      <el-card class="stat-card" shadow="never">-->
+    <!--        <div class="flex flex-col">-->
+    <!--          <div class="flex justify-between items-center mb-1">-->
+    <!--            <span class="text-gray-500 text-base font-medium">巡检工单数量</span>-->
+    <!--            <Icon icon="ep:camera" class="text-[32px] text-teal-400" />-->
+    <!--          </div>-->
+    <!--          <span class="text-3xl font-bold text-gray-700">-->
+    <!--            {{ inspect.total }}-->
+    <!--          </span>-->
+    <!--          <el-divider class="my-2" />-->
+    <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+    <!--            <span>今日新增</span>-->
+    <!--            <span class="text-green-500">+{{ inspect.today }}</span>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </el-card>-->
+    <!--    </el-col>-->
+    <!--  </el-row>-->
+
+    <!-- 第二行:图表行 -->
+    <el-row :gutter="16" class="mb-4">
+      <el-col :span="6">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium" style="color: #b6c8da">{{ t('stat.mttr') }}</span>
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.materialsUnderInventory')
+              }}</span>
+            </div>
+          </template>
+          <div class="flex flex-col h-[250px]">
+            <!--          <div class="flex justify-between items-center text-gray-400">-->
+            <!--            <span>MTTR</span>-->
+
+            <!--          </div>-->
+            <!--          <el-divider />-->
+            <div class="flex justify-between items-center mb-1 mt-15">
+              <!--            <span class="text-gray-500 text-base font-medium">平均解决时间</span>-->
+              <!--            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
+              <span class="text-5xl font-bold text-gray-700" style="color: lightseagreen">
+                {{ mttr + 'h' }}
+              </span>
+              <span class="text-5xl font-bold text-gray-700" style="color: indianred">
+                {{ safe }}
+              </span>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="9">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.deviceStatus')
+              }}</span>
+            </div>
+          </template>
+          <div ref="statusChartRef" class="h-[250px]"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="9">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.deviceClassifyTop5')
+              }}</span>
+            </div>
+          </template>
+          <div ref="topContainer" class="h-[250px]"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 第三行:消息统计行 -->
+    <el-row :gutter="16" class="mb-4">
+      <el-col :span="8">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium" style="color: #b6c8da">日报完成率</span>
+              <div>
+                <el-date-picker
+                  v-model="completeRateParams.createTime"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  type="daterange"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+                  class="!w-200px"
+                  @change="handleDateChange"
+                  size="small"
+                />
+              </div>
+            </div>
+          </template>
+          <!-- <div ref="activeDom" class="h-[320px]"></div> -->
+          <Echart :options="myoption" :height="320" />
+        </el-card>
+      </el-col>
+      <el-col :span="16">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.orderCount')
+              }}</span>
+            </div>
+          </template>
+          <div ref="qxRef" class="h-[320px]"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts'; // 显式导入柱状图模块
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { useElementSize } from '@vueuse/core'
+import {
+  IotStatisticsDeviceMessageSummaryRespVO,
+  IotStatisticsSummaryRespVO
+} from '@/api/iot/statistics'
+
+import { IotStatApi } from '@/api/pms/stat'
+import SummaryCard from '@/components/SummaryCard/index.vue'
+import { reactive, ref } from 'vue'
+import { useLocaleStore } from '@/store/modules/locale'
+import Echart from '@/components/Echart/src/Echart.vue'
+
+// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
+
+/** IoT 首页 */
+defineOptions({ name: 'IoTHome' })
+
+// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+
+const myoption = ref<any>({
+  xAxis: {
+    type: 'category',
+    data: ['四川瑞都', '瑞恒兴域', '瑞鹰国际'],
+    // 文字颜色
+    axisLabel: {
+      color: '#fff'
+    }
+  },
+
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985'
+      }
+    },
+    formatter: function (params: any) {
+      const p = params && params[0] ? params[0] : params
+      return `${p.name}: ${p.data}%`
+    }
+  },
+
+  legend: {
+    top: '5%',
+    right: '10%',
+    align: 'left',
+    orient: 'vertical',
+    icon: 'circle',
+    textStyle: {
+      color: 'white'
+    }
+  },
+  yAxis: {
+    type: 'value',
+    axisLabel: {
+      color: '#fff',
+      formatter: '{value}%'
+    }
+  },
+  series: [
+    {
+      data: [],
+      type: 'bar'
+    }
+  ]
+})
+const completeRateParams = reactive({
+  createTime: []
+})
+
+// 计算近一周时间
+const end = new Date()
+const start = new Date()
+
+const now = new Date()
+// 构造:今年 1 月 1 日 00:00:00
+const firstDay = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
+// 转时间戳(毫秒),如需秒级时间戳,加 .getTime() / 1000
+
+start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
+
+// 格式化日期为后端需要的格式
+const formatDate = (date): string => {
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+const initMyoption = async () => {
+  // 只在初始化时设置默认值,不覆盖已有值
+  if (!completeRateParams.createTime || completeRateParams.createTime.length === 0) {
+    const start = new Date()
+    start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
+
+    const end = new Date()
+
+    // 将开始时间设置为当天的 00:00:00
+    const startTime = new Date(start)
+    startTime.setHours(0, 0, 0, 0)
+
+    // 将结束时间设置为当天的 23:59:59
+    const endTime = new Date(end)
+    endTime.setHours(23, 59, 59, 999)
+
+    // 格式化为 YYYY-MM-DD HH:mm:ss
+    const formatDate = (date) => {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      const hours = String(date.getHours()).padStart(2, '0')
+      const minutes = String(date.getMinutes()).padStart(2, '0')
+      const seconds = String(date.getSeconds()).padStart(2, '0')
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+    }
+
+    completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
+  }
+
+  const res = await IotStatApi.getCompleteRate(completeRateParams)
+
+  const list = []
+  res.forEach((item) => {
+    // 后端返回 rate 为小数(如 0.85),这里转换为整数百分比(如 85)以便图表显示
+    if (item.department === '瑞恒兴域') {
+      list.push(Math.trunc(Number(item['瑞恒兴域']) * 100))
+    } else {
+      list.push(Math.trunc(Number(item.rate) * 100))
+    }
+  })
+
+  myoption.value.series[0].data = list
+}
+
+const handleDateChange = (val) => {
+  if (!val || val.length !== 2) return
+
+  const [start, end] = val
+
+  // 将开始时间设置为当天的 00:00:00
+  const startTime = new Date(start)
+  startTime.setHours(0, 0, 0, 0)
+
+  // 将结束时间设置为当天的 23:59:59
+  const endTime = new Date(end)
+  endTime.setHours(23, 59, 59, 999)
+
+  // 格式化为 YYYY-MM-DD HH:mm:ss
+  const formatDate = (date) => {
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+  }
+
+  completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
+  initMyoption()
+}
+
+const timeRange = ref('7d') // 修改默认选择为近一周
+const dateRange = ref<[Date, Date] | null>(null)
+
+const queryParams = reactive({
+  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+  endTime: Date.now() // 设置默认结束时间为当前时间
+})
+const backendData = ref([])
+const statusChartRef = ref() // 设备数量统计的图表
+const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
+const { t } = useI18n() // 国际化
+// 基础统计数据
+// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
+const statsData = ref<IotStatisticsSummaryRespVO>({
+  productCategoryCount: 0,
+  productCount: 0,
+  deviceCount: 0,
+  deviceMessageCount: 0,
+  productCategoryTodayCount: 0,
+  productTodayCount: 0,
+  deviceTodayCount: 0,
+  deviceMessageTodayCount: 0,
+  deviceOnlineCount: 0,
+  deviceOfflineCount: 0,
+  deviceInactiveCount: 0,
+  productCategoryDeviceCounts: {}
+})
+const orderSevenData = ref({})
+const by = ref({
+  todo: undefined,
+  finished: undefined
+})
+const fill = ref({
+  filledCount: undefined,
+  unfilledCount: undefined
+})
+const inspectt = ref({
+  finished: 0,
+  todo: 0
+})
+const device = ref({
+  total: undefined,
+  today: undefined
+})
+const maintain = ref({
+  total: undefined,
+  today: undefined
+})
+const work = ref({
+  total: undefined,
+  today: undefined
+})
+const inspect = ref({
+  total: undefined,
+  today: undefined
+})
+
+const status = ref({
+  finished: 0,
+  todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+const typeData = ref({})
+// 消息统计数据
+const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
+  upstreamCounts: {},
+  downstreamCounts: {}
+})
+const mttr = ref(0)
+const safe = ref()
+/** 获取统计数据 */
+const getStats = async () => {
+  // 获取基础统计数据
+  IotStatApi.getDeviceCount().then((res) => {
+    device.value = res
+  })
+  IotStatApi.getMaintainCount().then((res) => {
+    maintain.value = res
+  })
+  IotStatApi.getMainWorkCount().then((res) => {
+    work.value = res
+  })
+  IotStatApi.getInspectCount().then((res) => {
+    inspect.value = res
+  })
+  IotStatApi.getMaintenanceStatus().then((res) => {
+    status.value = res
+    initCharts()
+  })
+  IotStatApi.getMaintenanceTodayStatus().then((res) => {
+    todayStatus.value = res
+    initCharts()
+  })
+  IotStatApi.getDeviceStatusCount().then((res) => {
+    typeData.value = res
+    initCharts()
+  })
+  IotStatApi.getSafeCount().then((res) => {
+    safe.value = res
+  })
+  IotStatApi.getMttr().then((res) => {
+    mttr.value = res
+  })
+  IotStatApi.getOrderSeven('156').then((res) => {
+    orderSevenData.value = res
+    initQxChart()
+  })
+
+  IotStatApi.getMaintenanceStatus().then((res) => {
+    by.value = res
+  })
+  const fillQueryParams = reactive({
+    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+    endTime: Date.now(), // 设置默认结束时间为当前时间
+    createTime: [],
+    deptId: null, // 选中的部门ID
+    status: null // 填写状态
+  })
+  IotStatApi.getInspectStatuss(fillQueryParams, '156').then((res) => {
+    inspectt.value = res
+  })
+  fillQueryParams.deptId = '156'
+  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
+    fill.value = res.totalList[0] || []
+  })
+}
+
+/** 初始化图表 */
+const initCharts = () => {
+  // 设备数量统计
+  echarts.init(statusChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      top: '5%',
+      right: '10%',
+      align: 'left',
+      orient: 'vertical',
+      icon: 'circle',
+      textStyle: {
+        color: 'white'
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['30%', '50%'],
+        label: {
+          show: false,
+          position: 'outside',
+          color: 'white'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 20,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: typeData.value
+      }
+    ]
+  })
+  //待执行
+  // initGaugeChart(
+  //   writeTodayChartRef.value,
+  //   todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
+  //   '#05b'
+  // )
+  // //已执行
+  // initGaugeChart(
+  //   finishedTodayChartRef.value,
+  //   todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
+  //   '#f50'
+  // )
+  // // 待执行
+  // initGaugeChart(
+  //   writeChartRef.value,
+  //   status.value.todo === undefined ? 0 : status.value.todo,
+  //   '#05b'
+  // )
+  // //已执行
+  // initGaugeChart(
+  //   finishedChartRef.value,
+  //   status.value.finished === undefined ? 0 : status.value.finished,
+  //   '#f50'
+  // )
+  // 消息量统计
+  //initMessageChart()
+}
+
+/** 初始化仪表盘图表 */
+const initGaugeChart = (el: any, value: number, color: string) => {
+  echarts.init(el).setOption({
+    series: [
+      {
+        type: 'gauge',
+        startAngle: 360,
+        endAngle: 0,
+        min: 0,
+        max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
+        progress: {
+          show: true,
+          width: 12,
+          itemStyle: {
+            color: color
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            width: 12,
+            color: [[1, '#E5E7EB']]
+          }
+        },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+        pointer: { show: false },
+        anchor: { show: false },
+        title: { show: false },
+        detail: {
+          valueAnimation: true,
+          fontSize: 24,
+          fontWeight: 'bold',
+          fontFamily: 'Inter, sans-serif',
+          color: color,
+          offsetCenter: [0, '0'],
+          formatter: (value: number) => {
+            return `${value} `
+          }
+        },
+        data: [{ value: value }]
+      }
+    ]
+  })
+}
+
+/** 初始化消息统计图表 */
+const initMessageChart = () => {
+  // 获取所有时间戳并排序
+  // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
+  const timestamps = Array.from(
+    new Set([
+      ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
+      ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
+    ])
+  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
+
+  // 准备数据
+  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
+  const upData = timestamps.map((ts) => {
+    const item = messageStats.value.upstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+  const downData = timestamps.map((ts) => {
+    const item = messageStats.value.downstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+
+  // 配置图表
+  echarts.init(deviceMessageCountChartRef.value).setOption({
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#E5E7EB',
+      textStyle: {
+        color: '#374151'
+      }
+    },
+    legend: {
+      data: ['上行消息量', '下行消息量'],
+      textStyle: {
+        color: '#374151',
+        fontWeight: 500
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xdata,
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#F3F4F6'
+        }
+      }
+    },
+    series: [
+      {
+        name: '上行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: upData,
+        itemStyle: {
+          color: '#3B82F6'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
+            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
+          ])
+        }
+      },
+      {
+        name: '下行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: downData,
+        itemStyle: {
+          color: '#10B981'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
+            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+const chartContainer = ref(null)
+let chartInstance = null
+
+// 生成过去12个月份的标签 (格式: YYYY-MM)
+const generateMonthLabels = () => {
+  const months = []
+  const date = new Date()
+  for (let i = 11; i >= 0; i--) {
+    const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
+    const year = tempDate.getFullYear()
+    const month = String(tempDate.getMonth() + 1).padStart(2, '0')
+    months.push(`${year}-${month}`)
+  }
+  return months
+}
+
+// 模拟数据获取
+const fetchChartData = async () => {
+  // 模拟异步请求
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        months: generateMonthLabels(),
+        faults: [20, 30, 100, 40, 20, 50, 70, 80, 60, 90, 100, 100],
+        repairs: [10, 30, 90, 30, 10, 20, 60, 50, 22, 34, 70, 85]
+      })
+    }, 300)
+  })
+}
+
+// 初始化图表配置
+const initChart = async () => {
+  if (!chartContainer.value) return
+
+  // 获取数据
+  const { months, faults, repairs } = await fetchChartData()
+
+  // ECharts配置
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      formatter: (params) => {
+        return `${params[0].axisValue}<br/>
+                ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
+      }
+    },
+    legend: {
+      data: ['保养工单数量'],
+      top: 25
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: months,
+      axisLabel: {
+        rotate: 45,
+        margin: 15
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: {
+        formatter: (value) => Math.floor(value).toString()
+      }
+    },
+    series: [
+      {
+        name: '保养工单数量',
+        type: 'bar',
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#2196df' },
+            { offset: 1, color: '#2196df' }
+          ])
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: repairs
+      }
+    ]
+  }
+
+  // 初始化图表
+  chartInstance = echarts.init(chartContainer.value)
+  chartInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleResize)
+  handleResize()
+}
+
+// 自适应调整
+const handleResize = () => {
+  chartInstance?.resize()
+}
+
+const topContainer = ref(null)
+let topInstance = null
+// 响应式容器尺寸
+const { width, height } = useElementSize(topContainer)
+// 处理数据(排序+限制5条)
+const processedData = () => {
+  const data = IotStatApi.getDeviceTypeCount()
+  backendData.value = data
+  return [...backendData.value].sort((a, b) => a.value - b.value)
+}
+
+const fetchTop = () => {
+  IotStatApi.getDeviceTypeCount().then((res) => {
+    backendData.value = res
+  })
+}
+// 初始化图表配置
+const getTopOption = () => {
+  // backendData.value = data
+  const data = backendData.value.sort((a, b) => a.value - b.value)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: (params) => {
+        const item = params[0]
+        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
+      }
+    },
+    grid: {
+      height: '200px',
+      left: '6%',
+      right: '6%',
+      bottom: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'value',
+      axisLabel: {
+        color: 'white',
+        formatter: (value) => {
+          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
+          return value.toLocaleString()
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#B6C8DA', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: data.map((item) => item.category),
+      axisTick: { show: false },
+      axisLabel: { color: '#B6C8DA' },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        data: data.map((item) => item.value),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: '#83bff6' },
+            { offset: 0.7, color: '#188df0' },
+            { offset: 1, color: '#188df0' }
+          ]),
+          borderRadius: [0, 8, 8, 0]
+        },
+        label: {
+          show: true,
+          position: 'right',
+          formatter: '{@value}',
+          color: 'white',
+          fontWeight: 'bold'
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+}
+
+// 初始化图表
+const initTopChart = async () => {
+  await IotStatApi.getDeviceTypeCount('yf').then((res) => {
+    backendData.value = res
+  })
+  if (!topContainer.value) return
+  topInstance = echarts.init(topContainer.value)
+  updateTopChart()
+}
+
+// 更新图表
+const updateTopChart = () => {
+  if (!topInstance) return
+  topInstance.setOption(getTopOption())
+}
+
+// 自适应调整
+watch([width, height], () => {
+  topInstance?.resize()
+})
+
+// 监听数据变化
+watch(
+  backendData,
+  () => {
+    updateTopChart()
+  },
+  { deep: true }
+)
+
+const activeDom = ref(null)
+let activeInstance = null
+
+// 模拟后端数据结构
+// const activeData = ref([
+//   { department: '瑞恒兴域', total: 356, active: 278 },
+//   { department: '瑞鹰国际', total: 284, active: 192 },
+//   { department: '四川瑞都', total: 432, active: 325 },
+//   { department: '运营中心', total: 157, active: 89 }
+// ])
+const activeData = ref([])
+const initActiveChart = async (total: any, active: any, people: any) => {
+  if (!activeDom.value) return
+  activeData.value = await IotStatApi.getDeptCount()
+  activeInstance = echarts.init(activeDom.value)
+
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' }, //:ml-citation{ref="1,7" data="citationList"}
+      formatter: (params) => `
+        ${params[0].name}<br/>
+        ${params[0].marker} ${total}: ${params[0].value}<br/>
+        ${params[1].marker} ${active}: ${params[1].value}
+      `
+    },
+    legend: {
+      data: [total, active],
+      top: 30,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true //:ml-citation{ref="2,7" data="citationList"}
+    },
+    xAxis: {
+      type: 'category',
+      data: activeData.value.map((item) => item.department),
+      axisLabel: {
+        color: '#B6C8DA',
+        interval: 0,
+        rotate: 0 //:ml-citation{ref="5" data="citationList"}
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: people,
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    series: [
+      {
+        name: total,
+        type: 'bar',
+        data: activeData.value.map((item) => item.total),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#5470c6' },
+            { offset: 1, color: '#83bff6' }
+          ])
+        },
+        barWidth: 30
+      },
+      {
+        name: active,
+        type: 'bar',
+        data: activeData.value.map((item) => item.active),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#91cc75' },
+            { offset: 1, color: '#e6f4d2' }
+          ])
+        },
+        barWidth: 30
+      }
+    ]
+  }
+
+  activeInstance.setOption(option)
+}
+
+const qxRef = ref(null)
+let qxInstance = null
+
+// 生成近12个月份 (包含当年和去年)
+const generateMonths = () => {
+  const months = []
+  const date = new Date()
+  date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
+
+  for (let i = 0; i < 12; i++) {
+    date.setMonth(date.getMonth() - 1)
+    months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
+  }
+  return months.reverse()
+}
+
+const initQxChart = () => {
+  if (!qxRef.value) return
+  qxInstance = echarts.init(qxRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: orderSevenData.value.series.map((item) => item.name),
+      top: 30,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: orderSevenData.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'right', // 右侧 Y 轴
+        splitLine: {
+          show: false // 隐藏右侧 Y 轴的分割线
+        }
+      }
+    ],
+    series: orderSevenData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      const yAxisIndex = index < 2 ? 0 : 1
+      return {
+        name: item.name,
+        type: 'line',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
+      }
+    })
+  }
+
+  qxInstance.setOption(option)
+}
+
+// 响应式调整
+const resizeQxChart = () => qxInstance?.resize()
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  // initChart()
+  initTopChart()
+  const localeStore = useLocaleStore()
+  const lang = localeStore.getCurrentLocale.lang
+  if (lang === 'zh-CN') {
+    initActiveChart('总人数', '活跃人数', '人数')
+  } else if (lang === 'en') {
+    initActiveChart('totalPeople', 'activePeople', 'count')
+  }
+  initMyoption()
+
+  // initQxChart()
+  window.addEventListener('resize', resizeQxChart)
+  // fetchTop()
+  window.addEventListener('resize', () => topInstance?.resize())
+})
+onBeforeUnmount(() => {
+  chartInstance?.dispose()
+  window.removeEventListener('resize', () => chartInstance?.resize())
+  topInstance?.dispose()
+  window.removeEventListener('resize', handleResize)
+  qxInstance?.dispose()
+  window.removeEventListener('resize', resizeQxChart)
+})
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 90vh;
+  padding: 20px;
+  background-color: #3a6fa3;
+}
+
+.summary {
+  margin-bottom: 20px;
+}
+
+::v-deep .chart-card {
+  background-color: rgb(0 0 0 / 30%);
+  border: none;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgb(0 0 0 / 50%);
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
+  }
+}
+
+.safety-days-card {
+  .safety-days-content {
+    position: relative;
+    display: flex;
+    height: 150px;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    .days-number {
+      font-size: 58px;
+      font-weight: bold;
+      line-height: 1;
+      color: darkorange;
+      transition: all 0.3s ease;
+    }
+
+    .days-number:hover {
+      transform: scale(1.05);
+    }
+
+    .days-label {
+      margin-top: 8px;
+      font-size: 20px;
+      color: white;
+    }
+
+    .safety-desc {
+      position: absolute;
+      bottom: 10px;
+      width: 90%;
+      font-size: 14px;
+      color: #999;
+      text-align: center;
+    }
+  }
+}
+
+@media (width <= 768px) {
+  .page-container {
+    padding: 10px;
+  }
+}
+
+::v-deep .el-card__header {
+  padding-bottom: 0;
+  border-bottom: none !important;
+}
+</style>

+ 246 - 0
src/views/Home/kb/dayfinish.vue

@@ -0,0 +1,246 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { IotStatApi } from '@/api/pms/stat'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { ANIMATION, CHART_RENDERER, ChartItem, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import dayjs from 'dayjs'
+
+interface CompleteRateRow {
+  department?: string
+  rate?: number | string | null
+  [key: string]: unknown
+}
+
+const DEFAULT_TIME_RANGE = rangeShortcuts[2]
+  .value()
+  .map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+
+const createTime = ref<string[]>(DEFAULT_TIME_RANGE)
+const chartData = ref<ChartItem[]>([])
+const loading = ref(false)
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+function toPercentValue(value: unknown) {
+  const num = toNumber(value)
+  const percent = num <= 1 ? num * 100 : num
+  return Number(percent.toFixed(2))
+}
+
+function formatPercent(value: unknown) {
+  return `${toPercentValue(value).toFixed(2)}%`
+}
+
+function getRateValue(item: CompleteRateRow) {
+  const departmentRate = item.department ? item[item.department] : undefined
+  return toPercentValue(item.rate ?? departmentRate)
+}
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  const names = data.map((item) => item.name)
+  const values = data.map((item) => item.value)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 30 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      data: names,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    yAxis: {
+      type: 'value',
+      min: 0,
+      max: 100,
+      interval: 20,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return `${value}%`
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        name: '日报完成率',
+        type: 'bar',
+        data: values,
+        barWidth: 44,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: [12, 12, 0, 0],
+          shadowBlur: 12,
+          shadowColor: THEME.color.blue.bg,
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: THEME.color.blue.light },
+            { offset: 0.5, color: THEME.color.blue.mid },
+            { offset: 1, color: THEME.color.blue.line }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: THEME.color.blue.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return formatPercent(params.value)
+          }
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 18,
+            shadowColor: THEME.color.blue.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  chart.setOption(getChartOption(chartData.value || []), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+function handleDateChange() {
+  loadChart()
+}
+
+async function loadChart() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getCompleteRate({ createTime: createTime.value })
+    const list = Array.isArray(res) ? (res as CompleteRateRow[]) : []
+
+    chartData.value = list
+      .map((item) => ({
+        name: String(item.department ?? '').trim(),
+        value: getRateValue(item)
+      }))
+      .filter((item) => item.name)
+
+    renderChart()
+  } catch (error) {
+    console.error('获取日报完成率失败:', error)
+    chartData.value = []
+    renderChart()
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-12 flex items-center justify-between">
+      <div class="flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        日报完成率
+      </div>
+      <div class="w-260px! -translate-y-[2px]">
+        <el-date-picker
+          v-model="createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          :shortcuts="rangeShortcuts"
+          :clearable="false"
+          class="w-260px!"
+          @change="handleDateChange"
+        />
+      </div>
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 134 - 0
src/views/Home/kb/hdeviceStatus.vue

@@ -0,0 +1,134 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, ChartItem, createLegend, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  return {
+    ...ANIMATION,
+    grid: THEME.grid,
+    tooltip: createTooltip({ trigger: 'item' }),
+    legend: createLegend(
+      { bottom: 10, itemWidth: 12, itemHeight: 12 },
+      data.map((item) => item.name)
+    ),
+    series: [
+      {
+        name: '设备状态',
+        type: 'pie',
+        center: ['50%', '46%'],
+        radius: ['50%', '70%'],
+        minAngle: 5,
+        label: {
+          show: false,
+          position: 'center',
+          formatter(params: any) {
+            return `{name|${params.name}}\n{value|${params.value}}`
+          },
+          rich: {
+            name: {
+              color: THEME.text.regular,
+              fontSize: 14,
+              fontWeight: 500,
+              lineHeight: 24,
+              fontFamily: FONT_FAMILY
+            },
+            value: {
+              color: THEME.text.strong,
+              fontSize: 28,
+              fontWeight: 700,
+              lineHeight: 36,
+              fontFamily: FONT_FAMILY
+            }
+          }
+        },
+        emphasis: {
+          label: {
+            show: true
+          },
+          itemStyle: {
+            shadowBlur: 14,
+            shadowColor: THEME.color.blue.shadow
+          }
+        },
+        data
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getDeviceStatusCount()
+    chartData.value = Array.isArray(res) ? res : []
+    renderChart()
+  } catch (error) {
+    console.error('获取设备状态失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备状态统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 179 - 0
src/views/Home/kb/hdeviceType.vue

@@ -0,0 +1,179 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, ChartItem, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  const names = data.map((item) => item.name)
+  const values = data.map((item) => item.value)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, right: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    xAxis: {
+      type: 'value',
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: names,
+      inverse: true,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        margin: 16,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    series: [
+      {
+        name: '设备类型',
+        type: 'bar',
+        data: values,
+        barWidth: 14,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: 999,
+          shadowBlur: 12,
+          shadowColor: THEME.color.blue.bg,
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: THEME.color.blue.light },
+            { offset: 0.5, color: THEME.color.blue.mid },
+            { offset: 1, color: THEME.color.blue.line }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'right',
+          distance: 8,
+          color: THEME.color.blue.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 16,
+            shadowColor: THEME.color.blue.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getDeviceTypeCount()
+    chartData.value = Array.isArray(res)
+      ? res.map((item) => ({ name: item.category, value: item.value }))
+      : []
+    renderChart()
+  } catch (error) {
+    console.error('获取设备类型失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-12">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备类别top
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 160 - 0
src/views/Home/kb/hmttr.vue

@@ -0,0 +1,160 @@
+<script lang="ts" setup>
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+const mttr = ref(0)
+const loading = ref(false)
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+async function loadMttr() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getMttr()
+    mttr.value = toNumber(res)
+  } catch (error) {
+    console.error('获取 MTTR 失败:', error)
+    mttr.value = 0
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  loadMttr()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-10">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      MTTR(平均解决时间)
+    </div>
+
+    <div
+      class="metric-panel flex-1 min-h-0"
+      style="
+        /* stylelint-disable-next-line custom-property-empty-line-before */
+        --metric-accent: #21b7b1;
+        --metric-glow: rgb(33 183 177 / 16%);
+        --metric-border: rgb(33 183 177 / 14%);
+        --metric-tag-bg: rgb(33 183 177 / 10%);
+      "
+    >
+      <div class="metric-panel__orbit metric-panel__orbit--outer"></div>
+
+      <div class="metric-panel__top">
+        <span class="metric-panel__tag">REPAIR</span>
+      </div>
+
+      <div class="metric-panel__center">
+        <div class="metric-panel__headline">平均处理时长</div>
+        <div class="metric-panel__value-row translate-x-1">
+          <CountTo
+            class="metric-panel__value"
+            :start-val="0"
+            :end-val="mttr"
+            :duration="1200"
+            :decimals="1"
+          />
+          <span class="metric-panel__unit">h</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.metric-panel {
+  position: relative;
+  display: flex;
+  padding: 12px 12px 9px;
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.metric-panel__orbit {
+  position: absolute;
+  left: 50%;
+  border: 1px solid var(--metric-border);
+  border-radius: 999px;
+  transform: translateX(-50%);
+}
+
+.metric-panel__orbit--outer {
+  top: 20px;
+  width: 180px;
+  height: 180px;
+}
+
+.metric-panel__top,
+.metric-panel__center {
+  position: relative;
+  z-index: 1;
+}
+
+.metric-panel__tag {
+  display: inline-flex;
+  padding: 4px 10px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 14px;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: var(--metric-accent);
+  background: var(--metric-tag-bg);
+  border: 1px solid rgb(255 255 255 / 72%);
+  border-radius: 999px;
+  box-shadow: inset 0 1px 0 rgb(255 255 255 / 78%);
+}
+
+.metric-panel__center {
+  display: flex;
+  margin: auto 0;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.metric-panel__headline {
+  font-size: 16px;
+  font-weight: 600;
+  letter-spacing: 1px;
+  color: #4f678a;
+}
+
+.metric-panel__value-row {
+  display: flex;
+  margin-top: 10px;
+  align-items: flex-end;
+  justify-content: center;
+}
+
+.metric-panel__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 54px;
+  line-height: 0.92;
+  letter-spacing: 1px;
+  color: var(--metric-accent);
+  text-shadow: 0 10px 18px var(--metric-glow);
+}
+
+.metric-panel__unit {
+  padding-bottom: 7px;
+  margin-left: 4px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 22px;
+  line-height: 1;
+  color: #5b789e;
+}
+</style>

+ 212 - 0
src/views/Home/kb/horderTrend.vue

@@ -0,0 +1,212 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      }
+    }),
+    legend: createLegend(
+      { top: 5 },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatDateLabel(value)
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          lineStyle: {
+            color: THEME.split,
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'left'
+      },
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          show: false
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'right'
+      }
+    ],
+    series: seriesData.map((item, index) => {
+      const colorList = Object.values(THEME.color)
+      const color = colorList[index % colorList.length]
+      const yAxisIndex = index < 2 ? 0 : 1
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'line',
+        smooth: true,
+        data: item.data,
+        yAxisIndex,
+        symbol: 'circle',
+        symbolSize: 8,
+        showSymbol: true,
+        lineStyle: {
+          width: 2,
+          color: color.line
+        },
+        itemStyle: {
+          color: color.line
+        },
+        areaStyle: {
+          color: color.bg
+        },
+        emphasis: {
+          focus: 'series',
+          scale: true
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getOrderSeven()
+    chartData.value = {
+      xAxis: res.xAxis || [],
+      series: (res.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取近7日工单趋势失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单数量统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 149 - 0
src/views/Home/kb/hsafe.vue

@@ -0,0 +1,149 @@
+<script lang="ts" setup>
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+const safeCount = ref(0)
+const loading = ref(false)
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+async function loadSafeCount() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getSafeCount()
+    safeCount.value = toNumber(res)
+  } catch (error) {
+    console.error('获取库存预警数量失败:', error)
+    safeCount.value = 0
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  loadSafeCount()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-10">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      库存预警物料数量
+    </div>
+
+    <div
+      class="metric-panel flex-1 min-h-0"
+      style="
+        /* stylelint-disable-next-line custom-property-empty-line-before */
+        --metric-accent: #ef6b6f;
+        --metric-glow: rgb(239 107 111 / 18%);
+        --metric-border: rgb(239 107 111 / 14%);
+        --metric-tag-bg: rgb(239 107 111 / 10%);
+      "
+    >
+      <div class="metric-panel__orbit metric-panel__orbit--outer"></div>
+
+      <div class="metric-panel__top">
+        <span class="metric-panel__tag">ALERT</span>
+      </div>
+
+      <div class="metric-panel__center">
+        <div class="metric-panel__headline">当前预警物料</div>
+        <div class="metric-panel__value-row">
+          <CountTo
+            class="metric-panel__value"
+            :start-val="0"
+            :end-val="safeCount"
+            :duration="1200"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.metric-panel {
+  position: relative;
+  display: flex;
+  padding: 12px 12px 9px;
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.metric-panel__orbit {
+  position: absolute;
+  left: 50%;
+  border: 1px solid var(--metric-border);
+  border-radius: 999px;
+  transform: translateX(-50%);
+}
+
+.metric-panel__orbit--outer {
+  top: 20px;
+  width: 180px;
+  height: 180px;
+}
+
+.metric-panel__top,
+.metric-panel__center {
+  position: relative;
+  z-index: 1;
+}
+
+.metric-panel__tag {
+  display: inline-flex;
+  padding: 4px 10px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 14px;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: var(--metric-accent);
+  background: var(--metric-tag-bg);
+  border: 1px solid rgb(255 255 255 / 72%);
+  border-radius: 999px;
+  box-shadow: inset 0 1px 0 rgb(255 255 255 / 78%);
+}
+
+.metric-panel__center {
+  display: flex;
+  margin: auto 0;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.metric-panel__headline {
+  font-size: 16px;
+  font-weight: 600;
+  letter-spacing: 1px;
+  color: #4f678a;
+}
+
+.metric-panel__value-row {
+  display: flex;
+  margin-top: 10px;
+  align-items: flex-end;
+  justify-content: center;
+}
+
+.metric-panel__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 56px;
+  line-height: 0.92;
+  letter-spacing: 1px;
+  color: var(--metric-accent);
+  text-shadow: 0 10px 18px var(--metric-glow);
+}
+</style>

+ 274 - 0
src/views/Home/kb/hsummary.vue

@@ -0,0 +1,274 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { CardStateItem, SummaryCardConfig, THEME } from '@/utils/kb'
+import dayjs from 'dayjs'
+
+type CardKey =
+  | 'device'
+  | 'maintain'
+  | 'unfilledCount'
+  | 'filledCount'
+  | 'bytodo'
+  | 'byfinished'
+  | 'inspectttodo'
+  | 'inspecttfinished'
+
+type CardConfig = SummaryCardConfig<CardKey>
+
+const cardConfigs: CardConfig[] = [
+  {
+    key: 'device',
+    title: '设备数',
+    icon: 'i-material-symbols:device-hub-rounded',
+    accent: THEME.color.blue.strong,
+    glow: THEME.color.blue.glow
+  },
+  {
+    key: 'maintain',
+    title: '维修工单',
+    icon: 'i-material-symbols:home-repair-service',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'unfilledCount',
+    title: '运行未填写',
+    icon: 'i-solar:clipboard-remove-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'filledCount',
+    title: '运行已填写',
+    icon: 'i-solar:clipboard-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'bytodo',
+    title: '待保养',
+    icon: 'i-solar:shield-warning-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'byfinished',
+    title: '已保养',
+    icon: 'i-solar:shield-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'inspectttodo',
+    title: '待巡检',
+    icon: 'i-solar:map-point-search-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'inspecttfinished',
+    title: '已巡检',
+    icon: 'i-solar:check-circle-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  }
+]
+
+function createDefaultCardState(): Record<CardKey, CardStateItem> {
+  return {
+    device: { value: 0, loading: true },
+    maintain: { value: 0, loading: true },
+    unfilledCount: { value: 0, loading: true },
+    filledCount: { value: 0, loading: true },
+    bytodo: { value: 0, loading: true },
+    byfinished: { value: 0, loading: true },
+    inspectttodo: { value: 0, loading: true },
+    inspecttfinished: { value: 0, loading: true }
+  }
+}
+
+const cardState = reactive<Record<CardKey, CardStateItem>>(createDefaultCardState())
+
+const summaryCards = computed(() =>
+  cardConfigs.map((card) => ({
+    ...card,
+    value: cardState[card.key].value,
+    loading: cardState[card.key].loading
+  }))
+)
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+function setCardValue(key: CardKey, value: unknown) {
+  cardState[key].value = toNumber(value)
+}
+
+function setCardLoading(keys: CardKey[], loading: boolean) {
+  keys.forEach((key) => {
+    cardState[key].loading = loading
+  })
+}
+
+async function loadDeviceCard() {
+  const keys: CardKey[] = ['device']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getDeviceCount()
+    setCardValue('device', res?.total)
+  } catch (error) {
+    console.error('获取设备数失败:', error)
+    setCardValue('device', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainCard() {
+  const keys: CardKey[] = ['maintain']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintainCount()
+    setCardValue('maintain', res?.total)
+  } catch (error) {
+    console.error('获取维修工单失败:', error)
+    setCardValue('maintain', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadFillCards() {
+  const keys: CardKey[] = ['unfilledCount', 'filledCount']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    createTime: [],
+    deptId: 156,
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getDeptStatistics(params)
+    const totalStats = res?.totalList?.[0] ?? {}
+
+    setCardValue('unfilledCount', totalStats.unfilledCount)
+    setCardValue('filledCount', totalStats.filledCount)
+  } catch (error) {
+    console.error('获取运行填写数据失败:', error)
+    setCardValue('unfilledCount', 0)
+    setCardValue('filledCount', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainStatusCards() {
+  const keys: CardKey[] = ['bytodo', 'byfinished']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintenanceStatus()
+    setCardValue('bytodo', res?.todo)
+    setCardValue('byfinished', res?.finished)
+  } catch (error) {
+    console.error('获取保养状态失败:', error)
+    setCardValue('bytodo', 0)
+    setCardValue('byfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadInspectCards() {
+  const keys: CardKey[] = ['inspectttodo', 'inspecttfinished']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    deptId: '',
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getInspectStatuss(params, '156')
+    setCardValue('inspectttodo', res?.todo)
+    setCardValue('inspecttfinished', res?.finished)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('inspectttodo', 0)
+    setCardValue('inspecttfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+function loadAllCards() {
+  loadDeviceCard()
+  loadMaintainCard()
+  loadFillCards()
+  loadMaintainStatusCards()
+  loadInspectCards()
+}
+
+onMounted(() => {
+  loadAllCards()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-28 flex flex-col">
+    <div class="panel-title h-8">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单情况
+    </div>
+
+    <div class="grid grid-cols-8 gap-2.5 flex-1 px-2.5 py-1.5">
+      <article
+        v-for="card in summaryCards"
+        :key="card.key"
+        class="summary-card relative flex h-full overflow-hidden rounded-md items-center gap-2 p-2"
+        :style="{
+          '--card-accent': card.accent,
+          '--card-glow': card.glow
+        }"
+      >
+        <div class="summary-card__shine"></div>
+
+        <div class="summary-card__icon">
+          <div :class="card.icon" class="summary-card__icon-glyph"></div>
+        </div>
+
+        <div class="summary-card__body">
+          <div class="summary-card__label">{{ card.title }}</div>
+          <div class="summary-card__value">
+            <CountTo
+              v-if="!card.loading"
+              style="color: #1f5bb8"
+              :start-val="0"
+              :end-val="card.value"
+              :duration="1200"
+            />
+            <span v-else class="summary-card__placeholder">--</span>
+          </div>
+        </div>
+
+        <div class="summary-card__corner"></div>
+      </article>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 1354 - 0
src/views/pms/stat/backup/rhkb-a.vue

@@ -0,0 +1,1354 @@
+<template>
+  <div class="page-container">
+  <el-row :gutter="16" class="summary">
+    <!-- 原有的统计卡片部分保持不变 -->
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="device.total || 0"
+        icon="fa-solid:project-diagram"
+        icon-bg-color="text-blue-500"
+        icon-color="bg-blue-100"
+        :title="t('stat.deviceCount')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="maintain.total || 0"
+        icon="fa-solid:list"
+        icon-bg-color="text-pink-500"
+        icon-color="bg-blue-100"
+        :title="t('stat.repairOrder')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="fill.unfilledCount || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-purple-500"
+        icon-color="bg-purple-100"
+        :title="t('stat.operationNotFilled')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="fill.filledCount || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-purple-500"
+        icon-color="bg-purple-100"
+        :title="t('stat.operationFilled')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="by.todo || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-green-500"
+        icon-color="bg-green-100"
+        :title="t('stat.notMaintained')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="by.finished || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-green-500"
+        icon-color="bg-green-100"
+        :title="t('stat.maintained')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="inspectt.todo || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.notInspected')"
+      />
+    </el-col>
+    <el-col  :sm="3" :xs="12">
+      <SummaryCard
+        :value="inspectt.finished || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.inspected')"
+      />
+    </el-col>
+    <!-- 其他统计卡片... -->
+  </el-row>
+  <!-- 第二行:图表行 -->
+  <el-row :gutter="16" class="mb-4 mt-3">
+    <el-col :span="6">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceStatus')}}</span>
+          </div>
+        </template>
+        <div ref="statusChartRef" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="10">
+      <el-card class="chart-card" shadow="never" >
+        <template #header>
+          <div class="flex items-center">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceClassifyTop5')}}</span>
+          </div>
+        </template>
+        <div ref="topContainer" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="8">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.operationCost')}}</span>
+          </div>
+        </template>
+        <div ref="chartContainer" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- 第三行:消息统计行 -->
+  <el-row :gutter="16" class="mb-1">
+    <el-col :span="12">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div style="display: flex; flex-direction: row; justify-content: space-between">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceRate')}}</span>
+            <div>
+            <el-date-picker
+              v-model="rateQueryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              class="!w-220px"
+              @change="handleDateChange"
+            />
+            </div>
+          </div>
+        </template>
+<!--        <div ref="materialChartRef" class="h-[320px]"></div>-->
+        <div class="table-container">
+          <el-table
+            :data="materialTableData"
+            border
+            @row-click="handleRowClick"
+            style="width: 100%"
+            :header-cell-style="{
+                'background-color': '#2196df',
+                'color': 'white',
+                'border-color': '#457794'
+              }"
+            :cell-style="{
+                'border-color': '#457794',
+                'background-color': '#284D72',
+                'color':'#F1D209'
+              }"
+          >
+            <el-table-column
+              prop="projectDeptName"
+              label="项目部"
+              align="center"
+            />
+            <el-table-column
+              prop="teamCount"
+              label="队伍数量"
+              align="center"
+            />
+            <el-table-column
+              prop="cumulativeDays"
+              label="累计天数"
+              align="center"
+            />
+            <el-table-column
+              prop="constructionDays"
+              label="施工天数"
+              align="center"
+            />
+            <el-table-column
+              prop="gasInjection"
+              :formatter="gasInjectionFormatter"
+              label="注气量(万方)"
+              align="center"
+            />
+            <el-table-column
+              prop="utilizationRate"
+              label="设备利用率"
+              align="center"
+              :formatter="formatRate"
+            />
+          </el-table>
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="12">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.orderCount')}}</span>
+          </div>
+        </template>
+        <div ref="qxRef" class="h-[320px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+  <el-row :gutter="16" class="mb-1 mt-4">
+    <el-col :span="12">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.dailyGasWater')}}</span>
+          </div>
+        </template>
+        <div ref="todayZqlRef" class="h-[300px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="12">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.gasWater')}}</span>
+          </div>
+        </template>
+        <div ref="zqlChartRef" class="h-[300px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+  </div>
+
+
+  <el-dialog
+    v-model="teamDialogVisible"
+    :title="t('stat.teamDetail')"
+    width="80vh"
+    :before-close="handleDialogClose"
+    class="custom-scroll-dialog"
+  >
+    <div class="dialog-scroll-content">
+    <el-table
+      :data="teamTableData"
+      border
+      style="width: 100%;"
+      :header-cell-style="{
+        'background-color': 'rgba(0, 0, 0, 0.2)',
+        'color': 'black',
+        'border-color': '#457794'
+      }"
+      :cell-style="{
+        'border-color': '#457794',
+        'background-color': 'transparent'
+      }"
+    >
+      <el-table-column
+        prop="teamName"
+        label="队伍名称"
+        align="center"
+      />
+      <el-table-column
+        prop="cumulativeDays"
+        label="累计天数"
+        align="center"
+      />
+      <el-table-column
+        prop="constructionDays"
+        label="施工天数"
+        align="center"
+      />
+      <el-table-column
+        prop="utilizationRate"
+        label="设备利用率"
+        align="center"
+        :formatter="formatRate"
+      />
+    </el-table>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { useElementSize } from '@vueuse/core'
+import { IotStatApi } from '@/api/pms/stat'
+import SummaryCard from '@/components/SummaryCard/index.vue'
+import { reactive, ref } from 'vue'
+import {useLocaleStore} from "@/store/modules/locale";
+
+/** IoT 首页 */
+defineOptions({ name: 'IotRhStat' })
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+
+const teamDialogVisible = ref(false)
+const teamTableData = ref([])
+const currentDeptId = ref('')
+const currentDateRange = ref([])
+
+const materialTableData = ref([])
+
+const queryParams = reactive({
+  createTime: [],
+})
+const handleRowClick = (row, column, event) => {
+  console.log('点击的行数据:', row);
+  currentDeptId.value = row.deptId // 假设行数据中包含deptId字段
+  currentDateRange.value = rateQueryParams.createTime
+
+  // 打开弹窗并加载队伍详情数据
+  teamDialogVisible.value = true
+  // fetchTeamDetailData(row.deptId, rateQueryParams.createTime)
+  const teamParams = {
+    deptId: row.projectDeptId,
+    createTime: rateQueryParams.createTime
+  }
+  IotStatApi.getRhTeamRate(teamParams).then(res => {
+    teamTableData.value = res
+  })
+
+};
+
+const handleDialogClose = () => {
+  teamDialogVisible.value = false
+  teamTableData.value = [] // 清空表格数据
+}
+// 格式化利用率为百分比
+const formatRate = (row) => {
+  return (row.utilizationRate * 100).toFixed(2) + '%'
+}
+
+// 注气量格式化函数(单位转换:方 -> 万方)
+const gasInjectionFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
+  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+  // 将方转换为万方,保留两位小数
+  const value = parseFloat(cellValue)
+  return (value / 10000).toFixed(2)
+}
+
+const by = ref({
+  todo: undefined,
+  finished: undefined
+})
+const fill = ref({
+  filledCount: undefined,
+  unfilledCount: undefined
+})
+const inspectt = ref({
+  finished: 0,
+  todo: 0
+})
+const rateQueryParams = reactive({
+  createTime: [],
+})
+const backendData = ref([])
+const statusChartRef = ref() // 设备数量统计的图表
+const materialChartRef = ref() // 设备数量统计的图表
+const zqlChartRef = ref() // 注气量统计的图表
+const todayZqlRef = ref() // 注气量统计的图表
+const { t } = useI18n() // 国际化
+const device = ref({
+  total: undefined,
+  today: undefined
+})
+const maintain = ref({
+  total: undefined,
+  today: undefined
+})
+const work = ref({
+  total: undefined,
+  today: undefined
+})
+const inspect = ref({
+  total: undefined,
+  today: undefined
+})
+
+const status = ref({
+  finished: 0,
+  todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+const handleDateChange = () => {
+  IotStatApi.getRhRate(rateQueryParams).then((res) =>{
+    materialTableData.value = res
+  })
+}
+const typeData = ref({})
+const materialData = ref({})
+const orderSevenData = ref({})
+const ywcbSevenData = ref({})
+const zqlData = ref({})
+const zqlTodayData = ref({})
+/** 获取统计数据 */
+const getStats = () => {
+  initYwcbChart()
+  // 获取基础统计数据
+  IotStatApi.getDeviceCount('rh').then((res) => {
+    device.value = res
+  })
+  IotStatApi.getMaintainCount('rh').then((res) => {
+    maintain.value = res
+  })
+  IotStatApi.getMainWorkCount("").then((res) => {
+    work.value = res
+  })
+  IotStatApi.getInspectCount("").then((res) => {
+    inspect.value = res
+  })
+  // IotStatApi.getMaintenanceStatus("").then((res) => {
+  //   status.value = res
+  //   // initCharts()
+  // })
+  IotStatApi.getMaintenanceTodayStatus("").then((res) => {
+    todayStatus.value = res
+    initTopChart()
+  })
+
+  IotStatApi.getDeviceStatusCount('rh').then((res) => {
+    typeData.value = res
+    initDeviceStatusCharts()
+  })
+  // IotStatApi.getSafeCount().then((res) => {
+  //   safe.value = res
+  // })
+  IotStatApi.getMaterial().then((res) => {
+    materialData.value = res
+    initMaterials()
+  })
+
+  IotStatApi.getOrderSeven('rh').then((res) => {
+    orderSevenData.value = res
+    initQxChart()
+  })
+  IotStatApi.getOrderYwcb('rh').then((res) => {
+    ywcbSevenData.value = res
+    initYwcbChart()
+  })
+  IotStatApi.getMaintenanceStatus('rh').then((res) => {
+    by.value = res
+  })
+  IotStatApi.getRhZqlGases('rh').then((res) => {
+    zqlData.value = res
+    const localeStore = useLocaleStore()
+    const lang = localeStore.getCurrentLocale.lang
+    if (lang==='zh-CN') {
+      initZqlChart('累计注气量','累计注水量')
+    } else if (lang==='en') {
+      initZqlChart('gas','water')
+    }
+
+  })
+  IotStatApi.getRhZqlDaily('rh').then((res) => {
+    zqlTodayData.value = res
+    const localeStore = useLocaleStore()
+    const lang = localeStore.getCurrentLocale.lang
+    if (lang==='zh-CN') {
+      initTodayZqlChart('当日注气量','当日注水量')
+    } else if (lang==='en') {
+      initTodayZqlChart('gas','water')
+    }
+  })
+  const fillQueryParams = reactive({
+    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+    endTime: Date.now(), // 设置默认结束时间为当前时间
+    createTime: [],
+    deptId: '', // 选中的部门ID
+    status: null // 填写状态
+  })
+  IotStatApi.getInspectStatuss(fillQueryParams, 'rh').then((res) => {
+    inspectt.value = res
+  })
+  fillQueryParams.deptId = '157'
+  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
+    fill.value = res.totalList[0] || []
+  })
+
+  // 计算近一周时间
+  const end = new Date();
+  const start = new Date();
+  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000);
+
+  // 格式化日期为后端需要的格式
+  const formatDate = (date) => {
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, '0');
+    const day = String(date.getDate()).padStart(2, '0');
+    const hours = String(date.getHours()).padStart(2, '0');
+    const minutes = String(date.getMinutes()).padStart(2, '0');
+    const seconds = String(date.getSeconds()).padStart(2, '0');
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+  };
+
+  rateQueryParams.createTime = [formatDate(start), formatDate(end)];
+  IotStatApi.getRhRate(rateQueryParams).then((res) =>{
+    materialTableData.value = res
+  })
+}
+let materialInstance;
+const initMaterials = () => {
+  if (!materialChartRef.value) return
+  const option = {
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color:'#B6C8DA'
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '44%'],
+        label: {
+          show: false,
+          position: 'outside',
+          color:'#B6C8DA'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: materialData.value
+      }
+    ]
+  }
+  // 初始化图表
+  materialInstance = echarts.init(materialChartRef.value)
+  materialInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleMaterialResize)
+  handleMaterialResize()
+}
+let zqlTodayInstance = null
+const initTodayZqlChart = async (gas:any,water:any) => {
+  if (!todayZqlRef.value) return
+  zqlTodayInstance = echarts.init(todayZqlRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: zqlTodayData.value.series.map((item) => item.name),
+      top: 30,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: zqlTodayData.value.xAxis,
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name:gas+'(m³)',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      // {
+      //   type: 'value',
+      //   name:water+'(m³)',
+      //   axisLabel: {
+      //     color: '#B6C8DA',
+      //     formatter: '{value}'
+      //   },
+      //   splitLine: {
+      //     show: true, // 显示水平网格线(默认显示)
+      //     lineStyle: {
+      //       // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+      //       color: '#457794', // 浅灰色半透明
+      //       // 可选:设置线条类型(实线/虚线/点线)
+      //       type: 'dashed'
+      //     }
+      //   },
+      //   axisLine: {
+      //     lineStyle: {
+      //       color: '#B6C8DA'
+      //     }
+      //   },
+      //   position: 'right', // 右侧 Y 轴
+      //   splitLine: {
+      //     show: false // 隐藏右侧 Y 轴的分割线
+      //   }
+      // }
+    ],
+
+    series: zqlTodayData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      // const yAxisIndex = index < 1 ? 0 : 1
+      return {
+        name: item.name,
+        type: 'line',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: ['#5470c6', '#f1d209'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+        // yAxisIndex: yAxisIndex // 指定使用的 Y 轴
+      }
+    })
+  }
+
+  zqlTodayInstance.setOption(option)
+  // 窗口缩放监听
+  window.addEventListener('resize', handleZqlTodayResize)
+  handleZqlTodayResize()
+}
+
+/** 初始化图表 */
+const initDeviceStatusCharts = () => {
+  // 设备数量统计
+  echarts.init(statusChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '44%'],
+        label: {
+          show: false,
+          position: 'outside',
+          color: 'white'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: typeData.value
+      }
+    ]
+  })
+}
+
+/** 初始化消息统计图表 */
+const chartContainer = ref(null)
+let chartInstance;
+
+// 模拟数据获取
+const fetchChartData = async () => {
+  // 模拟异步请求
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        months: ['空压机', '增压机', '提纯撬'],
+        repairs: [10, 30, 90]
+      })
+    }, 300)
+  })
+}
+
+// 初始化图表配置
+const initYwcbChart = async () => {
+  if (!chartContainer.value) return
+
+  // 获取数据
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      // formatter: (params) => {
+      //   return `${params[0].axisValue}<br/>
+      //           ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
+      // }
+    },
+    // legend: {
+    //   data: ['当日运维成本'],
+    //   top: 1,
+    //   textStyle: {
+    //     color: '#B6C8DA'
+    //   }
+    // },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '1%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: ywcbSevenData.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/')
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: {
+        color: '#B6C8DA',
+        // formatter: (value) => Math.floor(value).toString()
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+
+    },
+    series: [
+      {
+        name: '当日运维成本',
+        type: 'bar',
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#f69606' }])
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: ywcbSevenData.value.series
+      }
+    ]
+  }
+
+  // 初始化图表
+  chartInstance = echarts.init(chartContainer.value)
+  chartInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleResize)
+  handleResize()
+}
+
+// 自适应调整
+const handleResize = () => {
+  chartInstance?.resize()
+}
+
+// 自适应调整
+const handleQxResize = () => {
+  qxInstance?.resize()
+}
+const handleZqlResize = () => {
+  zqlInstance?.resize()
+}
+const handleZqlTodayResize = () => {
+  zqlTodayInstance?.resize()
+}
+const handleMaterialResize = () => {
+  materialInstance?.resize()
+}
+const topContainer = ref(null)
+let topInstance;
+// 响应式容器尺寸
+const { width, height } = useElementSize(topContainer)
+
+// 初始化图表配置
+const getTopOption = () => {
+  // backendData.value = data
+  const data = backendData.value.sort((a, b) => a.value - b.value)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: (params) => {
+        const item = params[0]
+        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
+      }
+    },
+    grid: {
+      height: '200px',
+      left: '6%',
+      right: '6%',
+      bottom: '18%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'value',
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => {
+          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
+          return value.toLocaleString()
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: data.map((item) => item.category),
+      axisTick: { show: false },
+      axisLabel: { color: '#B6C8DA' },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        data: data.map((item) => item.value),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: '#83bff6' },
+            { offset: 0.7, color: '#188df0' },
+            { offset: 1, color: '#188df0' }
+          ]),
+          borderRadius: [0, 8, 8, 0]
+        },
+        label: {
+          show: true,
+          position: 'right',
+          formatter: '{@value}',
+          color: '#B6C8DA',
+          fontWeight: 'bold'
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+}
+
+// 初始化图表
+const initTopChart = async () => {
+  await IotStatApi.getDeviceTypeCount('rh').then((res) => {
+    backendData.value = res
+  })
+  if (!topContainer.value) return
+  topInstance = echarts.init(topContainer.value)
+  updateTopChart()
+}
+
+// 更新图表
+const updateTopChart = () => {
+  if (!topInstance) return
+  topInstance.setOption(getTopOption())
+}
+
+// 自适应调整
+watch([width, height], () => {
+  topInstance?.resize()
+})
+
+// 监听数据变化
+watch(
+  backendData,
+  () => {
+    updateTopChart()
+  },
+  { deep: true }
+)
+
+const qxRef = ref(null)
+let qxInstance;
+let zqlInstance;
+
+const initZqlChart = (gas:any,water:any) => {
+  if (!zqlChartRef.value) return
+
+  // 获取数据
+  // ECharts配置
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+      // trigger: 'axis',
+      // axisPointer: {
+      //   type: 'shadow'
+      // },
+      // formatter: (params) => {
+      //   return `${params[0].axisValue}<br/>
+      //           ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
+      // }
+    },
+    legend: {
+      data: zqlData.value.series.map((item) => item.name),
+      top: 30,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: zqlData.value.xAxis,
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/')
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: gas+'(m³)',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+    ],
+    series: zqlData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      return {
+        name: item.name,
+        type: 'bar',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: [ '#91cc75','#f1d209'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+      }
+    })
+  }
+
+  // 初始化图表
+  zqlInstance = echarts.init(zqlChartRef.value)
+  zqlInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleZqlResize)
+  handleZqlResize()
+
+}
+
+const initQxChart = () => {
+  if (!qxRef.value) return
+  qxInstance = echarts.init(qxRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: orderSevenData.value.series.map((item) => item.name),
+      top: 30,
+      textStyle: {
+        color:'#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: orderSevenData.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/')
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+    },
+    yAxis: [
+      {
+        type: 'value',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'right', // 右侧 Y 轴
+        splitLine: {
+          show: false // 隐藏右侧 Y 轴的分割线
+        }
+      }
+    ],
+
+    series: orderSevenData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      const yAxisIndex = index < 2 ? 0 : 1
+      return {
+        name: item.name,
+        type: 'line',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
+      }
+    })
+  }
+
+  qxInstance.setOption(option)
+  // 窗口缩放监听
+  window.addEventListener('resize', handleQxResize)
+  handleQxResize()
+}
+
+// 响应式调整
+const resizeQxChart = () => qxInstance?.resize()
+const resizeZqlChart = () => zqlInstance?.resize()
+const resizeZqlTodayChart = () => zqlTodayInstance?.resize()
+const resizeMaterialChart = () => materialInstance?.resize()
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  // initChart()
+  // initTopChart()
+  // initActiveChart()
+  // initQxChart()
+  window.addEventListener('resize', resizeQxChart)
+  // fetchTop()
+  window.addEventListener('resize', () => topInstance?.resize())
+})
+onBeforeUnmount(() => {
+  chartInstance?.dispose()
+  window.removeEventListener('resize', () => chartInstance?.resize())
+  topInstance?.dispose()
+  window.removeEventListener('resize', handleResize)
+  qxInstance?.dispose()
+  window.removeEventListener('resize', resizeQxChart)
+  zqlInstance?.dispose()
+  window.removeEventListener('resize', resizeZqlChart)
+  zqlTodayInstance?.dispose()
+  window.removeEventListener('resize', resizeZqlTodayChart)
+  materialInstance?.dispose()
+  window.removeEventListener('resize', resizeMaterialChart)
+})
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  background-color: #3a6fa3;
+  min-height: 100vh;
+  padding: 20px;
+}
+
+.summary {
+  margin-bottom: 20px;
+}
+
+::v-deep .chart-card {
+  background-color: rgba(0, 0, 0, 0.3);
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
+  transition: all 0.3s ease;
+  border: none;
+
+  &:hover {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+  }
+}
+
+.safety-days-card {
+  .safety-days-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 150px;
+    position: relative;
+
+    .days-number {
+      font-size: 58px;
+      font-weight: bold;
+      color: darkorange;
+      line-height: 1;
+      transition: all 0.3s ease;
+    }
+
+    .days-number:hover {
+      transform: scale(1.05);
+    }
+
+    .days-label {
+      font-size: 20px;
+      color: white;
+      margin-top: 8px;
+    }
+
+    .safety-desc {
+      font-size: 14px;
+      color: #999;
+      position: absolute;
+      bottom: 10px;
+      text-align: center;
+      width: 90%;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .page-container {
+    padding: 10px;
+  }
+}
+::v-deep .el-card__header {
+  border-bottom: none !important;
+  padding-bottom: 0;
+}
+
+.table-container {
+  padding: 16px;
+  height: 320px;
+  box-sizing: border-box;
+  overflow: auto;
+
+  // 滚动条样式优化
+  &::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: rgba(255, 255, 255, 0.2);
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background-color: transparent;
+  }
+}
+
+// 修复表格hover样式
+::v-deep .el-table__row:hover > td {
+  background-color: rgba(255, 255, 255, 0.05) !important;
+}
+.custom-scroll-dialog {
+  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
+  max-height: 90vh;
+  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
+}
+/* 滚动内容容器:核心样式 */
+.dialog-scroll-content {
+  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
+  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
+  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
+}
+
+/* 优化滚动条样式(可选,提升UI体验) */
+.dialog-scroll-content::-webkit-scrollbar {
+  width: 6px; /* 滚动条宽度 */
+}
+.dialog-scroll-content::-webkit-scrollbar-thumb {
+  background-color: #e5e7eb; /* 滚动条滑块颜色 */
+  border-radius: 3px; /* 滚动条圆角 */
+}
+.dialog-scroll-content::-webkit-scrollbar-thumb:hover {
+  background-color: #d1d5db; /*  hover时滑块颜色 */
+}
+</style>

+ 1471 - 0
src/views/pms/stat/backup/rykb-a.vue

@@ -0,0 +1,1471 @@
+<template>
+  <div class="page-container">
+    <div class="grid mb-5 2xl:grid-cols-10 2xl:grid-rows-1 gap-4 xl:grid-cols-5 xl:grid-rows-2">
+      <SummaryCard
+        :value="device.total || 0"
+        icon="fa-solid:project-diagram"
+        icon-bg-color="text-blue-500"
+        icon-color="bg-blue-100"
+        :title="t('stat.deviceCount')"
+      />
+      <SummaryCard
+        :value="maintain.total || 0"
+        icon="fa-solid:list"
+        icon-bg-color="text-pink-500"
+        icon-color="bg-blue-100"
+        :title="t('stat.repairOrder')"
+      />
+      <SummaryCard
+        :value="fill.unfilledCount || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-purple-500"
+        icon-color="bg-purple-100"
+        :title="t('stat.operationNotFilled')"
+      />
+      <SummaryCard
+        :value="fill.filledCount || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-purple-500"
+        icon-color="bg-purple-100"
+        :title="t('stat.operationFilled')"
+      />
+      <SummaryCard
+        :value="by.todo || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-green-500"
+        icon-color="bg-green-100"
+        :title="t('stat.notMaintained')"
+      />
+      <SummaryCard
+        :value="by.finished || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-green-500"
+        icon-color="bg-green-100"
+        :title="t('stat.maintained')"
+      />
+      <SummaryCard
+        :value="inspectt.todo || 0"
+        icon="fa-solid:times-circle"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.notInspected')"
+      />
+      <SummaryCard
+        :value="inspectt.finished || 0"
+        icon="fa-solid:award"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.inspected')"
+      />
+      <SummaryCard
+        :value="inspectZjxjCount.zj || 0"
+        icon="solar:ruler-bold"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.zj')"
+      />
+      <SummaryCard
+        :value="inspectZjxjCount.xj || 0"
+        icon="mdi:wrench-check-outline"
+        icon-bg-color="text-yellow-500"
+        icon-color="bg-yellow-100"
+        :title="t('stat.xj')"
+      />
+    </div>
+    <!-- <el-row :gutter="16" class="mb-5">
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="device.total || 0"
+          icon="fa-solid:project-diagram"
+          icon-bg-color="text-blue-500"
+          icon-color="bg-blue-100"
+          :title="t('stat.deviceCount')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="maintain.total || 0"
+          icon="fa-solid:list"
+          icon-bg-color="text-pink-500"
+          icon-color="bg-blue-100"
+          :title="t('stat.repairOrder')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="fill.unfilledCount || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          :title="t('stat.operationNotFilled')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="fill.filledCount || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          :title="t('stat.operationFilled')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="by.todo || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          :title="t('stat.notMaintained')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="by.finished || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          :title="t('stat.maintained')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectt.todo || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.notInspected')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectt.finished || 0"
+          icon="fa-solid:award"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.inspected')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectZjxjCount.zj || 0"
+          icon="solar:ruler-bold"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.zj')"
+        />
+      </el-col>
+      <el-col :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspectZjxjCount.xj || 0"
+          icon="mdi:wrench-check-outline"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          :title="t('stat.xj')"
+        />
+      </el-col>
+    </el-row> -->
+    <!-- 第二行:图表行 -->
+    <el-row :gutter="16" class="mb-4 mt-3">
+      <el-col :span="6">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.deviceStatus')
+              }}</span>
+            </div>
+          </template>
+          <div ref="statusChartRef" class="h-[320px]"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="10">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div style="display: flex; flex-direction: row; justify-content: space-between">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.deviceRate')
+              }}</span>
+              <div>
+                <el-date-picker
+                  v-model="rateQueryParams.createTime"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  type="daterange"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+                  class="!w-220px"
+                  @change="handleDateChange"
+                />
+              </div>
+            </div>
+          </template>
+          <!--          <div ref="topContainer" class="h-[320px]"></div>-->
+          <div class="table-container">
+            <el-table
+              class="custom-table"
+              :data="materialTableData"
+              border
+              @row-click="handleRowClick"
+              style="width: 100%; color: #4c4c4c"
+              :header-cell-style="{
+                'background-color': '#2196df',
+                color: 'white',
+                'border-color': '#457794'
+              }"
+              :cell-style="{
+                'border-color': '#457794',
+                'background-color': '#284D72',
+                color: '#F1D209'
+              }"
+            >
+              <el-table-column prop="projectDeptName" label="项目部" align="center" />
+              <el-table-column prop="teamCount" label="队伍数量" align="center" />
+              <el-table-column prop="cumulativeDays" label="累计天数" align="center" />
+              <el-table-column prop="constructionDays" label="施工天数" align="center" />
+              <el-table-column
+                prop="utilizationRate"
+                label="设备利用率"
+                align="center"
+                :formatter="formatRate"
+              />
+            </el-table>
+          </div>
+        </el-card>
+      </el-col>
+      <!--      <el-col :span="7">-->
+      <!--        <el-card class="chart-card" shadow="never">-->
+      <!--          <template #header>-->
+      <!--            <div class="flex items-center">-->
+      <!--              <span class="text-base font-medium" style="color: #b6c8da">设备类别TOP5数量</span>-->
+      <!--            </div>-->
+      <!--          </template>-->
+      <!--          <div ref="topContainer" class="h-[320px]"></div>-->
+      <!--        </el-card>-->
+      <!--      </el-col>-->
+      <el-col :span="8">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.orderCount')
+              }}</span>
+            </div>
+          </template>
+          <div ref="qxRef" class="h-[320px]"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 新增:修井完成情况图表 -->
+    <el-row :gutter="16" class="mb-4">
+      <el-col :span="8">
+        <div class="flex flex-col justify-between">
+          <el-card class="safety-days-card chart-card h-[230px]" shadow="never">
+            <template #header>
+              <div class="flex items-center">
+                <span class="text-base font-medium" style="color: #b6c8da">{{
+                  t('stat.safeProduction')
+                }}</span>
+              </div>
+            </template>
+            <div class="safety-days-content">
+              <div class="days-number">{{ 165 }}</div>
+              <div class="days-label">天</div>
+              <div class="safety-desc">{{ t('stat.daysToDate') }}</div>
+            </div>
+          </el-card>
+          <el-card class="chart-card mt-1" shadow="never">
+            <template #header>
+              <div class="flex items-center">
+                <span class="text-base font-medium" style="color: #b6c8da">{{
+                  t('stat.drillingCompletion')
+                }}</span>
+              </div>
+            </template>
+            <div ref="drillingWellChartRef" class="h-[170px]"></div>
+          </el-card>
+        </div>
+      </el-col>
+      <el-col :span="8">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.drillingWork')
+              }}</span>
+            </div>
+          </template>
+          <div ref="drillingWorkloadChartRef" class="h-[405px]"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="8">
+        <el-card class="chart-card" shadow="never">
+          <template #header>
+            <div class="flex items-center">
+              <span class="text-base font-medium" style="color: #b6c8da">{{
+                t('stat.wellWorkoverWorkload')
+              }}</span>
+            </div>
+          </template>
+          <div ref="repairWorkloadChartRef" class="h-[405px]"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+
+  <el-dialog
+    v-model="teamDialogVisible"
+    :title="t('stat.teamDetail')"
+    width="80vh"
+    :before-close="handleDialogClose"
+    class="custom-scroll-dialog"
+  >
+    <div class="dialog-scroll-content">
+      <el-table
+        :data="teamTableData"
+        border
+        style="width: 100%"
+        :header-cell-style="{
+          'background-color': 'rgba(0, 0, 0, 0.2)',
+          color: 'black',
+          'border-color': '#457794'
+        }"
+        :cell-style="{
+          'border-color': '#457794',
+          'background-color': 'transparent'
+        }"
+      >
+        <el-table-column prop="teamName" label="队伍名称" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" align="center" />
+        <el-table-column
+          prop="utilizationRate"
+          label="设备利用率"
+          align="center"
+          :formatter="formatRate"
+        />
+      </el-table>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { useElementSize } from '@vueuse/core'
+import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
+import { IotStatApi } from '@/api/pms/stat'
+import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
+
+// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
+
+/** IoT 首页 */
+defineOptions({ name: 'IotRyStat' })
+
+// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+const { t } = useI18n() // 国际化
+const teamDialogVisible = ref(false)
+const teamTableData = ref([])
+const currentDeptId = ref('')
+const currentDateRange = ref([])
+const materialTableData = ref([])
+const handleDateChange = () => {
+  IotStatApi.getRyRate(rateQueryParams).then((res) => {
+    materialTableData.value = res
+  })
+}
+const rateQueryParams = reactive({
+  createTime: []
+})
+const handleDialogClose = () => {
+  teamDialogVisible.value = false
+  teamTableData.value = [] // 清空表格数据
+}
+// 格式化利用率为百分比
+const formatRate = (row) => {
+  return (row.utilizationRate * 100).toFixed(2) + '%'
+}
+const handleRowClick = (row, column, event) => {
+  console.log('点击的行数据:', row)
+  currentDeptId.value = row.deptId // 假设行数据中包含deptId字段
+  currentDateRange.value = rateQueryParams.createTime
+
+  // 打开弹窗并加载队伍详情数据
+  teamDialogVisible.value = true
+  // fetchTeamDetailData(row.deptId, rateQueryParams.createTime)
+  debugger
+  const teamParams = {
+    deptId: row.projectDeptId,
+    createTime: rateQueryParams.createTime
+  }
+  IotStatApi.getRyTeamRate(teamParams).then((res) => {
+    teamTableData.value = res
+  })
+}
+const dateRange = ref<[Date, Date] | null>(null)
+const by = ref({
+  todo: undefined,
+  finished: undefined
+})
+const fill = ref({
+  filledCount: undefined,
+  unfilledCount: undefined
+})
+const inspectt = ref({
+  finished: 0,
+  todo: 0
+})
+const queryParams = reactive({
+  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+  endTime: Date.now() // 设置默认结束时间为当前时间
+})
+const backendData = ref([])
+const statusChartRef = ref() // 设备数量统计的图表
+// const materialChartRef = ref() // 设备数量统计的图表
+const drillingWorkloadChartRef = ref()
+const repairWorkloadChartRef = ref()
+// 基础统计数据
+// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
+const statsData = ref<IotStatisticsSummaryRespVO>({
+  productCategoryCount: 0,
+  productCount: 0,
+  deviceCount: 0,
+  deviceMessageCount: 0,
+  productCategoryTodayCount: 0,
+  productTodayCount: 0,
+  deviceTodayCount: 0,
+  deviceMessageTodayCount: 0,
+  deviceOnlineCount: 0,
+  deviceOfflineCount: 0,
+  deviceInactiveCount: 0,
+  productCategoryDeviceCounts: {}
+})
+
+const device = ref({
+  total: undefined,
+  today: undefined
+})
+const maintain = ref({
+  total: undefined,
+  today: undefined
+})
+const work = ref({
+  total: undefined,
+  today: undefined
+})
+const inspect = ref({
+  total: undefined,
+  today: undefined
+})
+
+const status = ref({
+  finished: 0,
+  todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+const typeData = ref({})
+const materialData = ref({})
+const orderSevenData = ref({})
+const safe = ref()
+
+// 新增:修井完成情况数据
+const repairWellData = ref({
+  xAxis: ['小修10队', '小修8队', '小修9队'],
+  series: [
+    { name: '日累完成井数', data: [10, 15, 20] },
+    { name: '月累完成井数', data: [50, 60, 70] },
+    { name: '年累完成井数', data: [200, 220, 250] }
+  ]
+})
+
+// 新增:钻井完成情况数据
+const drillingWellData = ref({})
+const drillingWorkloadData = ref({})
+
+// 新增:修井工作量情况数据
+const repairWorkloadData = ref({})
+const repairWellChartRef = ref()
+const drillingWellChartRef = ref()
+
+const inspectZjxjCount = ref<any>({})
+
+/** 获取统计数据 */
+const getStats = () => {
+  // 获取基础统计数据
+  IotStatApi.getDeviceCount('ry').then((res) => {
+    device.value = res
+  })
+  IotStatApi.getMaintainCount('ry').then((res) => {
+    maintain.value = res
+  })
+  IotStatApi.getMainWorkCount().then((res) => {
+    work.value = res
+  })
+  IotStatApi.getInspectCount().then((res) => {
+    inspect.value = res
+  })
+  IotStatApi.getMaintenanceStatus('ry').then((res) => {
+    status.value = res
+    // initCharts()
+  })
+  // IotStatApi.getMaintenanceTodayStatus().then((res) => {
+  //   todayStatus.value = res
+  // })
+
+  IotStatApi.getDeviceStatusCount('ry').then((res) => {
+    typeData.value = res
+    initDeviceStatusCharts()
+  })
+  IotStatApi.getSafeCount().then((res) => {
+    safe.value = res
+  })
+  // IotStatApi.getMaterial().then((res) => {
+  //   materialData.value = res
+  //   initMaterials()
+  // })
+
+  IotStatApi.getOrderSeven('ry').then((res) => {
+    orderSevenData.value = res
+    initQxChart()
+  })
+
+  IotStatApi.getMaintenanceStatus('ry').then((res) => {
+    by.value = res
+  })
+
+  IotStatApi.getRepairRigWork('repair').then((res) => {
+    repairWorkloadData.value = res
+    initRepairWorkloadChart(res)
+  })
+  IotStatApi.getRepairRigWork('rig').then((res) => {
+    drillingWorkloadData.value = res
+    initDrillingWorkloadChart(res)
+  })
+  IotStatApi.getRigFinished().then((res) => {
+    debugger
+    drillingWellData.value = res
+    initDrillingWellChart(res)
+  })
+  const fillQueryParams = reactive({
+    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+    endTime: Date.now(), // 设置默认结束时间为当前时间
+    createTime: [],
+    deptId: null, // 选中的部门ID
+    status: null // 填写状态
+  })
+  IotStatApi.getInspectStatuss(fillQueryParams, 'ry').then((res) => {
+    inspectt.value = res
+  })
+
+  IotStatApi.getInspectZjxjCount().then((res) => {
+    inspectZjxjCount.value = res
+
+    console.log('inspectZjxjCount.value :>> ', inspectZjxjCount.value)
+  })
+  fillQueryParams.deptId = '158'
+  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
+    fill.value = res.totalList[0] || []
+  })
+  // IotStatApi.getProject('ry').then((res) => {
+  //   typeData.value = res;
+  //   initProjectCharts()
+  // })
+
+  // 计算近一周时间
+  const end = new Date()
+  const start = new Date()
+  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000)
+
+  // 格式化日期为后端需要的格式
+  const formatDate = (date) => {
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+  }
+
+  rateQueryParams.createTime = [formatDate(start), formatDate(end)]
+  IotStatApi.getRyRate(rateQueryParams).then((res) => {
+    materialTableData.value = res
+  })
+}
+
+const initDrillingWorkloadChart = (res) => {
+  if (!drillingWorkloadChartRef.value) return
+  const chart = echarts.init(drillingWorkloadChartRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: res.series.map((item) => item.name),
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: res.xAxis,
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '进尺(米)',
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    series: res.series.map((item) => ({
+      name: item.name,
+      type: 'bar',
+      data: item.data
+    }))
+  }
+  chart.setOption(option)
+}
+
+// 新增:初始化修井工作量情况图表
+const initRepairWorkloadChart = (res) => {
+  if (!repairWorkloadChartRef.value) return
+  const chart = echarts.init(repairWorkloadChartRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: res.series.map((item) => item.name),
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: res.xAxis,
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '完井数(个)',
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    series: res.series.map((item) => ({
+      name: item.name,
+      type: 'bar',
+      data: item.data
+    }))
+  }
+  chart.setOption(option)
+}
+
+/** 初始化图表 */
+const initDeviceStatusCharts = () => {
+  // 设备数量统计
+  echarts.init(statusChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '46%'],
+        label: {
+          show: false,
+          position: 'outside',
+          color: 'white'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: typeData.value
+      }
+    ]
+  })
+}
+
+// 自适应调整
+const handleResize = () => {
+  topInstance?.resize()
+}
+
+const topContainer = ref(null)
+let topInstance = null
+// 响应式容器尺寸
+const { width, height } = useElementSize(topContainer)
+
+// 处理数据(排序+限制5条)
+const processedData = () => {
+  const data = IotStatApi.getDeviceTypeCount()
+  backendData.value = data
+  return [...backendData.value].sort((a, b) => a.value - b.value)
+}
+
+const fetchTop = () => {
+  IotStatApi.getDeviceTypeCount().then((res) => {
+    backendData.value = res
+  })
+}
+
+watch(
+  backendData,
+  () => {
+    updateTopChart()
+  },
+  { deep: true }
+)
+
+// 初始化图表配置
+const getTopOption = () => {
+  // backendData.value = data
+  const data = backendData.value.sort((a, b) => a.value - b.value)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: (params) => {
+        const item = params[0]
+        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
+      }
+    },
+    grid: {
+      height: '200px',
+      left: '6%',
+      right: '6%',
+      bottom: '18%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'value',
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => {
+          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
+          return value.toLocaleString()
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: data.map((item) => item.category),
+      axisTick: { show: false },
+      axisLabel: { color: '#B6C8DA' },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        data: data.map((item) => item.value),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: '#83bff6' },
+            { offset: 0.7, color: '#188df0' },
+            { offset: 1, color: '#188df0' }
+          ]),
+          borderRadius: [0, 8, 8, 0]
+        },
+        label: {
+          show: true,
+          position: 'right',
+          formatter: '{@value}',
+          color: '#B6C8DA',
+          fontWeight: 'bold'
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+}
+
+// // 初始化图表
+// const initTopChart = async () => {
+//   await IotStatApi.getDeviceTypeCount('ry').then((res) => {
+//     backendData.value = res
+//   })
+//   if (!topContainer.value) return
+//   topInstance = echarts.init(topContainer.value)
+//   updateTopChart()
+// }
+//
+// // 更新图表
+// const updateTopChart = () => {
+//   if (!topInstance) return
+//   topInstance.setOption(getTopOption())
+// }
+
+const initProjectCharts = () => {
+  const chart = echarts.init(topContainer.value)
+  chart.setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      // top: '5%',
+      // right: '10%',
+      // align: 'center',
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['0%', '70%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '45%'],
+        label: {
+          show: false,
+          position: 'outside',
+          color: '#B6C8DA'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: true
+        },
+        data: typeData.value
+      }
+    ]
+  })
+}
+
+// 自适应调整
+watch([width, height], () => {
+  topInstance?.resize()
+})
+const activeDom = ref(null)
+let activeInstance = null
+
+const activeData = ref([])
+const initActiveChart = async () => {
+  if (!activeDom.value) return
+  activeData.value = await IotStatApi.getDeptCount()
+  activeInstance = echarts.init(activeDom.value)
+
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: (params) => `
+        ${params[0].name}<br/>
+        ${params[0].marker} 总人数: ${params[0].value}<br/>
+        ${params[1].marker} 活跃人数: ${params[1].value}
+      `
+    },
+    legend: {
+      data: ['总人数', '活跃人数'],
+      top: 30
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: activeData.value.map((item) => item.department),
+      axisLabel: {
+        interval: 0,
+        rotate: 0
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '人数',
+      splitLine: {
+        show: true,
+        lineStyle: { type: 'dashed' }
+      }
+    },
+    series: [
+      {
+        name: '总人数',
+        type: 'bar',
+        data: activeData.value.map((item) => item.total),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#5470c6' },
+            { offset: 1, color: '#83bff6' }
+          ])
+        },
+        barWidth: 30
+      },
+      {
+        name: '活跃人数',
+        type: 'bar',
+        data: activeData.value.map((item) => item.active),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#91cc75' },
+            { offset: 1, color: '#e6f4d2' }
+          ])
+        },
+        barWidth: 30
+      }
+    ]
+  }
+
+  activeInstance.setOption(option)
+}
+
+const qxRef = ref(null)
+let qxInstance = null
+
+// 生成近12个月份 (包含当年和去年)
+const generateMonths = () => {
+  const months = []
+  const date = new Date()
+  date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
+
+  for (let i = 0; i < 12; i++) {
+    date.setMonth(date.getMonth() - 1)
+    months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
+  }
+  return months.reverse()
+}
+
+const initQxChart = () => {
+  if (!qxRef.value) return
+  qxInstance = echarts.init(qxRef.value)
+  debugger
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: orderSevenData.value.series.map((item) => item.name),
+      top: 30,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: orderSevenData.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        position: 'right', // 右侧 Y 轴
+        splitLine: {
+          show: false // 隐藏右侧 Y 轴的分割线
+        }
+      }
+    ],
+    series: orderSevenData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      const yAxisIndex = index < 2 ? 0 : 1
+      return {
+        name: item.name,
+        type: 'line',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
+      }
+    })
+  }
+
+  qxInstance.setOption(option)
+}
+
+// 响应式调整
+const resizeQxChart = () => qxInstance?.resize()
+
+// 新增:初始化修井完成情况图表
+const initRepairWellChart = () => {
+  if (!repairWellChartRef.value) return
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: repairWellData.value.series.map((item) => item.name),
+      top: 10,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: repairWellData.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '完成井数(口)',
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: '{value}'
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    series: repairWellData.value.series.map((item) => ({
+      name: item.name,
+      type: 'bar',
+      data: item.data
+    }))
+  }
+  const chart = echarts.init(repairWellChartRef.value)
+  chart.setOption(option)
+  window.addEventListener('resize', () => chart.resize())
+}
+
+// 新增:初始化钻井完成情况图表
+const initDrillingWellChart = (res) => {
+  if (!drillingWellChartRef.value) return
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: res.series.map((item) => item.name),
+      top: 10,
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: res.xAxis,
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '完成井数(口)',
+      axisLabel: {
+        color: '#B6C8DA',
+        formatter: '{value}'
+      },
+      splitLine: {
+        show: true, // 显示水平网格线(默认显示)
+        lineStyle: {
+          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+          color: '#457794', // 浅灰色半透明
+          // 可选:设置线条类型(实线/虚线/点线)
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA'
+        }
+      }
+    },
+    series: res.series.map((item) => ({
+      name: item.name,
+      type: 'bar',
+      data: item.data
+    }))
+  }
+  const chart = echarts.init(drillingWellChartRef.value)
+  chart.setOption(option)
+  window.addEventListener('resize', () => chart.resize())
+}
+
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  // initChart()
+  // initTopChart()
+  // initActiveChart()
+  // initQxChart()
+  window.addEventListener('resize', resizeQxChart)
+  // fetchTop()
+  window.addEventListener('resize', () => topInstance?.resize())
+  initRepairWellChart()
+  // initDrillingWellChart()
+  //initDrillingWorkloadChart()
+  //initRepairWorkloadChart()
+})
+
+onBeforeUnmount(() => {
+  // chartInstance?.dispose()
+  // window.removeEventListener('resize', () => chartInstance?.resize())
+  // topInstance?.dispose()
+  // window.removeEventListener('resize', handleResize)
+  // qxInstance?.dispose()
+  // window.removeEventListener('resize', resizeQxChart)
+  // repairWellChartRef.dispose()
+  // window.removeEventListener('resize', () =>
+  //   echarts.getInstanceByDom(repairWellChartRef.value)?.resize()
+  // )
+  // echarts.getInstanceByDom(drillingWellChartRef.value)?.dispose()
+  // window.removeEventListener('resize', () =>
+  //   echarts.getInstanceByDom(drillingWellChartRef.value)?.resize()
+  // )
+})
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  background-color: #3a6fa3;
+  min-height: 100vh;
+  padding: 20px;
+}
+
+.summary {
+  margin-bottom: 20px;
+}
+
+::v-deep .chart-card {
+  background-color: rgba(0, 0, 0, 0.3);
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
+  transition: all 0.3s ease;
+  border: none;
+
+  &:hover {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+  }
+}
+
+.safety-days-card {
+  .safety-days-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 150px;
+    position: relative;
+
+    .days-number {
+      font-size: 58px;
+      font-weight: bold;
+      color: darkorange;
+      line-height: 1;
+      transition: all 0.3s ease;
+    }
+
+    .days-number:hover {
+      transform: scale(1.05);
+    }
+
+    .days-label {
+      font-size: 20px;
+      color: white;
+      margin-top: 8px;
+    }
+
+    .safety-desc {
+      font-size: 14px;
+      color: #999;
+      position: absolute;
+      bottom: 10px;
+      text-align: center;
+      width: 90%;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .page-container {
+    padding: 10px;
+  }
+}
+::v-deep .el-card__header {
+  border-bottom: none !important;
+  padding-bottom: 0;
+}
+
+.table-container {
+  padding: 16px;
+  height: 320px;
+  box-sizing: border-box;
+  overflow: auto;
+
+  // 滚动条样式优化
+  &::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: rgba(255, 255, 255, 0.2);
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background-color: transparent;
+  }
+}
+
+// 修复表格hover样式
+::v-deep .el-table__row:hover > td {
+  background-color: rgba(255, 255, 255, 0.05) !important;
+}
+.custom-scroll-dialog {
+  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
+  max-height: 90vh;
+  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
+}
+/* 滚动内容容器:核心样式 */
+.dialog-scroll-content {
+  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
+  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
+  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
+}
+
+/* 优化滚动条样式(可选,提升UI体验) */
+.dialog-scroll-content::-webkit-scrollbar {
+  width: 6px; /* 滚动条宽度 */
+}
+.dialog-scroll-content::-webkit-scrollbar-thumb {
+  background-color: #e5e7eb; /* 滚动条滑块颜色 */
+  border-radius: 3px; /* 滚动条圆角 */
+}
+.dialog-scroll-content::-webkit-scrollbar-thumb:hover {
+  background-color: #d1d5db; /*  hover时滑块颜色 */
+}
+.custom-table :deep .el-table__row {
+  height: 45px !important; /* 高度根据需求调整 */
+}
+</style>

+ 70 - 1332
src/views/pms/stat/rhkb.vue

@@ -1,1354 +1,92 @@
-<template>
-  <div class="page-container">
-  <el-row :gutter="16" class="summary">
-    <!-- 原有的统计卡片部分保持不变 -->
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="device.total || 0"
-        icon="fa-solid:project-diagram"
-        icon-bg-color="text-blue-500"
-        icon-color="bg-blue-100"
-        :title="t('stat.deviceCount')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="maintain.total || 0"
-        icon="fa-solid:list"
-        icon-bg-color="text-pink-500"
-        icon-color="bg-blue-100"
-        :title="t('stat.repairOrder')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="fill.unfilledCount || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-purple-500"
-        icon-color="bg-purple-100"
-        :title="t('stat.operationNotFilled')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="fill.filledCount || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-purple-500"
-        icon-color="bg-purple-100"
-        :title="t('stat.operationFilled')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="by.todo || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-green-500"
-        icon-color="bg-green-100"
-        :title="t('stat.notMaintained')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="by.finished || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-green-500"
-        icon-color="bg-green-100"
-        :title="t('stat.maintained')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="inspectt.todo || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.notInspected')"
-      />
-    </el-col>
-    <el-col  :sm="3" :xs="12">
-      <SummaryCard
-        :value="inspectt.finished || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.inspected')"
-      />
-    </el-col>
-    <!-- 其他统计卡片... -->
-  </el-row>
-  <!-- 第二行:图表行 -->
-  <el-row :gutter="16" class="mb-4 mt-3">
-    <el-col :span="6">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceStatus')}}</span>
-          </div>
-        </template>
-        <div ref="statusChartRef" class="h-[290px]"></div>
-      </el-card>
-    </el-col>
-    <el-col :span="10">
-      <el-card class="chart-card" shadow="never" >
-        <template #header>
-          <div class="flex items-center">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceClassifyTop5')}}</span>
-          </div>
-        </template>
-        <div ref="topContainer" class="h-[290px]"></div>
-      </el-card>
-    </el-col>
-    <el-col :span="8">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.operationCost')}}</span>
-          </div>
-        </template>
-        <div ref="chartContainer" class="h-[290px]"></div>
-      </el-card>
-    </el-col>
-  </el-row>
-
-  <!-- 第三行:消息统计行 -->
-  <el-row :gutter="16" class="mb-1">
-    <el-col :span="12">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div style="display: flex; flex-direction: row; justify-content: space-between">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.deviceRate')}}</span>
-            <div>
-            <el-date-picker
-              v-model="rateQueryParams.createTime"
-              value-format="YYYY-MM-DD HH:mm:ss"
-              type="daterange"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-220px"
-              @change="handleDateChange"
-            />
-            </div>
-          </div>
-        </template>
-<!--        <div ref="materialChartRef" class="h-[320px]"></div>-->
-        <div class="table-container">
-          <el-table
-            :data="materialTableData"
-            border
-            @row-click="handleRowClick"
-            style="width: 100%"
-            :header-cell-style="{
-                'background-color': '#2196df',
-                'color': 'white',
-                'border-color': '#457794'
-              }"
-            :cell-style="{
-                'border-color': '#457794',
-                'background-color': '#284D72',
-                'color':'#F1D209'
-              }"
-          >
-            <el-table-column
-              prop="projectDeptName"
-              label="项目部"
-              align="center"
-            />
-            <el-table-column
-              prop="teamCount"
-              label="队伍数量"
-              align="center"
-            />
-            <el-table-column
-              prop="cumulativeDays"
-              label="累计天数"
-              align="center"
-            />
-            <el-table-column
-              prop="constructionDays"
-              label="施工天数"
-              align="center"
-            />
-            <el-table-column
-              prop="gasInjection"
-              :formatter="gasInjectionFormatter"
-              label="注气量(万方)"
-              align="center"
-            />
-            <el-table-column
-              prop="utilizationRate"
-              label="设备利用率"
-              align="center"
-              :formatter="formatRate"
-            />
-          </el-table>
-        </div>
-      </el-card>
-    </el-col>
-    <el-col :span="12">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.orderCount')}}</span>
-          </div>
-        </template>
-        <div ref="qxRef" class="h-[320px]"></div>
-      </el-card>
-    </el-col>
-  </el-row>
-  <el-row :gutter="16" class="mb-1 mt-4">
-    <el-col :span="12">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.dailyGasWater')}}</span>
-          </div>
-        </template>
-        <div ref="todayZqlRef" class="h-[300px]"></div>
-      </el-card>
-    </el-col>
-    <el-col :span="12">
-      <el-card class="chart-card" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium " style="color: #b6c8da">{{t('stat.gasWater')}}</span>
-          </div>
-        </template>
-        <div ref="zqlChartRef" class="h-[300px]"></div>
-      </el-card>
-    </el-col>
-  </el-row>
-  </div>
-
-
-  <el-dialog
-    v-model="teamDialogVisible"
-    :title="t('stat.teamDetail')"
-    width="80vh"
-    :before-close="handleDialogClose"
-    class="custom-scroll-dialog"
-  >
-    <div class="dialog-scroll-content">
-    <el-table
-      :data="teamTableData"
-      border
-      style="width: 100%;"
-      :header-cell-style="{
-        'background-color': 'rgba(0, 0, 0, 0.2)',
-        'color': 'black',
-        'border-color': '#457794'
-      }"
-      :cell-style="{
-        'border-color': '#457794',
-        'background-color': 'transparent'
-      }"
-    >
-      <el-table-column
-        prop="teamName"
-        label="队伍名称"
-        align="center"
-      />
-      <el-table-column
-        prop="cumulativeDays"
-        label="累计天数"
-        align="center"
-      />
-      <el-table-column
-        prop="constructionDays"
-        label="施工天数"
-        align="center"
-      />
-      <el-table-column
-        prop="utilizationRate"
-        label="设备利用率"
-        align="center"
-        :formatter="formatRate"
-      />
-    </el-table>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts" name="Index">
-import * as echarts from 'echarts/core'
-import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
-import {
-  GridComponent,
-  LegendComponent,
-  TitleComponent,
-  ToolboxComponent,
-  TooltipComponent
-} from 'echarts/components'
-import { LabelLayout, UniversalTransition } from 'echarts/features'
-import { CanvasRenderer } from 'echarts/renderers'
-import { useElementSize } from '@vueuse/core'
-import { IotStatApi } from '@/api/pms/stat'
-import SummaryCard from '@/components/SummaryCard/index.vue'
-import { reactive, ref } from 'vue'
-import {useLocaleStore} from "@/store/modules/locale";
-
-/** IoT 首页 */
-defineOptions({ name: 'IotRhStat' })
-echarts.use([
-  TooltipComponent,
-  LegendComponent,
-  PieChart,
-  CanvasRenderer,
-  LabelLayout,
-  TitleComponent,
-  ToolboxComponent,
-  GridComponent,
-  LineChart,
-  UniversalTransition,
-  GaugeChart,
-  BarChart
-])
-
-const teamDialogVisible = ref(false)
-const teamTableData = ref([])
-const currentDeptId = ref('')
-const currentDateRange = ref([])
-
-const materialTableData = ref([])
-
-const queryParams = reactive({
-  createTime: [],
-})
-const handleRowClick = (row, column, event) => {
-  console.log('点击的行数据:', row);
-  currentDeptId.value = row.deptId // 假设行数据中包含deptId字段
-  currentDateRange.value = rateQueryParams.createTime
-
-  // 打开弹窗并加载队伍详情数据
-  teamDialogVisible.value = true
-  // fetchTeamDetailData(row.deptId, rateQueryParams.createTime)
-  const teamParams = {
-    deptId: row.projectDeptId,
-    createTime: rateQueryParams.createTime
-  }
-  IotStatApi.getRhTeamRate(teamParams).then(res => {
-    teamTableData.value = res
-  })
-
-};
-
-const handleDialogClose = () => {
-  teamDialogVisible.value = false
-  teamTableData.value = [] // 清空表格数据
-}
-// 格式化利用率为百分比
-const formatRate = (row) => {
-  return (row.utilizationRate * 100).toFixed(2) + '%'
-}
-
-// 注气量格式化函数(单位转换:方 -> 万方)
-const gasInjectionFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
-  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
-  // 将方转换为万方,保留两位小数
-  const value = parseFloat(cellValue)
-  return (value / 10000).toFixed(2)
-}
-
-const by = ref({
-  todo: undefined,
-  finished: undefined
-})
-const fill = ref({
-  filledCount: undefined,
-  unfilledCount: undefined
-})
-const inspectt = ref({
-  finished: 0,
-  todo: 0
-})
-const rateQueryParams = reactive({
-  createTime: [],
-})
-const backendData = ref([])
-const statusChartRef = ref() // 设备数量统计的图表
-const materialChartRef = ref() // 设备数量统计的图表
-const zqlChartRef = ref() // 注气量统计的图表
-const todayZqlRef = ref() // 注气量统计的图表
-const { t } = useI18n() // 国际化
-const device = ref({
-  total: undefined,
-  today: undefined
-})
-const maintain = ref({
-  total: undefined,
-  today: undefined
-})
-const work = ref({
-  total: undefined,
-  today: undefined
-})
-const inspect = ref({
-  total: undefined,
-  today: undefined
+<script lang="ts" setup>
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
+
+import rhsummary from './rhkb/rhsummary.vue'
+import deviceType from './rhkb/deviceType.vue'
+import deviceStatus from './rhkb/deviceStatus.vue'
+import operation from './rhkb/operation.vue'
+import orderTrend from './rhkb/orderTrend.vue'
+import todayGas from './rhkb/todayGas.vue'
+import historyGas from './rhkb/historyGas.vue'
+import deviceList from './rhkb/deviceList.vue'
+
+defineOptions({
+  name: 'IotRhStatt'
 })
 
-const status = ref({
-  finished: 0,
-  todo: 0
-})
-const todayStatus = ref({
-  finished: 0,
-  todo: 0
-})
-const handleDateChange = () => {
-  IotStatApi.getRhRate(rateQueryParams).then((res) =>{
-    materialTableData.value = res
-  })
-}
-const typeData = ref({})
-const materialData = ref({})
-const orderSevenData = ref({})
-const ywcbSevenData = ref({})
-const zqlData = ref({})
-const zqlTodayData = ref({})
-/** 获取统计数据 */
-const getStats = () => {
-  initYwcbChart()
-  // 获取基础统计数据
-  IotStatApi.getDeviceCount('rh').then((res) => {
-    device.value = res
-  })
-  IotStatApi.getMaintainCount('rh').then((res) => {
-    maintain.value = res
-  })
-  IotStatApi.getMainWorkCount("").then((res) => {
-    work.value = res
-  })
-  IotStatApi.getInspectCount("").then((res) => {
-    inspect.value = res
-  })
-  // IotStatApi.getMaintenanceStatus("").then((res) => {
-  //   status.value = res
-  //   // initCharts()
-  // })
-  IotStatApi.getMaintenanceTodayStatus("").then((res) => {
-    todayStatus.value = res
-    initTopChart()
-  })
+const company = ref('瑞恒')
 
-  IotStatApi.getDeviceStatusCount('rh').then((res) => {
-    typeData.value = res
-    initDeviceStatusCharts()
-  })
-  // IotStatApi.getSafeCount().then((res) => {
-  //   safe.value = res
-  // })
-  IotStatApi.getMaterial().then((res) => {
-    materialData.value = res
-    initMaterials()
-  })
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
 
-  IotStatApi.getOrderSeven('rh').then((res) => {
-    orderSevenData.value = res
-    initQxChart()
-  })
-  IotStatApi.getOrderYwcb('rh').then((res) => {
-    ywcbSevenData.value = res
-    initYwcbChart()
-  })
-  IotStatApi.getMaintenanceStatus('rh').then((res) => {
-    by.value = res
-  })
-  IotStatApi.getRhZqlGases('rh').then((res) => {
-    zqlData.value = res
-    const localeStore = useLocaleStore()
-    const lang = localeStore.getCurrentLocale.lang
-    if (lang==='zh-CN') {
-      initZqlChart('累计注气量','累计注水量')
-    } else if (lang==='en') {
-      initZqlChart('gas','water')
-    }
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
 
-  })
-  IotStatApi.getRhZqlDaily('rh').then((res) => {
-    zqlTodayData.value = res
-    const localeStore = useLocaleStore()
-    const lang = localeStore.getCurrentLocale.lang
-    if (lang==='zh-CN') {
-      initTodayZqlChart('当日注气量','当日注水量')
-    } else if (lang==='en') {
-      initTodayZqlChart('gas','water')
-    }
-  })
-  const fillQueryParams = reactive({
-    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-    endTime: Date.now(), // 设置默认结束时间为当前时间
-    createTime: [],
-    deptId: '', // 选中的部门ID
-    status: null // 填写状态
-  })
-  IotStatApi.getInspectStatuss(fillQueryParams, 'rh').then((res) => {
-    inspectt.value = res
-  })
-  fillQueryParams.deptId = '157'
-  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
-    fill.value = res.totalList[0] || []
-  })
-
-  // 计算近一周时间
-  const end = new Date();
-  const start = new Date();
-  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000);
-
-  // 格式化日期为后端需要的格式
-  const formatDate = (date) => {
-    const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, '0');
-    const day = String(date.getDate()).padStart(2, '0');
-    const hours = String(date.getHours()).padStart(2, '0');
-    const minutes = String(date.getMinutes()).padStart(2, '0');
-    const seconds = String(date.getSeconds()).padStart(2, '0');
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
-  };
-
-  rateQueryParams.createTime = [formatDate(start), formatDate(end)];
-  IotStatApi.getRhRate(rateQueryParams).then((res) =>{
-    materialTableData.value = res
-  })
-}
-let materialInstance;
-const initMaterials = () => {
-  if (!materialChartRef.value) return
-  const option = {
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color:'#B6C8DA'
-      }
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['50%', '80%'],
-        avoidLabelOverlap: false,
-        center: ['50%', '44%'],
-        label: {
-          show: false,
-          position: 'outside',
-          color:'#B6C8DA'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: false
-        },
-        data: materialData.value
-      }
-    ]
-  }
-  // 初始化图表
-  materialInstance = echarts.init(materialChartRef.value)
-  materialInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleMaterialResize)
-  handleMaterialResize()
-}
-let zqlTodayInstance = null
-const initTodayZqlChart = async (gas:any,water:any) => {
-  if (!todayZqlRef.value) return
-  zqlTodayInstance = echarts.init(todayZqlRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'cross',
-        label: {
-          backgroundColor: '#6a7985'
-        }
-      }
-    },
-    legend: {
-      data: zqlTodayData.value.series.map((item) => item.name),
-      top: 30,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: zqlTodayData.value.xAxis,
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
-      }
-    },
-    yAxis: [
-      {
-        type: 'value',
-        name:gas+'(m³)',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'left' // 左侧 Y 轴
-      },
-      // {
-      //   type: 'value',
-      //   name:water+'(m³)',
-      //   axisLabel: {
-      //     color: '#B6C8DA',
-      //     formatter: '{value}'
-      //   },
-      //   splitLine: {
-      //     show: true, // 显示水平网格线(默认显示)
-      //     lineStyle: {
-      //       // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-      //       color: '#457794', // 浅灰色半透明
-      //       // 可选:设置线条类型(实线/虚线/点线)
-      //       type: 'dashed'
-      //     }
-      //   },
-      //   axisLine: {
-      //     lineStyle: {
-      //       color: '#B6C8DA'
-      //     }
-      //   },
-      //   position: 'right', // 右侧 Y 轴
-      //   splitLine: {
-      //     show: false // 隐藏右侧 Y 轴的分割线
-      //   }
-      // }
-    ],
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
 
-    series: zqlTodayData.value.series.map((item, index) => {
-      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
-      // const yAxisIndex = index < 1 ? 0 : 1
-      return {
-        name: item.name,
-        type: 'line',
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 8,
-        itemStyle: {
-          color: ['#5470c6', '#f1d209'][index]
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(84,112,198,0.4)' },
-            { offset: 1, color: 'rgba(84,112,198,0.1)' }
-          ])
-        },
-        data: item.data,
-        // yAxisIndex: yAxisIndex // 指定使用的 Y 轴
-      }
-    })
-  }
+const targetAreaStyle = computed(() => ({
+  width: `${DESIGN_WIDTH}px`,
+  height: `${DESIGN_HEIGHT}px`,
+  transform: `scale(${scale.value})`,
+  transformOrigin: '0 0'
+}))
 
-  zqlTodayInstance.setOption(option)
-  // 窗口缩放监听
-  window.addEventListener('resize', handleZqlTodayResize)
-  handleZqlTodayResize()
-}
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
 
-/** 初始化图表 */
-const initDeviceStatusCharts = () => {
-  // 设备数量统计
-  echarts.init(statusChartRef.value).setOption({
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['50%', '80%'],
-        avoidLabelOverlap: false,
-        center: ['50%', '44%'],
-        label: {
-          show: false,
-          position: 'outside',
-          color: 'white'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: false
-        },
-        data: typeData.value
-      }
-    ]
-  })
-}
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
 
-/** 初始化消息统计图表 */
-const chartContainer = ref(null)
-let chartInstance;
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
 
-// 模拟数据获取
-const fetchChartData = async () => {
-  // 模拟异步请求
-  return new Promise((resolve) => {
-    setTimeout(() => {
-      resolve({
-        months: ['空压机', '增压机', '提纯撬'],
-        repairs: [10, 30, 90]
-      })
-    }, 300)
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
   })
 }
 
-// 初始化图表配置
-const initYwcbChart = async () => {
-  if (!chartContainer.value) return
-
-  // 获取数据
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      },
-      // formatter: (params) => {
-      //   return `${params[0].axisValue}<br/>
-      //           ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
-      // }
-    },
-    // legend: {
-    //   data: ['当日运维成本'],
-    //   top: 1,
-    //   textStyle: {
-    //     color: '#B6C8DA'
-    //   }
-    // },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '1%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: ywcbSevenData.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/')
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-    },
-    yAxis: {
-      type: 'value',
-      axisLabel: {
-        color: '#B6C8DA',
-        // formatter: (value) => Math.floor(value).toString()
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-
-    },
-    series: [
-      {
-        name: '当日运维成本',
-        type: 'bar',
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#f69606' }])
-        },
-        emphasis: {
-          focus: 'series'
-        },
-        data: ywcbSevenData.value.series
-      }
-    ]
-  }
-
-  // 初始化图表
-  chartInstance = echarts.init(chartContainer.value)
-  chartInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleResize)
-  handleResize()
-}
-
-// 自适应调整
-const handleResize = () => {
-  chartInstance?.resize()
-}
-
-// 自适应调整
-const handleQxResize = () => {
-  qxInstance?.resize()
-}
-const handleZqlResize = () => {
-  zqlInstance?.resize()
-}
-const handleZqlTodayResize = () => {
-  zqlTodayInstance?.resize()
-}
-const handleMaterialResize = () => {
-  materialInstance?.resize()
-}
-const topContainer = ref(null)
-let topInstance;
-// 响应式容器尺寸
-const { width, height } = useElementSize(topContainer)
-
-// 初始化图表配置
-const getTopOption = () => {
-  // backendData.value = data
-  const data = backendData.value.sort((a, b) => a.value - b.value)
-  return {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'shadow' },
-      formatter: (params) => {
-        const item = params[0]
-        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
-      }
-    },
-    grid: {
-      height: '200px',
-      left: '6%',
-      right: '6%',
-      bottom: '18%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'value',
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => {
-          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
-          return value.toLocaleString()
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      }
-    },
-    yAxis: {
-      type: 'category',
-      data: data.map((item) => item.category),
-      axisTick: { show: false },
-      axisLabel: { color: '#B6C8DA' },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      }
-    },
-    series: [
-      {
-        type: 'bar',
-        data: data.map((item) => item.value),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
-            { offset: 0, color: '#83bff6' },
-            { offset: 0.7, color: '#188df0' },
-            { offset: 1, color: '#188df0' }
-          ]),
-          borderRadius: [0, 8, 8, 0]
-        },
-        label: {
-          show: true,
-          position: 'right',
-          formatter: '{@value}',
-          color: '#B6C8DA',
-          fontWeight: 'bold'
-        },
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowColor: 'rgba(0, 0, 0, 0.5)'
-          }
-        }
-      }
-    ]
+onMounted(() => {
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
   }
-}
 
-// 初始化图表
-const initTopChart = async () => {
-  await IotStatApi.getDeviceTypeCount('rh').then((res) => {
-    backendData.value = res
-  })
-  if (!topContainer.value) return
-  topInstance = echarts.init(topContainer.value)
-  updateTopChart()
-}
-
-// 更新图表
-const updateTopChart = () => {
-  if (!topInstance) return
-  topInstance.setOption(getTopOption())
-}
-
-// 自适应调整
-watch([width, height], () => {
-  topInstance?.resize()
+  window.addEventListener('resize', updateScale)
 })
 
-// 监听数据变化
-watch(
-  backendData,
-  () => {
-    updateTopChart()
-  },
-  { deep: true }
-)
-
-const qxRef = ref(null)
-let qxInstance;
-let zqlInstance;
-
-const initZqlChart = (gas:any,water:any) => {
-  if (!zqlChartRef.value) return
-
-  // 获取数据
-  // ECharts配置
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow',
-        label: {
-          backgroundColor: '#6a7985'
-        }
-      }
-      // trigger: 'axis',
-      // axisPointer: {
-      //   type: 'shadow'
-      // },
-      // formatter: (params) => {
-      //   return `${params[0].axisValue}<br/>
-      //           ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
-      // }
-    },
-    legend: {
-      data: zqlData.value.series.map((item) => item.name),
-      top: 30,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: zqlData.value.xAxis,
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/')
-      }
-    },
-    yAxis: [
-      {
-        type: 'value',
-        name: gas+'(m³)',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'left' // 左侧 Y 轴
-      },
-    ],
-    series: zqlData.value.series.map((item, index) => {
-      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
-      return {
-        name: item.name,
-        type: 'bar',
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 8,
-        itemStyle: {
-          color: [ '#91cc75','#f1d209'][index]
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(84,112,198,0.4)' },
-            { offset: 1, color: 'rgba(84,112,198,0.1)' }
-          ])
-        },
-        data: item.data,
-      }
-    })
-  }
-
-  // 初始化图表
-  zqlInstance = echarts.init(zqlChartRef.value)
-  zqlInstance.setOption(option)
-
-  // 窗口缩放监听
-  window.addEventListener('resize', handleZqlResize)
-  handleZqlResize()
-
-}
-
-const initQxChart = () => {
-  if (!qxRef.value) return
-  qxInstance = echarts.init(qxRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'cross',
-        label: {
-          backgroundColor: '#6a7985'
-        }
-      }
-    },
-    legend: {
-      data: orderSevenData.value.series.map((item) => item.name),
-      top: 30,
-      textStyle: {
-        color:'#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: orderSevenData.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/')
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-    },
-    yAxis: [
-      {
-        type: 'value',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'left' // 左侧 Y 轴
-      },
-      {
-        type: 'value',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'right', // 右侧 Y 轴
-        splitLine: {
-          show: false // 隐藏右侧 Y 轴的分割线
-        }
-      }
-    ],
-
-    series: orderSevenData.value.series.map((item, index) => {
-      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
-      const yAxisIndex = index < 2 ? 0 : 1
-      return {
-        name: item.name,
-        type: 'line',
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 8,
-        itemStyle: {
-          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(84,112,198,0.4)' },
-            { offset: 1, color: 'rgba(84,112,198,0.1)' }
-          ])
-        },
-        data: item.data,
-        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
-      }
-    })
-  }
-
-  qxInstance.setOption(option)
-  // 窗口缩放监听
-  window.addEventListener('resize', handleQxResize)
-  handleQxResize()
-}
-
-// 响应式调整
-const resizeQxChart = () => qxInstance?.resize()
-const resizeZqlChart = () => zqlInstance?.resize()
-const resizeZqlTodayChart = () => zqlTodayInstance?.resize()
-const resizeMaterialChart = () => materialInstance?.resize()
-/** 初始化 */
-onMounted(() => {
-  getStats()
-  // initChart()
-  // initTopChart()
-  // initActiveChart()
-  // initQxChart()
-  window.addEventListener('resize', resizeQxChart)
-  // fetchTop()
-  window.addEventListener('resize', () => topInstance?.resize())
-})
-onBeforeUnmount(() => {
-  chartInstance?.dispose()
-  window.removeEventListener('resize', () => chartInstance?.resize())
-  topInstance?.dispose()
-  window.removeEventListener('resize', handleResize)
-  qxInstance?.dispose()
-  window.removeEventListener('resize', resizeQxChart)
-  zqlInstance?.dispose()
-  window.removeEventListener('resize', resizeZqlChart)
-  zqlTodayInstance?.dispose()
-  window.removeEventListener('resize', resizeZqlTodayChart)
-  materialInstance?.dispose()
-  window.removeEventListener('resize', resizeMaterialChart)
+onUnmounted(() => {
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
 })
 </script>
 
-<style lang="scss" scoped>
-.page-container {
-  background-color: #3a6fa3;
-  min-height: 100vh;
-  padding: 20px;
-}
-
-.summary {
-  margin-bottom: 20px;
-}
-
-::v-deep .chart-card {
-  background-color: rgba(0, 0, 0, 0.3);
-  border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
-  transition: all 0.3s ease;
-  border: none;
-
-  &:hover {
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
-  }
-}
-
-.safety-days-card {
-  .safety-days-content {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 150px;
-    position: relative;
-
-    .days-number {
-      font-size: 58px;
-      font-weight: bold;
-      color: darkorange;
-      line-height: 1;
-      transition: all 0.3s ease;
-    }
-
-    .days-number:hover {
-      transform: scale(1.05);
-    }
-
-    .days-label {
-      font-size: 20px;
-      color: white;
-      margin-top: 8px;
-    }
-
-    .safety-desc {
-      font-size: 14px;
-      color: #999;
-      position: absolute;
-      bottom: 10px;
-      text-align: center;
-      width: 90%;
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .page-container {
-    padding: 10px;
-  }
-}
-::v-deep .el-card__header {
-  border-bottom: none !important;
-  padding-bottom: 0;
-}
-
-.table-container {
-  padding: 16px;
-  height: 320px;
-  box-sizing: border-box;
-  overflow: auto;
-
-  // 滚动条样式优化
-  &::-webkit-scrollbar {
-    width: 6px;
-    height: 6px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    background-color: rgba(255, 255, 255, 0.2);
-    border-radius: 3px;
-  }
-
-  &::-webkit-scrollbar-track {
-    background-color: transparent;
-  }
-}
-
-// 修复表格hover样式
-::v-deep .el-table__row:hover > td {
-  background-color: rgba(255, 255, 255, 0.05) !important;
-}
-.custom-scroll-dialog {
-  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
-  max-height: 90vh;
-  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
-}
-/* 滚动内容容器:核心样式 */
-.dialog-scroll-content {
-  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
-  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
-}
+<template>
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="bg" id="rhkb" :style="targetAreaStyle">
+        <header class="header">{{ company }}</header>
+        <div class="mt-3 px-5">
+          <rhsummary class="kb-stage-card kb-stage-card--1" />
+          <div class="w-full h-148 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
+            <deviceStatus class="kb-stage-card kb-stage-card--2" />
+            <deviceType class="kb-stage-card kb-stage-card--3" />
+            <operation class="kb-stage-card kb-stage-card--4" />
+            <orderTrend class="kb-stage-card kb-stage-card--5" />
+            <todayGas class="kb-stage-card kb-stage-card--6" />
+            <historyGas class="kb-stage-card kb-stage-card--7" />
+          </div>
+          <deviceList class="kb-stage-card kb-stage-card--8 kb-stage-card--list" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
 
-/* 优化滚动条样式(可选,提升UI体验) */
-.dialog-scroll-content::-webkit-scrollbar {
-  width: 6px; /* 滚动条宽度 */
-}
-.dialog-scroll-content::-webkit-scrollbar-thumb {
-  background-color: #e5e7eb; /* 滚动条滑块颜色 */
-  border-radius: 3px; /* 滚动条圆角 */
-}
-.dialog-scroll-content::-webkit-scrollbar-thumb:hover {
-  background-color: #d1d5db; /*  hover时滑块颜色 */
-}
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
 </style>

+ 191 - 0
src/views/pms/stat/rhkb/deviceList.vue

@@ -0,0 +1,191 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { rangeShortcuts } from '@/utils/formatTime'
+import dayjs from 'dayjs'
+
+interface RhDeviceListRow {
+  projectDeptId: number
+  projectDeptName: string
+  teamCount: number
+  cumulativeDays: number
+  constructionDays: number
+  utilizationRate: number
+  gasInjection: number
+  sort?: number | null
+}
+
+interface RhTeamRateRow {
+  teamName: string
+  cumulativeDays: number
+  constructionDays: number
+  utilizationRate: number
+}
+
+const TABLE_HEIGHT = 220
+const TEAM_TABLE_HEIGHT = 500
+const DEFAULT_TIME_RANGE = rangeShortcuts[2]
+  .value()
+  .map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+
+const createTime = ref<string[]>(DEFAULT_TIME_RANGE)
+const loading = ref(false)
+const list = ref<RhDeviceListRow[]>([])
+const teamDialogVisible = ref(false)
+const teamLoading = ref(false)
+const currentProjectDeptName = ref('')
+const teamList = ref<RhTeamRateRow[]>([])
+
+const tableData = computed(() => list.value)
+
+function formatRate(value?: number | null) {
+  return `${(Number(value ?? 0) * 100).toFixed(2)}%`
+}
+
+function formatGasInjection(value?: number | null) {
+  return (Number(value ?? 0) / 10000).toFixed(2)
+}
+
+function handleDateChange() {
+  getList()
+}
+
+async function handleRowClick(row: RhDeviceListRow) {
+  currentProjectDeptName.value = row.projectDeptName || '项目部'
+  teamDialogVisible.value = true
+  teamLoading.value = true
+
+  try {
+    const res = await IotStatApi.getRhTeamRate({
+      deptId: row.projectDeptId,
+      createTime: createTime.value
+    })
+    teamList.value = Array.isArray(res) ? (res as RhTeamRateRow[]) : []
+  } catch (error) {
+    console.error('获取瑞恒队伍明细失败:', error)
+    teamList.value = []
+  } finally {
+    teamLoading.value = false
+  }
+}
+
+function handleDialogClosed() {
+  teamList.value = []
+  currentProjectDeptName.value = ''
+  teamLoading.value = false
+}
+
+async function getList() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getRhRate({ createTime: createTime.value })
+    list.value = Array.isArray(res) ? (res as RhDeviceListRow[]) : []
+  } catch (error) {
+    console.error('获取瑞恒项目部汇总失败:', error)
+    list.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-[280px] flex flex-col mt-3">
+    <div class="panel-title h-9 flex items-center justify-between">
+      <div class="flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        项目部统计
+      </div>
+      <div class="w-260px! -translate-y-[4px]">
+        <el-date-picker
+          v-model="createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          :clearable="false"
+          :shortcuts="rangeShortcuts"
+          class="w-260px!"
+          @change="handleDateChange"
+        />
+      </div>
+    </div>
+    <!-- v-loading="loading" -->
+    <div class="flex-1 min-h-0 px-4 py-2">
+      <el-table
+        :data="tableData"
+        :height="TABLE_HEIGHT"
+        class="device-list-table"
+        @row-click="handleRowClick"
+      >
+        <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
+        <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" min-width="120" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" min-width="120" align="center" />
+        <el-table-column label="注气量(万方)" min-width="150" align="center">
+          <template #default="{ row }">
+            {{ formatGasInjection(row.gasInjection) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="设备利用率" min-width="140" align="center">
+          <template #default="{ row }">
+            {{ formatRate(row.utilizationRate) }}
+          </template>
+        </el-table-column>
+
+        <template #empty>
+          <div class="h-full min-h-[220px] flex items-center justify-center">
+            <el-empty description="暂无数据" :image-size="72" />
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <el-dialog
+      v-model="teamDialogVisible"
+      :title="`${currentProjectDeptName || '项目部'}队伍明细`"
+      width="1200px"
+      destroy-on-close
+      class="device-list-dialog"
+      @closed="handleDialogClosed"
+      append-to-body
+    >
+      <el-table
+        v-loading="teamLoading"
+        :data="teamList"
+        :height="TEAM_TABLE_HEIGHT"
+        class="team-list-table"
+      >
+        <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />
+        <el-table-column label="设备利用率" min-width="160" align="center">
+          <template #default="{ row }">
+            {{ formatRate(row.utilizationRate) }}
+          </template>
+        </el-table-column>
+
+        <template #empty>
+          <div class="h-full min-h-[220px] flex items-center justify-center">
+            <el-empty description="暂无队伍明细" :image-size="72" />
+          </div>
+        </template>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>
+
+<style>
+@import url('@/styles/kb-dialog.scss');
+</style>

+ 134 - 0
src/views/pms/stat/rhkb/deviceStatus.vue

@@ -0,0 +1,134 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, ChartItem, createLegend, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  return {
+    ...ANIMATION,
+    grid: THEME.grid,
+    tooltip: createTooltip({ trigger: 'item' }),
+    legend: createLegend(
+      { bottom: 10, itemWidth: 12, itemHeight: 12 },
+      data.map((item) => item.name)
+    ),
+    series: [
+      {
+        name: '设备状态',
+        type: 'pie',
+        center: ['50%', '44%'],
+        radius: ['50%', '70%'],
+        minAngle: 5,
+        label: {
+          show: false,
+          position: 'center',
+          formatter(params: any) {
+            return `{name|${params.name}}\n{value|${params.value}}`
+          },
+          rich: {
+            name: {
+              color: THEME.text.regular,
+              fontSize: 14,
+              fontWeight: 500,
+              lineHeight: 24,
+              fontFamily: FONT_FAMILY
+            },
+            value: {
+              color: THEME.text.strong,
+              fontSize: 28,
+              fontWeight: 700,
+              lineHeight: 36,
+              fontFamily: FONT_FAMILY
+            }
+          }
+        },
+        emphasis: {
+          label: {
+            show: true
+          },
+          itemStyle: {
+            shadowBlur: 14,
+            shadowColor: THEME.color.blue.shadow
+          }
+        },
+        data
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getDeviceStatusCount('rh')
+    chartData.value = Array.isArray(res) ? res : []
+    renderChart()
+  } catch (error) {
+    console.error('获取设备状态失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备状态统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 179 - 0
src/views/pms/stat/rhkb/deviceType.vue

@@ -0,0 +1,179 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, ChartItem, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  const names = data.map((item) => item.name)
+  const values = data.map((item) => item.value)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, right: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    xAxis: {
+      type: 'value',
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: names,
+      inverse: true,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        margin: 16,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY
+      }
+    },
+    series: [
+      {
+        name: '设备类型',
+        type: 'bar',
+        data: values,
+        barWidth: 14,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: 999,
+          shadowBlur: 12,
+          shadowColor: THEME.color.blue.bg,
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: THEME.color.blue.light },
+            { offset: 0.5, color: THEME.color.blue.mid },
+            { offset: 1, color: THEME.color.blue.line }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'right',
+          distance: 8,
+          color: THEME.color.blue.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 16,
+            shadowColor: THEME.color.blue.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getDeviceTypeCount('rh')
+    chartData.value = Array.isArray(res)
+      ? res.map((item) => ({ name: item.category, value: item.value }))
+      : []
+    renderChart()
+  } catch (error) {
+    console.error('获取设备类型失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备类别top
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 207 - 0
src/views/pms/stat/rhkb/historyGas.vue

@@ -0,0 +1,207 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartItem,
+  createTooltip,
+  FONT_FAMILY,
+  formatGasAxisValue,
+  formatGasLabelValue,
+  formatMonthLabel,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  const names = data.map((item) => item.name)
+  const values = data.map((item) => item.value)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 36, right: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      data: names,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatMonthLabel(value)
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '累计注气量(m³)',
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return formatGasAxisValue(value)
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        name: '累计注气量',
+        type: 'bar',
+        data: values,
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: [12, 12, 0, 0],
+          shadowBlur: 12,
+          shadowColor: THEME.color.green.bg,
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: THEME.color.green.light },
+            { offset: 0.5, color: THEME.color.green.mid },
+            { offset: 1, color: THEME.color.green.line }
+          ])
+        },
+        label: {
+          show: false
+        },
+        emphasis: {
+          label: {
+            show: true,
+            position: 'top',
+            distance: 10,
+            color: THEME.color.green.strong,
+            fontSize: 16,
+            fontWeight: 700,
+            fontFamily: FONT_FAMILY,
+            formatter(params: any) {
+              const value = Number(params.value || 0)
+              return formatGasLabelValue(value)
+            }
+          },
+          itemStyle: {
+            shadowBlur: 18,
+            shadowColor: THEME.color.green.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRhZqlGases('rh')
+    const seriesData = res.series?.[0]?.data || []
+
+    chartData.value = (res.xAxis || []).map((item, index) => ({
+      name: item,
+      value: Number(seriesData[index] || 0)
+    }))
+    renderChart()
+  } catch (error) {
+    console.error('获取累计注气量失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      累计注气量统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 187 - 0
src/views/pms/stat/rhkb/operation.vue

@@ -0,0 +1,187 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartItem,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  const names = data.map((item) => item.name)
+  const values = data.map((item) => item.value)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      data: names,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatDateLabel(value)
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        name: '运维成本',
+        type: 'bar',
+        data: values,
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: [12, 12, 0, 0],
+          shadowBlur: 12,
+          shadowColor: THEME.color.orange.bg,
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: THEME.color.orange.light },
+            { offset: 0.5, color: THEME.color.orange.mid },
+            { offset: 1, color: THEME.color.orange.line }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: THEME.color.orange.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 18,
+            shadowColor: THEME.color.orange.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getOrderYwcb('rh')
+    chartData.value = res.xAxis.map((item, index) => ({
+      name: item,
+      value: res.series[index]
+    }))
+    renderChart()
+  } catch (error) {
+    console.error('运维成本:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      运维成本
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 212 - 0
src/views/pms/stat/rhkb/orderTrend.vue

@@ -0,0 +1,212 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      }
+    }),
+    legend: createLegend(
+      { top: 5 },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatDateLabel(value)
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          lineStyle: {
+            color: THEME.split,
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'left'
+      },
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          show: false
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'right'
+      }
+    ],
+    series: seriesData.map((item, index) => {
+      const colorList = Object.values(THEME.color)
+      const color = colorList[index % colorList.length]
+      const yAxisIndex = index < 2 ? 0 : 1
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'line',
+        smooth: true,
+        data: item.data,
+        yAxisIndex,
+        symbol: 'circle',
+        symbolSize: 8,
+        showSymbol: true,
+        lineStyle: {
+          width: 2,
+          color: color.line
+        },
+        itemStyle: {
+          color: color.line
+        },
+        areaStyle: {
+          color: color.bg
+        },
+        emphasis: {
+          focus: 'series',
+          scale: true
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getOrderSeven('rh')
+    chartData.value = {
+      xAxis: res.xAxis || [],
+      series: (res.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取近7日工单趋势失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单数量统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 274 - 0
src/views/pms/stat/rhkb/rhsummary.vue

@@ -0,0 +1,274 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { CardStateItem, SummaryCardConfig, THEME } from '@/utils/kb'
+import dayjs from 'dayjs'
+
+type CardKey =
+  | 'device'
+  | 'maintain'
+  | 'unfilledCount'
+  | 'filledCount'
+  | 'bytodo'
+  | 'byfinished'
+  | 'inspectttodo'
+  | 'inspecttfinished'
+
+type CardConfig = SummaryCardConfig<CardKey>
+
+const cardConfigs: CardConfig[] = [
+  {
+    key: 'device',
+    title: '设备数',
+    icon: 'i-material-symbols:device-hub-rounded',
+    accent: THEME.color.blue.strong,
+    glow: THEME.color.blue.glow
+  },
+  {
+    key: 'maintain',
+    title: '维修工单',
+    icon: 'i-material-symbols:home-repair-service',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'unfilledCount',
+    title: '运行未填写',
+    icon: 'i-solar:clipboard-remove-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'filledCount',
+    title: '运行已填写',
+    icon: 'i-solar:clipboard-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'bytodo',
+    title: '待保养',
+    icon: 'i-solar:shield-warning-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'byfinished',
+    title: '已保养',
+    icon: 'i-solar:shield-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'inspectttodo',
+    title: '待巡检',
+    icon: 'i-solar:map-point-search-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'inspecttfinished',
+    title: '已巡检',
+    icon: 'i-solar:check-circle-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  }
+]
+
+function createDefaultCardState(): Record<CardKey, CardStateItem> {
+  return {
+    device: { value: 0, loading: true },
+    maintain: { value: 0, loading: true },
+    unfilledCount: { value: 0, loading: true },
+    filledCount: { value: 0, loading: true },
+    bytodo: { value: 0, loading: true },
+    byfinished: { value: 0, loading: true },
+    inspectttodo: { value: 0, loading: true },
+    inspecttfinished: { value: 0, loading: true }
+  }
+}
+
+const cardState = reactive<Record<CardKey, CardStateItem>>(createDefaultCardState())
+
+const summaryCards = computed(() =>
+  cardConfigs.map((card) => ({
+    ...card,
+    value: cardState[card.key].value,
+    loading: cardState[card.key].loading
+  }))
+)
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+function setCardValue(key: CardKey, value: unknown) {
+  cardState[key].value = toNumber(value)
+}
+
+function setCardLoading(keys: CardKey[], loading: boolean) {
+  keys.forEach((key) => {
+    cardState[key].loading = loading
+  })
+}
+
+async function loadDeviceCard() {
+  const keys: CardKey[] = ['device']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getDeviceCount('rh')
+    setCardValue('device', res?.total)
+  } catch (error) {
+    console.error('获取设备数失败:', error)
+    setCardValue('device', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainCard() {
+  const keys: CardKey[] = ['maintain']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintainCount('rh')
+    setCardValue('maintain', res?.total)
+  } catch (error) {
+    console.error('获取维修工单失败:', error)
+    setCardValue('maintain', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadFillCards() {
+  const keys: CardKey[] = ['unfilledCount', 'filledCount']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    createTime: [],
+    deptId: 157,
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getDeptStatistics(params)
+    const totalStats = res?.totalList?.[0] ?? {}
+
+    setCardValue('unfilledCount', totalStats.unfilledCount)
+    setCardValue('filledCount', totalStats.filledCount)
+  } catch (error) {
+    console.error('获取运行填写数据失败:', error)
+    setCardValue('unfilledCount', 0)
+    setCardValue('filledCount', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainStatusCards() {
+  const keys: CardKey[] = ['bytodo', 'byfinished']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintenanceStatus('rh')
+    setCardValue('bytodo', res?.todo)
+    setCardValue('byfinished', res?.finished)
+  } catch (error) {
+    console.error('获取保养状态失败:', error)
+    setCardValue('bytodo', 0)
+    setCardValue('byfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadInspectCards() {
+  const keys: CardKey[] = ['inspectttodo', 'inspecttfinished']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    deptId: '',
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getInspectStatuss(params, 'rh')
+    setCardValue('inspectttodo', res?.todo)
+    setCardValue('inspecttfinished', res?.finished)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('inspectttodo', 0)
+    setCardValue('inspecttfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+function loadAllCards() {
+  loadDeviceCard()
+  loadMaintainCard()
+  loadFillCards()
+  loadMaintainStatusCards()
+  loadInspectCards()
+}
+
+onMounted(() => {
+  loadAllCards()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-28 flex flex-col">
+    <div class="panel-title h-8">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单情况
+    </div>
+
+    <div class="grid grid-cols-8 gap-2.5 flex-1 px-2.5 py-1.5">
+      <article
+        v-for="card in summaryCards"
+        :key="card.key"
+        class="summary-card relative flex h-full overflow-hidden rounded-md items-center gap-2 p-2"
+        :style="{
+          '--card-accent': card.accent,
+          '--card-glow': card.glow
+        }"
+      >
+        <div class="summary-card__shine"></div>
+
+        <div class="summary-card__icon">
+          <div :class="card.icon" class="summary-card__icon-glyph"></div>
+        </div>
+
+        <div class="summary-card__body">
+          <div class="summary-card__label">{{ card.title }}</div>
+          <div class="summary-card__value">
+            <CountTo
+              v-if="!card.loading"
+              style="color: #1f5bb8"
+              :start-val="0"
+              :end-val="card.value"
+              :duration="1200"
+            />
+            <span v-else class="summary-card__placeholder">--</span>
+          </div>
+        </div>
+
+        <div class="summary-card__corner"></div>
+      </article>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 218 - 0
src/views/pms/stat/rhkb/todayGas.vue

@@ -0,0 +1,218 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 36, right: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      },
+      formatter(params: any) {
+        const list = Array.isArray(params) ? params : [params]
+        const axisValue = list[0]?.axisValue || '--'
+
+        const lines = list
+          .map((item) => {
+            const value = typeof item.value === 'number' ? item.value.toLocaleString() : item.value
+            return `
+            <div style="display:flex;align-items:center;margin-top:4px;">
+              <span style="
+                display:inline-block;
+                width:10px;
+                height:10px;
+                border-radius:50%;
+                background:${item.color};
+                margin-right:6px;
+              "></span>
+              <span>${item.seriesName}:${value}</span>
+            </div>
+          `
+          })
+          .join('')
+
+        return `
+          <div>
+            <div style="font-weight:600;margin-bottom:4px;">${axisValue}</div>
+            ${lines}
+          </div>
+        `
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatDateLabel(value)
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '当日注气量(m³)',
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      splitNumber: 4,
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return value.toLocaleString()
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: THEME.split
+        }
+      }
+    },
+    series: seriesData.map((item) => ({
+      name: formatSeriesName(item.name),
+      type: 'line',
+      smooth: true,
+      data: item.data || [],
+      symbol: 'circle',
+      symbolSize: 8,
+      showSymbol: true,
+      lineStyle: {
+        width: 2,
+        color: THEME.color.blue.line
+      },
+      itemStyle: {
+        color: THEME.color.blue.line
+      },
+      areaStyle: {
+        color: THEME.color.blue.bg
+      },
+      emphasis: {
+        focus: 'series',
+        scale: true
+      }
+    }))
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRhZqlDaily('rh')
+    chartData.value = {
+      xAxis: res.xAxis || [],
+      series: (res.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取日注气量趋势失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      当日注气量统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 68 - 1448
src/views/pms/stat/rykb.vue

@@ -1,1471 +1,91 @@
-<template>
-  <div class="page-container">
-    <div class="grid mb-5 2xl:grid-cols-10 2xl:grid-rows-1 gap-4 xl:grid-cols-5 xl:grid-rows-2">
-      <SummaryCard
-        :value="device.total || 0"
-        icon="fa-solid:project-diagram"
-        icon-bg-color="text-blue-500"
-        icon-color="bg-blue-100"
-        :title="t('stat.deviceCount')"
-      />
-      <SummaryCard
-        :value="maintain.total || 0"
-        icon="fa-solid:list"
-        icon-bg-color="text-pink-500"
-        icon-color="bg-blue-100"
-        :title="t('stat.repairOrder')"
-      />
-      <SummaryCard
-        :value="fill.unfilledCount || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-purple-500"
-        icon-color="bg-purple-100"
-        :title="t('stat.operationNotFilled')"
-      />
-      <SummaryCard
-        :value="fill.filledCount || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-purple-500"
-        icon-color="bg-purple-100"
-        :title="t('stat.operationFilled')"
-      />
-      <SummaryCard
-        :value="by.todo || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-green-500"
-        icon-color="bg-green-100"
-        :title="t('stat.notMaintained')"
-      />
-      <SummaryCard
-        :value="by.finished || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-green-500"
-        icon-color="bg-green-100"
-        :title="t('stat.maintained')"
-      />
-      <SummaryCard
-        :value="inspectt.todo || 0"
-        icon="fa-solid:times-circle"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.notInspected')"
-      />
-      <SummaryCard
-        :value="inspectt.finished || 0"
-        icon="fa-solid:award"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.inspected')"
-      />
-      <SummaryCard
-        :value="inspectZjxjCount.zj || 0"
-        icon="solar:ruler-bold"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.zj')"
-      />
-      <SummaryCard
-        :value="inspectZjxjCount.xj || 0"
-        icon="mdi:wrench-check-outline"
-        icon-bg-color="text-yellow-500"
-        icon-color="bg-yellow-100"
-        :title="t('stat.xj')"
-      />
-    </div>
-    <!-- <el-row :gutter="16" class="mb-5">
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="device.total || 0"
-          icon="fa-solid:project-diagram"
-          icon-bg-color="text-blue-500"
-          icon-color="bg-blue-100"
-          :title="t('stat.deviceCount')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="maintain.total || 0"
-          icon="fa-solid:list"
-          icon-bg-color="text-pink-500"
-          icon-color="bg-blue-100"
-          :title="t('stat.repairOrder')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="fill.unfilledCount || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-purple-500"
-          icon-color="bg-purple-100"
-          :title="t('stat.operationNotFilled')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="fill.filledCount || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-purple-500"
-          icon-color="bg-purple-100"
-          :title="t('stat.operationFilled')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="by.todo || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-green-500"
-          icon-color="bg-green-100"
-          :title="t('stat.notMaintained')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="by.finished || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-green-500"
-          icon-color="bg-green-100"
-          :title="t('stat.maintained')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectt.todo || 0"
-          icon="fa-solid:times-circle"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.notInspected')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectt.finished || 0"
-          icon="fa-solid:award"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.inspected')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectZjxjCount.zj || 0"
-          icon="solar:ruler-bold"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.zj')"
-        />
-      </el-col>
-      <el-col :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspectZjxjCount.xj || 0"
-          icon="mdi:wrench-check-outline"
-          icon-bg-color="text-yellow-500"
-          icon-color="bg-yellow-100"
-          :title="t('stat.xj')"
-        />
-      </el-col>
-    </el-row> -->
-    <!-- 第二行:图表行 -->
-    <el-row :gutter="16" class="mb-4 mt-3">
-      <el-col :span="6">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.deviceStatus')
-              }}</span>
-            </div>
-          </template>
-          <div ref="statusChartRef" class="h-[320px]"></div>
-        </el-card>
-      </el-col>
-      <el-col :span="10">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div style="display: flex; flex-direction: row; justify-content: space-between">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.deviceRate')
-              }}</span>
-              <div>
-                <el-date-picker
-                  v-model="rateQueryParams.createTime"
-                  value-format="YYYY-MM-DD HH:mm:ss"
-                  type="daterange"
-                  start-placeholder="开始日期"
-                  end-placeholder="结束日期"
-                  :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-                  class="!w-220px"
-                  @change="handleDateChange"
-                />
-              </div>
-            </div>
-          </template>
-          <!--          <div ref="topContainer" class="h-[320px]"></div>-->
-          <div class="table-container">
-            <el-table
-              class="custom-table"
-              :data="materialTableData"
-              border
-              @row-click="handleRowClick"
-              style="width: 100%; color: #4c4c4c"
-              :header-cell-style="{
-                'background-color': '#2196df',
-                color: 'white',
-                'border-color': '#457794'
-              }"
-              :cell-style="{
-                'border-color': '#457794',
-                'background-color': '#284D72',
-                color: '#F1D209'
-              }"
-            >
-              <el-table-column prop="projectDeptName" label="项目部" align="center" />
-              <el-table-column prop="teamCount" label="队伍数量" align="center" />
-              <el-table-column prop="cumulativeDays" label="累计天数" align="center" />
-              <el-table-column prop="constructionDays" label="施工天数" align="center" />
-              <el-table-column
-                prop="utilizationRate"
-                label="设备利用率"
-                align="center"
-                :formatter="formatRate"
-              />
-            </el-table>
-          </div>
-        </el-card>
-      </el-col>
-      <!--      <el-col :span="7">-->
-      <!--        <el-card class="chart-card" shadow="never">-->
-      <!--          <template #header>-->
-      <!--            <div class="flex items-center">-->
-      <!--              <span class="text-base font-medium" style="color: #b6c8da">设备类别TOP5数量</span>-->
-      <!--            </div>-->
-      <!--          </template>-->
-      <!--          <div ref="topContainer" class="h-[320px]"></div>-->
-      <!--        </el-card>-->
-      <!--      </el-col>-->
-      <el-col :span="8">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center justify-between">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.orderCount')
-              }}</span>
-            </div>
-          </template>
-          <div ref="qxRef" class="h-[320px]"></div>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <!-- 新增:修井完成情况图表 -->
-    <el-row :gutter="16" class="mb-4">
-      <el-col :span="8">
-        <div class="flex flex-col justify-between">
-          <el-card class="safety-days-card chart-card h-[230px]" shadow="never">
-            <template #header>
-              <div class="flex items-center">
-                <span class="text-base font-medium" style="color: #b6c8da">{{
-                  t('stat.safeProduction')
-                }}</span>
-              </div>
-            </template>
-            <div class="safety-days-content">
-              <div class="days-number">{{ 165 }}</div>
-              <div class="days-label">天</div>
-              <div class="safety-desc">{{ t('stat.daysToDate') }}</div>
-            </div>
-          </el-card>
-          <el-card class="chart-card mt-1" shadow="never">
-            <template #header>
-              <div class="flex items-center">
-                <span class="text-base font-medium" style="color: #b6c8da">{{
-                  t('stat.drillingCompletion')
-                }}</span>
-              </div>
-            </template>
-            <div ref="drillingWellChartRef" class="h-[170px]"></div>
-          </el-card>
-        </div>
-      </el-col>
-      <el-col :span="8">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.drillingWork')
-              }}</span>
-            </div>
-          </template>
-          <div ref="drillingWorkloadChartRef" class="h-[405px]"></div>
-        </el-card>
-      </el-col>
-      <el-col :span="8">
-        <el-card class="chart-card" shadow="never">
-          <template #header>
-            <div class="flex items-center">
-              <span class="text-base font-medium" style="color: #b6c8da">{{
-                t('stat.wellWorkoverWorkload')
-              }}</span>
-            </div>
-          </template>
-          <div ref="repairWorkloadChartRef" class="h-[405px]"></div>
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-
-  <el-dialog
-    v-model="teamDialogVisible"
-    :title="t('stat.teamDetail')"
-    width="80vh"
-    :before-close="handleDialogClose"
-    class="custom-scroll-dialog"
-  >
-    <div class="dialog-scroll-content">
-      <el-table
-        :data="teamTableData"
-        border
-        style="width: 100%"
-        :header-cell-style="{
-          'background-color': 'rgba(0, 0, 0, 0.2)',
-          color: 'black',
-          'border-color': '#457794'
-        }"
-        :cell-style="{
-          'border-color': '#457794',
-          'background-color': 'transparent'
-        }"
-      >
-        <el-table-column prop="teamName" label="队伍名称" align="center" />
-        <el-table-column prop="cumulativeDays" label="累计天数" align="center" />
-        <el-table-column prop="constructionDays" label="施工天数" align="center" />
-        <el-table-column
-          prop="utilizationRate"
-          label="设备利用率"
-          align="center"
-          :formatter="formatRate"
-        />
-      </el-table>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts" name="Index">
-import * as echarts from 'echarts/core'
-import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
-import {
-  GridComponent,
-  LegendComponent,
-  TitleComponent,
-  ToolboxComponent,
-  TooltipComponent
-} from 'echarts/components'
-import { LabelLayout, UniversalTransition } from 'echarts/features'
-import { CanvasRenderer } from 'echarts/renderers'
-import { useElementSize } from '@vueuse/core'
-import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
-import { IotStatApi } from '@/api/pms/stat'
-import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
-
-// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
-
-/** IoT 首页 */
-defineOptions({ name: 'IotRyStat' })
-
-// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
-echarts.use([
-  TooltipComponent,
-  LegendComponent,
-  PieChart,
-  CanvasRenderer,
-  LabelLayout,
-  TitleComponent,
-  ToolboxComponent,
-  GridComponent,
-  LineChart,
-  UniversalTransition,
-  GaugeChart,
-  BarChart
-])
-const { t } = useI18n() // 国际化
-const teamDialogVisible = ref(false)
-const teamTableData = ref([])
-const currentDeptId = ref('')
-const currentDateRange = ref([])
-const materialTableData = ref([])
-const handleDateChange = () => {
-  IotStatApi.getRyRate(rateQueryParams).then((res) => {
-    materialTableData.value = res
-  })
-}
-const rateQueryParams = reactive({
-  createTime: []
-})
-const handleDialogClose = () => {
-  teamDialogVisible.value = false
-  teamTableData.value = [] // 清空表格数据
-}
-// 格式化利用率为百分比
-const formatRate = (row) => {
-  return (row.utilizationRate * 100).toFixed(2) + '%'
-}
-const handleRowClick = (row, column, event) => {
-  console.log('点击的行数据:', row)
-  currentDeptId.value = row.deptId // 假设行数据中包含deptId字段
-  currentDateRange.value = rateQueryParams.createTime
+<script setup lang="ts">
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
+import rysummary from './rykb/rysummary.vue'
+import safeday from './rykb/safeday.vue'
+import rydeviceStatus from './rykb/rydeviceStatus.vue'
+import ryorderTrend from './rykb/ryorderTrend.vue'
+import zjfinish from './rykb/zjfinish.vue'
+import zjwork from './rykb/zjwork.vue'
+import xjwork from './rykb/xjwork.vue'
+import rydeviceList from './rykb/rydeviceList.vue'
 
-  // 打开弹窗并加载队伍详情数据
-  teamDialogVisible.value = true
-  // fetchTeamDetailData(row.deptId, rateQueryParams.createTime)
-  debugger
-  const teamParams = {
-    deptId: row.projectDeptId,
-    createTime: rateQueryParams.createTime
-  }
-  IotStatApi.getRyTeamRate(teamParams).then((res) => {
-    teamTableData.value = res
-  })
-}
-const dateRange = ref<[Date, Date] | null>(null)
-const by = ref({
-  todo: undefined,
-  finished: undefined
-})
-const fill = ref({
-  filledCount: undefined,
-  unfilledCount: undefined
-})
-const inspectt = ref({
-  finished: 0,
-  todo: 0
-})
-const queryParams = reactive({
-  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-  endTime: Date.now() // 设置默认结束时间为当前时间
-})
-const backendData = ref([])
-const statusChartRef = ref() // 设备数量统计的图表
-// const materialChartRef = ref() // 设备数量统计的图表
-const drillingWorkloadChartRef = ref()
-const repairWorkloadChartRef = ref()
-// 基础统计数据
-// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
-const statsData = ref<IotStatisticsSummaryRespVO>({
-  productCategoryCount: 0,
-  productCount: 0,
-  deviceCount: 0,
-  deviceMessageCount: 0,
-  productCategoryTodayCount: 0,
-  productTodayCount: 0,
-  deviceTodayCount: 0,
-  deviceMessageTodayCount: 0,
-  deviceOnlineCount: 0,
-  deviceOfflineCount: 0,
-  deviceInactiveCount: 0,
-  productCategoryDeviceCounts: {}
-})
-
-const device = ref({
-  total: undefined,
-  today: undefined
+defineOptions({
+  name: 'IotRyStatt'
 })
-const maintain = ref({
-  total: undefined,
-  today: undefined
-})
-const work = ref({
-  total: undefined,
-  today: undefined
-})
-const inspect = ref({
-  total: undefined,
-  today: undefined
-})
-
-const status = ref({
-  finished: 0,
-  todo: 0
-})
-const todayStatus = ref({
-  finished: 0,
-  todo: 0
-})
-const typeData = ref({})
-const materialData = ref({})
-const orderSevenData = ref({})
-const safe = ref()
-
-// 新增:修井完成情况数据
-const repairWellData = ref({
-  xAxis: ['小修10队', '小修8队', '小修9队'],
-  series: [
-    { name: '日累完成井数', data: [10, 15, 20] },
-    { name: '月累完成井数', data: [50, 60, 70] },
-    { name: '年累完成井数', data: [200, 220, 250] }
-  ]
-})
-
-// 新增:钻井完成情况数据
-const drillingWellData = ref({})
-const drillingWorkloadData = ref({})
-
-// 新增:修井工作量情况数据
-const repairWorkloadData = ref({})
-const repairWellChartRef = ref()
-const drillingWellChartRef = ref()
-
-const inspectZjxjCount = ref<any>({})
-
-/** 获取统计数据 */
-const getStats = () => {
-  // 获取基础统计数据
-  IotStatApi.getDeviceCount('ry').then((res) => {
-    device.value = res
-  })
-  IotStatApi.getMaintainCount('ry').then((res) => {
-    maintain.value = res
-  })
-  IotStatApi.getMainWorkCount().then((res) => {
-    work.value = res
-  })
-  IotStatApi.getInspectCount().then((res) => {
-    inspect.value = res
-  })
-  IotStatApi.getMaintenanceStatus('ry').then((res) => {
-    status.value = res
-    // initCharts()
-  })
-  // IotStatApi.getMaintenanceTodayStatus().then((res) => {
-  //   todayStatus.value = res
-  // })
-
-  IotStatApi.getDeviceStatusCount('ry').then((res) => {
-    typeData.value = res
-    initDeviceStatusCharts()
-  })
-  IotStatApi.getSafeCount().then((res) => {
-    safe.value = res
-  })
-  // IotStatApi.getMaterial().then((res) => {
-  //   materialData.value = res
-  //   initMaterials()
-  // })
-
-  IotStatApi.getOrderSeven('ry').then((res) => {
-    orderSevenData.value = res
-    initQxChart()
-  })
-
-  IotStatApi.getMaintenanceStatus('ry').then((res) => {
-    by.value = res
-  })
-
-  IotStatApi.getRepairRigWork('repair').then((res) => {
-    repairWorkloadData.value = res
-    initRepairWorkloadChart(res)
-  })
-  IotStatApi.getRepairRigWork('rig').then((res) => {
-    drillingWorkloadData.value = res
-    initDrillingWorkloadChart(res)
-  })
-  IotStatApi.getRigFinished().then((res) => {
-    debugger
-    drillingWellData.value = res
-    initDrillingWellChart(res)
-  })
-  const fillQueryParams = reactive({
-    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-    endTime: Date.now(), // 设置默认结束时间为当前时间
-    createTime: [],
-    deptId: null, // 选中的部门ID
-    status: null // 填写状态
-  })
-  IotStatApi.getInspectStatuss(fillQueryParams, 'ry').then((res) => {
-    inspectt.value = res
-  })
-
-  IotStatApi.getInspectZjxjCount().then((res) => {
-    inspectZjxjCount.value = res
-
-    console.log('inspectZjxjCount.value :>> ', inspectZjxjCount.value)
-  })
-  fillQueryParams.deptId = '158'
-  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
-    fill.value = res.totalList[0] || []
-  })
-  // IotStatApi.getProject('ry').then((res) => {
-  //   typeData.value = res;
-  //   initProjectCharts()
-  // })
-
-  // 计算近一周时间
-  const end = new Date()
-  const start = new Date()
-  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000)
-
-  // 格式化日期为后端需要的格式
-  const formatDate = (date) => {
-    const year = date.getFullYear()
-    const month = String(date.getMonth() + 1).padStart(2, '0')
-    const day = String(date.getDate()).padStart(2, '0')
-    const hours = String(date.getHours()).padStart(2, '0')
-    const minutes = String(date.getMinutes()).padStart(2, '0')
-    const seconds = String(date.getSeconds()).padStart(2, '0')
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
-  }
-
-  rateQueryParams.createTime = [formatDate(start), formatDate(end)]
-  IotStatApi.getRyRate(rateQueryParams).then((res) => {
-    materialTableData.value = res
-  })
-}
-
-const initDrillingWorkloadChart = (res) => {
-  if (!drillingWorkloadChartRef.value) return
-  const chart = echarts.init(drillingWorkloadChartRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      }
-    },
-    legend: {
-      data: res.series.map((item) => item.name),
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: res.xAxis,
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '进尺(米)',
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    series: res.series.map((item) => ({
-      name: item.name,
-      type: 'bar',
-      data: item.data
-    }))
-  }
-  chart.setOption(option)
-}
-
-// 新增:初始化修井工作量情况图表
-const initRepairWorkloadChart = (res) => {
-  if (!repairWorkloadChartRef.value) return
-  const chart = echarts.init(repairWorkloadChartRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      }
-    },
-    legend: {
-      data: res.series.map((item) => item.name),
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: res.xAxis,
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '完井数(个)',
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    series: res.series.map((item) => ({
-      name: item.name,
-      type: 'bar',
-      data: item.data
-    }))
-  }
-  chart.setOption(option)
-}
 
-/** 初始化图表 */
-const initDeviceStatusCharts = () => {
-  // 设备数量统计
-  echarts.init(statusChartRef.value).setOption({
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['50%', '80%'],
-        avoidLabelOverlap: false,
-        center: ['50%', '46%'],
-        label: {
-          show: false,
-          position: 'outside',
-          color: 'white'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: false
-        },
-        data: typeData.value
-      }
-    ]
-  })
-}
+const company = ref('瑞鹰')
 
-// 自适应调整
-const handleResize = () => {
-  topInstance?.resize()
-}
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
 
-const topContainer = ref(null)
-let topInstance = null
-// 响应式容器尺寸
-const { width, height } = useElementSize(topContainer)
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
 
-// 处理数据(排序+限制5条)
-const processedData = () => {
-  const data = IotStatApi.getDeviceTypeCount()
-  backendData.value = data
-  return [...backendData.value].sort((a, b) => a.value - b.value)
-}
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
 
-const fetchTop = () => {
-  IotStatApi.getDeviceTypeCount().then((res) => {
-    backendData.value = res
-  })
-}
+const targetAreaStyle = computed(() => ({
+  width: `${DESIGN_WIDTH}px`,
+  height: `${DESIGN_HEIGHT}px`,
+  transform: `scale(${scale.value})`,
+  transformOrigin: '0 0'
+}))
 
-watch(
-  backendData,
-  () => {
-    updateTopChart()
-  },
-  { deep: true }
-)
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
 
-// 初始化图表配置
-const getTopOption = () => {
-  // backendData.value = data
-  const data = backendData.value.sort((a, b) => a.value - b.value)
-  return {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'shadow' },
-      formatter: (params) => {
-        const item = params[0]
-        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
-      }
-    },
-    grid: {
-      height: '200px',
-      left: '6%',
-      right: '6%',
-      bottom: '18%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'value',
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => {
-          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
-          return value.toLocaleString()
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      }
-    },
-    yAxis: {
-      type: 'category',
-      data: data.map((item) => item.category),
-      axisTick: { show: false },
-      axisLabel: { color: '#B6C8DA' },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      }
-    },
-    series: [
-      {
-        type: 'bar',
-        data: data.map((item) => item.value),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
-            { offset: 0, color: '#83bff6' },
-            { offset: 0.7, color: '#188df0' },
-            { offset: 1, color: '#188df0' }
-          ]),
-          borderRadius: [0, 8, 8, 0]
-        },
-        label: {
-          show: true,
-          position: 'right',
-          formatter: '{@value}',
-          color: '#B6C8DA',
-          fontWeight: 'bold'
-        },
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowColor: 'rgba(0, 0, 0, 0.5)'
-          }
-        }
-      }
-    ]
-  }
-}
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
 
-// // 初始化图表
-// const initTopChart = async () => {
-//   await IotStatApi.getDeviceTypeCount('ry').then((res) => {
-//     backendData.value = res
-//   })
-//   if (!topContainer.value) return
-//   topInstance = echarts.init(topContainer.value)
-//   updateTopChart()
-// }
-//
-// // 更新图表
-// const updateTopChart = () => {
-//   if (!topInstance) return
-//   topInstance.setOption(getTopOption())
-// }
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
 
-const initProjectCharts = () => {
-  const chart = echarts.init(topContainer.value)
-  chart.setOption({
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      // top: '5%',
-      // right: '10%',
-      // align: 'center',
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['0%', '70%'],
-        avoidLabelOverlap: false,
-        center: ['50%', '45%'],
-        label: {
-          show: false,
-          position: 'outside',
-          color: '#B6C8DA'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: true
-        },
-        data: typeData.value
-      }
-    ]
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
   })
 }
 
-// 自适应调整
-watch([width, height], () => {
-  topInstance?.resize()
-})
-const activeDom = ref(null)
-let activeInstance = null
-
-const activeData = ref([])
-const initActiveChart = async () => {
-  if (!activeDom.value) return
-  activeData.value = await IotStatApi.getDeptCount()
-  activeInstance = echarts.init(activeDom.value)
-
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'shadow' },
-      formatter: (params) => `
-        ${params[0].name}<br/>
-        ${params[0].marker} 总人数: ${params[0].value}<br/>
-        ${params[1].marker} 活跃人数: ${params[1].value}
-      `
-    },
-    legend: {
-      data: ['总人数', '活跃人数'],
-      top: 30
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: activeData.value.map((item) => item.department),
-      axisLabel: {
-        interval: 0,
-        rotate: 0
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '人数',
-      splitLine: {
-        show: true,
-        lineStyle: { type: 'dashed' }
-      }
-    },
-    series: [
-      {
-        name: '总人数',
-        type: 'bar',
-        data: activeData.value.map((item) => item.total),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#5470c6' },
-            { offset: 1, color: '#83bff6' }
-          ])
-        },
-        barWidth: 30
-      },
-      {
-        name: '活跃人数',
-        type: 'bar',
-        data: activeData.value.map((item) => item.active),
-        itemStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: '#91cc75' },
-            { offset: 1, color: '#e6f4d2' }
-          ])
-        },
-        barWidth: 30
-      }
-    ]
-  }
-
-  activeInstance.setOption(option)
-}
-
-const qxRef = ref(null)
-let qxInstance = null
-
-// 生成近12个月份 (包含当年和去年)
-const generateMonths = () => {
-  const months = []
-  const date = new Date()
-  date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
-
-  for (let i = 0; i < 12; i++) {
-    date.setMonth(date.getMonth() - 1)
-    months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
-  }
-  return months.reverse()
-}
-
-const initQxChart = () => {
-  if (!qxRef.value) return
-  qxInstance = echarts.init(qxRef.value)
-  debugger
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'cross',
-        label: {
-          backgroundColor: '#6a7985'
-        }
-      }
-    },
-    legend: {
-      data: orderSevenData.value.series.map((item) => item.name),
-      top: 30,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      boundaryGap: false,
-      data: orderSevenData.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: [
-      {
-        type: 'value',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'left' // 左侧 Y 轴
-      },
-      {
-        type: 'value',
-        axisLabel: {
-          formatter: '{value}'
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        position: 'right', // 右侧 Y 轴
-        splitLine: {
-          show: false // 隐藏右侧 Y 轴的分割线
-        }
-      }
-    ],
-    series: orderSevenData.value.series.map((item, index) => {
-      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
-      const yAxisIndex = index < 2 ? 0 : 1
-      return {
-        name: item.name,
-        type: 'line',
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 8,
-        itemStyle: {
-          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
-        },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(84,112,198,0.4)' },
-            { offset: 1, color: 'rgba(84,112,198,0.1)' }
-          ])
-        },
-        data: item.data,
-        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
-      }
-    })
-  }
-
-  qxInstance.setOption(option)
-}
-
-// 响应式调整
-const resizeQxChart = () => qxInstance?.resize()
-
-// 新增:初始化修井完成情况图表
-const initRepairWellChart = () => {
-  if (!repairWellChartRef.value) return
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      }
-    },
-    legend: {
-      data: repairWellData.value.series.map((item) => item.name),
-      top: 10,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: repairWellData.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '完成井数(口)',
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: '{value}'
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    series: repairWellData.value.series.map((item) => ({
-      name: item.name,
-      type: 'bar',
-      data: item.data
-    }))
-  }
-  const chart = echarts.init(repairWellChartRef.value)
-  chart.setOption(option)
-  window.addEventListener('resize', () => chart.resize())
-}
-
-// 新增:初始化钻井完成情况图表
-const initDrillingWellChart = (res) => {
-  if (!drillingWellChartRef.value) return
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      }
-    },
-    legend: {
-      data: res.series.map((item) => item.name),
-      top: 10,
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: res.xAxis,
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '完成井数(口)',
-      axisLabel: {
-        color: '#B6C8DA',
-        formatter: '{value}'
-      },
-      splitLine: {
-        show: true, // 显示水平网格线(默认显示)
-        lineStyle: {
-          // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-          color: '#457794', // 浅灰色半透明
-          // 可选:设置线条类型(实线/虚线/点线)
-          type: 'dashed'
-        }
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA'
-        }
-      }
-    },
-    series: res.series.map((item) => ({
-      name: item.name,
-      type: 'bar',
-      data: item.data
-    }))
+onMounted(() => {
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
   }
-  const chart = echarts.init(drillingWellChartRef.value)
-  chart.setOption(option)
-  window.addEventListener('resize', () => chart.resize())
-}
 
-/** 初始化 */
-onMounted(() => {
-  getStats()
-  // initChart()
-  // initTopChart()
-  // initActiveChart()
-  // initQxChart()
-  window.addEventListener('resize', resizeQxChart)
-  // fetchTop()
-  window.addEventListener('resize', () => topInstance?.resize())
-  initRepairWellChart()
-  // initDrillingWellChart()
-  //initDrillingWorkloadChart()
-  //initRepairWorkloadChart()
+  window.addEventListener('resize', updateScale)
 })
 
-onBeforeUnmount(() => {
-  // chartInstance?.dispose()
-  // window.removeEventListener('resize', () => chartInstance?.resize())
-  // topInstance?.dispose()
-  // window.removeEventListener('resize', handleResize)
-  // qxInstance?.dispose()
-  // window.removeEventListener('resize', resizeQxChart)
-  // repairWellChartRef.dispose()
-  // window.removeEventListener('resize', () =>
-  //   echarts.getInstanceByDom(repairWellChartRef.value)?.resize()
-  // )
-  // echarts.getInstanceByDom(drillingWellChartRef.value)?.dispose()
-  // window.removeEventListener('resize', () =>
-  //   echarts.getInstanceByDom(drillingWellChartRef.value)?.resize()
-  // )
+onUnmounted(() => {
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
 })
 </script>
 
-<style lang="scss" scoped>
-.page-container {
-  background-color: #3a6fa3;
-  min-height: 100vh;
-  padding: 20px;
-}
-
-.summary {
-  margin-bottom: 20px;
-}
-
-::v-deep .chart-card {
-  background-color: rgba(0, 0, 0, 0.3);
-  border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
-  transition: all 0.3s ease;
-  border: none;
-
-  &:hover {
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
-  }
-}
-
-.safety-days-card {
-  .safety-days-content {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 150px;
-    position: relative;
-
-    .days-number {
-      font-size: 58px;
-      font-weight: bold;
-      color: darkorange;
-      line-height: 1;
-      transition: all 0.3s ease;
-    }
-
-    .days-number:hover {
-      transform: scale(1.05);
-    }
-
-    .days-label {
-      font-size: 20px;
-      color: white;
-      margin-top: 8px;
-    }
-
-    .safety-desc {
-      font-size: 14px;
-      color: #999;
-      position: absolute;
-      bottom: 10px;
-      text-align: center;
-      width: 90%;
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .page-container {
-    padding: 10px;
-  }
-}
-::v-deep .el-card__header {
-  border-bottom: none !important;
-  padding-bottom: 0;
-}
-
-.table-container {
-  padding: 16px;
-  height: 320px;
-  box-sizing: border-box;
-  overflow: auto;
-
-  // 滚动条样式优化
-  &::-webkit-scrollbar {
-    width: 6px;
-    height: 6px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    background-color: rgba(255, 255, 255, 0.2);
-    border-radius: 3px;
-  }
-
-  &::-webkit-scrollbar-track {
-    background-color: transparent;
-  }
-}
-
-// 修复表格hover样式
-::v-deep .el-table__row:hover > td {
-  background-color: rgba(255, 255, 255, 0.05) !important;
-}
-.custom-scroll-dialog {
-  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
-  max-height: 90vh;
-  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
-}
-/* 滚动内容容器:核心样式 */
-.dialog-scroll-content {
-  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
-  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
-}
+<template>
+  <div ref="wrapperRef" class="bg absolute top-0 left-0 size-full z-10">
+    <div class="mx-a overflow-hidden" :style="targetWrapperStyle">
+      <div class="bg" :style="targetAreaStyle">
+        <header class="header">{{ company }}</header>
+        <div class="mt-3 px-5">
+          <rysummary class="kb-stage-card kb-stage-card--1" />
+          <div class="w-full h-148 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
+            <rydeviceStatus class="kb-stage-card kb-stage-card--2" />
+            <safeday class="kb-stage-card kb-stage-card--3" />
+            <ryorderTrend class="kb-stage-card kb-stage-card--4" />
+            <zjfinish class="kb-stage-card kb-stage-card--5" />
+            <zjwork class="kb-stage-card kb-stage-card--6" />
+            <xjwork class="kb-stage-card kb-stage-card--7" />
+          </div>
+          <rydeviceList class="kb-stage-card kb-stage-card--8 kb-stage-card--list" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
 
-/* 优化滚动条样式(可选,提升UI体验) */
-.dialog-scroll-content::-webkit-scrollbar {
-  width: 6px; /* 滚动条宽度 */
-}
-.dialog-scroll-content::-webkit-scrollbar-thumb {
-  background-color: #e5e7eb; /* 滚动条滑块颜色 */
-  border-radius: 3px; /* 滚动条圆角 */
-}
-.dialog-scroll-content::-webkit-scrollbar-thumb:hover {
-  background-color: #d1d5db; /*  hover时滑块颜色 */
-}
-.custom-table :deep .el-table__row {
-  height: 45px !important; /* 高度根据需求调整 */
-}
+<style scoped lang="scss">
+@import url('@/styles/kb.scss');
 </style>

+ 190 - 0
src/views/pms/stat/rykb/rydeviceList.vue

@@ -0,0 +1,190 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { rangeShortcuts } from '@/utils/formatTime'
+import dayjs from 'dayjs'
+
+interface RhDeviceListRow {
+  projectDeptId: number
+  projectDeptName: string
+  teamCount: number
+  cumulativeDays: number
+  constructionDays: number
+  utilizationRate: number
+  gasInjection: number
+  sort?: number | null
+}
+
+interface RhTeamRateRow {
+  teamName: string
+  cumulativeDays: number
+  constructionDays: number
+  utilizationRate: number
+}
+
+const TABLE_HEIGHT = 220
+const TEAM_TABLE_HEIGHT = 500
+const DEFAULT_TIME_RANGE = rangeShortcuts[2]
+  .value()
+  .map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+
+const createTime = ref<string[]>(DEFAULT_TIME_RANGE)
+const loading = ref(false)
+const list = ref<RhDeviceListRow[]>([])
+const teamDialogVisible = ref(false)
+const teamLoading = ref(false)
+const currentProjectDeptName = ref('')
+const teamList = ref<RhTeamRateRow[]>([])
+
+const tableData = computed(() => list.value)
+
+function formatRate(value?: number | null) {
+  return `${(Number(value ?? 0) * 100).toFixed(2)}%`
+}
+
+// function formatGasInjection(value?: number | null) {
+//   return (Number(value ?? 0) / 10000).toFixed(2)
+// }
+
+function handleDateChange() {
+  getList()
+}
+
+async function handleRowClick(row: RhDeviceListRow) {
+  currentProjectDeptName.value = row.projectDeptName || '项目部'
+  teamDialogVisible.value = true
+  teamLoading.value = true
+
+  try {
+    const res = await IotStatApi.getRyTeamRate({
+      deptId: row.projectDeptId,
+      createTime: createTime.value
+    })
+    teamList.value = Array.isArray(res) ? (res as RhTeamRateRow[]) : []
+  } catch (error) {
+    console.error('获取瑞鹰队伍明细失败:', error)
+    teamList.value = []
+  } finally {
+    teamLoading.value = false
+  }
+}
+
+function handleDialogClosed() {
+  teamList.value = []
+  currentProjectDeptName.value = ''
+  teamLoading.value = false
+}
+
+async function getList() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getRyRate({ createTime: createTime.value })
+    list.value = Array.isArray(res) ? (res as RhDeviceListRow[]) : []
+  } catch (error) {
+    console.error('获取瑞鹰项目部汇总失败:', error)
+    list.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-[280px] flex flex-col mt-3">
+    <div class="panel-title h-9 flex items-center justify-between">
+      <div class="flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        项目部统计
+      </div>
+      <div class="w-260px! -translate-y-[4px]">
+        <el-date-picker
+          v-model="createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          :clearable="false"
+          class="w-260px!"
+          @change="handleDateChange"
+        />
+      </div>
+    </div>
+    <!-- v-loading="loading" -->
+    <div class="flex-1 min-h-0 px-4 py-2">
+      <el-table
+        :data="tableData"
+        :height="TABLE_HEIGHT"
+        class="device-list-table"
+        @row-click="handleRowClick"
+      >
+        <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
+        <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" min-width="120" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" min-width="120" align="center" />
+        <!-- <el-table-column label="注气量(万方)" min-width="150" align="center">
+          <template #default="{ row }">
+            {{ formatGasInjection(row.gasInjection) }}
+          </template>
+        </el-table-column> -->
+        <el-table-column label="设备利用率" min-width="140" align="center">
+          <template #default="{ row }">
+            {{ formatRate(row.utilizationRate) }}
+          </template>
+        </el-table-column>
+
+        <template #empty>
+          <div class="h-full min-h-[220px] flex items-center justify-center">
+            <el-empty description="暂无数据" :image-size="72" />
+          </div>
+        </template>
+      </el-table>
+    </div>
+    <el-dialog
+      v-model="teamDialogVisible"
+      :title="`${currentProjectDeptName || '项目部'}队伍明细`"
+      width="1200px"
+      destroy-on-close
+      class="device-list-dialog"
+      @closed="handleDialogClosed"
+      append-to-body
+    >
+      <el-table
+        v-loading="teamLoading"
+        :data="teamList"
+        :height="TEAM_TABLE_HEIGHT"
+        class="team-list-table"
+      >
+        <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />
+        <el-table-column label="设备利用率" min-width="160" align="center">
+          <template #default="{ row }">
+            {{ formatRate(row.utilizationRate) }}
+          </template>
+        </el-table-column>
+
+        <template #empty>
+          <div class="h-full min-h-[220px] flex items-center justify-center">
+            <el-empty description="暂无队伍明细" :image-size="72" />
+          </div>
+        </template>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>
+
+<style>
+@import url('@/styles/kb-dialog.scss');
+</style>

+ 134 - 0
src/views/pms/stat/rykb/rydeviceStatus.vue

@@ -0,0 +1,134 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, ChartItem, createLegend, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartItem[]): echarts.EChartsOption {
+  return {
+    ...ANIMATION,
+    grid: THEME.grid,
+    tooltip: createTooltip({ trigger: 'item' }),
+    legend: createLegend(
+      { bottom: 10, itemWidth: 12, itemHeight: 12 },
+      data.map((item) => item.name)
+    ),
+    series: [
+      {
+        name: '设备状态',
+        type: 'pie',
+        center: ['50%', '44%'],
+        radius: ['50%', '70%'],
+        minAngle: 5,
+        label: {
+          show: false,
+          position: 'center',
+          formatter(params: any) {
+            return `{name|${params.name}}\n{value|${params.value}}`
+          },
+          rich: {
+            name: {
+              color: THEME.text.regular,
+              fontSize: 14,
+              fontWeight: 500,
+              lineHeight: 24,
+              fontFamily: FONT_FAMILY
+            },
+            value: {
+              color: THEME.text.strong,
+              fontSize: 28,
+              fontWeight: 700,
+              lineHeight: 36,
+              fontFamily: FONT_FAMILY
+            }
+          }
+        },
+        emphasis: {
+          label: {
+            show: true
+          },
+          itemStyle: {
+            shadowBlur: 14,
+            shadowColor: THEME.color.blue.shadow
+          }
+        },
+        data
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getDeviceStatusCount('ry')
+    chartData.value = Array.isArray(res) ? res : []
+    renderChart()
+  } catch (error) {
+    console.error('获取设备状态失败:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备状态统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 212 - 0
src/views/pms/stat/rykb/ryorderTrend.vue

@@ -0,0 +1,212 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 36 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      }
+    }),
+    legend: createLegend(
+      { top: 5 },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatDateLabel(value)
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          lineStyle: {
+            color: THEME.split,
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'left'
+      },
+      {
+        type: 'value',
+        splitNumber: 4,
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY
+        },
+        splitLine: {
+          show: false
+        },
+        axisLine: {
+          lineStyle: {
+            color: THEME.split
+          }
+        },
+        position: 'right'
+      }
+    ],
+    series: seriesData.map((item, index) => {
+      const colorList = Object.values(THEME.color)
+      const color = colorList[index % colorList.length]
+      const yAxisIndex = index < 2 ? 0 : 1
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'line',
+        smooth: true,
+        data: item.data,
+        yAxisIndex,
+        symbol: 'circle',
+        symbolSize: 8,
+        showSymbol: true,
+        lineStyle: {
+          width: 2,
+          color: color.line
+        },
+        itemStyle: {
+          color: color.line
+        },
+        areaStyle: {
+          color: color.bg
+        },
+        emphasis: {
+          focus: 'series',
+          scale: true
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getOrderSeven('ry')
+    chartData.value = {
+      xAxis: res.xAxis || [],
+      series: (res.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取近7日工单趋势失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单数量统计
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 310 - 0
src/views/pms/stat/rykb/rysummary.vue

@@ -0,0 +1,310 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { CardStateItem, SummaryCardConfig, THEME } from '@/utils/kb'
+import dayjs from 'dayjs'
+
+type CardKey =
+  | 'device'
+  | 'maintain'
+  | 'unfilledCount'
+  | 'filledCount'
+  | 'bytodo'
+  | 'byfinished'
+  | 'inspectttodo'
+  | 'inspecttfinished'
+  | 'zj'
+  | 'xj'
+
+type CardConfig = SummaryCardConfig<CardKey>
+
+const cardConfigs: CardConfig[] = [
+  {
+    key: 'device',
+    title: '设备数',
+    icon: 'i-material-symbols:device-hub-rounded',
+    accent: THEME.color.blue.strong,
+    glow: THEME.color.blue.glow
+  },
+  {
+    key: 'maintain',
+    title: '维修工单',
+    icon: 'i-material-symbols:home-repair-service',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'unfilledCount',
+    title: '运行未填写',
+    icon: 'i-solar:clipboard-remove-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'filledCount',
+    title: '运行已填写',
+    icon: 'i-solar:clipboard-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'bytodo',
+    title: '待保养',
+    icon: 'i-solar:shield-warning-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'byfinished',
+    title: '已保养',
+    icon: 'i-solar:shield-check-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'inspectttodo',
+    title: '待巡检',
+    icon: 'i-solar:map-point-search-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'inspecttfinished',
+    title: '已巡检',
+    icon: 'i-solar:check-circle-linear',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'zj',
+    title: '钻井总进尺(m)',
+    icon: 'i-solar:ruler-bold',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  },
+  {
+    key: 'xj',
+    title: '修井总完成井数',
+    icon: 'i-mdi:wrench-check-outline',
+    accent: THEME.color.green.strong,
+    glow: THEME.color.green.glow
+  }
+]
+
+function createDefaultCardState(): Record<CardKey, CardStateItem> {
+  return {
+    device: { value: 0, loading: true },
+    maintain: { value: 0, loading: true },
+    unfilledCount: { value: 0, loading: true },
+    filledCount: { value: 0, loading: true },
+    bytodo: { value: 0, loading: true },
+    byfinished: { value: 0, loading: true },
+    inspectttodo: { value: 0, loading: true },
+    inspecttfinished: { value: 0, loading: true },
+    zj: { value: 0, loading: true },
+    xj: { value: 0, loading: true }
+  }
+}
+
+const cardState = reactive<Record<CardKey, CardStateItem>>(createDefaultCardState())
+
+const summaryCards = computed(() =>
+  cardConfigs.map((card) => ({
+    ...card,
+    value: cardState[card.key].value,
+    loading: cardState[card.key].loading
+  }))
+)
+
+function toNumber(value: unknown) {
+  const num = Number(value)
+  return Number.isFinite(num) ? num : 0
+}
+
+function setCardValue(key: CardKey, value: unknown) {
+  cardState[key].value = toNumber(value)
+}
+
+function setCardLoading(keys: CardKey[], loading: boolean) {
+  keys.forEach((key) => {
+    cardState[key].loading = loading
+  })
+}
+
+async function loadDeviceCard() {
+  const keys: CardKey[] = ['device']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getDeviceCount('ry')
+    setCardValue('device', res?.total)
+  } catch (error) {
+    console.error('获取设备数失败:', error)
+    setCardValue('device', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainCard() {
+  const keys: CardKey[] = ['maintain']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintainCount('ry')
+    setCardValue('maintain', res?.total)
+  } catch (error) {
+    console.error('获取维修工单失败:', error)
+    setCardValue('maintain', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadFillCards() {
+  const keys: CardKey[] = ['unfilledCount', 'filledCount']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    createTime: [],
+    deptId: 158,
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getDeptStatistics(params)
+    const totalStats = res?.totalList?.[0] ?? {}
+
+    setCardValue('unfilledCount', totalStats.unfilledCount)
+    setCardValue('filledCount', totalStats.filledCount)
+  } catch (error) {
+    console.error('获取运行填写数据失败:', error)
+    setCardValue('unfilledCount', 0)
+    setCardValue('filledCount', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainStatusCards() {
+  const keys: CardKey[] = ['bytodo', 'byfinished']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintenanceStatus('ry')
+    setCardValue('bytodo', res?.todo)
+    setCardValue('byfinished', res?.finished)
+  } catch (error) {
+    console.error('获取保养状态失败:', error)
+    setCardValue('bytodo', 0)
+    setCardValue('byfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadInspectCards() {
+  const keys: CardKey[] = ['inspectttodo', 'inspecttfinished']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    deptId: '',
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getInspectStatuss(params, 'ry')
+    setCardValue('inspectttodo', res?.todo)
+    setCardValue('inspecttfinished', res?.finished)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('inspectttodo', 0)
+    setCardValue('inspecttfinished', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadZjXj() {
+  const keys: CardKey[] = ['zj', 'xj']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getInspectZjxjCount()
+    setCardValue('zj', res?.zj)
+    setCardValue('xj', res?.xj)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('zj', 0)
+    setCardValue('xj', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+function loadAllCards() {
+  loadDeviceCard()
+  loadMaintainCard()
+  loadFillCards()
+  loadMaintainStatusCards()
+  loadInspectCards()
+  loadZjXj()
+}
+
+onMounted(() => {
+  loadAllCards()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-28 flex flex-col">
+    <div class="panel-title h-8">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      工单情况
+    </div>
+
+    <div class="grid grid-cols-10 gap-2.5 flex-1 px-2.5 py-1.5">
+      <article
+        v-for="card in summaryCards"
+        :key="card.key"
+        class="summary-card relative flex h-full overflow-hidden rounded-md items-center gap-2 p-2"
+        :style="{
+          '--card-accent': card.accent,
+          '--card-glow': card.glow
+        }"
+      >
+        <div class="summary-card__shine"></div>
+
+        <div class="summary-card__icon">
+          <div :class="card.icon" class="summary-card__icon-glyph"></div>
+        </div>
+
+        <div class="summary-card__body">
+          <div class="summary-card__label">{{ card.title }}</div>
+          <div class="summary-card__value">
+            <CountTo
+              v-if="!card.loading"
+              style="color: #1f5bb8"
+              :start-val="0"
+              :end-val="card.value"
+              :duration="1200"
+            />
+            <span v-else class="summary-card__placeholder">--</span>
+          </div>
+        </div>
+
+        <div class="summary-card__corner"></div>
+      </article>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 162 - 0
src/views/pms/stat/rykb/safeday.vue

@@ -0,0 +1,162 @@
+<script lang="ts" setup>
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+const FIXED_SAFE_DAYS = 165
+
+const safeDays = ref(FIXED_SAFE_DAYS)
+
+async function loadSafeDays() {
+  try {
+    await IotStatApi.getSafeCount()
+  } catch (error) {
+    console.error('获取安全生产天数失败:', error)
+  } finally {
+    safeDays.value = FIXED_SAFE_DAYS
+  }
+}
+
+onMounted(() => {
+  loadSafeDays()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      安全生产天数
+    </div>
+
+    <div class="safe-day-panel flex-1 min-h-0">
+      <div class="safe-day-panel__orbit safe-day-panel__orbit--outer"></div>
+
+      <div class="safe-day-panel__top">
+        <span class="safe-day-panel__tag">SAFE RUN</span>
+      </div>
+
+      <div class="safe-day-panel__center">
+        <div class="safe-day-panel__headline">连续安全生产</div>
+        <div class="safe-day-panel__value-row translate-x-1.5">
+          <CountTo
+            class="safe-day-panel__value"
+            :start-val="0"
+            :end-val="safeDays"
+            :duration="1200"
+          />
+          <span class="safe-day-panel__unit">天</span>
+        </div>
+        <div class="safe-day-panel__subline">截至当前未发生安全生产事故</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.safe-day-panel {
+  position: relative;
+  display: flex;
+  padding: 22px 24px 18px;
+  overflow: hidden;
+  flex-direction: column;
+}
+
+.safe-day-panel__orbit {
+  position: absolute;
+  left: 50%;
+  border: 1px solid rgb(31 91 184 / 15%);
+  border-radius: 999px;
+  transform: translateX(-50%);
+}
+
+.safe-day-panel__orbit--outer {
+  top: 32px;
+  width: 320px;
+  height: 320px;
+}
+
+.safe-day-panel__orbit--inner {
+  top: 62px;
+  width: 240px;
+  height: 240px;
+  border-style: dashed;
+}
+
+.safe-day-panel__top,
+.safe-day-panel__center {
+  position: relative;
+  z-index: 1;
+}
+
+.safe-day-panel__top {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+}
+
+.safe-day-panel__tag {
+  padding: 5px 12px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 16px;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: #1f5bb8;
+  background: rgb(255 255 255 / 72%);
+  border: 1px solid rgb(255 255 255 / 75%);
+  border-radius: 999px;
+  box-shadow: inset 0 1px 0 rgb(255 255 255 / 85%);
+}
+
+.safe-day-panel__center {
+  display: flex;
+  padding-top: 14px;
+  margin-top: auto;
+  margin-bottom: auto;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.safe-day-panel__headline {
+  font-size: 20px;
+  font-weight: 600;
+  letter-spacing: 1px;
+  color: #24364f;
+}
+
+.safe-day-panel__value-row {
+  display: flex;
+  margin-top: 10px;
+  align-items: flex-end;
+  justify-content: center;
+}
+
+.safe-day-panel__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 82px;
+  line-height: 0.9;
+  letter-spacing: 2px;
+  color: #1f5bb8;
+  text-shadow: 0 12px 24px rgb(31 91 184 / 12%);
+}
+
+.safe-day-panel__unit {
+  padding-bottom: 10px;
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 30px;
+  line-height: 1;
+  color: #f08c2e;
+}
+
+.safe-day-panel__subline {
+  margin-top: 10px;
+  font-size: 15px;
+  color: #6f85aa;
+}
+</style>

+ 224 - 0
src/views/pms/stat/rykb/xjwork.vue

@@ -0,0 +1,224 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getBarStyle(color: (typeof THEME.color)[keyof typeof THEME.color]) {
+  return {
+    borderRadius: [12, 12, 0, 0],
+    shadowBlur: 12,
+    shadowColor: color.bg,
+    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      { offset: 0, color: color.light },
+      { offset: 0.55, color: color.mid },
+      { offset: 1, color: color.line }
+    ])
+  }
+}
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+  const colorList = [THEME.color.orange, THEME.color.blue]
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: 50,
+      bottom: 10
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    legend: createLegend(
+      {
+        top: 4,
+        itemWidth: 12,
+        itemHeight: 12
+      },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        interval: 0,
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        width: 78,
+        overflow: 'break'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '完井数(口)',
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: seriesData.map((item, index) => {
+      const color = colorList[index % colorList.length]
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'bar',
+        data: item.data || [],
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: [12, 12, 0, 0]
+        },
+        itemStyle: getBarStyle(color),
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: color.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return Number(params.value) ? `${params.value}` : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(color),
+            shadowColor: color.shadow,
+            shadowBlur: 18
+          }
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+  chart.setOption(getChartOption(chartData.value), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRepairRigWork('repair')
+    chartData.value = {
+      xAxis: res?.xAxis || [],
+      series: (res?.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取修井工作量情况失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      修井工作量情况
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 225 - 0
src/views/pms/stat/rykb/zjfinish.vue

@@ -0,0 +1,225 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getBarStyle(color: (typeof THEME.color)[keyof typeof THEME.color]) {
+  return {
+    borderRadius: [12, 12, 0, 0],
+    shadowBlur: 12,
+    shadowColor: color.bg,
+    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      { offset: 0, color: color.light },
+      { offset: 0.55, color: color.mid },
+      { offset: 1, color: color.line }
+    ])
+  }
+}
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+  const colorList = [THEME.color.orange, THEME.color.blue]
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: 50,
+      bottom: 10
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    legend: createLegend(
+      {
+        top: 4,
+        itemWidth: 12,
+        itemHeight: 12
+      },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        interval: 0,
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        width: 54,
+        overflow: 'break'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '完成井数(口)',
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY
+      },
+
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: seriesData.map((item, index) => {
+      const color = colorList[index % colorList.length]
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'bar',
+        data: item.data || [],
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: [12, 12, 0, 0]
+        },
+        itemStyle: getBarStyle(color),
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: color.strong,
+          fontSize: 16,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return Number(params.value) ? `${params.value}` : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(color),
+            shadowColor: color.shadow,
+            shadowBlur: 18
+          }
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+  chart.setOption(getChartOption(chartData.value), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRigFinished()
+    chartData.value = {
+      xAxis: res?.xAxis || [],
+      series: (res?.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取钻井完成情况失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      钻井完成情况
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>

+ 268 - 0
src/views/pms/stat/rykb/zjwork.vue

@@ -0,0 +1,268 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatSeriesName,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartData>({
+  xAxis: [],
+  series: []
+})
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getBarStyle(color: (typeof THEME.color)[keyof typeof THEME.color]) {
+  return {
+    borderRadius: [12, 12, 0, 0],
+    shadowBlur: 12,
+    shadowColor: color.bg,
+    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      { offset: 0, color: color.light },
+      { offset: 0.55, color: color.mid },
+      { offset: 1, color: color.line }
+    ])
+  }
+}
+
+function formatValue(value: number | string) {
+  return Number(value || 0).toLocaleString('en-US')
+}
+
+function isAnnualSeries(name: string) {
+  return name.includes('annual') || formatSeriesName(name).includes('年累')
+}
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+  const colorList = [THEME.color.orange, THEME.color.blue, THEME.color.green]
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: 50,
+      bottom: 10
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      }
+    }),
+    legend: createLegend(
+      {
+        top: 4,
+        itemWidth: 12,
+        itemHeight: 12
+      },
+      seriesData.map((item) => formatSeriesName(item.name))
+    ),
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        interval: 0,
+        color: THEME.text.regular,
+        fontSize: 14,
+        fontWeight: 500,
+        fontFamily: FONT_FAMILY,
+        width: 78,
+        overflow: 'break'
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '日/月进尺(m)',
+        nameTextStyle: {
+          color: THEME.text.regular,
+          fontSize: 13,
+          fontFamily: FONT_FAMILY
+        },
+        splitNumber: 4,
+        axisLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY,
+          formatter(value: number) {
+            return formatValue(value)
+          }
+        },
+        splitLine: {
+          lineStyle: {
+            color: THEME.split,
+            type: 'dashed'
+          }
+        },
+        position: 'left'
+      },
+      {
+        type: 'value',
+        name: '年累进尺(m)',
+        splitNumber: 4,
+        nameTextStyle: {
+          color: THEME.text.regular,
+          fontSize: 13,
+          fontFamily: FONT_FAMILY
+        },
+        position: 'right',
+        axisLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          color: THEME.text.regular,
+          fontSize: 12,
+          fontFamily: FONT_FAMILY,
+          formatter(value: number) {
+            return formatValue(value)
+          }
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ],
+    series: seriesData.map((item, index) => {
+      const color = colorList[index % colorList.length]
+      const annual = isAnnualSeries(item.name)
+
+      return {
+        name: formatSeriesName(item.name),
+        type: 'bar',
+        yAxisIndex: annual ? 1 : 0,
+        data: item.data || [],
+        barWidth: 22,
+        showBackground: true,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: [12, 12, 0, 0]
+        },
+        itemStyle: getBarStyle(color),
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: color.strong,
+          fontSize: 14,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return Number(params.value) ? formatValue(params.value) : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(color),
+            shadowColor: color.shadow,
+            shadowBlur: 18
+          }
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: 'svg'
+  })
+  renderChart()
+}
+
+function renderChart() {
+  if (!chart) return
+  chart.setOption(getChartOption(chartData.value), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRepairRigWork('rig')
+    chartData.value = {
+      xAxis: res?.xAxis || [],
+      series: (res?.series || []).map((item) => ({
+        name: item.name,
+        data: item.data || []
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取钻井工作量情况失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      钻井工作量情况
+    </div>
+    <div ref="chartRef" class="flex-1 min-h-0"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+</style>