Răsfoiți Sursa

pms 瑞都看板 功能优化

zhangcl 19 ore în urmă
părinte
comite
304776092d

+ 2 - 2
.env.local

@@ -3,8 +3,8 @@ NODE_ENV=development
 
 VITE_DEV=true
 
-# 请求路径  http://192.168.188.79:48080  https://iot.deepoil.cc  http://172.26.0.56:48080
-VITE_BASE_URL='http://192.168.188.200:48080'
+# 请求路径  http://192.168.188.200:48080  https://iot.deepoil.cc  http://172.26.0.56:48080
+VITE_BASE_URL='https://172.21.0.198'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server

+ 13 - 0
src/api/pms/stat/index.ts

@@ -66,6 +66,9 @@ export const IotStatApi = {
   getInspectZjxjCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/ry/count/zjxj`, params })
   },
+  getInspectLayerWellCount: async (params?: any) => {
+    return await request.get({ url: `/rq/stat/rd/device/workloadStatistics`, params })
+  },
   getInspectRate: async (params: any) => {
     return await request.get({ url: `/rq/stat/ry/device/totalUtilizationRate`, params })
   },
@@ -165,6 +168,9 @@ export const IotStatApi = {
   getRhSevenDayUtilization: async () => {
     return await request.get({ url: `/rq/stat/rh/device/sevenDayUtilization` })
   },
+  getRdSevenDayUtilization: async () => {
+    return await request.get({ url: `/rq/stat/rd/device/sevenDayUtilization` })
+  },
   getRyRate: async (params: any) => {
     return await request.get({ url: `/rq/stat/ry/device/utilizationRate`, params })
   },
@@ -183,9 +189,13 @@ export const IotStatApi = {
   getRyNptCount: async (params: any) => {
     return await request.get({ url: `/rq/stat/ry/device/nptCount`, params })
   },
+  getRdNptCount: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rd/device/productionAbnormality`, params })
+  },
   getRdTeamRate: async (params: any) => {
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
   },
+
   getMaintainCount: async (params?: any) => {
     return await request.get({ url: `/rq/stat/home/maintain/count/` + params })
   },
@@ -247,6 +257,9 @@ export const IotStatApi = {
   getConstructionBriefing: async () => {
     return await request.get({ url: `/pms/iot-rd-daily-report/constructionBriefing` })
   },
+  getRdRecentWbcs: async () => {
+    return await request.get({ url: `/rq/stat/recent/wbcs/rd` })
+  },
 
   // 瑞都看板(新)
   // 获取ssoToken

+ 3 - 1
src/views/Home/Index.vue

@@ -2,6 +2,7 @@
 import { IotOpeationFillApi } from '@/api/pms/iotopeationfill'
 import { IotStatApi } from '@/api/pms/stat'
 import { useUserStore } from '@/store/modules/user'
+import Rdkb from '@/views/pms/stat/rdkb.vue'
 import Rhkb from '@/views/pms/stat/rhkb.vue'
 import Rykb from '@/views/pms/stat/rykb.vue'
 import Stat from './stat.vue'
@@ -15,13 +16,13 @@ const iframeLoading = ref(false)
 let iframeRequestId = 0
 
 const reportUrls: Record<string, string> = {
-  rd: 'https://report.deepoil.cc/webroot/decision/v10/entry/access/a12df128-c84f-44be-a55d-bababbf4a132?preview=true&page_number=1',
   jt: 'https://report.deepoil.cc/webroot/decision/v10/entry/access/dbc9cf73-81ce-43f1-9923-45cdfa5d5d3a?preview=true&page_number=1'
 }
 
 const currentCompany = computed(() => company.value?.toLowerCase())
 const currentView = computed(() => {
   if (companyLoading.value) return 'loading'
+  if (currentCompany.value === 'rd') return 'rd'
   if (currentCompany.value === 'rh') return 'rh'
   if (currentCompany.value === 'ry') return 'ry'
   if (currentCompany.value && reportUrls[currentCompany.value]) return 'report'
@@ -80,6 +81,7 @@ watch(
       class="full-screen-iframe"
       allowfullscreen></iframe>
   </div>
+  <Rdkb v-else-if="currentView === 'rd'" />
   <Rhkb v-else-if="currentView === 'rh'" />
   <Rykb v-else-if="currentView === 'ry'" />
   <Stat v-else />

+ 167 - 1962
src/views/pms/stat/rdkb.vue

@@ -1,2009 +1,214 @@
-<template>
-  <div class="min-h-screen p-4 bg-[#3a6fa3] flex flex-col">
-    <el-row :gutter="16" class="summary">
-      <!-- 原有的统计卡片部分保持不变 -->
-      <el-col v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <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 v-loading="loading" :sm="3" :xs="24">
-        <SummaryCard
-          :value="inspect.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 v-loading="loading" :sm="3" :xs="12">
-        <SummaryCard
-          :value="inspect.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" :xs="24">
-        <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
-            style="
-              display: flex;
-              align-items: center;
-              justify-content: center;
-              min-height: 290px;
-              flex-direction: column;
-              gap: 6px;
-            "
-          >
-            <div ref="statusChartRef" style="width: 100%; height: 170px; max-width: 200px"></div>
-            <div class="text-[12px] h-[100px] w-[90%] flex flex-col justify-between items-center">
-              <div v-for="item in legendData" :key="item.name" class="flex">
-                <div class="flex items-center gap-1">
-                  <span class="status-legend-color" :style="{ background: item.color }"></span>
-                  <span class="w-[100px] text-[#fff]">{{ item.name }}</span>
-                </div>
-                <div class="flex items-center">
-                  <span class="text-[#fff] text-right w-12">{{ item.value }} 台</span>
-                  <span class="status-legend-percent text-right w-14">{{ item.percent }}%</span>
-                </div>
-              </div>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :span="10" :xs="24">
-        <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"
-                >设备分布及价值(国内RMB)</span
-              >
-            </div>
-          </template>
-          <div
-            style="
-              display: flex;
-              min-height: 295px;
-              flex-direction: column;
-              align-items: center;
-              gap: 8px;
-            "
-          >
-            <div ref="domesticChartRef" style="width: 100%; height: 200px; max-width: 420px"></div>
-            <div
-              class="domestic-legend"
-              style="
-                display: flex;
-                width: 100%;
-                max-width: 520px;
-                font-size: 12px;
-                flex-wrap: wrap;
-                gap: 1px;
-              "
-            >
-              <div
-                v-for="(item, idx) in domesticData"
-                :key="item.dept"
-                class="legend-item"
-                style="display: flex; align-items: center; gap: 8px; padding: 4px 8px"
-              >
-                <span
-                  class="legend-dot"
-                  :style="{
-                    background: statusColors[idx % statusColors.length],
-                    width: '10px',
-                    height: '10px',
-                    display: 'inline-block',
-                    borderRadius: '50%'
-                  }"
-                ></span>
-                <span
-                  class="legend-name"
-                  style="
-                    display: inline-block;
-                    max-width: 150px;
-                    overflow: hidden;
-                    color: #fff;
-                    text-overflow: ellipsis;
-                    white-space: nowrap;
-                  "
-                  >{{ item.dept }}</span
-                >
-              </div>
-            </div>
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :span="8" :xs="24">
-        <WorkloadChart />
-        <!-- <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-[290px]"></div>
-        </el-card> -->
-      </el-col>
-    </el-row>
-    <el-row :gutter="16" class="mb-4">
-      <el-col class="mb-4" :span="12" :xs="24">
-        <AvailabilityChart />
-      </el-col>
-      <el-col class="mb-4" :span="12" :xs="24">
-        <ExceptionChart />
-      </el-col>
-    </el-row>
-    <el-row :gutter="16" class="mb-4">
-      <el-col :span="12" :xs="24">
-        <UtilizationChart />
-      </el-col>
-      <!-- <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 class="table-container">
-            <div ref="utilizationRef" style="width: 100%; height: 360px"></div>
-          </div>
-        </el-card> -->
-      <!-- 添加两个卡片 -->
-      <!--          <div class="flex justify-between mb-2">-->
-      <!--            <el-card class="stat-card">-->
-      <!--              <div class="flex flex-row justify-evenly">-->
-      <!--                <div>-->
-      <!--                  <Icon icon="fa-solid:award" size="30" color="blue" />-->
-      <!--                </div>-->
-      <!--                <div class="flex flex-col items-center">-->
-      <!--                  <span class="text-sm " style="color: #101010">{{t('stat.spareCount')}}</span>-->
-      <!--                  <span class="text-lg font-bold">{{ totalMaterialCount }}</span>-->
-      <!--                </div>-->
-      <!--              </div>-->
-      <!--            </el-card>-->
-      <!--            <el-card class="stat-card">-->
-      <!--              <div class="flex flex-row justify-evenly">-->
-      <!--                <div>-->
-      <!--                  <Icon icon="fa-solid:yen-sign" size="30" color="orange" />-->
-      <!--                </div>-->
-      <!--                <div class="flex flex-col items-center">-->
-      <!--                  <span class="text-sm " style="color: #101010">{{t('stat.spareAmount')}}</span>-->
-      <!--                  <span class="text-lg font-bold">{{ totalMaterialCost }}</span>-->
-      <!--                </div>-->
-      <!--              </div>-->
-      <!--            </el-card>-->
-      <!--          </div>-->
-      <!--          <div ref="sparePartRef" class="h-[330px]"></div>-->
-      <!-- 月度工作量表 -->
-      <el-col :span="12" :xs="24">
-        <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>
-          </template>
-          <div class="overflow-auto">
-            <el-table
-              :data="monthData"
-              border
-              height="420px"
-              :disabled-affix="true"
-              style="width: 100%"
-              :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
-              :cell-style="{ color: '#fff' }"
-              :summary-method="tableSummary"
-              show-summary
-            >
-              <el-table-column prop="month" label="月份" width="50" fixed="left" align="center" />
-
-              <el-table-column label="2025年" align="center">
-                <el-table-column
-                  prop="y2025_fractures"
-                  label="压裂井数"
-                  align="center"
-                  min-width="50"
-                />
-                <el-table-column
-                  prop="y2025_layers"
-                  label="压裂层数"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="y2025_pump_trips"
-                  label="泵车台次"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="y2025_oil_wells"
-                  label="连油井数"
-                  min-width="50"
-                  align="center"
-                />
-              </el-table-column>
-
-              <el-table-column label="2024年" align="center">
-                <el-table-column
-                  prop="y2024_fractures"
-                  label="压裂井数"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="y2024_layers"
-                  label="压裂层数"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="y2024_pump_trips"
-                  label="泵车台次"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="y2024_oil_wells"
-                  label="连油井数"
-                  min-width="50"
-                  align="center"
-                />
-              </el-table-column>
-
-              <el-table-column label="同比增长量" align="center">
-                <el-table-column
-                  prop="diff_fractures"
-                  label="压裂井数"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="diff_layers"
-                  label="压裂层数"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="diff_pump_trips"
-                  label="泵车台次"
-                  min-width="50"
-                  align="center"
-                />
-                <el-table-column
-                  prop="diff_oil_wells"
-                  label="连油井数"
-                  min-width="50"
-                  align="center"
-                />
-              </el-table-column>
-            </el-table>
-          </div>
-        </el-card>
-      </el-col>
-      <!-- 月度工作量表结束 -->
-    </el-row>
-    <ConstructionBriefing />
-    <!-- <el-row :gutter="16" class="mb-4">
-      <el-col :span="24" :xs="24">
-
-      </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': '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>
-
-  <el-dialog v-model="projectDataDialog" width="45vw" class="custom-scroll-dialog">
-    <div class="dialog-scroll-content">
-      <el-card class="bg-[#284d72]!" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium" style="color: #b6c8da"
-              >国内设备分布及价值(国内RMB)</span
-            >
-          </div>
-        </template>
-        <div class="overflow-auto" style="min-height: 260px">
-          <el-table
-            :data="domesticData"
-            border
-            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
-            :cell-style="{ color: '#fff' }"
-            style="width: 100%"
-            :summary-method="tableSummary"
-            show-summary
-          >
-            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
-            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
-            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
-            <el-table-column
-              prop="orig_value"
-              label="原值(万元)"
-              min-width="120"
-              align="center"
-            />
-            <el-table-column prop="net_value" label="净值(万元)" min-width="120" align="center" />
-            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
-          </el-table>
-        </div>
-      </el-card>
-    </div>
-  </el-dialog>
-  <el-dialog v-model="projectDataDialog2" width="45vw" class="custom-scroll-dialog">
-    <div class="dialog-scroll-content">
-      <el-card class="bg-[#284d72]!" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium" style="color: #b6c8da"
-              >伊拉克设备分布及价值(美元)</span
-            >
-          </div>
-        </template>
-        <div class="overflow-auto" style="min-height: 260px">
-          <el-table
-            :data="iraqTableData"
-            border
-            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
-            :cell-style="{ color: '#fff' }"
-            style="width: 100%"
-          >
-            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
-            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
-            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
-            <el-table-column
-              prop="orig_value_usd"
-              label="原值(万元)"
-              min-width="120"
-              align="center"
-            />
-            <el-table-column
-              prop="net_value_usd"
-              label="净值(万元)"
-              min-width="120"
-              align="center"
-            />
-            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
-          </el-table>
-        </div>
-      </el-card>
-    </div>
-  </el-dialog>
-
-  <el-dialog v-model="projectDataDialog3" width="45vw" class="custom-scroll-dialog">
-    <div class="dialog-scroll-content">
-      <el-card class="bg-[#284d72]!" shadow="never">
-        <template #header>
-          <div class="flex items-center justify-between">
-            <span class="text-base font-medium" style="color: #b6c8da"
-              >利比亚设备分布及价值(第纳尔)</span
-            >
-          </div>
-        </template>
-        <div class="overflow-auto" style="min-height: 260px">
-          <el-table
-            :data="libyaTableData"
-            border
-            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
-            :cell-style="{ color: '#fff' }"
-            :row-class-name="tableRowClass"
-            style="width: 100%"
-            :summary-method="tableSummary"
-          >
-            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
-            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
-            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
-            <el-table-column
-              prop="orig_value"
-              label="原值(万元)"
-              min-width="120"
-              align="center"
-            />
-            <el-table-column prop="net_value" label="净值(万元)" min-width="120" align="center" />
-            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
-          </el-table>
-        </div>
-      </el-card>
-    </div>
-  </el-dialog>
-</template>
-<script lang="ts" setup>
-import { MemberSummaryRespVO } from '@/api/mall/statistics/member'
-import SummaryCard from '@/components/SummaryCard/index.vue'
-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 { IotStatApi } from '@/api/pms/stat'
-import { ref, onMounted, computed, watch, nextTick, reactive } from 'vue'
-import { useLocaleStore } from '@/store/modules/locale'
-import WorkloadChart from './rdkb/workload.vue'
-import UtilizationChart from './rdkb/utilization.vue'
-import AvailabilityChart from './rdkb/availability.vue'
-import ExceptionChart from './rdkb/exception.vue'
-import ConstructionBriefing from './rdkb/constructionBriefing.vue'
+<script setup lang="ts">
+import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
+import rdsummary from './rdkb/rdsummary.vue'
+import rdInventorySafety from './rdkb/rd-Inventory-safety.vue'
+import rdMaintenanceTimes from './rdkb/rd-maintenance-times.vue'
+import rdCost from './rdkb/rd-cost.vue'
+import rdExceptionPrompt from './rdkb/rd-exception-prompt.vue'
+import rdValue from './rdkb/rd-value.vue'
+import rdRate from './rdkb/rd-rate.vue'
+import rdUseStatus from './rdkb/rd-use-status.vue'
+import rdDeviceStatus from './rdkb/rd-device-status.vue'
+import rdDeviceCategory from './rdkb/rd-device-category.vue'
+import rdProductionBriefs from './rdkb/rdProductionBriefs.vue'
 
-/** 会员统计 */
-defineOptions({ name: 'IotRdStat' })
-
-echarts.use([
-  TooltipComponent,
-  LegendComponent,
-  PieChart,
-  CanvasRenderer,
-  LabelLayout,
-  TitleComponent,
-  ToolboxComponent,
-  GridComponent,
-  LineChart,
-  UniversalTransition,
-  GaugeChart,
-  BarChart
-])
-
-const loading = ref(true) // 加载中
-const teamDialogVisible = ref(false)
-const teamTableData = ref([])
-const currentDeptId = ref('')
-const currentDateRange = ref([])
-const materialTableData = ref([])
-const handleDateChange = () => {
-  IotStatApi.getRdRate(rateQueryParams).then((res) => {
-    materialTableData.value = res
-  })
-}
-const rateQueryParams = reactive({
-  createTime: []
+defineOptions({
+  name: 'IotRdStatt'
 })
-const handleDialogClose = () => {
-  teamDialogVisible.value = false
-  teamTableData.value = [] // 清空表格数据
-}
 
-// 格式化利用率为百分比
-const formatRate = (row) => {
-  return (row.utilizationRate * 100).toFixed(2) + '%'
-}
-// 数字格式化为千分位,保留两位小数
-const formatNumber = (v) => {
-  if (v == null || v === '') return '-'
-  const n = Number(v)
-  if (isNaN(n)) return String(v)
-  return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
-}
-const handleRowClick = (row, column, event) => {
-  console.log('点击的行数据:', row)
-  currentDeptId.value = row.deptId // 假设行数据中包含deptId字段
-  currentDateRange.value = rateQueryParams.createTime
+const company = ref('瑞都')
+const activePage = ref<'home' | 'production'>('home')
 
-  // 打开弹窗并加载队伍详情数据
-  teamDialogVisible.value = true
-  // fetchTeamDetailData(row.deptId, rateQueryParams.createTime)
-  // debugger
-  const teamParams = {
-    deptId: row.projectDeptId,
-    createTime: rateQueryParams.createTime
-  }
-  IotStatApi.getRdTeamRate(teamParams).then((res) => {
-    teamTableData.value = res
-  })
-}
-const dateRange = ref<[Date, Date] | null>(null)
-const summary = ref<MemberSummaryRespVO>() // 会员统计数据
-const statusChartRef = ref() // 设备数量统计的图表
-let statusChartInstance = null
-const qxRef = ref(null)
-let qxInstance = null
-const sparePartRef = ref(null)
-let sparePartInstance = null
-const maintenanceChartRef = ref(null)
-let maintenanceChartInstance = null
-const inspectChartRef = ref(null)
-let inspectChartInstance = null
-const inspectionChartRef = ref(null)
-let inspectionChartInstance = null
-const { t } = useI18n() // 国际化
-const maintenanceChartRef1 = ref(null)
+const pageTabs = [
+  { label: '看板首页', value: 'home' },
+  { label: '生产日报', value: 'production' }
+] as const
 
-const typeData = ref([])
-// 配色(与图表保持一致)
-const statusColors = ['#2ed3df', '#34d399', '#ff6b95', '#4aa3ff', '#f59e0b', '#ef4444', '#7dd3fc']
+const wrapperRef = ref<HTMLDivElement>()
+const scale = ref(1)
 
-/**
- * 将后端返回的多种可能结构规范化为数组形式:
- * - 如果已经是数组则直接使用
- * - 如果包含 data 或 series 字段且为数组则优先使用
- * - 否则把对象的键值对转换为 {name, value} 数组
- */
-const normalizeTypeData = (val) => {
-  if (Array.isArray(val)) return val
-  if (!val || typeof val !== 'object') return []
-  if (Array.isArray(val.data)) return val.data
-  if (Array.isArray(val.series)) return val.series
-  // plain object with keys -> map to array
-  return Object.keys(val).map((k) => ({ name: k, value: val[k] }))
-}
+let resizeObserver: ResizeObserver | null = null
+let resizeRaf = 0
 
-const legendData = computed(() => {
-  const arr = normalizeTypeData(typeData.value)
-  const total = arr.reduce((s, it) => s + (Number(it.value) || 0), 0)
-  return arr.map((it, idx) => ({
-    name: it.name,
-    value: it.value || 0,
-    percent: total ? ((Number(it.value) / total) * 100).toFixed(2) : '0.00',
-    color: statusColors[idx % statusColors.length]
-  }))
-})
-const orderSevenData = ref({})
-const device = ref({
-  total: undefined,
-  today: undefined
-})
-const maintain = ref({
-  total: undefined,
-  today: undefined
-})
-const by = ref({
-  todo: undefined,
-  finished: undefined
-})
-const fill = ref({
-  filledCount: undefined,
-  unfilledCount: undefined
-})
-const abnormalDevice = ref({
-  total: undefined,
-  today: undefined
-})
-const outliers = ref({
-  total: undefined,
-  today: undefined
-})
-const inspect = ref({
-  finished: 0,
-  todo: 0
-})
-const sparePartData = ref({
-  xAxis: ['扳手', '水杯', '皮带', '螺丝'],
-  series: [
-    {
-      name: '数量',
-      type: 'bar',
-      data: [10, 20, 15, 25],
-      yAxisIndex: 0
-    },
-    {
-      name: '金额',
-      type: 'line',
-      data: [100, 200, 150, 250],
-      yAxisIndex: 1
-    }
-  ]
-})
-const sparePartDataEng = ref({
-  xAxis: ['spanner', 'cup', 'belt', 'screw'],
-  series: [
-    {
-      name: 'count',
-      type: 'bar',
-      data: [10, 20, 15, 25],
-      yAxisIndex: 0
-    },
-    {
-      name: 'amount',
-      type: 'line',
-      data: [100, 200, 150, 250],
-      yAxisIndex: 1
-    }
-  ]
-})
-// 模拟巡检工单数据
-const totalInspectionOrders = ref(80)
-const completedInspectionOrders = ref(60)
+provide('rdKbScale', scale)
 
-// 计算物料消耗数量及费用
-const totalMaterialCount = computed(() => {
-  const quantitySeries = sparePartData.value.series.find((item) => item.name === '数量')
-  return quantitySeries ? quantitySeries.data.reduce((sum, val) => sum + val, 0) : 0
-})
+const targetWrapperStyle = computed(() => ({
+  width: `${DESIGN_WIDTH * scale.value}px`,
+  height: `${DESIGN_HEIGHT * scale.value}px`
+}))
 
-const totalMaterialCost = computed(() => {
-  const costSeries = sparePartData.value.series.find((item) => item.name === '金额')
-  return costSeries ? costSeries.data.reduce((sum, val) => sum + val, 0) : 0
-})
-
-const getStats = () => {
-  IotStatApi.getDeviceStatusCount('rd').then((res) => {
-    typeData.value = res
-    initDeviceStatusCharts()
-  })
-  IotStatApi.getOrderSeven('rd').then((res) => {
-    orderSevenData.value = res
-    initQxChart()
-  })
-  IotStatApi.getDeviceCount('rd').then((res) => {
-    device.value = res
-  })
-  IotStatApi.getMaintainCount('rd').then((res) => {
-    maintain.value = res
-  })
-  IotStatApi.getMaintenanceStatus('rd').then((res) => {
-    by.value = res
-    initMaintenanceChart()
-  })
-  const fillQueryParams = reactive({
-    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
-    endTime: Date.now(), // 设置默认结束时间为当前时间
-    createTime: [],
-    deptId: null, // 选中的部门ID
-    status: null // 填写状态
-  })
-  IotStatApi.getInspectStatuss(fillQueryParams, 'rd').then((res) => {
-    inspect.value = res
-    initInspectChart()
-  })
-  fillQueryParams.deptId = '163'
-  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
-    fill.value = res.totalList[0] || []
-  })
-  const localeStore = useLocaleStore()
-  const lang = localeStore.getCurrentLocale.lang
-  if (lang === 'zh-CN') {
-    initSparePartChart('数量', '金额', sparePartData)
-  } else if (lang === 'en') {
-    initSparePartChart('count', 'amount', sparePartDataEng)
-  }
-  initInspectionChart()
-
-  // 计算近一周时间
-  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(firstDay.getTime())
-
-  // 格式化日期为后端需要的格式
-  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}`
+const targetAreaStyle = computed(() => {
+  return {
+    '--kb-scale': scale.value,
+    width: `${DESIGN_WIDTH * scale.value}px`,
+    height: `${DESIGN_HEIGHT * scale.value}px`
   }
-
-  rateQueryParams.createTime = [formatDate(start), formatDate(end)]
-  IotStatApi.getRdRate(rateQueryParams).then((res) => {
-    materialTableData.value = res
-  })
-}
-
-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: {
-          formatter: '{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}'
-        },
-        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) => {
-      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 initSparePartChart = (count: any, amount: any, spare: {}) => {
-  if (!sparePartRef.value) return
-  sparePartInstance = echarts.init(sparePartRef.value)
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'cross',
-        crossStyle: {
-          color: '#999'
-        }
-      }
-    },
-    legend: {
-      data: [count, amount],
-      textStyle: {
-        color: '#fff'
-      }
-    },
-    grid: {
-      left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: spare.value.xAxis,
-      axisLabel: {
-        color: '#B6C8DA'
-      },
-      axisLine: {
-        lineStyle: {
-          color: '#B6C8DA' // X轴线白色半透明
-        }
-      }
-    },
-    yAxis: [
-      {
-        type: 'value',
-        name: count,
-        position: 'left',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        }
-      },
-      {
-        type: 'value',
-        name: amount,
-        position: 'right',
-        axisLabel: {
-          color: '#B6C8DA',
-          formatter: '{value}'
-        },
-        splitLine: {
-          show: true, // 显示水平网格线(默认显示)
-          lineStyle: {
-            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
-            color: '#457794', // 浅灰色半透明
-            // 可选:设置线条类型(实线/虚线/点线)
-            type: 'dashed'
-          }
-        },
-        axisLine: {
-          lineStyle: {
-            color: '#B6C8DA'
-          }
-        },
-        splitLine: {
-          show: false
-        }
-      }
-    ],
-    series: spare.value.series.map((item, index) => ({
-      name: item.name,
-      type: item.type,
-      data: item.data,
-      yAxisIndex: item.yAxisIndex,
-      itemStyle: {
-        color: ['#6084fb', '#5aef13'][index]
-      }
-    }))
-  }
-
-  sparePartInstance.setOption(option)
-}
-
-// 设备利用率柱状图
-const utilizationRef = ref(null)
-let utilizationInstance: any = null
-const lastHoveredIndex = ref<number | null>(null)
-
-const initUtilizationChart = () => {
-  if (!utilizationRef.value) return
-  try {
-    if (utilizationInstance) {
-      utilizationInstance.dispose()
-      utilizationInstance = null
-    }
-  } catch (e) {}
-  utilizationInstance = echarts.init(utilizationRef.value)
-  const xData = (materialTableData.value as any[]).map(
-    (it) => it.projectDeptName || it.dept || it.projectDept
-  )
-  const seriesData = (materialTableData.value as any[]).map((it) => {
-    const v = (it as any).utilizationRate
-    // 只保留整数部分(百分比的整数),例如 0.528 -> 52
-    return v == null ? 0 : Math.trunc(Number(v) * 100)
-  })
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      formatter: function (params) {
-        const p = params[0] || params
-        return `${p.name}: ${p.data}%`
-      }
-    },
-    xAxis: {
-      type: 'category',
-      data: xData,
-      axisLabel: { color: '#B6C8DA', rotate: 20 }
-    },
-    yAxis: {
-      type: 'value',
-      axisLabel: { formatter: '{value}%', color: '#B6C8DA' }
-    },
-    grid: { left: '3%', right: '4%', bottom: '12%', containLabel: true },
-    series: [
-      {
-        type: 'bar',
-        data: seriesData,
-        itemStyle: { color: statusColors[1] },
-        barWidth: '40%',
-        label: { show: true, position: 'top', formatter: '{c}%' }
-      }
-    ]
-  }
-  utilizationInstance.setOption(option)
-  // 鼠标悬停时展示队伍详情弹窗
-  try {
-    // 移除旧的监听,防止重复绑定
-    if (utilizationInstance.off) utilizationInstance.off('mouseover')
-    utilizationInstance.on('mouseover', (params: any) => {
-      try {
-        if (!params) return
-        // 仅处理柱状图系列的悬停
-        if (params.componentType !== 'series' || params.seriesType !== 'bar') return
-        const idx = params.dataIndex
-        if (idx == null) return
-        // 防止重复请求
-        if (lastHoveredIndex.value === idx && teamDialogVisible.value) return
-        lastHoveredIndex.value = idx
-        const row = ((materialTableData.value as any[])[idx] || {}) as any
-        const teamParams: any = {
-          deptId: row.projectDeptId || row.deptId || row.dept || row.projectDept,
-          createTime: rateQueryParams.createTime
-        }
-        teamDialogVisible.value = true
-        teamTableData.value = []
-        IotStatApi.getRdTeamRate(teamParams).then((res) => {
-          teamTableData.value = res
-        })
-      } catch (e) {}
-    })
-  } catch (e) {}
-
-  window.addEventListener('resize', () => {
-    try {
-      utilizationInstance && utilizationInstance.resize()
-    } catch (e) {}
-  })
-}
-
-watch(
-  materialTableData,
-  () => {
-    nextTick(() => initUtilizationChart())
-  },
-  { deep: true }
-)
-
-onMounted(() => {
-  initUtilizationChart()
-})
-
-onUnmounted(() => {
-  try {
-    if (utilizationInstance) {
-      try {
-        if (utilizationInstance.off) utilizationInstance.off('mouseover')
-      } catch (e) {}
-      utilizationInstance.dispose()
-      utilizationInstance = null
-    }
-  } catch (e) {}
 })
 
-const initInspectChart = () => {
-  if (!inspectChartRef.value) return
-  inspectChartInstance = echarts.init(inspectChartRef.value)
-  const completionRate =
-    (inspect.value.finished / (inspect.value.finished + inspect.value.todo)) * 100
-  // debugger
-  const option = {
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    series: [
-      {
-        type: 'pie',
-        radius: ['40%', '70%'],
-        label: {
-          show: false,
-          position: 'outside'
-        },
-        emphasis: {
-          label: {
-            color: '#B6C8DA',
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: true
-        },
-        // itemStyle: {
-        //   color: ['#e4a317', '#5aef13']
-        // },
-        data: [
-          { name: '完成率', value: completionRate.toFixed(2) },
-          { name: '未完成率', value: 100 - completionRate.toFixed(2) }
-        ]
-      }
-    ]
-  }
-  inspectChartInstance.setOption(option)
-}
-
-const initMaintenanceChart = () => {
-  if (!maintenanceChartRef.value) return
-  maintenanceChartInstance = echarts.init(maintenanceChartRef.value)
-  const completionRate = (by.value.finished / (by.value.finished + by.value.todo)) * 100
-  // debugger
-  const option = {
-    tooltip: {
-      trigger: 'item'
-    },
-    legend: {
-      orient: 'horizontal', // 水平排列图例项
-      bottom: '0%', // 放置在底部
-      icon: 'circle',
-      textStyle: {
-        color: '#B6C8DA'
-      }
-    },
-    series: [
-      {
-        type: 'pie',
-        radius: ['40%', '70%'],
-        label: {
-          show: false,
-          position: 'outside'
-        },
-        emphasis: {
-          label: {
-            color: '#B6C8DA',
-            show: true,
-            fontSize: 15,
-            fontWeight: 'bold'
-          }
-        },
-        labelLine: {
-          show: true
-        },
-        data: [
-          { name: '完成率', value: completionRate.toFixed(2) },
-          { name: '未完成率', value: 100 - completionRate.toFixed(2) }
-        ]
-      }
-    ]
-  }
-  maintenanceChartInstance.setOption(option)
+function dispatchResizeEvent() {
+  window.dispatchEvent(new Event('rdkb:resize'))
 }
 
-const initInspectionChart = () => {
-  if (!inspectionChartRef.value) return
-  inspectionChartInstance = echarts.init(inspectionChartRef.value)
-  const completionRate = (completedInspectionOrders.value / totalInspectionOrders.value) * 100
-  const option = {
-    series: [
-      {
-        type: 'pie',
-        radius: ['50%', '80%'],
-        data: [
-          { value: completionRate, name: '完成率' },
-          { value: 100 - completionRate, name: '未完成率' }
-        ]
-      }
-    ]
-  }
-  inspectionChartInstance.setOption(option)
-}
+function updateScale() {
+  cancelAnimationFrame(resizeRaf)
 
-/** 初始化图表 */
-const initDeviceStatusCharts = () => {
-  if (!statusChartRef.value) return
-  // dispose old instance
-  try {
-    if (statusChartInstance) {
-      statusChartInstance.dispose()
-      statusChartInstance = null
-    }
-  } catch (e) {
-    // ignore
-  }
-  statusChartInstance = echarts.init(statusChartRef.value)
-  const data = normalizeTypeData(typeData.value)
-  const option = {
-    color: statusColors,
-    tooltip: {
-      trigger: 'item',
-      formatter: '{b}: {c} ({d}%)'
-    },
-    series: [
-      {
-        name: '',
-        type: 'pie',
-        radius: ['38%', '58%'],
-        center: ['50%', '30%'],
-        avoidLabelOverlap: false,
-        label: { show: false },
-        labelLine: { show: false },
-        data: data.map((it) => ({ name: it.name, value: it.value }))
-      }
-    ]
-  }
-  statusChartInstance.setOption(option)
-}
+  resizeRaf = requestAnimationFrame(() => {
+    const wrapper = wrapperRef.value
+    if (!wrapper) return
 
-// 月度工作量表数据
-const monthData = ref<any[]>([
-  {
-    month: '1月',
-    y2025_fractures: 49,
-    y2025_layers: 102,
-    y2025_pump_trips: 621,
-    y2025_oil_wells: 17,
-    y2024_fractures: 23,
-    y2024_layers: 42,
-    y2024_pump_trips: 347,
-    y2024_oil_wells: 9
-  },
-  {
-    month: '2月',
-    y2025_fractures: 30,
-    y2025_layers: 52,
-    y2025_pump_trips: 438,
-    y2025_oil_wells: 15,
-    y2024_fractures: 14,
-    y2024_layers: 41,
-    y2024_pump_trips: 46,
-    y2024_oil_wells: 11
-  },
-  {
-    month: '3月',
-    y2025_fractures: 48,
-    y2025_layers: 127,
-    y2025_pump_trips: 326,
-    y2025_oil_wells: 30,
-    y2024_fractures: 42,
-    y2024_layers: 58,
-    y2024_pump_trips: 321,
-    y2024_oil_wells: 17
-  },
-  {
-    month: '4月',
-    y2025_fractures: 49,
-    y2025_layers: 130,
-    y2025_pump_trips: 1203,
-    y2025_oil_wells: 45,
-    y2024_fractures: 32,
-    y2024_layers: 184,
-    y2024_pump_trips: 1046,
-    y2024_oil_wells: 30
-  },
-  {
-    month: '5月',
-    y2025_fractures: 13,
-    y2025_layers: 76,
-    y2025_pump_trips: 701,
-    y2025_oil_wells: 18,
-    y2024_fractures: 73,
-    y2024_layers: 133,
-    y2024_pump_trips: 414,
-    y2024_oil_wells: 24
-  },
-  {
-    month: '6月',
-    y2025_fractures: 34,
-    y2025_layers: 187,
-    y2025_pump_trips: 831,
-    y2025_oil_wells: 30,
-    y2024_fractures: 29,
-    y2024_layers: 113,
-    y2024_pump_trips: 750,
-    y2024_oil_wells: 35
-  },
-  {
-    month: '7月',
-    y2025_fractures: 30,
-    y2025_layers: 230,
-    y2025_pump_trips: 1157,
-    y2025_oil_wells: 51,
-    y2024_fractures: 24,
-    y2024_layers: 47,
-    y2024_pump_trips: 326,
-    y2024_oil_wells: 17
-  },
-  {
-    month: '8月',
-    y2025_fractures: 59,
-    y2025_layers: 218,
-    y2025_pump_trips: 1163,
-    y2025_oil_wells: 12,
-    y2024_fractures: 36,
-    y2024_layers: 121,
-    y2024_pump_trips: 1107,
-    y2024_oil_wells: 26
-  },
-  {
-    month: '9月',
-    y2025_fractures: 48,
-    y2025_layers: 80,
-    y2025_pump_trips: 440,
-    y2025_oil_wells: 39,
-    y2024_fractures: 35,
-    y2024_layers: 162,
-    y2024_pump_trips: 928,
-    y2024_oil_wells: 36
-  },
-  {
-    month: '10月',
-    y2025_fractures: 45,
-    y2025_layers: 99,
-    y2025_pump_trips: 697,
-    y2025_oil_wells: 49,
-    y2024_fractures: 36,
-    y2024_layers: 116,
-    y2024_pump_trips: 891,
-    y2024_oil_wells: 33
-  },
-  {
-    month: '11月',
-    y2025_fractures: null,
-    y2025_layers: null,
-    y2025_pump_trips: null,
-    y2025_oil_wells: null,
-    y2024_fractures: 19,
-    y2024_layers: 102,
-    y2024_pump_trips: 582,
-    y2024_oil_wells: 36
-  },
-  {
-    month: '12月',
-    y2025_fractures: null,
-    y2025_layers: null,
-    y2025_pump_trips: null,
-    y2025_oil_wells: null,
-    y2024_fractures: 51,
-    y2024_layers: 186,
-    y2024_pump_trips: 971,
-    y2024_oil_wells: 32
-  }
-])
-
-// 计算同比差值(2025 - 2024),保留 null 的情况
-monthData.value.forEach((row) => {
-  const f2025 = row.y2025_fractures
-  const f2024 = row.y2024_fractures
-  row.diff_fractures = f2025 == null || f2024 == null ? null : f2025 - f2024
-
-  const l2025 = row.y2025_layers
-  const l2024 = row.y2024_layers
-  row.diff_layers = l2025 == null || l2024 == null ? null : l2025 - l2024
-
-  const p2025 = row.y2025_pump_trips
-  const p2024 = row.y2024_pump_trips
-  row.diff_pump_trips = p2025 == null || p2024 == null ? null : p2025 - p2024
-
-  const o2025 = row.y2025_oil_wells
-  const o2024 = row.y2024_oil_wells
-  row.diff_oil_wells = o2025 == null || o2024 == null ? null : o2025 - o2024
-})
+    const { clientWidth, clientHeight } = wrapper
+    if (!clientWidth || !clientHeight) return
 
-// 表格合计方法(稳健实现,避免把图表配置或数据插入此处)
-const tableSummary = ({ columns, data }: any) => {
-  const sums: any[] = []
-  columns.forEach((column: any, index: number) => {
-    if (index === 0) {
-      sums[index] = '合计'
-      return
-    }
-    const property = column.property
-    if (!property) {
-      sums[index] = ''
-      return
-    }
-    let total = 0
-    let hasValue = false
-    data.forEach((row: any) => {
-      const val = row[property]
-      if (val != null && !isNaN(Number(val))) {
-        total += Number(val)
-        hasValue = true
-      }
-    })
-    // 对原值占比列(orig_ratio)在国内表中不显示合计,留空
-    if (data === domesticData.value && property === 'orig_ratio') {
-      sums[index] = ''
-    } else {
-      sums[index] = hasValue ? total : ''
-    }
+    scale.value = Math.min(clientWidth / DESIGN_WIDTH, clientHeight / DESIGN_HEIGHT)
+    nextTick(dispatchResizeEvent)
   })
-  return sums
 }
 
-let projectDataDialog = ref(false)
-let projectDataDialog2 = ref(false)
-let projectDataDialog3 = ref(false)
-
-// projectDataRowClick
-const projectDataRowClick = (row, column, event) => {
-  console.log('点击的行数据:', row)
-
-  if (row.dept === '国内') {
-    // 显示国内表格
-    projectDataDialog.value = true
-  } else if (row.dept === '伊拉克') {
-    // 显示伊拉克表格
-    projectDataDialog2.value = true
-  } else if (row.dept === '利比亚') {
-    // 显示利比亚表格
-    projectDataDialog3.value = true
-  }
-}
-
-const domesticData = ref<any[]>([
-  {
-    index: 1,
-    dept: '公摊',
-    count: 13,
-    orig_value: 1506.88,
-    net_value: 559.95,
-    orig_ratio: '2.77%'
-  },
-  {
-    index: 2,
-    dept: '新疆项目部',
-    count: 58,
-    orig_value: 5118.1,
-    net_value: 1182.49,
-    orig_ratio: '9.40%'
-  },
-  {
-    index: 3,
-    dept: '青海项目',
-    count: 33,
-    orig_value: 7004.23,
-    net_value: 1758.8,
-    orig_ratio: '12.86%'
-  },
-  {
-    index: 4,
-    dept: '东部项目部',
-    count: 49,
-    orig_value: 5273.54,
-    net_value: 683.58,
-    orig_ratio: '9.68%'
-  },
-  {
-    index: 5,
-    dept: '西南连油项目部',
-    count: 24,
-    orig_value: 4059.36,
-    net_value: 743.24,
-    orig_ratio: '7.45%'
-  },
-  {
-    index: 6,
-    dept: '西南压裂项目部',
-    count: 47,
-    orig_value: 14591.62,
-    net_value: 6030.84,
-    orig_ratio: '26.79%'
-  },
-  {
-    index: 7,
-    dept: '伊拉克 哈法亚连油',
-    count: 120,
-    orig_value: 694.78,
-    net_value: 91.26,
-    orig_ratio: '9.07%'
-  },
-  {
-    index: 8,
-    dept: '伊拉克 哈法亚压裂',
-    count: 132,
-    orig_value: 1008.92,
-    net_value: 575.91,
-    orig_ratio: '13.17%%'
-  },
-  {
-    index: 9,
-    dept: '伊拉克 B9增产',
-    count: 27,
-    orig_value: 304.72,
-    net_value: 124.1,
-    orig_ratio: '3.98%'
-  },
-  {
-    index: 10,
-    dept: '利比亚连油8队',
-    count: 22,
-    orig_value: 2025.52,
-    net_value: 1731.79,
-    orig_ratio: '4.85%'
-  }
-])
-
-// 初始化 国内设备分布饼图相关(放在 domesticData 声明后)
-const domesticChartRef = ref(null)
-let domesticInstance: any = null
-
-const handleDomesticResize = () => {
-  try {
-    if (domesticInstance) domesticInstance.resize()
-  } catch (e) {}
-}
-
-const initDomesticChart = () => {
-  if (!domesticChartRef.value) return
-  try {
-    if (domesticInstance) {
-      domesticInstance.dispose()
-      domesticInstance = null
-    }
-  } catch (e) {}
-  domesticInstance = echarts.init(domesticChartRef.value)
-  const data = domesticData.value.map((it) => ({
-    name: it.dept,
-    value: Number(it.orig_value) || 0,
-    orig_ratio: it.orig_ratio || ''
-  }))
-  const option = {
-    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
-    legend: { show: false },
-    color: statusColors,
-    series: [
-      {
-        name: '国内设备价值',
-        type: 'pie',
-        // 使用实心扇形(非环形)
-        radius: ['0%', '70%'],
-        center: ['50%', '50%'],
-        avoidLabelOverlap: false,
-        label: {
-          show: true,
-          position: 'outside',
-          formatter: function (params) {
-            const val = Number(params.value) || 0
-            const formatted = val.toLocaleString(undefined, {
-              minimumFractionDigits: 2,
-              maximumFractionDigits: 2
-            })
-            // 优先使用后端提供的 orig_ratio 字段(如 "22.87%"),若不存在则退回计算值
-            const ratio =
-              params.data && params.data.orig_ratio
-                ? params.data.orig_ratio
-                : `${Math.round(params.percent || 0)}%`
-            return `${formatted}, ${ratio}`
-          },
-          color: '#fff',
-          fontSize: 12
-        },
-        labelLine: { length: 18, length2: 6 },
-        data,
-        color: [
-          '#5b9bd5',
-          '#ed7d31',
-          '#a5a5a5',
-          '#ffc000',
-          '#4472c4',
-          '#70ad47',
-          '#255e91',
-          '#9e480e',
-          '#636363',
-          '#997300'
-        ]
-      }
-    ]
-  }
-  domesticInstance.setOption(option)
-  window.addEventListener('resize', handleDomesticResize)
-}
-
-// 在 domesticData 变化时重绘
-watch(
-  domesticData,
-  () => {
-    nextTick(() => initDomesticChart())
-  },
-  { deep: true }
-)
-
 onMounted(() => {
-  // 初始绘制
-  initDomesticChart()
-})
-
-onUnmounted(() => {
-  try {
-    if (domesticInstance) {
-      domesticInstance.dispose()
-      domesticInstance = null
-    }
-  } catch (e) {}
-  try {
-    window.removeEventListener('resize', handleDomesticResize)
-  } catch (e) {}
-})
-
-const iraqData = ref<any[]>([
-  {
-    index: 1,
-    dept: '伊拉克 哈法亚连油',
-    count: 120,
-    orig_value_usd: 694.78,
-    net_value_usd: 94.44,
-    orig_ratio: '8.02%'
-  },
-  {
-    index: 2,
-    dept: '伊拉克 哈法亚压裂',
-    count: 132,
-    orig_value_usd: 1008.92,
-    net_value_usd: 587.07,
-    orig_ratio: '11.65%'
-  },
-  {
-    index: 3,
-    dept: '伊拉克 B9增产',
-    count: 27,
-    orig_value_usd: 304.72,
-    net_value_usd: 128.62,
-    orig_ratio: '3.52%'
+  nextTick(updateScale)
+  resizeObserver = new ResizeObserver(updateScale)
+  if (wrapperRef.value) {
+    resizeObserver.observe(wrapperRef.value)
   }
-])
-
-const iraqTableData = computed(() => {
-  const base = iraqData.value.slice()
-  base.push({
-    index: '',
-    dept: '合计',
-    count: 279,
-    orig_value: Number(279),
-    orig_value_usd: Number(2008.41),
-    net_value_usd: Number(810.13),
-    orig_ratio: ''
-  })
-  base.push({
-    index: '',
-    dept: '折算RMB合计',
-    count: '',
-    orig_value: Number(61574.3),
-    orig_value_usd: Number(14279.83),
-    net_value_usd: Number(5760.03),
-    orig_ratio: '23.19%'
-  })
-  return base
+  window.addEventListener('resize', updateScale)
 })
 
-// 折算RMB合计(数值)
-const iraqRmbTotal = ref(14279.83)
-
-const libyaData = ref<any[]>([
-  {
-    index: 1,
-    dept: '利比亚 连油8队',
-    count: 22,
-    orig_value: 2025.52,
-    net_value: 1961.69,
-    orig_ratio: ''
-  }
-])
-
-const libyaRmbTotal = ref(2641.07)
-const libyaRmbNet = ref(2557.84)
-
-const libyaTableData = computed(() => {
-  const base = libyaData.value.slice()
-  base.push({
-    index: '',
-    dept: '折算RMB合计',
-    count: '',
-    orig_value: Number(libyaRmbTotal.value),
-    net_value: Number(libyaRmbNet.value),
-    orig_ratio: '4.29%'
-  })
-  base.push({
-    index: '',
-    dept: '国内外合计',
-    count: 525,
-    orig_value: Number(61574.3),
-    net_value: Number(18256.63),
-    orig_ratio: ''
-  })
-  return base
+watch(activePage, () => {
+  nextTick(dispatchResizeEvent)
 })
 
-// 行样式:合计行与折算RMB合计高亮
-const tableRowClass = (row: any) => {
-  if (!row) return ''
-  if (row.dept === '合计') return 'summary-row'
-  if (row.dept && typeof row.dept === 'string' && row.dept.indexOf('折算') !== -1) return 'rmb-row'
-  return ''
-}
-
-/** 初始化 **/
-onMounted(async () => {
-  loading.value = true
-  await Promise.all([getStats()])
-  loading.value = false
+onUnmounted(() => {
+  resizeObserver?.disconnect()
+  window.removeEventListener('resize', updateScale)
+  cancelAnimationFrame(resizeRaf)
 })
 </script>
-<style lang="scss" scoped>
-@media (width <= 768px) {
-  .page-container {
-    padding: 10px;
-  }
-}
-
-@media (width <= 520px) {
-  .status-legend-item {
-    min-width: 100%;
-  }
-
-  .status-legend {
-    justify-content: flex-start;
-    max-height: none;
-    overflow: visible;
-  }
-}
-
-::v-deep .el-table,
-::v-deep .el-table__expanded-cell {
-  background-color: transparent !important;
-}
-
-/* 表格内背景颜色 */
-
-::v-deep .el-table tr,
-::v-deep .el-table td {
-  background-color: transparent !important;
-}
-
-::v-deep .el-table__footer {
-  color: #fff !important;
-}
-
-::v-deep .el-table__footer-wrapper .is-leaf {
-  color: #fff !important;
-}
-
-.summary {
-  .el-col {
-    margin-bottom: 1rem;
-  }
-}
-
-.stat-card {
-  width: 48%;
-}
-
-.page-container {
-  min-height: 100vh;
-  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;
-    }
-  }
-}
-
-::v-deep .el-card__header {
-  padding-bottom: 0;
-  border-bottom: none !important;
-}
-
-.table-container {
-  height: 420px;
-  padding: 16px;
-  overflow: auto;
-  box-sizing: border-box;
-
-  // 滚动条样式优化
-  &::-webkit-scrollbar {
-    width: 6px;
-    height: 6px;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    background-color: rgb(255 255 255 / 20%);
-    border-radius: 3px;
-  }
-
-  &::-webkit-scrollbar-track {
-    background-color: transparent;
-  }
-}
-
-// 修复表格hover样式
-::v-deep .el-table__row:hover > td {
-  background-color: rgb(255 255 255 / 5%) !important;
-}
-
-.custom-scroll-dialog {
-  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
-  max-height: 90vh;
-  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
-}
-
-/* 滚动内容容器:核心样式 */
-.dialog-scroll-content {
-  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
-  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
-}
+<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 kb-screen" :style="targetAreaStyle">
+        <header class="header">{{ company }}</header>
+        <div class="kb-content">
+          <div class="page-tabs">
+            <button
+              v-for="tab in pageTabs"
+              :key="tab.value"
+              type="button"
+              class="page-tab"
+              :class="{ 'is-active': activePage === tab.value }"
+              @click="activePage = tab.value">
+              {{ tab.label }}
+            </button>
+          </div>
 
-/* 优化滚动条样式(可选,提升UI体验) */
-.dialog-scroll-content::-webkit-scrollbar {
-  width: 6px; /* 滚动条宽度 */
-}
+          <div v-if="activePage === 'home'" class="kb-home-page">
+            <rdsummary class="kb-stage-card kb-stage-card--1" />
+            <div class="kb-chart-grid">
+              <rd-device-status class="kb-stage-card kb-stage-card--1" />
+              <rd-value class="kb-stage-card kb-stage-card--2" />
+              <rd-device-category class="kb-stage-card kb-stage-card--3" />
+              <rd-rate class="kb-stage-card kb-stage-card--4" />
+              <rd-use-status class="kb-stage-card kb-stage-card--5" />
+              <rd-exception-prompt class="kb-stage-card kb-stage-card--6" />
+              <rd-cost class="kb-stage-card kb-stage-card--7" />
+              <rd-maintenance-times class="kb-stage-card kb-stage-card--8" />
+              <rd-inventory-safety class="kb-stage-card kb-stage-card--9" />
+            </div>
+          </div>
 
-.dialog-scroll-content::-webkit-scrollbar-thumb {
-  background-color: #e5e7eb; /* 滚动条滑块颜色 */
-  border-radius: 3px; /* 滚动条圆角 */
-}
+          <div v-else class="kb-production-page">
+            <rd-production-briefs
+              class="kb-stage-card kb-stage-card--8 kb-stage-card--list"
+              page-mode="full" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
 
-.dialog-scroll-content::-webkit-scrollbar-thumb:hover {
-  background-color: #d1d5db; /*  hover时滑块颜色 */
-}
+<style scoped lang="scss">
+@import url('@/styles/kb.scss');
 
-.custom-table :deep .el-table__row {
-  height: 50px !important; /* 高度根据需求调整 */
+.kb-screen {
+  overflow: hidden;
 }
 
-/* 设备状态图例自适应样式 */
-.status-legend {
-  display: flex;
-  width: 100%;
-  max-height: 90px; /* 限制高度,超出显示滚动 */
-  padding: 6px 0;
-  overflow-y: auto;
-  box-sizing: border-box;
-  flex-wrap: wrap;
-  justify-content: center;
-  gap: 8px;
+.kb-content {
+  position: relative;
+  height: calc(100% - 52px * var(--kb-scale));
+  padding: calc(44px * var(--kb-scale)) calc(20px * var(--kb-scale)) calc(20px * var(--kb-scale));
 }
 
-.status-legend-item {
+.page-tabs {
+  position: absolute;
+  top: calc(10px * var(--kb-scale));
+  left: calc(20px * var(--kb-scale));
+  z-index: 3;
   display: flex;
-  max-width: 100%;
-  min-width: 120px;
-  padding: 6px 10px;
-  box-sizing: border-box;
-  align-items: center;
-  justify-content: space-between;
-  gap: 12px;
-}
-
-.status-legend-left {
+  width: fit-content;
+  gap: calc(12px * var(--kb-scale));
+}
+
+.page-tab {
+  height: calc(28px * var(--kb-scale));
+  min-width: calc(82px * var(--kb-scale));
+  padding: 0 calc(14px * var(--kb-scale));
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(15px * var(--kb-scale));
+  line-height: calc(28px * var(--kb-scale));
+  color: #f5f9ff;
+  cursor: pointer;
+  background: linear-gradient(180deg, #83bcff 0%, #2f7ee9 58%, #1762d6 100%);
+  border: 1px solid rgb(255 255 255 / 55%);
+  border-radius: calc(5px * var(--kb-scale));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 58%),
+    0 calc(4px * var(--kb-scale)) calc(8px * var(--kb-scale)) rgb(30 89 179 / 22%);
+  transition:
+    transform 0.2s ease,
+    filter 0.2s ease,
+    box-shadow 0.2s ease;
+}
+
+.page-tab:hover,
+.page-tab.is-active {
+  filter: brightness(1.08);
+  transform: translateY(calc(-1px * var(--kb-scale)));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 72%),
+    0 calc(5px * var(--kb-scale)) calc(10px * var(--kb-scale)) rgb(30 89 179 / 28%);
+}
+
+.page-tab.is-active {
+  background: linear-gradient(180deg, #4d9cff 0%, #1f6ee7 56%, #0e4fc4 100%);
+}
+
+.kb-home-page {
   display: flex;
-  align-items: center;
-  gap: 8px;
-  min-width: 0;
+  height: 100%;
+  min-height: 0;
+  flex-direction: column;
 }
 
-.status-legend-color {
-  display: inline-block;
-  width: 12px;
-  height: 12px;
-  border-radius: 50%;
-  flex: 0 0 12px;
+.kb-summary-placeholder {
+  height: calc(112px * var(--kb-scale));
 }
 
-.status-legend-name {
-  max-width: calc(100% - 60px);
-  overflow: hidden;
-  color: #fff;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.status-legend-right {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  flex-shrink: 0;
-}
-
-.status-legend-value {
-  font-weight: 700;
-  color: #fff;
+.kb-chart-grid {
+  display: grid;
+  width: 100%;
+  min-height: 0;
+  flex: 1;
+  margin-top: calc(12px * var(--kb-scale));
+  gap: calc(12px * var(--kb-scale));
+  grid-template-rows: repeat(3, minmax(0, 1fr));
+  grid-template-columns: repeat(3, minmax(0, 1fr));
 }
 
-.status-legend-percent {
-  color: #fff;
+.kb-production-page {
+  height: 100%;
+  min-height: 0;
 }
-
-/* 最外层透明 */
 </style>

+ 550 - 0
src/views/pms/stat/rdkb/rd-Inventory-safety.vue

@@ -0,0 +1,550 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  CHART_RENDERER,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  THEME
+} from '@/utils/kb'
+
+type ActivePanel = 'distribution' | 'trend'
+
+type InventoryItem = {
+  project: string
+  mayInventoryAmount: number
+  yearBeginningBacklog: number
+  mayBacklogAmount: number
+}
+
+const activePanel = ref<ActivePanel>('distribution')
+const distributionChartRef = ref<HTMLDivElement>()
+const trendChartRef = ref<HTMLDivElement>()
+
+let distributionChart: echarts.ECharts | null = null
+let trendChart: echarts.ECharts | null = null
+
+const panelOptions: Array<{ label: string; value: ActivePanel }> = [
+  {
+    label: '分布',
+    value: 'distribution'
+  },
+  {
+    label: '趋势',
+    value: 'trend'
+  }
+]
+
+const activeTitle = computed(() => (activePanel.value === 'distribution' ? '存货分布' : '积压趋势'))
+
+const inventoryData: InventoryItem[] = [
+  {
+    project: '东营中心仓',
+    mayInventoryAmount: 7.35,
+    yearBeginningBacklog: 10.13,
+    mayBacklogAmount: 7.02
+  },
+  {
+    project: '吉尔吉斯',
+    mayInventoryAmount: 23.82,
+    yearBeginningBacklog: 23.82,
+    mayBacklogAmount: 25.18
+  },
+  {
+    project: '新疆-连油',
+    mayInventoryAmount: 21.9,
+    yearBeginningBacklog: 177.02,
+    mayBacklogAmount: 19.55
+  },
+  {
+    project: '青海',
+    mayInventoryAmount: 52.03,
+    yearBeginningBacklog: 177.97,
+    mayBacklogAmount: 53.46
+  },
+  {
+    project: '东部-陕蒙',
+    mayInventoryAmount: 5.63,
+    yearBeginningBacklog: 84.75,
+    mayBacklogAmount: 7.65
+  },
+  {
+    project: '东部-胜利',
+    mayInventoryAmount: 8.82,
+    yearBeginningBacklog: 59.9,
+    mayBacklogAmount: 10.67
+  },
+  {
+    project: '西南压裂',
+    mayInventoryAmount: 41.78,
+    yearBeginningBacklog: 174.18,
+    mayBacklogAmount: 27.72
+  },
+  {
+    project: '西南连油',
+    mayInventoryAmount: 24.13,
+    yearBeginningBacklog: 90.01,
+    mayBacklogAmount: 23.37
+  },
+  {
+    project: '伊拉克(国内)',
+    mayInventoryAmount: 0.1,
+    yearBeginningBacklog: 190.71,
+    mayBacklogAmount: 0.1
+  },
+  {
+    project: '大庆工具',
+    mayInventoryAmount: 0.12,
+    yearBeginningBacklog: 1.79,
+    mayBacklogAmount: 0.05
+  },
+  {
+    project: '利比亚(国内)',
+    mayInventoryAmount: 0,
+    yearBeginningBacklog: 31.33,
+    mayBacklogAmount: 0
+  }
+]
+
+function formatProjectName(value: string) {
+  return value.replace(/项目$/, '')
+}
+
+function formatAmount(value: number) {
+  return Number(value || 0).toFixed(2)
+}
+
+function getInventoryChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
+  const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
+  const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
+
+  return {
+    compact,
+    trendGridTop: compact ? 34 : 40,
+    trendGridLeft: compact ? 36 : 44,
+    trendGridRight: compact ? 8 : 16,
+    trendGridBottom: compact ? 12 : 12,
+    legendTop: compact ? 0 : 4,
+    legendRight: compact ? 2 : 6,
+    legendItemSize: compact ? 8 : 10,
+    legendGap: compact ? 8 : 12,
+    legendFontSize: compact ? 10 : 12,
+    axisFontSize: compact ? 10 : 10,
+    yAxisLabelMargin: compact ? 6 : 8,
+    xAxisLabelRotate: compact ? 45 : 36,
+    distributionTitleTop: compact ? 6 : 4,
+    distributionTitleFontSize: compact ? 12 : 14,
+    distributionTitleLineHeight: compact ? 14 : 16,
+    distributionPieRadius: compact ? ['36%', '54%'] : ['48%', '68%'],
+    distributionPieCenterY: compact ? '62%' : '57%',
+    distributionLegendBottom: compact ? 0 : 10,
+    distributionLegendItemSize: compact ? 9 : 13,
+    distributionLegendGap: compact ? 8 : 14,
+    distributionLegendFontSize: compact ? 10 : 14,
+    pieLabelNameFontSize: compact ? 11 : 13,
+    pieLabelNameLineHeight: compact ? 18 : 22,
+    pieLabelValueFontSize: compact ? 16 : 20,
+    pieLabelValueLineHeight: compact ? 22 : 30,
+    barWidth: compact ? 10 : 12,
+    barGap: compact ? '10%' : '8%',
+    barCategoryGap: compact ? '44%' : '38%',
+    labelDistance: compact ? 4 : 7,
+    labelFontSize: compact ? 10 : 12
+  }
+}
+
+function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
+  const layout = getInventoryChartLayout(distributionChartRef)
+  const inventoryTotal = data.reduce((total, item) => total + item.mayInventoryAmount, 0)
+  const backlogTotal = data.reduce((total, item) => total + item.yearBeginningBacklog, 0)
+
+  return {
+    ...ANIMATION,
+    color: [
+      THEME.color.blue.line,
+      THEME.color.orange.line,
+      THEME.color.green.line,
+      THEME.color.red.line,
+      THEME.color.blue.mid,
+      THEME.color.orange.mid,
+      THEME.color.green.mid,
+      THEME.color.red.mid
+    ],
+    tooltip: createTooltip({
+      trigger: 'item',
+      formatter(params: any) {
+        return `${params.seriesName}<br/>${params.marker}${params.name}:${formatAmount(
+          params.value
+        )}万元<br/>占比:${params.percent}%`
+      }
+    }),
+    title: [
+      {
+        text: `5月总积压库存金额\n${formatAmount(inventoryTotal)} 万元`,
+        left: '26.5%',
+        top: layout.distributionTitleTop,
+        textAlign: 'center',
+        textStyle: {
+          color: THEME.text.strong,
+          fontSize: layout.distributionTitleFontSize,
+          fontWeight: 700,
+          lineHeight: layout.distributionTitleLineHeight,
+          fontFamily: FONT_FAMILY
+        }
+      },
+      {
+        text: `总库存金额\n${formatAmount(backlogTotal)} 万元`,
+        left: '71.5%',
+        top: layout.distributionTitleTop,
+        textAlign: 'center',
+        textStyle: {
+          color: THEME.text.strong,
+          fontSize: layout.distributionTitleFontSize,
+          fontWeight: 700,
+          lineHeight: layout.distributionTitleLineHeight,
+          fontFamily: FONT_FAMILY
+        }
+      }
+    ],
+    legend: createLegend({
+      type: 'scroll',
+      bottom: layout.distributionLegendBottom,
+      left: 10,
+      right: 10,
+      itemWidth: layout.distributionLegendItemSize,
+      itemHeight: layout.distributionLegendItemSize,
+      itemGap: layout.distributionLegendGap,
+      textStyle: {
+        color: THEME.text.regular,
+        fontSize: layout.distributionLegendFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      }
+    }),
+    series: [
+      {
+        name: '5月总积压库存金额',
+        type: 'pie',
+        radius: layout.distributionPieRadius,
+        center: ['27%', layout.distributionPieCenterY],
+        minAngle: 5,
+        label: { show: false },
+        data: data.map((item) => ({
+          name: item.project,
+          value: item.mayInventoryAmount
+        }))
+      },
+      {
+        name: '总库存金额',
+        type: 'pie',
+        radius: layout.distributionPieRadius,
+        center: ['73%', layout.distributionPieCenterY],
+        minAngle: 5,
+        label: { show: false },
+        data: data
+          .filter((item) => item.yearBeginningBacklog > 0)
+          .map((item) => ({
+            name: item.project,
+            value: item.yearBeginningBacklog
+          }))
+      }
+    ]
+  }
+}
+
+function getTrendOption(data: InventoryItem[]): echarts.EChartsOption {
+  const projects = data.map((item) => formatProjectName(item.project))
+  const maxBacklog = Math.max(
+    ...data.map((item) => Math.max(item.mayInventoryAmount, item.mayBacklogAmount)),
+    1
+  )
+  const backlogAxisMax = Math.ceil((maxBacklog * 1.15) / 50) * 50
+  const layout = getInventoryChartLayout(trendChartRef)
+  const barLabel = {
+    show: false,
+    position: 'top' as any,
+    distance: layout.labelDistance,
+    color: THEME.text.strong,
+    fontSize: layout.labelFontSize,
+    fontWeight: 700,
+    fontFamily: FONT_FAMILY,
+    formatter(params: any) {
+      const value = Number(params.value)
+
+      return formatAmount(value)
+    }
+  }
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: layout.trendGridTop,
+      left: layout.trendGridLeft,
+      right: layout.trendGridRight,
+      bottom: layout.trendGridBottom
+    },
+    color: [THEME.color.blue.line, THEME.color.orange.line],
+    legend: createLegend(
+      {
+        top: layout.legendTop,
+        right: layout.legendRight,
+        itemWidth: layout.legendItemSize,
+        itemHeight: layout.legendItemSize,
+        itemGap: layout.legendGap,
+        textStyle: {
+          color: THEME.text.regular,
+          fontSize: layout.legendFontSize,
+          fontWeight: 600,
+          fontFamily: FONT_FAMILY
+        }
+      },
+      ['2026期初', '5月总积压']
+    ),
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      },
+      valueFormatter(value: number) {
+        return `${formatAmount(value)}万元`
+      }
+    }),
+    xAxis: {
+      type: 'category',
+      data: projects,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: layout.axisFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY,
+        interval: 0,
+        margin: layout.yAxisLabelMargin,
+        rotate: layout.xAxisLabelRotate
+      }
+    },
+    yAxis: {
+      type: 'value',
+      max: backlogAxisMax,
+      splitNumber: 4,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: layout.axisFontSize,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return `${value}`
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        name: '2026期初',
+        type: 'bar',
+        data: data.map((item) => item.mayBacklogAmount),
+        barWidth: layout.barWidth,
+        barGap: layout.barGap,
+        barCategoryGap: layout.barCategoryGap,
+        barMinHeight: 0,
+        showBackground: false,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        label: barLabel,
+        labelLayout: {
+          hideOverlap: true
+        },
+        itemStyle: {
+          shadowBlur: 10,
+          shadowColor: THEME.color.blue.bg,
+          color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
+            { offset: 0, color: THEME.color.blue.light },
+            { offset: 0.55, color: THEME.color.blue.mid },
+            { offset: 1, color: THEME.color.blue.line }
+          ])
+        }
+      },
+      {
+        name: '5月总积压',
+        type: 'bar',
+        data: data.map((item) => item.mayInventoryAmount),
+        barWidth: layout.barWidth,
+        barMinHeight: 0,
+        barGap: layout.barGap,
+        barCategoryGap: layout.barCategoryGap,
+        showBackground: false,
+        backgroundStyle: {
+          color: THEME.split,
+          borderRadius: 999
+        },
+        label: barLabel,
+        labelLayout: {
+          hideOverlap: true
+        },
+        itemStyle: {
+          shadowBlur: 10,
+          shadowColor: THEME.color.orange.bg,
+          color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
+            { offset: 0, color: THEME.color.orange.light },
+            { offset: 0.55, color: THEME.color.orange.mid },
+            { offset: 1, color: THEME.color.orange.line }
+          ])
+        }
+      }
+    ]
+  }
+}
+
+function initChart(
+  chartRef: Ref<HTMLDivElement | undefined>,
+  chart: echarts.ECharts | null,
+  option: echarts.EChartsOption
+) {
+  if (!chartRef.value) return chart
+
+  chart?.dispose()
+  const nextChart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  nextChart.setOption(option, true)
+
+  return nextChart
+}
+
+function renderDistributionChart() {
+  distributionChart?.setOption(getDistributionOption(inventoryData), true)
+}
+
+function renderTrendChart() {
+  trendChart?.setOption(getTrendOption(inventoryData), true)
+}
+
+function initDistributionChart() {
+  distributionChart = initChart(
+    distributionChartRef,
+    distributionChart,
+    getDistributionOption(inventoryData)
+  )
+}
+
+function initTrendChart() {
+  trendChart = initChart(trendChartRef, trendChart, getTrendOption(inventoryData))
+}
+
+function resizeCharts() {
+  distributionChart?.resize()
+  trendChart?.resize()
+  renderDistributionChart()
+  renderTrendChart()
+}
+
+function destroyCharts() {
+  distributionChart?.dispose()
+  trendChart?.dispose()
+  distributionChart = null
+  trendChart = null
+}
+
+watch(activePanel, (value) => {
+  nextTick(() => {
+    if (value === 'distribution') {
+      if (!distributionChart) initDistributionChart()
+      renderDistributionChart()
+    } else {
+      if (!trendChart) initTrendChart()
+      renderTrendChart()
+    }
+    resizeCharts()
+  })
+})
+
+onMounted(() => {
+  initDistributionChart()
+  window.addEventListener('resize', resizeCharts)
+  window.addEventListener('rdkb:resize', resizeCharts)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeCharts)
+  window.removeEventListener('rdkb:resize', resizeCharts)
+  destroyCharts()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        {{ activeTitle }}
+      </div>
+      <el-segmented
+        v-model="activePanel"
+        :options="panelOptions"
+        size="small"
+        class="inventory-switch" />
+    </div>
+    <div class="flex-1 min-h-0">
+      <div
+        v-show="activePanel === 'distribution'"
+        ref="distributionChartRef"
+        class="inventory-chart"></div>
+      <div v-show="activePanel === 'trend'" ref="trendChartRef" class="inventory-chart"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.inventory-switch {
+  --el-segmented-item-selected-color: #03409b;
+  --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
+  --el-segmented-bg-color: rgb(31 91 184 / 10%);
+  --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
+
+  min-height: calc(26px * var(--kb-scale, 1));
+  padding: calc(2px * var(--kb-scale, 1));
+  border: 1px solid rgb(31 91 184 / 12%);
+  transform: translateY(calc(-2px * var(--kb-scale, 1)));
+
+  :deep(.el-segmented__item) {
+    min-height: calc(22px * var(--kb-scale, 1));
+    padding: 0 calc(8px * var(--kb-scale, 1));
+    font-size: calc(13px * var(--kb-scale, 1));
+    font-weight: 600;
+    color: #29527f;
+  }
+}
+
+.inventory-chart {
+  width: 100%;
+  height: 100%;
+  min-height: 0;
+}
+</style>

+ 224 - 0
src/views/pms/stat/rdkb/rd-cost.vue

@@ -0,0 +1,224 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import dayjs from 'dayjs'
+import {
+  ANIMATION,
+  ChartItem,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  THEME
+} from '@/utils/kb'
+import { IotStatApi } from '@/api/pms/stat'
+
+const chartData = ref<ChartItem[]>([])
+
+const router = useRouter()
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+let chartClickBound = false
+
+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: 40, left: 32 },
+    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',
+      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: [
+      {
+        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 getChartDateRange() {
+  const startDate = dayjs(chartData.value[0]?.name)
+  const endDate = dayjs(chartData.value[chartData.value.length - 1]?.name)
+  if (!startDate.isValid() || !endDate.isValid()) return undefined
+
+  return [
+    startDate.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    endDate.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function handleChartClick() {
+  router.push({
+    path: '/report-statistics/operational-costs',
+    query: {
+      createTime: getChartDateRange()
+    }
+  })
+}
+
+function renderChart() {
+  if (!chart) return
+
+  const data = chartData.value || []
+
+  chart.setOption(getChartOption(data), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    if (chartClickBound) {
+      chart.getZr().off('click', handleChartClick)
+      chartClickBound = false
+    }
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getOrderYwcb('rd')
+    chartData.value = res.xAxis.map((item, index) => ({
+      name: item,
+      value: res.series[index]
+    }))
+    renderChart()
+  } catch (error) {
+    console.error('运维成本:', error)
+    chartData.value = []
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  chart?.getZr().on('click', handleChartClick)
+  chartClickBound = true
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <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>

+ 229 - 0
src/views/pms/stat/rdkb/rd-device-category.vue

@@ -0,0 +1,229 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, CHART_RENDERER, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+
+interface DeviceCategoryItem {
+  name: string
+  count: number
+}
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+const categoryData: DeviceCategoryItem[] = [
+  { name: '压裂泵车', count: 75 },
+  { name: '连油主车', count: 36 },
+  { name: '其他', count: 35 },
+  { name: '小车', count: 32 },
+  { name: '井控装备', count: 22 },
+  { name: '连油辅助设备', count: 19 },
+  { name: '货车', count: 17 },
+  { name: '四川瑞都设备', count: 16 },
+  { name: '卡车', count: 16 },
+  { name: '供液橇', count: 12 }
+]
+
+function getChartLayout() {
+  const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
+  const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
+
+  return {
+    gridTop: compact ? 20 : 26,
+    gridRight: compact ? 32 : 40,
+    gridBottom: compact ? 22 : 28,
+    gridLeft: compact ? 12 : 12,
+    barWidth: compact ? 10 : 14,
+    axisFontSize: compact ? 10 : 12,
+    labelFontSize: compact ? 10 : 12
+  }
+}
+
+function formatValue(value: number) {
+  return Number(value || 0).toLocaleString('en-US')
+}
+
+function getChartOption(): echarts.EChartsOption {
+  const layout = getChartLayout()
+  const values = categoryData.map((item) => item.count)
+  const maxValue = Math.max(...values, 1)
+  const xAxisMax = Math.ceil((maxValue * 1.12) / 10) * 10
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: layout.gridTop,
+      right: layout.gridRight,
+      bottom: layout.gridBottom,
+      left: layout.gridLeft
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      },
+      formatter(params: any[]) {
+        const first = params[0]
+        const row = categoryData[first?.dataIndex ?? 0]
+        if (!row) return ''
+
+        return [
+          `<div style="min-width:132px;font-weight:700;color:#24364f;margin-bottom:6px;">${row.name}</div>`,
+          `<div style="display:flex;justify-content:space-between;gap:18px;line-height:22px;"><span>设备数量</span><b style="color:#1f5bb8;">${row.count} 台</b></div>`
+        ].join('')
+      }
+    }),
+    xAxis: {
+      type: 'value',
+      name: '台',
+      max: xAxisMax,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: layout.axisFontSize,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return formatValue(value)
+        }
+      },
+      nameTextStyle: {
+        color: '#4c6c9b',
+        fontSize: layout.axisFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      },
+      splitLine: {
+        lineStyle: {
+          color: 'rgba(31, 91, 184, 0.24)',
+          type: 'dashed'
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: categoryData.map((item) => item.name),
+      inverse: true,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: '#102a43',
+        fontSize: layout.axisFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY,
+        width: 80,
+        overflow: 'truncate'
+      }
+    },
+    series: [
+      {
+        name: '设备数量',
+        type: 'bar',
+        data: values,
+        barWidth: layout.barWidth,
+        showBackground: true,
+        backgroundStyle: {
+          color: 'rgba(31, 91, 184, 0.08)',
+          borderRadius: 999
+        },
+        itemStyle: {
+          borderRadius: 999,
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: THEME.color.blue.light },
+            { offset: 0.58, color: THEME.color.blue.mid },
+            { offset: 1, color: THEME.color.blue.line }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'right',
+          distance: 8,
+          color: THEME.text.strong,
+          fontSize: layout.labelFontSize,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return `${formatValue(params.value)}`
+          }
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 16,
+            shadowColor: THEME.color.blue.shadow
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+
+  chart?.dispose()
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  renderChart()
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+  renderChart()
+}
+
+function destroyChart() {
+  chart?.dispose()
+  chart = null
+}
+
+onMounted(() => {
+  initChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备分类
+    </div>
+    <div ref="chartRef" class="device-category-chart"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.device-category-chart {
+  width: 100%;
+  min-height: 0;
+  flex: 1;
+}
+</style>

+ 157 - 0
src/views/pms/stat/rdkb/rd-device-status.vue

@@ -0,0 +1,157 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, CHART_RENDERER, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+
+interface DeviceStatusItem {
+  name: string
+  value: number
+}
+
+const chartData: DeviceStatusItem[] = [
+  {
+    name: '正常',
+    value: 269
+  },
+  {
+    name: '带病运行',
+    value: 1
+  },
+  {
+    name: '故障',
+    value: 6
+  }
+]
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+function getChartLayout() {
+  const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
+  const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
+
+  return {
+    pieRadius: compact ? ['42%', '62%'] : ['48%', '68%'],
+    pieCenterY: compact ? '50%' : '50%',
+    legendBottom: compact ? 0 : 8,
+    legendItemSize: compact ? 9 : 13,
+    legendGap: compact ? 10 : 18,
+    legendFontSize: compact ? 10 : 14
+  }
+}
+
+function getChartOption(): echarts.EChartsOption {
+  const layout = getChartLayout()
+
+  return {
+    ...ANIMATION,
+    color: [THEME.color.blue.line, THEME.color.orange.line, THEME.color.red.line],
+    tooltip: createTooltip({
+      trigger: 'item',
+      formatter(params: any) {
+        return `${params.marker}${params.name}<br/>数量:${params.value} 台<br/>占比:${params.percent}%`
+      }
+    }),
+    legend: {
+      type: 'scroll',
+      bottom: layout.legendBottom,
+      left: 'center',
+      itemWidth: layout.legendItemSize,
+      itemHeight: layout.legendItemSize,
+      itemGap: layout.legendGap,
+      textStyle: {
+        color: THEME.text.regular,
+        fontSize: layout.legendFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      },
+      formatter(name: string) {
+        const item = chartData.find((status) => status.name === name)
+        return item ? `${name}  ${item.value}台` : name
+      }
+    },
+    series: [
+      {
+        name: '设备状态',
+        type: 'pie',
+        radius: layout.pieRadius,
+        center: ['50%', layout.pieCenterY],
+        minAngle: 5,
+        label: { show: false },
+        data: chartData
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+
+  chart?.dispose()
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  renderChart()
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+  renderChart()
+}
+
+function destroyChart() {
+  chart?.dispose()
+  chart = null
+}
+
+onMounted(() => {
+  initChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel device-status-panel flex flex-col">
+    <div class="panel-title">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备状态
+    </div>
+    <div class="device-status-body">
+      <div ref="chartRef" class="device-status-chart"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.device-status-panel {
+  overflow: hidden;
+}
+
+.device-status-body {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  padding: calc(10px * var(--kb-scale, 1)) calc(16px * var(--kb-scale, 1))
+    calc(14px * var(--kb-scale, 1));
+}
+
+.device-status-chart {
+  min-height: 0;
+  flex: 1;
+}
+</style>

+ 332 - 0
src/views/pms/stat/rdkb/rd-exception-prompt.vue

@@ -0,0 +1,332 @@
+<script lang="ts" setup>
+import dayjs from 'dayjs'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import { IotStatApi } from '@/api/pms/stat'
+
+dayjs.extend(quarterOfYear)
+
+type TimeRange = 'day' | 'month' | 'quarter' | 'year'
+
+type NptCountItem = {
+  nptName: string
+  teamCount: number
+}
+
+const activeTimeRange = ref<TimeRange>('day')
+const loading = ref(false)
+const productionList = ref<NptCountItem[]>([])
+
+const timeOptions: Array<{ label: string; value: TimeRange }> = [
+  { label: '本日', value: 'day' },
+  { label: '本月', value: 'month' },
+  { label: '本季', value: 'quarter' },
+  { label: '全年', value: 'year' }
+]
+
+const nptNameMap: Record<string, string> = {
+  no: '无工作量',
+  xcdm: '井场待命',
+  wxz: '维修设备',
+  wz: '物资影响',
+  yc: '异常/复杂'
+}
+
+const productionTopList = computed(() => productionList.value.slice(0, 9))
+const deviceExceptionCount = ref(0)
+
+function getNptDisplayName(nptName?: string) {
+  if (!nptName) return '-'
+
+  return nptNameMap[nptName] || nptName
+}
+
+function getCreateTime(type: TimeRange) {
+  const now = dayjs()
+  const start = now.startOf(type === 'day' ? 'day' : type === 'year' ? 'year' : type)
+  const end = now.endOf(type === 'day' ? 'day' : type === 'year' ? 'year' : type)
+
+  return [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')]
+}
+
+async function loadData() {
+  loading.value = true
+  try {
+    const res = await IotStatApi.getRdNptCount({
+      createTime: getCreateTime(activeTimeRange.value)
+    })
+
+    productionList.value = Array.isArray(res)
+      ? res.map((item) => ({
+          nptName: getNptDisplayName(item.nptName),
+          teamCount: Number(item.teamCount || 0)
+        }))
+      : []
+  } catch (error) {
+    console.error('获取生产异常失败:', error)
+    productionList.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+watch(activeTimeRange, () => {
+  loadData()
+})
+
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        异常警示
+      </div>
+      <el-segmented
+        v-model="activeTimeRange"
+        :options="timeOptions"
+        size="small"
+        class="exception-time-switch" />
+    </div>
+
+    <div v-loading="loading" class="exception-body">
+      <section class="exception-section exception-section--production">
+        <div class="exception-section__title">
+          <span class="exception-section__marker"></span>
+          生产异常
+          <span class="exception-section__hint">TOP 9</span>
+        </div>
+        <div class="production-grid">
+          <div
+            v-for="(item, index) in productionTopList"
+            :key="item.nptName"
+            class="production-item">
+            <span class="production-item__rank">{{ index + 1 }}</span>
+            <span class="production-item__name">{{ item.nptName }}</span>
+            <span class="production-item__count">
+              <span class="production-item__value">{{ item.teamCount }}</span>
+              <span class="production-item__unit">个</span>
+            </span>
+          </div>
+          <div v-if="!productionTopList.length && !loading" class="exception-empty">
+            暂无生产异常数据
+          </div>
+        </div>
+      </section>
+
+      <section class="exception-section exception-section--device">
+        <div class="exception-section__title">
+          <span class="exception-section__marker"></span>
+          设备异常
+        </div>
+        <div class="device-exception-card">
+          <span class="device-exception-card__label">异常数量:</span>
+          <span class="device-exception-card__value">{{ deviceExceptionCount }}</span>
+          <span class="device-exception-card__unit">个</span>
+        </div>
+      </section>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.exception-time-switch {
+  --el-segmented-item-selected-color: #03409b;
+  --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
+  --el-segmented-bg-color: rgb(31 91 184 / 10%);
+  --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
+
+  min-height: calc(26px * var(--kb-scale, 1));
+  padding: calc(2px * var(--kb-scale, 1));
+  border: 1px solid rgb(31 91 184 / 12%);
+  transform: translateY(calc(-2px * var(--kb-scale, 1)));
+
+  :deep(.el-segmented__item) {
+    min-height: calc(22px * var(--kb-scale, 1));
+    padding: 0 calc(7px * var(--kb-scale, 1));
+    font-size: calc(13px * var(--kb-scale, 1));
+    font-weight: 600;
+    color: #29527f;
+  }
+}
+
+.exception-body {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: calc(7px * var(--kb-scale, 1));
+  padding: calc(10px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1))
+    calc(12px * var(--kb-scale, 1));
+
+  :deep(.el-loading-mask) {
+    background-color: rgb(221 233 251 / 42%);
+  }
+}
+
+.exception-section {
+  min-height: 0;
+}
+
+.exception-section--production {
+  display: flex;
+  flex: 1 1 0;
+  flex-direction: column;
+}
+
+.exception-section--device {
+  position: relative;
+  z-index: 1;
+  flex: none;
+}
+
+.exception-section__title {
+  display: flex;
+  height: calc(19px * var(--kb-scale, 1));
+  align-items: center;
+  gap: calc(7px * var(--kb-scale, 1));
+  margin-bottom: calc(5px * var(--kb-scale, 1));
+  font-size: calc(14px * var(--kb-scale, 1));
+  font-weight: 700;
+  line-height: calc(20px * var(--kb-scale, 1));
+  color: #24364f;
+}
+
+.exception-section__marker {
+  width: calc(5px * var(--kb-scale, 1));
+  height: calc(16px * var(--kb-scale, 1));
+  background: linear-gradient(180deg, #2d7cf8 0%, #03409b 100%);
+  border-radius: 999px;
+  box-shadow: 0 0 calc(8px * var(--kb-scale, 1)) rgb(45 124 248 / 28%);
+}
+
+.exception-section__hint {
+  padding-top: calc(1px * var(--kb-scale, 1));
+  margin-left: auto;
+  font-size: calc(12px * var(--kb-scale, 1));
+  font-weight: 600;
+  color: #6f85aa;
+}
+
+.production-grid {
+  display: grid;
+  min-height: 0;
+  flex: 1;
+  padding: calc(6px * var(--kb-scale, 1));
+  gap: calc(5px * var(--kb-scale, 1)) calc(7px * var(--kb-scale, 1));
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  grid-template-rows: repeat(3, minmax(0, 1fr));
+}
+
+.production-item {
+  display: grid;
+  height: 100%;
+  min-width: 0;
+  min-height: 0;
+  align-items: center;
+  padding: 0 calc(7px * var(--kb-scale, 1));
+  overflow: hidden;
+  background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 36%) 100%);
+  border: 1px solid rgb(255 255 255 / 74%);
+  border-radius: calc(6px * var(--kb-scale, 1));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 calc(6px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1)) rgb(63 103 171 / 7%);
+  grid-template-columns:
+    calc(18px * var(--kb-scale, 1)) minmax(0, 1fr)
+    calc(46px * var(--kb-scale, 1));
+  gap: calc(6px * var(--kb-scale, 1));
+}
+
+.production-item__rank {
+  display: inline-flex;
+  width: calc(18px * var(--kb-scale, 1));
+  height: calc(18px * var(--kb-scale, 1));
+  align-items: center;
+  justify-content: center;
+  font-size: calc(11px * var(--kb-scale, 1));
+  font-weight: 700;
+  color: #1f5bb8;
+  background: rgb(31 91 184 / 8%);
+  border: 1px solid rgb(31 91 184 / 10%);
+  border-radius: 999px;
+}
+
+.production-item__name {
+  overflow: hidden;
+  font-size: calc(12px * var(--kb-scale, 1));
+  font-weight: 600;
+  line-height: calc(16px * var(--kb-scale, 1));
+  color: #24364f;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.production-item__count {
+  display: inline-flex;
+  align-items: baseline;
+  justify-content: flex-end;
+  gap: calc(2px * var(--kb-scale, 1));
+  min-width: 0;
+}
+
+.production-item__value {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(19px * var(--kb-scale, 1));
+  font-weight: 700;
+  line-height: 1;
+  color: #e43f5f;
+  text-align: right;
+}
+
+.production-item__unit {
+  font-size: calc(12px * var(--kb-scale, 1));
+  font-weight: 700;
+  color: #24364f;
+}
+
+.exception-empty {
+  grid-column: 1 / -1;
+  place-self: center;
+  font-size: calc(14px * var(--kb-scale, 1));
+  font-weight: 600;
+  color: #6f85aa;
+}
+
+.device-exception-card {
+  display: flex;
+  height: calc(42px * var(--kb-scale, 1));
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(180deg, rgb(255 255 255 / 58%) 0%, rgb(213 227 249 / 36%) 100%);
+  border: 1px solid rgb(255 255 255 / 74%);
+  border-radius: calc(6px * var(--kb-scale, 1));
+  box-shadow:
+    inset 0 1px 0 rgb(255 255 255 / 78%),
+    0 calc(6px * var(--kb-scale, 1)) calc(14px * var(--kb-scale, 1)) rgb(63 103 171 / 7%);
+}
+
+.device-exception-card__label,
+.device-exception-card__unit {
+  font-size: calc(15px * var(--kb-scale, 1));
+  font-weight: 700;
+  color: #24364f;
+}
+
+.device-exception-card__value {
+  margin: 0 calc(6px * var(--kb-scale, 1));
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: calc(23px * var(--kb-scale, 1));
+  font-weight: 700;
+  line-height: 1;
+  color: #e43f5f;
+}
+</style>

+ 250 - 0
src/views/pms/stat/rdkb/rd-maintenance-times.vue

@@ -0,0 +1,250 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  ChartData,
+  CHART_RENDERER,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  formatMonthLabel,
+  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
+const router = useRouter()
+let chartClickBound = false
+
+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 formatCount(value: number | string) {
+  return Number(value || 0).toLocaleString('en-US')
+}
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const xAxisData = data.xAxis || []
+  const seriesData = data.series || []
+  const colorList = [THEME.color.green, THEME.color.orange]
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: 50,
+      bottom: 10
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      },
+      valueFormatter(value: number | string) {
+        return formatCount(value)
+      }
+    }),
+    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,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatMonthLabel(value)
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '次数',
+      splitNumber: 4,
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return formatCount(value)
+        }
+      },
+      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,
+        barMaxWidth: 30,
+        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) ? formatCount(params.value) : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(color),
+            shadowColor: color.shadow,
+            shadowBlur: 18
+          }
+        }
+      }
+    })
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  chart?.dispose()
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  chart.getZr().on('click', handleChartClick)
+  chartClickBound = true
+  renderChart()
+}
+
+function handleChartClick() {
+  router.push({
+    name: 'WorkOrderCompletion'
+  })
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(chartData.value), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    if (chartClickBound) {
+      chart.getZr().off('click', handleChartClick)
+      chartClickBound = false
+    }
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRdRecentWbcs()
+    chartData.value = {
+      xAxis: (res?.xAxis || []).map((item) => `${item}`),
+      series: (res?.series || []).map((item) => ({
+        name: item.name,
+        data: (item.data || []).map((value) => Number(value || 0))
+      }))
+    }
+    renderChart()
+  } catch (error) {
+    console.error('获取维保次数失败:', error)
+    chartData.value = {
+      xAxis: [],
+      series: []
+    }
+    renderChart()
+  }
+}
+
+onMounted(() => {
+  initChart()
+  loadChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <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>

+ 244 - 0
src/views/pms/stat/rdkb/rd-rate.vue

@@ -0,0 +1,244 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import dayjs from 'dayjs'
+import {
+  ANIMATION,
+  ChartData,
+  CHART_RENDERER,
+  createTooltip,
+  FONT_FAMILY,
+  formatDateLabel,
+  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
+const router = useRouter()
+let chartClickBound = false
+
+function formatRate(value: number) {
+  return `${Number(value || 0).toFixed(2)}%`
+}
+
+function getChartOption(data: ChartData): echarts.EChartsOption {
+  const names = data.xAxis || []
+  const values = (data.series?.[0]?.data || []).map((value) => Number(value || 0) * 100)
+
+  return {
+    ...ANIMATION,
+    grid: { ...THEME.grid, top: 40, right: 28, left: 32 },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      },
+      valueFormatter(value: number) {
+        return formatRate(value)
+      }
+    }),
+    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',
+      name: '设备利用率(%)',
+      min: 0,
+      max: 100,
+      splitNumber: 4,
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      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: 'line',
+        smooth: true,
+        data: values,
+        symbol: 'circle',
+        symbolSize: 8,
+        showSymbol: true,
+        lineStyle: {
+          width: 2,
+          color: THEME.color.green.line
+        },
+        itemStyle: {
+          color: THEME.color.green.line
+        },
+        areaStyle: {
+          color: THEME.color.green.bg
+        },
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: THEME.color.green.strong,
+          fontSize: 14,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return formatRate(Number(params.value))
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          scale: true
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  if (chart) {
+    chart.dispose()
+  }
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  chart.getZr().on('click', handleChartClick)
+  chartClickBound = true
+  renderChart()
+}
+
+function getChartDayRange() {
+  const xAxis = chartData.value.xAxis || []
+  const startDate = dayjs(xAxis[0])
+  const endDate = dayjs(xAxis[xAxis.length - 1])
+  if (!startDate.isValid() || !endDate.isValid()) return null
+
+  return [
+    startDate.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    endDate.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function handleChartClick() {
+  const createTime = getChartDayRange()
+  if (!createTime) return
+
+  router.push({
+    name: 'IotRhDailyReportSummary',
+    query: {
+      activeTab: '日报统计',
+      view: 'kanban',
+      createTime
+    }
+  })
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(chartData.value), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  if (chart) {
+    if (chartClickBound) {
+      chart.getZr().off('click', handleChartClick)
+      chartClickBound = false
+    }
+    chart.dispose()
+    chart = null
+  }
+}
+
+async function loadChart() {
+  try {
+    const res = await IotStatApi.getRdSevenDayUtilization()
+    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)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <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>

+ 176 - 0
src/views/pms/stat/rdkb/rd-use-status.vue

@@ -0,0 +1,176 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import { ANIMATION, CHART_RENDERER, createTooltip, FONT_FAMILY, THEME } from '@/utils/kb'
+
+interface UseStatusItem {
+  name: string
+  value: number
+}
+
+const statusData: UseStatusItem[] = [
+  { name: '动迁', value: 0 },
+  { name: '施工', value: 162 },
+  { name: '维修中', value: 4 },
+  { name: '完工', value: 4 },
+  { name: '待维修', value: 2 },
+  { name: '封存', value: 61 },
+  { name: '驻地待命', value: 6 },
+  { name: '待命', value: 16 },
+  { name: '准备', value: 0 },
+  { name: '现场待命', value: 1 },
+  { name: '待保养', value: 0 },
+  { name: '闲置', value: 19 },
+  { name: '观察使用', value: 1 },
+  { name: '报废', value: 0 }
+]
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+
+const chartData = computed(() => statusData.filter((item) => item.value > 0))
+
+function getChartLayout() {
+  const { clientWidth = 0, clientHeight = 0 } = chartRef.value ?? {}
+  const compact = clientHeight > 0 && (clientHeight < 210 || clientWidth < 520)
+
+  return {
+    pieRadius: compact ? ['38%', '58%'] : ['44%', '64%'],
+    pieCenterX: compact ? '30%' : '31%',
+    pieCenterY: compact ? '50%' : '50%',
+    legendRight: compact ? 8 : 18,
+    legendTop: compact ? 34 : 40,
+    legendItemSize: compact ? 9 : 13,
+    legendGap: compact ? 8 : 12,
+    legendFontSize: compact ? 10 : 14
+  }
+}
+
+function getChartOption(): echarts.EChartsOption {
+  const layout = getChartLayout()
+
+  return {
+    ...ANIMATION,
+    color: [
+      THEME.color.blue.line,
+      THEME.color.orange.line,
+      THEME.color.red.line,
+      THEME.color.green.line,
+      THEME.color.blue.mid,
+      '#2a97c7',
+      '#68c7dd',
+      '#8aa4ff',
+      '#78d08f',
+      '#f7c66b'
+    ],
+    tooltip: createTooltip({
+      trigger: 'item',
+      formatter(params: any) {
+        return `${params.marker}${params.name}<br/>数量:${params.value} 台<br/>占比:${params.percent}%`
+      }
+    }),
+    legend: {
+      type: 'plain',
+      orient: 'vertical',
+      top: layout.legendTop,
+      right: layout.legendRight,
+      width: '45%',
+      itemWidth: layout.legendItemSize,
+      itemHeight: layout.legendItemSize,
+      itemGap: layout.legendGap,
+      textStyle: {
+        color: THEME.text.regular,
+        fontSize: layout.legendFontSize,
+        fontWeight: 600,
+        fontFamily: FONT_FAMILY
+      },
+      formatter(name: string) {
+        const item = chartData.value.find((status) => status.name === name)
+        return item ? `${name}  ${item.value}台` : name
+      }
+    },
+    series: [
+      {
+        name: '设备使用状态',
+        type: 'pie',
+        radius: layout.pieRadius,
+        center: [layout.pieCenterX, layout.pieCenterY],
+        minAngle: 5,
+        label: { show: false },
+        data: chartData.value
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+
+  chart?.dispose()
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  renderChart()
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+  renderChart()
+}
+
+function destroyChart() {
+  chart?.dispose()
+  chart = null
+}
+
+onMounted(() => {
+  initChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel use-status-panel flex flex-col">
+    <div class="panel-title">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      设备使用状态
+    </div>
+    <div class="use-status-body">
+      <div ref="chartRef" class="use-status-chart"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.use-status-panel {
+  overflow: hidden;
+}
+
+.use-status-body {
+  display: flex;
+  min-height: 0;
+  flex: 1;
+  padding: calc(10px * var(--kb-scale, 1)) calc(16px * var(--kb-scale, 1))
+    calc(14px * var(--kb-scale, 1));
+}
+
+.use-status-chart {
+  min-height: 0;
+  flex: 1;
+}
+</style>

+ 283 - 0
src/views/pms/stat/rdkb/rd-value.vue

@@ -0,0 +1,283 @@
+<script lang="ts" setup>
+import * as echarts from 'echarts'
+import {
+  ANIMATION,
+  CHART_RENDERER,
+  createLegend,
+  createTooltip,
+  FONT_FAMILY,
+  THEME
+} from '@/utils/kb'
+
+interface ValueDistributionItem {
+  projectDept: string
+  originalValue: number
+  netValue: number
+}
+
+const chartData: ValueDistributionItem[] = [
+  {
+    projectDept: '公摊',
+    originalValue: 2760.06,
+    netValue: 622.61
+  },
+  {
+    projectDept: '新疆',
+    originalValue: 3051.49,
+    netValue: 920.62
+  },
+  {
+    projectDept: '青海',
+    originalValue: 10038.67,
+    netValue: 3388.01
+  },
+  {
+    projectDept: '东部',
+    originalValue: 6060.15,
+    netValue: 881.46
+  },
+  {
+    projectDept: '西南连油',
+    originalValue: 4059.36,
+    netValue: 743.24
+  },
+  {
+    projectDept: '西南压裂',
+    originalValue: 11557.18,
+    netValue: 4401.63
+  },
+  {
+    projectDept: '伊拉克',
+    originalValue: 15417.01,
+    netValue: 6318.24
+  },
+  {
+    projectDept: '利比亚',
+    originalValue: 1942.14,
+    netValue: 1700.17
+  }
+]
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+const BAR_WIDTH = 22
+const LABEL_FONT_SIZE = 13
+
+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 formatMoney(value: number | string) {
+  return Number(value || 0).toLocaleString('en-US', {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 2
+  })
+}
+
+function formatProjectDept(value: string) {
+  if (value.length <= 5) return value
+  return value.replace('项目部', '\n项目部').replace('折算RMB', '\n折算RMB')
+}
+
+function getChartOption(data: ValueDistributionItem[]): echarts.EChartsOption {
+  const projectDepts = data.map((item) => item.projectDept)
+
+  return {
+    ...ANIMATION,
+    grid: {
+      ...THEME.grid,
+      top: 58,
+      right: 20,
+      bottom: 4
+    },
+    tooltip: createTooltip({
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: {
+          color: THEME.split
+        }
+      },
+      valueFormatter(value: number | string) {
+        return `${formatMoney(value)} 万元`
+      }
+    }),
+    legend: createLegend(
+      {
+        top: 4,
+        itemWidth: 12,
+        itemHeight: 12
+      },
+      ['原值', '净值']
+    ),
+    xAxis: {
+      type: 'category',
+      data: projectDepts,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        interval: 0,
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: string) {
+          return formatProjectDept(value)
+        }
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '价值(万元)',
+      splitNumber: 4,
+      nameTextStyle: {
+        color: THEME.text.regular,
+        fontSize: 13,
+        fontFamily: FONT_FAMILY
+      },
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: THEME.text.regular,
+        fontSize: 12,
+        fontFamily: FONT_FAMILY,
+        formatter(value: number) {
+          return formatMoney(value)
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: THEME.split,
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        name: '原值',
+        type: 'bar',
+        data: data.map((item) => item.originalValue),
+        barWidth: BAR_WIDTH,
+        barMaxWidth: 28,
+        barGap: '18%',
+        itemStyle: getBarStyle(THEME.color.blue),
+        label: {
+          show: true,
+          position: 'top',
+          distance: 10,
+          color: THEME.color.blue.strong,
+          fontSize: LABEL_FONT_SIZE,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return Number(params.value) ? formatMoney(params.value) : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(THEME.color.blue),
+            shadowColor: THEME.color.blue.shadow,
+            shadowBlur: 18
+          }
+        }
+      },
+      {
+        name: '净值',
+        type: 'bar',
+        data: data.map((item) => item.netValue),
+        barWidth: BAR_WIDTH,
+        barMaxWidth: 28,
+        itemStyle: getBarStyle(THEME.color.green),
+        label: {
+          show: true,
+          position: 'top',
+          distance: 6,
+          color: THEME.color.green.strong,
+          fontSize: LABEL_FONT_SIZE,
+          fontWeight: 700,
+          fontFamily: FONT_FAMILY,
+          formatter(params: any) {
+            return Number(params.value) ? formatMoney(params.value) : ''
+          }
+        },
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            ...getBarStyle(THEME.color.green),
+            shadowColor: THEME.color.green.shadow,
+            shadowBlur: 18
+          }
+        }
+      }
+    ]
+  }
+}
+
+function initChart() {
+  if (!chartRef.value) return
+  chart?.dispose()
+  chart = echarts.init(chartRef.value, undefined, {
+    renderer: CHART_RENDERER
+  })
+  renderChart()
+}
+
+function renderChart() {
+  chart?.setOption(getChartOption(chartData), true)
+}
+
+function resizeChart() {
+  chart?.resize()
+}
+
+function destroyChart() {
+  chart?.dispose()
+  chart = null
+}
+
+onMounted(() => {
+  initChart()
+  window.addEventListener('resize', resizeChart)
+  window.addEventListener('rdkb:resize', resizeChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  window.removeEventListener('rdkb:resize', resizeChart)
+  destroyChart()
+})
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title">
+      <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>

+ 300 - 0
src/views/pms/stat/rdkb/rdProductionBriefs.vue

@@ -0,0 +1,300 @@
+<script lang="ts" setup>
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import dayjs from 'dayjs'
+import type { Ref } from 'vue'
+
+interface RdProductionBriefRow {
+  id?: number
+  projectName?: string
+  deptName?: string
+  rdStatusLabel?: string
+  taskName?: string
+  techniqueNames?: string
+  deviceNames?: string
+  cumulativeWorkingLayers?: number | null
+  cumulativeWorkingWell?: number | null
+  constructionBrief?: string
+  projectSort?: number | null
+  teamSort?: number | null
+}
+
+interface SpanMethodProps {
+  rowIndex: number
+  columnIndex: number
+}
+
+const TABLE_HEIGHT = 220
+const RD_DEPT_ID = 163
+const DEFAULT_DATE = dayjs().subtract(1, 'day').format('YYYY-MM-DD')
+
+const props = withDefaults(
+  defineProps<{
+    pageMode?: 'compact' | 'full'
+  }>(),
+  {
+    pageMode: 'compact'
+  }
+)
+
+const selectedDate = ref(DEFAULT_DATE)
+const loading = ref(false)
+const list = ref<RdProductionBriefRow[]>([])
+const kbScale = inject<Ref<number>>('rdKbScale', ref(1))
+const tableHeight = computed<number | string>(() =>
+  props.pageMode === 'full' ? '100%' : Math.round(TABLE_HEIGHT * kbScale.value)
+)
+
+const tableData = computed(() => {
+  return [...list.value].sort((a, b) => {
+    const projectSort = Number(a.projectSort ?? 9999) - Number(b.projectSort ?? 9999)
+    if (projectSort !== 0) return projectSort
+
+    return Number(a.teamSort ?? 9999) - Number(b.teamSort ?? 9999)
+  })
+})
+
+const projectSpanMap = computed(() =>
+  createSpanMap(tableData.value, (row) => row.projectName || '-')
+)
+
+function normalizeList(res: any): RdProductionBriefRow[] {
+  if (Array.isArray(res?.list)) return res.list
+  return []
+}
+
+function createSpanMap(
+  rows: RdProductionBriefRow[],
+  getKey: (row: RdProductionBriefRow) => string
+) {
+  const spanMap: number[] = []
+
+  rows.forEach((row, index) => {
+    const key = getKey(row)
+
+    if (index > 0 && getKey(rows[index - 1]) === key) {
+      spanMap[index] = 0
+      return
+    }
+
+    let span = 1
+    for (let nextIndex = index + 1; nextIndex < rows.length; nextIndex++) {
+      if (getKey(rows[nextIndex]) !== key) break
+      span += 1
+    }
+    spanMap[index] = span
+  })
+
+  return spanMap
+}
+
+function tableSpanMethod({ rowIndex, columnIndex }: SpanMethodProps) {
+  if (columnIndex !== 0) {
+    return {
+      rowspan: 1,
+      colspan: 1
+    }
+  }
+
+  const rowspan = projectSpanMap.value[rowIndex]
+
+  return {
+    rowspan,
+    colspan: rowspan > 0 ? 1 : 0
+  }
+}
+
+function getCreateTimeRange() {
+  const date = selectedDate.value || DEFAULT_DATE
+
+  return [
+    dayjs(date).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    dayjs(date).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function formatText(value?: string | number | null) {
+  return value === null || value === undefined || value === '' ? '-' : value
+}
+
+function handleDateChange() {
+  getList()
+}
+
+async function getList() {
+  loading.value = true
+
+  try {
+    const res = await IotRdDailyReportApi.getIotRdDailyReportPage({
+      deptId: RD_DEPT_ID,
+      createTime: getCreateTimeRange()
+    })
+    list.value = normalizeList(res)
+  } catch (error) {
+    console.error('获取瑞都生产日报失败:', error)
+    list.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div
+    class="panel device-list-panel production-brief-panel w-full flex flex-col"
+    :class="{ 'production-brief-panel--full': props.pageMode === 'full' }">
+    <div class="panel-title flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        生产日报
+      </div>
+      <div class="production-brief-panel__picker">
+        <el-date-picker
+          v-model="selectedDate"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择日期"
+          :clearable="false"
+          class="production-brief-panel__picker-input"
+          @change="handleDateChange" />
+      </div>
+    </div>
+    <div class="device-list-panel__body flex-1 min-h-0">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        :height="tableHeight"
+        :span-method="tableSpanMethod"
+        class="device-list-table production-brief-table"
+        :class="{ 'production-brief-table--full': props.pageMode === 'full' }">
+        <el-table-column prop="projectName" label="项目" min-width="150" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.projectName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="deptName" label="队伍" min-width="110" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.deptName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="rdStatusLabel" label="状态" min-width="90" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.rdStatusLabel) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="taskName" label="井号" min-width="120" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.taskName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="techniqueNames" label="工艺" min-width="130" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.techniqueNames) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="deviceNames" label="使用设备" min-width="170" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.deviceNames) }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="cumulativeWorkingLayers"
+          label="压裂层数"
+          min-width="100"
+          align="center">
+          <template #default="{ row }">
+            {{ formatText(row.cumulativeWorkingLayers) }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="cumulativeWorkingWell"
+          label="连油井数"
+          min-width="100"
+          align="center">
+          <template #default="{ row }">
+            {{ formatText(row.cumulativeWorkingWell) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="constructionBrief" label="施工简要" min-width="220" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.constructionBrief) }}
+          </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>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.device-list-panel.production-brief-panel--full {
+  height: 100%;
+  margin-top: 0;
+}
+
+.production-brief-panel__picker {
+  display: flex;
+  width: calc(120px * var(--kb-scale, 1));
+  align-items: center;
+}
+
+.production-brief-panel__picker-input {
+  width: calc(120px * var(--kb-scale, 1)) !important;
+
+  :deep(.el-input__wrapper) {
+    min-height: calc(28px * var(--kb-scale, 1));
+    padding: 0 calc(10px * var(--kb-scale, 1));
+  }
+
+  :deep(.el-input__inner) {
+    font-size: calc(12px * var(--kb-scale, 1));
+  }
+
+  :deep(.el-input__prefix-inner),
+  :deep(.el-input__suffix-inner) {
+    font-size: calc(14px * var(--kb-scale, 1));
+  }
+}
+
+.production-brief-table {
+  width: 100%;
+
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-size: calc(16px * var(--kb-scale, 1));
+    line-height: 1.2;
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    padding: calc(7px * var(--kb-scale, 1)) 0;
+    font-size: calc(14px * var(--kb-scale, 1));
+  }
+}
+
+.production-brief-table--full {
+  :deep(.el-scrollbar__view) {
+    display: block;
+    height: 100%;
+  }
+
+  :deep(.el-table__body) {
+    height: 100%;
+  }
+
+  :deep(.el-table__body tbody) {
+    height: 100%;
+  }
+}
+</style>

+ 309 - 0
src/views/pms/stat/rdkb/rdsummary.vue

@@ -0,0 +1,309 @@
+<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'
+  | 'bytodo'
+  | 'utilizationRate'
+  | 'layers'
+  | 'wells'
+  | 'inspectttodo'
+
+type CardConfig = SummaryCardConfig<CardKey>
+
+const cardConfigs: CardConfig[] = [
+  {
+    key: 'layers',
+    title: '压裂层数',
+    icon: 'i-solar:clipboard-remove-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'wells',
+    title: '连油井数',
+    icon: 'i-solar:clipboard-remove-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    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: 'bytodo',
+    title: '待巡检',
+    icon: 'i-solar:shield-warning-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  },
+  {
+    key: 'inspectttodo',
+    title: '待保养',
+    icon: 'i-solar:map-point-search-linear',
+    accent: THEME.color.orange.strong,
+    glow: THEME.color.orange.glow
+  }
+]
+
+function createDefaultCardState(): Record<CardKey, CardStateItem> {
+  return {
+    device: { value: 0, loading: true },
+    maintain: { value: 0, loading: true },
+    unfilledCount: { value: 0, loading: true },
+    bytodo: { value: 0, loading: true },
+    inspectttodo: { value: 0, loading: true },
+    utilizationRate: { value: 0, loading: true },
+    layers: { value: 0, loading: true },
+    wells: { 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
+  })
+}
+
+function getCurrentYearCreateTime() {
+  return [
+    dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss'),
+    dayjs().endOf('year').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+async function loadDeviceCard() {
+  const keys: CardKey[] = ['device']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getDeviceCount('rd')
+    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('rd')
+    setCardValue('maintain', res?.total)
+  } catch (error) {
+    console.error('获取维修工单失败:', error)
+    setCardValue('maintain', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadFillCards() {
+  const keys: CardKey[] = ['unfilledCount']
+  setCardLoading(keys, true)
+
+  const params = {
+    startTime: dayjs().subtract(7, 'day').startOf('day').valueOf(),
+    endTime: dayjs().endOf('day').valueOf(),
+    createTime: [],
+    deptId: 163,
+    status: null
+  }
+
+  try {
+    const res = await IotStatApi.getDeptStatistics(params)
+    const totalStats = res?.totalList?.[0] ?? {}
+
+    setCardValue('unfilledCount', totalStats.unfilledCount)
+  } catch (error) {
+    console.error('获取运行填写数据失败:', error)
+    setCardValue('unfilledCount', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadMaintainStatusCards() {
+  const keys: CardKey[] = ['bytodo']
+  setCardLoading(keys, true)
+
+  try {
+    const res = await IotStatApi.getMaintenanceStatus('rd')
+    setCardValue('bytodo', res?.todo)
+  } catch (error) {
+    console.error('获取保养状态失败:', error)
+    setCardValue('bytodo', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadInspectCards() {
+  const keys: CardKey[] = ['inspectttodo']
+  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, 'rd')
+    setCardValue('inspectttodo', res?.todo)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('inspectttodo', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+async function loadZjXj() {
+  const keys: CardKey[] = ['layers', 'wells']
+  setCardLoading(keys, true)
+
+  const params = {
+    createTime: getCurrentYearCreateTime()
+  }
+
+  try {
+    const res = await IotStatApi.getInspectLayerWellCount(params)
+    setCardValue('layers', res?.layers)
+    setCardValue('wells', res?.wells)
+  } catch (error) {
+    console.error('获取巡检状态失败:', error)
+    setCardValue('layers', 0)
+    setCardValue('wells', 0)
+  } finally {
+    setCardLoading(keys, false)
+  }
+}
+
+// async function loadRate() {
+//   const keys: CardKey[] = ['utilizationRate']
+//   setCardLoading(keys, true)
+
+//   const params = {
+//     createTime: getCurrentYearCreateTime()
+//   }
+
+//   try {
+//     const res = await IotStatApi.getInspectRate(params)
+//     setCardValue('utilizationRate', (res * 100).toFixed(2))
+//   } catch (error) {
+//     console.error('获取累计设备利用率失败:', error)
+//     setCardValue('utilizationRate', 0)
+//   } finally {
+//     setCardLoading(keys, false)
+//   }
+// }
+
+function loadAllCards() {
+  loadDeviceCard()
+  loadMaintainCard()
+  loadFillCards()
+  loadMaintainStatusCards()
+  loadInspectCards()
+  loadZjXj()
+  // loadRate()
+}
+
+onMounted(() => {
+  loadAllCards()
+})
+</script>
+
+<template>
+  <div class="panel summary-panel w-full flex flex-col">
+    <div class="panel-title summary-panel__title">
+      <div class="icon-decorator">
+        <span></span>
+        <span></span>
+      </div>
+      运行概况
+    </div>
+
+    <div class="summary-panel__grid grid grid-cols-7 flex-1">
+      <article
+        v-for="card in summaryCards"
+        :key="card.key"
+        class="summary-card relative flex h-full overflow-hidden rounded-md items-center"
+        :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>

+ 2009 - 0
src/views/pms/stat/rdkb1.vue

@@ -0,0 +1,2009 @@
+<template>
+  <div class="min-h-screen p-4 bg-[#3a6fa3] flex flex-col">
+    <el-row :gutter="16" class="summary">
+      <!-- 原有的统计卡片部分保持不变 -->
+      <el-col v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <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 v-loading="loading" :sm="3" :xs="24">
+        <SummaryCard
+          :value="inspect.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 v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="inspect.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" :xs="24">
+        <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
+            style="
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              min-height: 290px;
+              flex-direction: column;
+              gap: 6px;
+            "
+          >
+            <div ref="statusChartRef" style="width: 100%; height: 170px; max-width: 200px"></div>
+            <div class="text-[12px] h-[100px] w-[90%] flex flex-col justify-between items-center">
+              <div v-for="item in legendData" :key="item.name" class="flex">
+                <div class="flex items-center gap-1">
+                  <span class="status-legend-color" :style="{ background: item.color }"></span>
+                  <span class="w-[100px] text-[#fff]">{{ item.name }}</span>
+                </div>
+                <div class="flex items-center">
+                  <span class="text-[#fff] text-right w-12">{{ item.value }} 台</span>
+                  <span class="status-legend-percent text-right w-14">{{ item.percent }}%</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="10" :xs="24">
+        <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"
+                >设备分布及价值(国内RMB)</span
+              >
+            </div>
+          </template>
+          <div
+            style="
+              display: flex;
+              min-height: 295px;
+              flex-direction: column;
+              align-items: center;
+              gap: 8px;
+            "
+          >
+            <div ref="domesticChartRef" style="width: 100%; height: 200px; max-width: 420px"></div>
+            <div
+              class="domestic-legend"
+              style="
+                display: flex;
+                width: 100%;
+                max-width: 520px;
+                font-size: 12px;
+                flex-wrap: wrap;
+                gap: 1px;
+              "
+            >
+              <div
+                v-for="(item, idx) in domesticData"
+                :key="item.dept"
+                class="legend-item"
+                style="display: flex; align-items: center; gap: 8px; padding: 4px 8px"
+              >
+                <span
+                  class="legend-dot"
+                  :style="{
+                    background: statusColors[idx % statusColors.length],
+                    width: '10px',
+                    height: '10px',
+                    display: 'inline-block',
+                    borderRadius: '50%'
+                  }"
+                ></span>
+                <span
+                  class="legend-name"
+                  style="
+                    display: inline-block;
+                    max-width: 150px;
+                    overflow: hidden;
+                    color: #fff;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                  "
+                  >{{ item.dept }}</span
+                >
+              </div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="8" :xs="24">
+        <WorkloadChart />
+        <!-- <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-[290px]"></div>
+        </el-card> -->
+      </el-col>
+    </el-row>
+    <el-row :gutter="16" class="mb-4">
+      <el-col class="mb-4" :span="12" :xs="24">
+        <AvailabilityChart />
+      </el-col>
+      <el-col class="mb-4" :span="12" :xs="24">
+        <ExceptionChart />
+      </el-col>
+    </el-row>
+    <el-row :gutter="16" class="mb-4">
+      <el-col :span="12" :xs="24">
+        <UtilizationChart />
+      </el-col>
+      <!-- <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 class="table-container">
+            <div ref="utilizationRef" style="width: 100%; height: 360px"></div>
+          </div>
+        </el-card> -->
+      <!-- 添加两个卡片 -->
+      <!--          <div class="flex justify-between mb-2">-->
+      <!--            <el-card class="stat-card">-->
+      <!--              <div class="flex flex-row justify-evenly">-->
+      <!--                <div>-->
+      <!--                  <Icon icon="fa-solid:award" size="30" color="blue" />-->
+      <!--                </div>-->
+      <!--                <div class="flex flex-col items-center">-->
+      <!--                  <span class="text-sm " style="color: #101010">{{t('stat.spareCount')}}</span>-->
+      <!--                  <span class="text-lg font-bold">{{ totalMaterialCount }}</span>-->
+      <!--                </div>-->
+      <!--              </div>-->
+      <!--            </el-card>-->
+      <!--            <el-card class="stat-card">-->
+      <!--              <div class="flex flex-row justify-evenly">-->
+      <!--                <div>-->
+      <!--                  <Icon icon="fa-solid:yen-sign" size="30" color="orange" />-->
+      <!--                </div>-->
+      <!--                <div class="flex flex-col items-center">-->
+      <!--                  <span class="text-sm " style="color: #101010">{{t('stat.spareAmount')}}</span>-->
+      <!--                  <span class="text-lg font-bold">{{ totalMaterialCost }}</span>-->
+      <!--                </div>-->
+      <!--              </div>-->
+      <!--            </el-card>-->
+      <!--          </div>-->
+      <!--          <div ref="sparePartRef" class="h-[330px]"></div>-->
+      <!-- 月度工作量表 -->
+      <el-col :span="12" :xs="24">
+        <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>
+          </template>
+          <div class="overflow-auto">
+            <el-table
+              :data="monthData"
+              border
+              height="420px"
+              :disabled-affix="true"
+              style="width: 100%"
+              :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
+              :cell-style="{ color: '#fff' }"
+              :summary-method="tableSummary"
+              show-summary
+            >
+              <el-table-column prop="month" label="月份" width="50" fixed="left" align="center" />
+
+              <el-table-column label="2025年" align="center">
+                <el-table-column
+                  prop="y2025_fractures"
+                  label="压裂井数"
+                  align="center"
+                  min-width="50"
+                />
+                <el-table-column
+                  prop="y2025_layers"
+                  label="压裂层数"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="y2025_pump_trips"
+                  label="泵车台次"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="y2025_oil_wells"
+                  label="连油井数"
+                  min-width="50"
+                  align="center"
+                />
+              </el-table-column>
+
+              <el-table-column label="2024年" align="center">
+                <el-table-column
+                  prop="y2024_fractures"
+                  label="压裂井数"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="y2024_layers"
+                  label="压裂层数"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="y2024_pump_trips"
+                  label="泵车台次"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="y2024_oil_wells"
+                  label="连油井数"
+                  min-width="50"
+                  align="center"
+                />
+              </el-table-column>
+
+              <el-table-column label="同比增长量" align="center">
+                <el-table-column
+                  prop="diff_fractures"
+                  label="压裂井数"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="diff_layers"
+                  label="压裂层数"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="diff_pump_trips"
+                  label="泵车台次"
+                  min-width="50"
+                  align="center"
+                />
+                <el-table-column
+                  prop="diff_oil_wells"
+                  label="连油井数"
+                  min-width="50"
+                  align="center"
+                />
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-card>
+      </el-col>
+      <!-- 月度工作量表结束 -->
+    </el-row>
+    <ConstructionBriefing />
+    <!-- <el-row :gutter="16" class="mb-4">
+      <el-col :span="24" :xs="24">
+
+      </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': '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>
+
+  <el-dialog v-model="projectDataDialog" width="45vw" class="custom-scroll-dialog">
+    <div class="dialog-scroll-content">
+      <el-card class="bg-[#284d72]!" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium" style="color: #b6c8da"
+              >国内设备分布及价值(国内RMB)</span
+            >
+          </div>
+        </template>
+        <div class="overflow-auto" style="min-height: 260px">
+          <el-table
+            :data="domesticData"
+            border
+            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
+            :cell-style="{ color: '#fff' }"
+            style="width: 100%"
+            :summary-method="tableSummary"
+            show-summary
+          >
+            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
+            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
+            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
+            <el-table-column
+              prop="orig_value"
+              label="原值(万元)"
+              min-width="120"
+              align="center"
+            />
+            <el-table-column prop="net_value" label="净值(万元)" min-width="120" align="center" />
+            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
+          </el-table>
+        </div>
+      </el-card>
+    </div>
+  </el-dialog>
+  <el-dialog v-model="projectDataDialog2" width="45vw" class="custom-scroll-dialog">
+    <div class="dialog-scroll-content">
+      <el-card class="bg-[#284d72]!" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium" style="color: #b6c8da"
+              >伊拉克设备分布及价值(美元)</span
+            >
+          </div>
+        </template>
+        <div class="overflow-auto" style="min-height: 260px">
+          <el-table
+            :data="iraqTableData"
+            border
+            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
+            :cell-style="{ color: '#fff' }"
+            style="width: 100%"
+          >
+            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
+            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
+            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
+            <el-table-column
+              prop="orig_value_usd"
+              label="原值(万元)"
+              min-width="120"
+              align="center"
+            />
+            <el-table-column
+              prop="net_value_usd"
+              label="净值(万元)"
+              min-width="120"
+              align="center"
+            />
+            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
+          </el-table>
+        </div>
+      </el-card>
+    </div>
+  </el-dialog>
+
+  <el-dialog v-model="projectDataDialog3" width="45vw" class="custom-scroll-dialog">
+    <div class="dialog-scroll-content">
+      <el-card class="bg-[#284d72]!" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium" style="color: #b6c8da"
+              >利比亚设备分布及价值(第纳尔)</span
+            >
+          </div>
+        </template>
+        <div class="overflow-auto" style="min-height: 260px">
+          <el-table
+            :data="libyaTableData"
+            border
+            :header-cell-style="{ color: '#fff', 'background-color': 'transparent' }"
+            :cell-style="{ color: '#fff' }"
+            :row-class-name="tableRowClass"
+            style="width: 100%"
+            :summary-method="tableSummary"
+          >
+            <el-table-column prop="index" label="序号" width="60" align="center" fixed="left" />
+            <el-table-column prop="dept" label="项目部" width="120" align="center" fixed="left" />
+            <el-table-column prop="count" label="设备数量" min-width="100" align="center" />
+            <el-table-column
+              prop="orig_value"
+              label="原值(万元)"
+              min-width="120"
+              align="center"
+            />
+            <el-table-column prop="net_value" label="净值(万元)" min-width="120" align="center" />
+            <el-table-column prop="orig_ratio" label="原值占比" min-width="100" align="center" />
+          </el-table>
+        </div>
+      </el-card>
+    </div>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { MemberSummaryRespVO } from '@/api/mall/statistics/member'
+import SummaryCard from '@/components/SummaryCard/index.vue'
+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 { IotStatApi } from '@/api/pms/stat'
+import { ref, onMounted, computed, watch, nextTick, reactive } from 'vue'
+import { useLocaleStore } from '@/store/modules/locale'
+import WorkloadChart from './rdkb/workload.vue'
+import UtilizationChart from './rdkb/utilization.vue'
+import AvailabilityChart from './rdkb/availability.vue'
+import ExceptionChart from './rdkb/exception.vue'
+import ConstructionBriefing from './rdkb/constructionBriefing.vue'
+
+/** 会员统计 */
+defineOptions({ name: 'IotRdStat' })
+
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+
+const loading = ref(true) // 加载中
+const teamDialogVisible = ref(false)
+const teamTableData = ref([])
+const currentDeptId = ref('')
+const currentDateRange = ref([])
+const materialTableData = ref([])
+const handleDateChange = () => {
+  IotStatApi.getRdRate(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 formatNumber = (v) => {
+  if (v == null || v === '') return '-'
+  const n = Number(v)
+  if (isNaN(n)) return String(v)
+  return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 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.getRdTeamRate(teamParams).then((res) => {
+    teamTableData.value = res
+  })
+}
+const dateRange = ref<[Date, Date] | null>(null)
+const summary = ref<MemberSummaryRespVO>() // 会员统计数据
+const statusChartRef = ref() // 设备数量统计的图表
+let statusChartInstance = null
+const qxRef = ref(null)
+let qxInstance = null
+const sparePartRef = ref(null)
+let sparePartInstance = null
+const maintenanceChartRef = ref(null)
+let maintenanceChartInstance = null
+const inspectChartRef = ref(null)
+let inspectChartInstance = null
+const inspectionChartRef = ref(null)
+let inspectionChartInstance = null
+const { t } = useI18n() // 国际化
+const maintenanceChartRef1 = ref(null)
+
+const typeData = ref([])
+// 配色(与图表保持一致)
+const statusColors = ['#2ed3df', '#34d399', '#ff6b95', '#4aa3ff', '#f59e0b', '#ef4444', '#7dd3fc']
+
+/**
+ * 将后端返回的多种可能结构规范化为数组形式:
+ * - 如果已经是数组则直接使用
+ * - 如果包含 data 或 series 字段且为数组则优先使用
+ * - 否则把对象的键值对转换为 {name, value} 数组
+ */
+const normalizeTypeData = (val) => {
+  if (Array.isArray(val)) return val
+  if (!val || typeof val !== 'object') return []
+  if (Array.isArray(val.data)) return val.data
+  if (Array.isArray(val.series)) return val.series
+  // plain object with keys -> map to array
+  return Object.keys(val).map((k) => ({ name: k, value: val[k] }))
+}
+
+const legendData = computed(() => {
+  const arr = normalizeTypeData(typeData.value)
+  const total = arr.reduce((s, it) => s + (Number(it.value) || 0), 0)
+  return arr.map((it, idx) => ({
+    name: it.name,
+    value: it.value || 0,
+    percent: total ? ((Number(it.value) / total) * 100).toFixed(2) : '0.00',
+    color: statusColors[idx % statusColors.length]
+  }))
+})
+const orderSevenData = ref({})
+const device = ref({
+  total: undefined,
+  today: undefined
+})
+const maintain = ref({
+  total: undefined,
+  today: undefined
+})
+const by = ref({
+  todo: undefined,
+  finished: undefined
+})
+const fill = ref({
+  filledCount: undefined,
+  unfilledCount: undefined
+})
+const abnormalDevice = ref({
+  total: undefined,
+  today: undefined
+})
+const outliers = ref({
+  total: undefined,
+  today: undefined
+})
+const inspect = ref({
+  finished: 0,
+  todo: 0
+})
+const sparePartData = ref({
+  xAxis: ['扳手', '水杯', '皮带', '螺丝'],
+  series: [
+    {
+      name: '数量',
+      type: 'bar',
+      data: [10, 20, 15, 25],
+      yAxisIndex: 0
+    },
+    {
+      name: '金额',
+      type: 'line',
+      data: [100, 200, 150, 250],
+      yAxisIndex: 1
+    }
+  ]
+})
+const sparePartDataEng = ref({
+  xAxis: ['spanner', 'cup', 'belt', 'screw'],
+  series: [
+    {
+      name: 'count',
+      type: 'bar',
+      data: [10, 20, 15, 25],
+      yAxisIndex: 0
+    },
+    {
+      name: 'amount',
+      type: 'line',
+      data: [100, 200, 150, 250],
+      yAxisIndex: 1
+    }
+  ]
+})
+// 模拟巡检工单数据
+const totalInspectionOrders = ref(80)
+const completedInspectionOrders = ref(60)
+
+// 计算物料消耗数量及费用
+const totalMaterialCount = computed(() => {
+  const quantitySeries = sparePartData.value.series.find((item) => item.name === '数量')
+  return quantitySeries ? quantitySeries.data.reduce((sum, val) => sum + val, 0) : 0
+})
+
+const totalMaterialCost = computed(() => {
+  const costSeries = sparePartData.value.series.find((item) => item.name === '金额')
+  return costSeries ? costSeries.data.reduce((sum, val) => sum + val, 0) : 0
+})
+
+const getStats = () => {
+  IotStatApi.getDeviceStatusCount('rd').then((res) => {
+    typeData.value = res
+    initDeviceStatusCharts()
+  })
+  IotStatApi.getOrderSeven('rd').then((res) => {
+    orderSevenData.value = res
+    initQxChart()
+  })
+  IotStatApi.getDeviceCount('rd').then((res) => {
+    device.value = res
+  })
+  IotStatApi.getMaintainCount('rd').then((res) => {
+    maintain.value = res
+  })
+  IotStatApi.getMaintenanceStatus('rd').then((res) => {
+    by.value = res
+    initMaintenanceChart()
+  })
+  const fillQueryParams = reactive({
+    startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+    endTime: Date.now(), // 设置默认结束时间为当前时间
+    createTime: [],
+    deptId: null, // 选中的部门ID
+    status: null // 填写状态
+  })
+  IotStatApi.getInspectStatuss(fillQueryParams, 'rd').then((res) => {
+    inspect.value = res
+    initInspectChart()
+  })
+  fillQueryParams.deptId = '163'
+  IotStatApi.getDeptStatistics(fillQueryParams).then((res) => {
+    fill.value = res.totalList[0] || []
+  })
+  const localeStore = useLocaleStore()
+  const lang = localeStore.getCurrentLocale.lang
+  if (lang === 'zh-CN') {
+    initSparePartChart('数量', '金额', sparePartData)
+  } else if (lang === 'en') {
+    initSparePartChart('count', 'amount', sparePartDataEng)
+  }
+  initInspectionChart()
+
+  // 计算近一周时间
+  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(firstDay.getTime())
+
+  // 格式化日期为后端需要的格式
+  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.getRdRate(rateQueryParams).then((res) => {
+    materialTableData.value = res
+  })
+}
+
+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: {
+          formatter: '{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}'
+        },
+        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) => {
+      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 initSparePartChart = (count: any, amount: any, spare: {}) => {
+  if (!sparePartRef.value) return
+  sparePartInstance = echarts.init(sparePartRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        crossStyle: {
+          color: '#999'
+        }
+      }
+    },
+    legend: {
+      data: [count, amount],
+      textStyle: {
+        color: '#fff'
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: spare.value.xAxis,
+      axisLabel: {
+        color: '#B6C8DA'
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#B6C8DA' // X轴线白色半透明
+        }
+      }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: count,
+        position: 'left',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        }
+      },
+      {
+        type: 'value',
+        name: amount,
+        position: 'right',
+        axisLabel: {
+          color: '#B6C8DA',
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: true, // 显示水平网格线(默认显示)
+          lineStyle: {
+            // 水平网格线颜色(可设置为纯色或带透明度的颜色)
+            color: '#457794', // 浅灰色半透明
+            // 可选:设置线条类型(实线/虚线/点线)
+            type: 'dashed'
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#B6C8DA'
+          }
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ],
+    series: spare.value.series.map((item, index) => ({
+      name: item.name,
+      type: item.type,
+      data: item.data,
+      yAxisIndex: item.yAxisIndex,
+      itemStyle: {
+        color: ['#6084fb', '#5aef13'][index]
+      }
+    }))
+  }
+
+  sparePartInstance.setOption(option)
+}
+
+// 设备利用率柱状图
+const utilizationRef = ref(null)
+let utilizationInstance: any = null
+const lastHoveredIndex = ref<number | null>(null)
+
+const initUtilizationChart = () => {
+  if (!utilizationRef.value) return
+  try {
+    if (utilizationInstance) {
+      utilizationInstance.dispose()
+      utilizationInstance = null
+    }
+  } catch (e) {}
+  utilizationInstance = echarts.init(utilizationRef.value)
+  const xData = (materialTableData.value as any[]).map(
+    (it) => it.projectDeptName || it.dept || it.projectDept
+  )
+  const seriesData = (materialTableData.value as any[]).map((it) => {
+    const v = (it as any).utilizationRate
+    // 只保留整数部分(百分比的整数),例如 0.528 -> 52
+    return v == null ? 0 : Math.trunc(Number(v) * 100)
+  })
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      formatter: function (params) {
+        const p = params[0] || params
+        return `${p.name}: ${p.data}%`
+      }
+    },
+    xAxis: {
+      type: 'category',
+      data: xData,
+      axisLabel: { color: '#B6C8DA', rotate: 20 }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { formatter: '{value}%', color: '#B6C8DA' }
+    },
+    grid: { left: '3%', right: '4%', bottom: '12%', containLabel: true },
+    series: [
+      {
+        type: 'bar',
+        data: seriesData,
+        itemStyle: { color: statusColors[1] },
+        barWidth: '40%',
+        label: { show: true, position: 'top', formatter: '{c}%' }
+      }
+    ]
+  }
+  utilizationInstance.setOption(option)
+  // 鼠标悬停时展示队伍详情弹窗
+  try {
+    // 移除旧的监听,防止重复绑定
+    if (utilizationInstance.off) utilizationInstance.off('mouseover')
+    utilizationInstance.on('mouseover', (params: any) => {
+      try {
+        if (!params) return
+        // 仅处理柱状图系列的悬停
+        if (params.componentType !== 'series' || params.seriesType !== 'bar') return
+        const idx = params.dataIndex
+        if (idx == null) return
+        // 防止重复请求
+        if (lastHoveredIndex.value === idx && teamDialogVisible.value) return
+        lastHoveredIndex.value = idx
+        const row = ((materialTableData.value as any[])[idx] || {}) as any
+        const teamParams: any = {
+          deptId: row.projectDeptId || row.deptId || row.dept || row.projectDept,
+          createTime: rateQueryParams.createTime
+        }
+        teamDialogVisible.value = true
+        teamTableData.value = []
+        IotStatApi.getRdTeamRate(teamParams).then((res) => {
+          teamTableData.value = res
+        })
+      } catch (e) {}
+    })
+  } catch (e) {}
+
+  window.addEventListener('resize', () => {
+    try {
+      utilizationInstance && utilizationInstance.resize()
+    } catch (e) {}
+  })
+}
+
+watch(
+  materialTableData,
+  () => {
+    nextTick(() => initUtilizationChart())
+  },
+  { deep: true }
+)
+
+onMounted(() => {
+  initUtilizationChart()
+})
+
+onUnmounted(() => {
+  try {
+    if (utilizationInstance) {
+      try {
+        if (utilizationInstance.off) utilizationInstance.off('mouseover')
+      } catch (e) {}
+      utilizationInstance.dispose()
+      utilizationInstance = null
+    }
+  } catch (e) {}
+})
+
+const initInspectChart = () => {
+  if (!inspectChartRef.value) return
+  inspectChartInstance = echarts.init(inspectChartRef.value)
+  const completionRate =
+    (inspect.value.finished / (inspect.value.finished + inspect.value.todo)) * 100
+  // debugger
+  const option = {
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['40%', '70%'],
+        label: {
+          show: false,
+          position: 'outside'
+        },
+        emphasis: {
+          label: {
+            color: '#B6C8DA',
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: true
+        },
+        // itemStyle: {
+        //   color: ['#e4a317', '#5aef13']
+        // },
+        data: [
+          { name: '完成率', value: completionRate.toFixed(2) },
+          { name: '未完成率', value: 100 - completionRate.toFixed(2) }
+        ]
+      }
+    ]
+  }
+  inspectChartInstance.setOption(option)
+}
+
+const initMaintenanceChart = () => {
+  if (!maintenanceChartRef.value) return
+  maintenanceChartInstance = echarts.init(maintenanceChartRef.value)
+  const completionRate = (by.value.finished / (by.value.finished + by.value.todo)) * 100
+  // debugger
+  const option = {
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal', // 水平排列图例项
+      bottom: '0%', // 放置在底部
+      icon: 'circle',
+      textStyle: {
+        color: '#B6C8DA'
+      }
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['40%', '70%'],
+        label: {
+          show: false,
+          position: 'outside'
+        },
+        emphasis: {
+          label: {
+            color: '#B6C8DA',
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: true
+        },
+        data: [
+          { name: '完成率', value: completionRate.toFixed(2) },
+          { name: '未完成率', value: 100 - completionRate.toFixed(2) }
+        ]
+      }
+    ]
+  }
+  maintenanceChartInstance.setOption(option)
+}
+
+const initInspectionChart = () => {
+  if (!inspectionChartRef.value) return
+  inspectionChartInstance = echarts.init(inspectionChartRef.value)
+  const completionRate = (completedInspectionOrders.value / totalInspectionOrders.value) * 100
+  const option = {
+    series: [
+      {
+        type: 'pie',
+        radius: ['50%', '80%'],
+        data: [
+          { value: completionRate, name: '完成率' },
+          { value: 100 - completionRate, name: '未完成率' }
+        ]
+      }
+    ]
+  }
+  inspectionChartInstance.setOption(option)
+}
+
+/** 初始化图表 */
+const initDeviceStatusCharts = () => {
+  if (!statusChartRef.value) return
+  // dispose old instance
+  try {
+    if (statusChartInstance) {
+      statusChartInstance.dispose()
+      statusChartInstance = null
+    }
+  } catch (e) {
+    // ignore
+  }
+  statusChartInstance = echarts.init(statusChartRef.value)
+  const data = normalizeTypeData(typeData.value)
+  const option = {
+    color: statusColors,
+    tooltip: {
+      trigger: 'item',
+      formatter: '{b}: {c} ({d}%)'
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['38%', '58%'],
+        center: ['50%', '30%'],
+        avoidLabelOverlap: false,
+        label: { show: false },
+        labelLine: { show: false },
+        data: data.map((it) => ({ name: it.name, value: it.value }))
+      }
+    ]
+  }
+  statusChartInstance.setOption(option)
+}
+
+// 月度工作量表数据
+const monthData = ref<any[]>([
+  {
+    month: '1月',
+    y2025_fractures: 49,
+    y2025_layers: 102,
+    y2025_pump_trips: 621,
+    y2025_oil_wells: 17,
+    y2024_fractures: 23,
+    y2024_layers: 42,
+    y2024_pump_trips: 347,
+    y2024_oil_wells: 9
+  },
+  {
+    month: '2月',
+    y2025_fractures: 30,
+    y2025_layers: 52,
+    y2025_pump_trips: 438,
+    y2025_oil_wells: 15,
+    y2024_fractures: 14,
+    y2024_layers: 41,
+    y2024_pump_trips: 46,
+    y2024_oil_wells: 11
+  },
+  {
+    month: '3月',
+    y2025_fractures: 48,
+    y2025_layers: 127,
+    y2025_pump_trips: 326,
+    y2025_oil_wells: 30,
+    y2024_fractures: 42,
+    y2024_layers: 58,
+    y2024_pump_trips: 321,
+    y2024_oil_wells: 17
+  },
+  {
+    month: '4月',
+    y2025_fractures: 49,
+    y2025_layers: 130,
+    y2025_pump_trips: 1203,
+    y2025_oil_wells: 45,
+    y2024_fractures: 32,
+    y2024_layers: 184,
+    y2024_pump_trips: 1046,
+    y2024_oil_wells: 30
+  },
+  {
+    month: '5月',
+    y2025_fractures: 13,
+    y2025_layers: 76,
+    y2025_pump_trips: 701,
+    y2025_oil_wells: 18,
+    y2024_fractures: 73,
+    y2024_layers: 133,
+    y2024_pump_trips: 414,
+    y2024_oil_wells: 24
+  },
+  {
+    month: '6月',
+    y2025_fractures: 34,
+    y2025_layers: 187,
+    y2025_pump_trips: 831,
+    y2025_oil_wells: 30,
+    y2024_fractures: 29,
+    y2024_layers: 113,
+    y2024_pump_trips: 750,
+    y2024_oil_wells: 35
+  },
+  {
+    month: '7月',
+    y2025_fractures: 30,
+    y2025_layers: 230,
+    y2025_pump_trips: 1157,
+    y2025_oil_wells: 51,
+    y2024_fractures: 24,
+    y2024_layers: 47,
+    y2024_pump_trips: 326,
+    y2024_oil_wells: 17
+  },
+  {
+    month: '8月',
+    y2025_fractures: 59,
+    y2025_layers: 218,
+    y2025_pump_trips: 1163,
+    y2025_oil_wells: 12,
+    y2024_fractures: 36,
+    y2024_layers: 121,
+    y2024_pump_trips: 1107,
+    y2024_oil_wells: 26
+  },
+  {
+    month: '9月',
+    y2025_fractures: 48,
+    y2025_layers: 80,
+    y2025_pump_trips: 440,
+    y2025_oil_wells: 39,
+    y2024_fractures: 35,
+    y2024_layers: 162,
+    y2024_pump_trips: 928,
+    y2024_oil_wells: 36
+  },
+  {
+    month: '10月',
+    y2025_fractures: 45,
+    y2025_layers: 99,
+    y2025_pump_trips: 697,
+    y2025_oil_wells: 49,
+    y2024_fractures: 36,
+    y2024_layers: 116,
+    y2024_pump_trips: 891,
+    y2024_oil_wells: 33
+  },
+  {
+    month: '11月',
+    y2025_fractures: null,
+    y2025_layers: null,
+    y2025_pump_trips: null,
+    y2025_oil_wells: null,
+    y2024_fractures: 19,
+    y2024_layers: 102,
+    y2024_pump_trips: 582,
+    y2024_oil_wells: 36
+  },
+  {
+    month: '12月',
+    y2025_fractures: null,
+    y2025_layers: null,
+    y2025_pump_trips: null,
+    y2025_oil_wells: null,
+    y2024_fractures: 51,
+    y2024_layers: 186,
+    y2024_pump_trips: 971,
+    y2024_oil_wells: 32
+  }
+])
+
+// 计算同比差值(2025 - 2024),保留 null 的情况
+monthData.value.forEach((row) => {
+  const f2025 = row.y2025_fractures
+  const f2024 = row.y2024_fractures
+  row.diff_fractures = f2025 == null || f2024 == null ? null : f2025 - f2024
+
+  const l2025 = row.y2025_layers
+  const l2024 = row.y2024_layers
+  row.diff_layers = l2025 == null || l2024 == null ? null : l2025 - l2024
+
+  const p2025 = row.y2025_pump_trips
+  const p2024 = row.y2024_pump_trips
+  row.diff_pump_trips = p2025 == null || p2024 == null ? null : p2025 - p2024
+
+  const o2025 = row.y2025_oil_wells
+  const o2024 = row.y2024_oil_wells
+  row.diff_oil_wells = o2025 == null || o2024 == null ? null : o2025 - o2024
+})
+
+// 表格合计方法(稳健实现,避免把图表配置或数据插入此处)
+const tableSummary = ({ columns, data }: any) => {
+  const sums: any[] = []
+  columns.forEach((column: any, index: number) => {
+    if (index === 0) {
+      sums[index] = '合计'
+      return
+    }
+    const property = column.property
+    if (!property) {
+      sums[index] = ''
+      return
+    }
+    let total = 0
+    let hasValue = false
+    data.forEach((row: any) => {
+      const val = row[property]
+      if (val != null && !isNaN(Number(val))) {
+        total += Number(val)
+        hasValue = true
+      }
+    })
+    // 对原值占比列(orig_ratio)在国内表中不显示合计,留空
+    if (data === domesticData.value && property === 'orig_ratio') {
+      sums[index] = ''
+    } else {
+      sums[index] = hasValue ? total : ''
+    }
+  })
+  return sums
+}
+
+let projectDataDialog = ref(false)
+let projectDataDialog2 = ref(false)
+let projectDataDialog3 = ref(false)
+
+// projectDataRowClick
+const projectDataRowClick = (row, column, event) => {
+  console.log('点击的行数据:', row)
+
+  if (row.dept === '国内') {
+    // 显示国内表格
+    projectDataDialog.value = true
+  } else if (row.dept === '伊拉克') {
+    // 显示伊拉克表格
+    projectDataDialog2.value = true
+  } else if (row.dept === '利比亚') {
+    // 显示利比亚表格
+    projectDataDialog3.value = true
+  }
+}
+
+const domesticData = ref<any[]>([
+  {
+    index: 1,
+    dept: '公摊',
+    count: 13,
+    orig_value: 1506.88,
+    net_value: 559.95,
+    orig_ratio: '2.77%'
+  },
+  {
+    index: 2,
+    dept: '新疆项目部',
+    count: 58,
+    orig_value: 5118.1,
+    net_value: 1182.49,
+    orig_ratio: '9.40%'
+  },
+  {
+    index: 3,
+    dept: '青海项目',
+    count: 33,
+    orig_value: 7004.23,
+    net_value: 1758.8,
+    orig_ratio: '12.86%'
+  },
+  {
+    index: 4,
+    dept: '东部项目部',
+    count: 49,
+    orig_value: 5273.54,
+    net_value: 683.58,
+    orig_ratio: '9.68%'
+  },
+  {
+    index: 5,
+    dept: '西南连油项目部',
+    count: 24,
+    orig_value: 4059.36,
+    net_value: 743.24,
+    orig_ratio: '7.45%'
+  },
+  {
+    index: 6,
+    dept: '西南压裂项目部',
+    count: 47,
+    orig_value: 14591.62,
+    net_value: 6030.84,
+    orig_ratio: '26.79%'
+  },
+  {
+    index: 7,
+    dept: '伊拉克 哈法亚连油',
+    count: 120,
+    orig_value: 694.78,
+    net_value: 91.26,
+    orig_ratio: '9.07%'
+  },
+  {
+    index: 8,
+    dept: '伊拉克 哈法亚压裂',
+    count: 132,
+    orig_value: 1008.92,
+    net_value: 575.91,
+    orig_ratio: '13.17%%'
+  },
+  {
+    index: 9,
+    dept: '伊拉克 B9增产',
+    count: 27,
+    orig_value: 304.72,
+    net_value: 124.1,
+    orig_ratio: '3.98%'
+  },
+  {
+    index: 10,
+    dept: '利比亚连油8队',
+    count: 22,
+    orig_value: 2025.52,
+    net_value: 1731.79,
+    orig_ratio: '4.85%'
+  }
+])
+
+// 初始化 国内设备分布饼图相关(放在 domesticData 声明后)
+const domesticChartRef = ref(null)
+let domesticInstance: any = null
+
+const handleDomesticResize = () => {
+  try {
+    if (domesticInstance) domesticInstance.resize()
+  } catch (e) {}
+}
+
+const initDomesticChart = () => {
+  if (!domesticChartRef.value) return
+  try {
+    if (domesticInstance) {
+      domesticInstance.dispose()
+      domesticInstance = null
+    }
+  } catch (e) {}
+  domesticInstance = echarts.init(domesticChartRef.value)
+  const data = domesticData.value.map((it) => ({
+    name: it.dept,
+    value: Number(it.orig_value) || 0,
+    orig_ratio: it.orig_ratio || ''
+  }))
+  const option = {
+    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
+    legend: { show: false },
+    color: statusColors,
+    series: [
+      {
+        name: '国内设备价值',
+        type: 'pie',
+        // 使用实心扇形(非环形)
+        radius: ['0%', '70%'],
+        center: ['50%', '50%'],
+        avoidLabelOverlap: false,
+        label: {
+          show: true,
+          position: 'outside',
+          formatter: function (params) {
+            const val = Number(params.value) || 0
+            const formatted = val.toLocaleString(undefined, {
+              minimumFractionDigits: 2,
+              maximumFractionDigits: 2
+            })
+            // 优先使用后端提供的 orig_ratio 字段(如 "22.87%"),若不存在则退回计算值
+            const ratio =
+              params.data && params.data.orig_ratio
+                ? params.data.orig_ratio
+                : `${Math.round(params.percent || 0)}%`
+            return `${formatted}, ${ratio}`
+          },
+          color: '#fff',
+          fontSize: 12
+        },
+        labelLine: { length: 18, length2: 6 },
+        data,
+        color: [
+          '#5b9bd5',
+          '#ed7d31',
+          '#a5a5a5',
+          '#ffc000',
+          '#4472c4',
+          '#70ad47',
+          '#255e91',
+          '#9e480e',
+          '#636363',
+          '#997300'
+        ]
+      }
+    ]
+  }
+  domesticInstance.setOption(option)
+  window.addEventListener('resize', handleDomesticResize)
+}
+
+// 在 domesticData 变化时重绘
+watch(
+  domesticData,
+  () => {
+    nextTick(() => initDomesticChart())
+  },
+  { deep: true }
+)
+
+onMounted(() => {
+  // 初始绘制
+  initDomesticChart()
+})
+
+onUnmounted(() => {
+  try {
+    if (domesticInstance) {
+      domesticInstance.dispose()
+      domesticInstance = null
+    }
+  } catch (e) {}
+  try {
+    window.removeEventListener('resize', handleDomesticResize)
+  } catch (e) {}
+})
+
+const iraqData = ref<any[]>([
+  {
+    index: 1,
+    dept: '伊拉克 哈法亚连油',
+    count: 120,
+    orig_value_usd: 694.78,
+    net_value_usd: 94.44,
+    orig_ratio: '8.02%'
+  },
+  {
+    index: 2,
+    dept: '伊拉克 哈法亚压裂',
+    count: 132,
+    orig_value_usd: 1008.92,
+    net_value_usd: 587.07,
+    orig_ratio: '11.65%'
+  },
+  {
+    index: 3,
+    dept: '伊拉克 B9增产',
+    count: 27,
+    orig_value_usd: 304.72,
+    net_value_usd: 128.62,
+    orig_ratio: '3.52%'
+  }
+])
+
+const iraqTableData = computed(() => {
+  const base = iraqData.value.slice()
+  base.push({
+    index: '',
+    dept: '合计',
+    count: 279,
+    orig_value: Number(279),
+    orig_value_usd: Number(2008.41),
+    net_value_usd: Number(810.13),
+    orig_ratio: ''
+  })
+  base.push({
+    index: '',
+    dept: '折算RMB合计',
+    count: '',
+    orig_value: Number(61574.3),
+    orig_value_usd: Number(14279.83),
+    net_value_usd: Number(5760.03),
+    orig_ratio: '23.19%'
+  })
+  return base
+})
+
+// 折算RMB合计(数值)
+const iraqRmbTotal = ref(14279.83)
+
+const libyaData = ref<any[]>([
+  {
+    index: 1,
+    dept: '利比亚 连油8队',
+    count: 22,
+    orig_value: 2025.52,
+    net_value: 1961.69,
+    orig_ratio: ''
+  }
+])
+
+const libyaRmbTotal = ref(2641.07)
+const libyaRmbNet = ref(2557.84)
+
+const libyaTableData = computed(() => {
+  const base = libyaData.value.slice()
+  base.push({
+    index: '',
+    dept: '折算RMB合计',
+    count: '',
+    orig_value: Number(libyaRmbTotal.value),
+    net_value: Number(libyaRmbNet.value),
+    orig_ratio: '4.29%'
+  })
+  base.push({
+    index: '',
+    dept: '国内外合计',
+    count: 525,
+    orig_value: Number(61574.3),
+    net_value: Number(18256.63),
+    orig_ratio: ''
+  })
+  return base
+})
+
+// 行样式:合计行与折算RMB合计高亮
+const tableRowClass = (row: any) => {
+  if (!row) return ''
+  if (row.dept === '合计') return 'summary-row'
+  if (row.dept && typeof row.dept === 'string' && row.dept.indexOf('折算') !== -1) return 'rmb-row'
+  return ''
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  loading.value = true
+  await Promise.all([getStats()])
+  loading.value = false
+})
+</script>
+<style lang="scss" scoped>
+@media (width <= 768px) {
+  .page-container {
+    padding: 10px;
+  }
+}
+
+@media (width <= 520px) {
+  .status-legend-item {
+    min-width: 100%;
+  }
+
+  .status-legend {
+    justify-content: flex-start;
+    max-height: none;
+    overflow: visible;
+  }
+}
+
+::v-deep .el-table,
+::v-deep .el-table__expanded-cell {
+  background-color: transparent !important;
+}
+
+/* 表格内背景颜色 */
+
+::v-deep .el-table tr,
+::v-deep .el-table td {
+  background-color: transparent !important;
+}
+
+::v-deep .el-table__footer {
+  color: #fff !important;
+}
+
+::v-deep .el-table__footer-wrapper .is-leaf {
+  color: #fff !important;
+}
+
+.summary {
+  .el-col {
+    margin-bottom: 1rem;
+  }
+}
+
+.stat-card {
+  width: 48%;
+}
+
+.page-container {
+  min-height: 100vh;
+  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;
+    }
+  }
+}
+
+::v-deep .el-card__header {
+  padding-bottom: 0;
+  border-bottom: none !important;
+}
+
+.table-container {
+  height: 420px;
+  padding: 16px;
+  overflow: auto;
+  box-sizing: border-box;
+
+  // 滚动条样式优化
+  &::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: rgb(255 255 255 / 20%);
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background-color: transparent;
+  }
+}
+
+// 修复表格hover样式
+::v-deep .el-table__row:hover > td {
+  background-color: rgb(255 255 255 / 5%) !important;
+}
+
+.custom-scroll-dialog {
+  /* 可选:限制对话框整体最大高度(避免超出屏幕) */
+  max-height: 90vh;
+  overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
+}
+
+/* 滚动内容容器:核心样式 */
+.dialog-scroll-content {
+  max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
+  padding-right: 8px; /* 避免滚动条遮挡内容(可选) */
+  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
+}
+
+/* 优化滚动条样式(可选,提升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: 50px !important; /* 高度根据需求调整 */
+}
+
+/* 设备状态图例自适应样式 */
+.status-legend {
+  display: flex;
+  width: 100%;
+  max-height: 90px; /* 限制高度,超出显示滚动 */
+  padding: 6px 0;
+  overflow-y: auto;
+  box-sizing: border-box;
+  flex-wrap: wrap;
+  justify-content: center;
+  gap: 8px;
+}
+
+.status-legend-item {
+  display: flex;
+  max-width: 100%;
+  min-width: 120px;
+  padding: 6px 10px;
+  box-sizing: border-box;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+}
+
+.status-legend-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  min-width: 0;
+}
+
+.status-legend-color {
+  display: inline-block;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  flex: 0 0 12px;
+}
+
+.status-legend-name {
+  max-width: calc(100% - 60px);
+  overflow: hidden;
+  color: #fff;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.status-legend-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-shrink: 0;
+}
+
+.status-legend-value {
+  font-weight: 700;
+  color: #fff;
+}
+
+.status-legend-percent {
+  color: #fff;
+}
+
+/* 最外层透明 */
+</style>

+ 1 - 1
src/views/pms/stat/rhkb/deviceType.vue

@@ -168,7 +168,7 @@ onUnmounted(() => {
         <span></span>
         <span></span>
       </div>
-      设备类别/状态
+      设备类别
     </div>
     <div ref="chartRef" class="flex-1 min-h-0"></div>
   </div>

+ 6 - 0
src/views/pms/stat/rhkb/inventorySituation.vue

@@ -205,6 +205,9 @@ function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
         radius: layout.distributionPieRadius,
         center: ['27%', layout.distributionPieCenterY],
         minAngle: 5,
+        label: {
+          show: false
+        },
         data: data.map((item) => ({
           name: item.project,
           value: item.mayInventoryAmount
@@ -216,6 +219,9 @@ function getDistributionOption(data: InventoryItem[]): echarts.EChartsOption {
         radius: layout.distributionPieRadius,
         center: ['73%', layout.distributionPieCenterY],
         minAngle: 5,
+        label: {
+          show: false
+        },
         data: data
           .filter((item) => item.mayBacklogAmount > 0)
           .map((item) => ({

+ 1 - 1
src/views/pms/stat/rykb/inventory-situation.vue

@@ -141,7 +141,7 @@ function getInventoryChartLayout(chartRef: Ref<HTMLDivElement | undefined>) {
     axisFontSize: compact ? 10 : 12,
     yAxisLabelWidth: compact ? 96 : 132,
     yAxisLabelMargin: compact ? 8 : 12,
-    distributionTitleTop: compact ? 6 : 18,
+    distributionTitleTop: compact ? 6 : 4,
     distributionTitleFontSize: compact ? 12 : 14,
     distributionTitleLineHeight: compact ? 14 : 16,
     distributionPieRadius: compact ? ['36%', '54%'] : ['48%', '68%'],