Bladeren bron

门户sso

yanghao 1 dag geleden
bovenliggende
commit
8daa0357bf

+ 8 - 0
src/api/pms/device/index.ts

@@ -153,6 +153,14 @@ export const IotDeviceApi = {
     return await request.download({ url: `/rq/iot-device/export-excel`, params })
   },
 
+  exportIotDeviceAdjust: async (params) => {
+    return await request.download({ url: `/pms/iot-device-status-log/export-excel`, params })
+  },
+
+  exportIotDevicePerson: async (params) => {
+    return await request.download({ url: `/pms/iot-device-person-log/export-excel`, params })
+  },
+
   exportIotDeviceAllot: async (params) => {
     return await request.download({ url: `/pms/iot-device-allot-log/export-excel`, params })
   },

+ 4 - 0
src/api/pms/iotrddailyreport/index.ts

@@ -99,6 +99,10 @@ export const IotRdDailyReportApi = {
     return await request.download({ url: `/pms/iot-rd-daily-report/export-excel`, params })
   },
 
+  exportIotRdDailyReportDetails: async (params) => {
+    return await request.download({ url: `/pms/iot-rd-daily-report/export-detail`, params })
+  },
+
   // 查询项目任务实际进度列表
   taskActualProgress: async (params: any) => {
     return await request.get({ url: `/pms/iot-rd-daily-report/taskActualProgress`, params })

+ 6 - 0
src/api/pms/iotrhdailyreport/index.ts

@@ -113,5 +113,11 @@ export const IotRhDailyReportApi = {
   // 导出瑞恒日报 Excel
   exportIotRhDailyReport: async (params) => {
     return await request.download({ url: `/pms/iot-rh-daily-report/export-excel`, params })
+  },
+  exportIotRhDailyReportWell: async (params) => {
+    return await request.download({ url: `/pms/iot-rh-daily-report/exportSingleWells`, params })
+  },
+  exportIotRhDailyReportTeam: async (params) => {
+    return await request.download({ url: `/pms/iot-rh-daily-report/exportSingleTeams`, params })
   }
 }

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

@@ -123,6 +123,12 @@ export const IotStatApi = {
   getDeviceCount: async (params: any) => {
     return await request.get({ url: `/rq/stat/home/device/count/` + params })
   },
+  getAbnormalDevice: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/exception/device` + params })
+  },
+  getOutliers: async (params: any) => {
+    return await request.get({ url: `/rq/iot-inspect-order-detail/report/status` + params })
+  },
   getRhRate: async (params: any) => {
     return await request.get({ url: `/rq/stat/rh/device/utilizationRate`, params })
   },
@@ -174,5 +180,29 @@ export const IotStatApi = {
   },
   getDevSta: async (params: any) => {
     return await request.get({ url: `/rq/iot-opeation-fill/getDeviceCount`, params })
+  },
+  getRdWorkload: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/summaryStatistics`, params })
+  },
+  getRdWorkloadYear: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/workloadKanban`, params })
+  },
+  getUtilization: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rd/device/utilizationRates`, params })
+  },
+  getWhl: async () => {
+    return await request.get({ url: `/rq/report/rd/whl` })
+  },
+  getDeviceException: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/exception/device`, params })
+  },
+  getStatusException: async (params: any) => {
+    return await request.get({ url: `/rq/iot-inspect-order-detail/report/status`, params })
+  },
+  getProductionException: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/abnormalAlert`, params })
+  },
+  getConstructionBriefing: async () => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/constructionBriefing` })
   }
 }

+ 8 - 7
src/components/ZmTable/ZmTableColumn.vue

@@ -117,19 +117,20 @@ const calculativeWidth = () => {
   if (props.zmFilterable) labelWidth += 22
   if (props.zmSortable) labelWidth += 22
 
-  const maxWidth = Math.max(...values.map((value) => getTextWidth(value) + 38), labelWidth)
+  const maxWidth = Math.min(
+    Math.max(...values.map((value) => getTextWidth(value) + 38), labelWidth),
+    360
+  )
 
   defaultOptions.value.minWidth = maxWidth
 }
 
 watch(
   () => tableContext.loading.value,
-  (loading) => {
-    if (!loading) {
-      nextTick(() => {
-        calculativeWidth()
-      })
-    }
+  () => {
+    nextTick(() => {
+      calculativeWidth()
+    })
   },
   { immediate: true }
 )

+ 10 - 2
src/components/ZmTable/index.vue

@@ -8,6 +8,7 @@ interface Props extends /* @vue-ignore */ Partial<Omit<TableProps<T>, 'data'>> {
   handleQuery?: (payload?: FilterPayload) => void
   sortingFields?: SortField[]
   sortFn?: (prop: string, order: SortOrder | null) => void
+  customClass?: boolean
 }
 
 const props = defineProps<Props>()
@@ -25,7 +26,8 @@ const defaultOptions: Partial<Props> = {
   border: true,
   highlightCurrentRow: true,
   showOverflowTooltip: true,
-  scrollbarAlwaysOn: false
+  scrollbarAlwaysOn: false,
+  customClass: false
 }
 
 const bindProps = computed(() => {
@@ -85,7 +87,13 @@ defineExpose({
 </script>
 
 <template>
-  <el-table ref="tableRef" v-loading="loading" class="zm-table" v-bind="bindProps" :data="data">
+  <el-table
+    ref="tableRef"
+    v-loading="loading"
+    :class="{ 'zm-table': !customClass }"
+    v-bind="bindProps"
+    :data="data"
+  >
     <template v-for="(_, name) in $slots" #[name]="slotData">
       <slot :name="name" v-bind="slotData || {}"></slot>
     </template>

+ 1 - 1
src/components/count-to1.vue

@@ -88,7 +88,7 @@ defineExpose({
 
 <template>
   <span class="flex">
-    <span v-if="endVal">{{ value }}</span>
+    <span v-if="endVal !== null && endVal !== undefined">{{ value }}</span>
     <slot v-else></slot>
   </span>
 </template>

+ 17 - 19
src/views/pms/device/personlog/DevicePerson.vue

@@ -16,7 +16,11 @@
           :inline="true"
           label-width="68px"
         >
-          <el-form-item :label="t('devicePerson.deviceCode')" prop="deviceCode" style="margin-left: 20px">
+          <el-form-item
+            :label="t('devicePerson.deviceCode')"
+            prop="deviceCode"
+            style="margin-left: 20px"
+          >
             <el-input
               v-model="queryParams.deviceCode"
               :placeholder="t('devicePerson.codeHolder')"
@@ -35,7 +39,11 @@
             />
           </el-form-item>
 
-          <el-form-item :label="t('devicePerson.responsiblePerson')" prop="setFlag" label-width="140px">
+          <el-form-item
+            :label="t('devicePerson.responsiblePerson')"
+            prop="setFlag"
+            label-width="140px"
+          >
             <el-select
               v-model="queryParams.setFlag"
               :placeholder="t('devicePerson.choose')"
@@ -75,13 +83,8 @@
             >
               <Icon icon="ep:plus" class="mr-5px" /> {{ t('devicePerson.setUp') }}
             </el-button>
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-device:export']"
-            >
+            <!-- v-hasPermi="['rq:iot-device:export']" -->
+            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
               <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
           </el-form-item>
@@ -131,7 +134,7 @@
   </el-row>
   <DevicePersonLogDrawer
     :model-value="drawerVisible"
-    @update:model-value="val => drawerVisible = val"
+    @update:model-value="(val) => (drawerVisible = val)"
     :device-id="currentDeviceId"
     ref="showDrawer"
   />
@@ -140,10 +143,9 @@
 <script setup lang="ts">
 import download from '@/utils/download'
 import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
-import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import DeptTree from '@/views/system/user/DeptTree.vue'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
-import DevicePersonLogDrawer from "@/views/pms/device/personlog/DevicePersonLogDrawer.vue";
+import DevicePersonLogDrawer from '@/views/pms/device/personlog/DevicePersonLogDrawer.vue'
 
 /** 设备台账 列表 */
 defineOptions({ name: 'IotDevicePerson' })
@@ -270,7 +272,7 @@ const resultOptions = computed(() => [
   {
     label: '否',
     value: 'N' // 空值会触发 clearable 效果
-  },
+  }
 ])
 
 const handleDetail = (id: number) => {
@@ -295,12 +297,8 @@ const handleView = async (deviceId: number) => {
 /** 导出按钮操作 */
 const handleExport = async () => {
   try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await IotDeviceApi.exportIotDevice(queryParams)
-    download.excel(data, '设备台账.xls')
+    const data = await IotDeviceApi.exportIotDevicePerson(queryParams)
+    download.excel(data, '设备责任人.xls')
   } catch {
   } finally {
     exportLoading.value = false

+ 24 - 20
src/views/pms/device/statuslog/DeviceStatus.vue

@@ -16,7 +16,11 @@
           :inline="true"
           label-width="68px"
         >
-          <el-form-item :label="t('devicePerson.deviceCode')" prop="deviceCode" style="margin-left: 25px">
+          <el-form-item
+            :label="t('devicePerson.deviceCode')"
+            prop="deviceCode"
+            style="margin-left: 25px"
+          >
             <el-input
               v-model="queryParams.deviceCode"
               :placeholder="t('devicePerson.codeHolder')"
@@ -49,7 +53,12 @@
               />
             </el-select>
           </el-form-item>
-          <el-form-item v-show="ifShow" :label="t('devicePerson.status')" label-width="85px" prop="deviceStatus">
+          <el-form-item
+            v-show="ifShow"
+            :label="t('devicePerson.status')"
+            label-width="85px"
+            prop="deviceStatus"
+          >
             <el-select
               v-model="queryParams.deviceStatus"
               :label="t('devicePerson.status')"
@@ -97,7 +106,8 @@
               {{ t('devicePerson.moreSearch') }}</el-button
             >
             <el-button v-if="ifShow" @click="moreQuery(false)" type="danger"
-              ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.closeSearch') }}</el-button
+              ><Icon icon="ep:search" class="mr-5px" />
+              {{ t('devicePerson.closeSearch') }}</el-button
             >
             <el-button @click="handleQuery"
               ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
@@ -113,13 +123,8 @@
             >
               <Icon icon="ep:plus" class="mr-5px" /> {{ t('deviceStatus.setUp') }}
             </el-button>
-            <el-button
-              type="success"
-              plain
-              @click="handleExport"
-              :loading="exportLoading"
-              v-hasPermi="['rq:iot-device:export']"
-            >
+            <!-- v-hasPermi="['rq:iot-device:export']" -->
+            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
               <Icon icon="ep:download" class="mr-5px" /> 导出
             </el-button>
           </el-form-item>
@@ -129,7 +134,7 @@
       <!-- 列表 -->
       <ContentWrap>
         <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-          <el-table-column :label="t('monitor.serial')" width="70" align="center" >
+          <el-table-column :label="t('monitor.serial')" width="70" align="center">
             <template #default="scope">
               {{ scope.$index + 1 }}
             </template>
@@ -181,7 +186,7 @@
   </el-row>
   <DeviceStatusLogDrawer
     :model-value="drawerVisible"
-    @update:model-value="val => drawerVisible = val"
+    @update:model-value="(val) => (drawerVisible = val)"
     :device-id="currentDeviceId"
     ref="showDrawer"
   />
@@ -191,10 +196,9 @@
 import download from '@/utils/download'
 import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
 import DeptTree from '@/views/system/user/DeptTree.vue'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
-import DeviceStatusLogDrawer from "@/views/pms/device/statuslog/DeviceStatusLogDrawer.vue";
+import DeviceStatusLogDrawer from '@/views/pms/device/statuslog/DeviceStatusLogDrawer.vue'
 
 /** 设备台账 列表 */
 defineOptions({ name: 'IotDeviceStatus' })
@@ -271,7 +275,7 @@ const resultOptions = computed(() => [
   {
     label: '否',
     value: 'N' // 空值会触发 clearable 效果
-  },
+  }
 ])
 
 const showDrawer = ref()
@@ -344,12 +348,12 @@ const handleDetail = (id: number) => {
 /** 导出按钮操作 */
 const handleExport = async () => {
   try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
+    // // 导出的二次确认
+    // await message.exportConfirm()
+    // // 发起导出
     exportLoading.value = true
-    const data = await IotDeviceApi.exportIotDevice(queryParams)
-    download.excel(data, '设备台账.xls')
+    const data = await IotDeviceApi.exportIotDeviceAdjust(queryParams)
+    download.excel(data, '设备状态调整.xls')
   } catch {
   } finally {
     exportLoading.value = false

+ 2 - 2
src/views/pms/iotrddailyreport/fillDailyReport.vue

@@ -60,9 +60,9 @@
             >
               <Icon icon="ep:plus" class="mr-5px" /> 新增
             </el-button>
-            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+            <!-- <el-button type="success" plain @click="handleExport" :loading="exportLoading">
               <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
+            </el-button> -->
           </el-form-item>
         </el-form>
       </ContentWrap>

+ 7 - 1
src/views/pms/iotrddailyreport/index.vue

@@ -286,6 +286,7 @@ import { useDebounceFn } from '@vueuse/core'
 
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import download from '@/utils/download'
 
 dayjs.extend(quarterOfYear)
 
@@ -688,7 +689,12 @@ const handleDelete = async (id: number) => {
 }
 
 const exportLoading = ref(false)
-const handleExport = async () => {}
+
+const handleExport = async () => {
+  const res = await IotRdDailyReportApi.exportIotRdDailyReportDetails(queryParams)
+
+  download.excel(res, '瑞都日报明细.xlsx')
+}
 
 // 声明 ResizeObserver 实例
 let resizeObserver: ResizeObserver | null = null

+ 10 - 3
src/views/pms/iotrhdailyreport/rh-form.vue

@@ -195,7 +195,9 @@ const sumNonProdTimes = () => {
 // 24小时平衡校验器
 const validateTotalTime =
   (isNon: boolean = false) =>
-  (_rule: any, _value: any, callback: any) => {
+  (rule: any, _value: any, callback: any) => {
+    const field = rule.field
+
     const gasTime = form.value.dailyInjectGasTime || 0
     const waterTime = form.value.dailyInjectWaterTime || 0
     const nonProdSum = sumNonProdTimes()
@@ -212,8 +214,13 @@ const validateTotalTime =
     }
 
     if (Math.abs(total - 24) > 0.01) {
-      if (!isNon) callback(new Error(msg))
-      else callback(new Error())
+      if (!isNon) {
+        if (field === 'dailyInjectWaterTime' && waterTime === 0) {
+          callback()
+        } else if (field === 'dailyInjectGasTime' && gasTime === 0 && waterTime !== 0) {
+          callback()
+        } else callback(new Error(msg))
+      } else callback(new Error())
     } else {
       callback()
     }

+ 3 - 3
src/views/pms/iotrydailyreport/summary.vue

@@ -227,9 +227,9 @@ const xAxisData = ref<string[]>([])
 
 const legend = ref<string[][]>([
   // ['累计油耗 (万升)', 'cumulativeFuelConsumption'],
-  ['累计油耗 (升)', 'cumulativeFuelConsumption'],
-  ['累计进尺 (M)', 'cumulativeFootage'],
-  ['累计用电量 (KWh)', 'cumulativePowerConsumption'],
+  ['油耗 (升)', 'cumulativeFuelConsumption'],
+  ['进尺 (M)', 'cumulativeFootage'],
+  ['用电量 (KWh)', 'cumulativePowerConsumption'],
   // ['累计用电量 (MWh)', 'cumulativePowerConsumption'],
   ['平均时效 (%)', 'transitTime']
 ])

+ 4 - 4
src/views/pms/iotrydailyreport/xsummary.vue

@@ -232,12 +232,12 @@ let chart: echarts.ECharts | null = null
 const xAxisData = ref<string[]>([])
 
 const legend = ref<string[][]>([
-  ['累计施工井数 (个)', 'cumulativeConstructWells'],
-  ['累计完工井数 (个)', 'cumulativeCompletedWells'],
+  ['施工井数 (个)', 'cumulativeConstructWells'],
+  ['完工井数 (个)', 'cumulativeCompletedWells'],
   // ['累计油耗 (万升)', 'cumulativeFuelConsumption'],
-  ['累计油耗 (升)', 'cumulativeFuelConsumption'],
+  ['油耗 (升)', 'cumulativeFuelConsumption'],
   // ['累计用电量 (MWh)', 'cumulativePowerConsumption'],
-  ['累计用电量 (KWh)', 'cumulativePowerConsumption'],
+  ['用电量 (KWh)', 'cumulativePowerConsumption'],
   ['平均时效 (%)', 'transitTime']
 ])
 

+ 154 - 109
src/views/pms/stat/rdkb.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-container">
+  <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">
@@ -96,7 +96,7 @@
               gap: 6px;
             "
           >
-            <div ref="statusChartRef" style="width: 100%; max-width: 200px; height: 170px"></div>
+            <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">
@@ -123,25 +123,23 @@
           </template>
           <div
             style="
-              min-height: 295px;
               display: flex;
+              min-height: 295px;
               flex-direction: column;
               align-items: center;
               gap: 8px;
             "
           >
-            <div ref="domesticChartRef" style="width: 100%; max-width: 420px; height: 200px"></div>
+            <div ref="domesticChartRef" style="width: 100%; height: 200px; max-width: 420px"></div>
             <div
               class="domestic-legend"
               style="
+                display: flex;
                 width: 100%;
                 max-width: 520px;
-                display: flex;
+                font-size: 12px;
                 flex-wrap: wrap;
-
                 gap: 1px;
-
-                font-size: 12px;
               "
             >
               <div
@@ -163,12 +161,12 @@
                 <span
                   class="legend-name"
                   style="
-                    color: #fff;
+                    display: inline-block;
                     max-width: 150px;
                     overflow: hidden;
+                    color: #fff;
                     text-overflow: ellipsis;
                     white-space: nowrap;
-                    display: inline-block;
                   "
                   >{{ item.dept }}</span
                 >
@@ -178,7 +176,8 @@
         </el-card>
       </el-col>
       <el-col :span="8" :xs="24">
-        <el-card class="chart-card" shadow="never">
+        <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">{{
@@ -187,14 +186,22 @@
             </div>
           </template>
           <div ref="qxRef" class="h-[290px]"></div>
-        </el-card>
+        </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">
-        <el-card class="chart-card" shadow="never">
+        <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">{{
@@ -214,37 +221,37 @@
               </div>
             </div>
           </template>
-          <!-- 添加两个卡片 -->
-          <!--          <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>-->
+
           <div class="table-container">
             <div ref="utilizationRef" style="width: 100%; height: 360px"></div>
           </div>
-        </el-card>
-      </el-col>
+        </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">
@@ -353,6 +360,12 @@
       </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"
@@ -511,6 +524,11 @@ 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' })
@@ -640,6 +658,14 @@ 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
@@ -1458,27 +1484,27 @@ const projectDataRowClick = (row, column, event) => {
 const domesticData = ref<any[]>([
   {
     index: 1,
-    dept: '公',
+    dept: '公',
     count: 13,
     orig_value: 1506.88,
     net_value: 559.95,
-    orig_ratio: '3%'
+    orig_ratio: '2.77%'
   },
   {
     index: 2,
     dept: '新疆项目部',
     count: 58,
     orig_value: 5118.1,
-    net_value: 1182.91,
-    orig_ratio: '11%'
+    net_value: 1182.49,
+    orig_ratio: '9.40%'
   },
   {
     index: 3,
     dept: '青海项目',
     count: 33,
-    orig_value: 14082.33,
-    net_value: 736.94,
-    orig_ratio: '29%'
+    orig_value: 7004.23,
+    net_value: 1758.8,
+    orig_ratio: '12.86%'
   },
   {
     index: 4,
@@ -1486,55 +1512,55 @@ const domesticData = ref<any[]>([
     count: 49,
     orig_value: 5273.54,
     net_value: 683.58,
-    orig_ratio: '11%'
+    orig_ratio: '9.68%'
   },
   {
     index: 5,
     dept: '西南连油项目部',
     count: 24,
-    orig_value: 4070.12,
-    net_value: 743.99,
-    orig_ratio: '8%'
+    orig_value: 4059.36,
+    net_value: 743.24,
+    orig_ratio: '7.45%'
   },
   {
     index: 6,
     dept: '西南压裂项目部',
     count: 47,
-    orig_value: 14602.44,
-    net_value: 6031.39,
-    orig_ratio: '30'
+    orig_value: 14591.62,
+    net_value: 6030.84,
+    orig_ratio: '26.79%'
   },
   {
     index: 7,
     dept: '伊拉克 哈法亚连油',
     count: 120,
     orig_value: 694.78,
-    net_value: 94.44,
-    orig_ratio: '1%'
+    net_value: 91.26,
+    orig_ratio: '9.07%'
   },
   {
     index: 8,
     dept: '伊拉克 哈法亚压裂',
     count: 132,
     orig_value: 1008.92,
-    net_value: 587.07,
-    orig_ratio: '2%'
+    net_value: 575.91,
+    orig_ratio: '13.17%%'
   },
   {
     index: 9,
     dept: '伊拉克 B9增产',
     count: 27,
     orig_value: 304.72,
-    net_value: 128.63,
-    orig_ratio: '1%'
+    net_value: 124.1,
+    orig_ratio: '3.98%'
   },
   {
     index: 10,
     dept: '利比亚连油8队',
     count: 22,
     orig_value: 2025.52,
-    net_value: 1961.69,
-    orig_ratio: '4%'
+    net_value: 1731.79,
+    orig_ratio: '4.85%'
   }
 ])
 
@@ -1744,11 +1770,29 @@ onMounted(async () => {
 })
 </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,
@@ -1769,13 +1813,15 @@ onMounted(async () => {
     margin-bottom: 1rem;
   }
 }
+
 .stat-card {
   width: 48%;
 }
+
 .page-container {
-  background-color: #3a6fa3;
   min-height: 100vh;
   padding: 20px;
+  background-color: #3a6fa3;
 }
 
 .summary {
@@ -1783,32 +1829,32 @@ onMounted(async () => {
 }
 
 ::v-deep .chart-card {
-  background-color: rgba(0, 0, 0, 0.3);
+  background-color: rgb(0 0 0 / 30%);
+  border: none;
   border-radius: 8px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
+  box-shadow: 0 2px 12px rgb(0 0 0 / 50%);
   transition: all 0.3s ease;
-  border: none;
 
   &:hover {
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+    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;
-    height: 150px;
-    position: relative;
 
     .days-number {
       font-size: 58px;
       font-weight: bold;
-      color: darkorange;
       line-height: 1;
+      color: darkorange;
       transition: all 0.3s ease;
     }
 
@@ -1817,36 +1863,32 @@ onMounted(async () => {
     }
 
     .days-label {
+      margin-top: 8px;
       font-size: 20px;
       color: white;
-      margin-top: 8px;
     }
 
     .safety-desc {
-      font-size: 14px;
-      color: #999;
       position: absolute;
       bottom: 10px;
-      text-align: center;
       width: 90%;
+      font-size: 14px;
+      color: #999;
+      text-align: center;
     }
   }
 }
 
-@media (max-width: 768px) {
-  .page-container {
-    padding: 10px;
-  }
-}
 ::v-deep .el-card__header {
-  border-bottom: none !important;
   padding-bottom: 0;
+  border-bottom: none !important;
 }
+
 .table-container {
-  padding: 16px;
   height: 420px;
-  box-sizing: border-box;
+  padding: 16px;
   overflow: auto;
+  box-sizing: border-box;
 
   // 滚动条样式优化
   &::-webkit-scrollbar {
@@ -1855,7 +1897,7 @@ onMounted(async () => {
   }
 
   &::-webkit-scrollbar-thumb {
-    background-color: rgba(255, 255, 255, 0.2);
+    background-color: rgb(255 255 255 / 20%);
     border-radius: 3px;
   }
 
@@ -1866,63 +1908,71 @@ onMounted(async () => {
 
 // 修复表格hover样式
 ::v-deep .el-table__row:hover > td {
-  background-color: rgba(255, 255, 255, 0.05) !important;
+  background-color: rgb(255 255 255 / 5%) !important;
 }
+
 .custom-scroll-dialog {
   /* 可选:限制对话框整体最大高度(避免超出屏幕) */
   max-height: 90vh;
   overflow: hidden; /* 隐藏整体溢出,避免出现双重滚动条 */
 }
+
 /* 滚动内容容器:核心样式 */
 .dialog-scroll-content {
   max-height: 60vh; /* 固定最大高度(可根据需求调整,如500px) */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
   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 {
-  width: 100%;
   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;
-  padding: 6px 0;
-  box-sizing: border-box;
-  max-height: 90px; /* 限制高度,超出显示滚动 */
-  overflow-y: auto;
 }
+
 .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;
-  padding: 6px 10px;
-  min-width: 120px;
-  max-width: 100%;
-  box-sizing: border-box;
 }
+
 .status-legend-left {
   display: flex;
   align-items: center;
   gap: 8px;
   min-width: 0;
 }
+
 .status-legend-color {
   display: inline-block;
   width: 12px;
@@ -1930,35 +1980,30 @@ onMounted(async () => {
   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;
-  color: #fff;
 }
+
 .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;
 }
 
-@media (max-width: 520px) {
-  .status-legend-item {
-    min-width: 100%;
-  }
-  .status-legend {
-    justify-content: flex-start;
-    max-height: none;
-    overflow: visible;
-  }
-}
+/* 最外层透明 */
 </style>

+ 138 - 0
src/views/pms/stat/rdkb/availability.vue

@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+// 定义数据接口结构
+interface DeviceStat {
+  percent: number
+  total: number
+}
+
+interface ApiData {
+  a: DeviceStat
+  b: DeviceStat
+  c: DeviceStat
+}
+
+// 响应式数据,设置初始默认值防止页面报错
+const stats = ref<ApiData>({
+  a: { percent: 0, total: 0 },
+  b: { percent: 0, total: 0 },
+  c: { percent: 0, total: 0 }
+})
+
+const loading = ref(false)
+
+// 配置项:定义每一项的名称、颜色和对应接口的key
+// 你可以在这里修改 name 为实际业务名称
+const config = [
+  { key: 'a', name: 'A(关键)', color: '#00E5FF' }, // 亮青
+  { key: 'b', name: 'B(重要)', color: '#FFD740' }, // 亮黄
+  { key: 'c', name: 'C(一般)', color: '#69F0AE' } // 荧光绿
+] as const
+
+const fetchData = async () => {
+  loading.value = true
+  try {
+    const res = await IotStatApi.getWhl()
+
+    if (res) {
+      stats.value = res
+    }
+  } catch (error) {
+    console.error('Whl API Error:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  fetchData()
+})
+</script>
+
+<template>
+  <div
+    class="card size-full rounded-lg p-4 flex flex-col"
+    v-loading="loading"
+    element-loading-background="rgba(0, 0, 0, 0.3)"
+  >
+    <div class="flex items-center gap-2 mb-6">
+      <div class="w-1 h-4 bg-[#00E5FF] rounded-full shadow-[0_0_8px_#00E5FF]"></div>
+      <div class="text-[#e0e0e0] text-lg font-bold">完好率</div>
+    </div>
+
+    <!-- 内容区域:三列布局 -->
+    <div class="flex-1 flex items-center justify-around w-full">
+      <div v-for="item in config" :key="item.key" class="flex flex-col items-center group">
+        <!-- 进度环 -->
+        <!-- stroke-width: 进度条宽度 -->
+        <!-- width: 圆环直径 -->
+        <div class="relative relative-glow">
+          <el-progress
+            type="dashboard"
+            :percentage="stats[item.key]?.percent || 0"
+            :color="item.color"
+            :width="130"
+            :stroke-width="10"
+            stroke-linecap="round"
+          >
+            <!-- 自定义圆环中间的内容 -->
+            <template #default="{ percentage }">
+              <div class="flex flex-col items-center">
+                <span
+                  class="text-2xl font-bold font-mono"
+                  :style="{ color: item.color, textShadow: `0 0 10px ${item.color}40` }"
+                >
+                  {{ percentage }}%
+                </span>
+                <span class="text-[#9ca3af] text-xs mt-1">{{ item.name }}</span>
+              </div>
+            </template>
+          </el-progress>
+        </div>
+
+        <!-- 底部文字:总数 -->
+        <div class="mt-2 text-center">
+          <!-- <div class="text-[#b6c8da] text-sm">在线总数</div> -->
+          <div class="text-xl font-bold text-white mt-1">
+            {{ stats[item.key]?.total || 0 }}
+            <span class="text-xs text-[#6b7280] font-normal">台</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+/* 卡片背景 - 保持统一风格 */
+.card {
+  background-color: rgb(0 0 0 / 30%);
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
+  }
+}
+
+/* 强制修改 Element Progress 的轨道背景色,使其适应深色主题 */
+:deep(.el-progress-circle__track) {
+  stroke: rgb(255 255 255 / 10%) !important;
+}
+
+/* 进度条文字居中修正 */
+:deep(.el-progress__text) {
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+/* 简单的光晕动效 */
+.relative-glow {
+  transition: transform 0.3s ease;
+}
+
+.group:hover .relative-glow {
+  transform: scale(1.05);
+}
+</style>

+ 144 - 0
src/views/pms/stat/rdkb/constructionBriefing.vue

@@ -0,0 +1,144 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+
+const loading = ref(false)
+
+interface ConstructionBriefing {
+  projectDeptName: string // 项目部
+  deptName: string // 队伍
+  rdStatus: string // 状态
+  wellName: string // 井号
+  techniques: string //  工艺
+  deviceNames: string // 使用设备
+  cumulativeWorkingLayers: number // 当日施工层
+  cumulativeWorkingWell: string // 当日施工井
+  productionBrief: string //  当日施工简要
+  yesterdayStatus: string //   上一天状态
+  yesterdayWorkingLayers: number // 上一天施工层
+  yesterdayWorkingWell: string //   上一天施工井
+  yesterdayProduct: string //  上一天施工简要
+}
+
+const list = ref<ConstructionBriefing[]>([])
+
+const loadList = async () => {
+  try {
+    loading.value = true
+    const res = await IotStatApi.getConstructionBriefing()
+    list.value = res.list ?? []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  loadList()
+})
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ConstructionBriefing>()
+</script>
+<template>
+  <div class="card min-h-80 rounded-lg p-4 flex flex-col overflow-hidden" v-loading="loading">
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2">
+        <!-- 红色装饰条,代表异常/警示 -->
+        <div class="w-1 h-4 bg-[#00E5FF] rounded-full shadow-[0_0_8px_#00E5FF]"></div>
+        <div class="text-[#e0e0e0] text-lg font-bold">施工简报</div>
+      </div>
+    </div>
+    <div class="flex-1 relative w-full">
+      <el-auto-resizer class="absolute">
+        <template #default="{ width, height }">
+          <ZmTable
+            class="custom-dark-table"
+            :data="list"
+            :loading="loading"
+            :width="width"
+            :max-height="height"
+            custom-class
+          >
+            <ZmTableColumn prop="projectDeptName" label="项目部" />
+            <ZmTableColumn prop="deptName" label="队伍" />
+            <ZmTableColumn prop="rdStatus" label="状态" />
+            <ZmTableColumn prop="wellName" label="井号" />
+            <ZmTableColumn prop="techniques" label="工艺" />
+            <ZmTableColumn prop="deviceNames" label="使用设备" />
+            <ZmTableColumn prop="cumulativeWorkingLayers" label="当日施工层" />
+            <ZmTableColumn prop="cumulativeWorkingWell" label="当日施工井" />
+            <ZmTableColumn prop="productionBrief" label="当日施工简要" />
+            <ZmTableColumn prop="yesterdayStatus" label="上一天状态" />
+            <ZmTableColumn prop="yesterdayWorkingLayers" label="上一天施工层" />
+            <ZmTableColumn prop="yesterdayWorkingWell" label="上一天施工井" />
+            <ZmTableColumn prop="yesterdayProduct" label="上一天施工简要" />
+          </ZmTable>
+        </template>
+      </el-auto-resizer>
+    </div>
+  </div>
+</template>
+<style scoped>
+.card {
+  background-color: rgb(0 0 0 / 30%);
+  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%);
+  }
+}
+
+/* --- 表格深度定制 --- */
+:deep(.custom-dark-table) {
+  --el-table-border-color: rgb(255 255 255 / 5%);
+  --el-table-header-bg-color: rgb(0 229 255 / 8%);
+  --el-table-row-hover-bg-color: rgb(0 229 255 / 10%);
+  --el-table-tr-bg-color: transparent;
+
+  color: #e5e7eb; /* 文字颜色:灰白 */
+  background-color: transparent !important;
+}
+
+/* 移除表格底部和周围的白线 */
+:deep(.el-table__inner-wrapper::before),
+:deep(.el-table__border-left-patch) {
+  display: none;
+}
+
+/* 表头样式 */
+:deep(.el-table th.el-table__cell) {
+  font-weight: 600;
+  color: #00e5ff; /* 表头文字荧光色 */
+  background-color: var(--el-table-header-bg-color) !important;
+  border-bottom: 1px solid rgb(0 229 255 / 20%);
+}
+
+/* 单元格样式 */
+:deep(.el-table td.el-table__cell) {
+  border-bottom: 1px solid rgb(255 255 255 / 5%);
+}
+
+/* 斑马纹 - 偶数行稍微亮一点点 */
+:deep(.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell) {
+  background-color: rgb(255 255 255 / 2%);
+}
+
+/* Hover 效果 - 这一行亮起 */
+:deep(.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell) {
+  background-color: var(--el-table-row-hover-bg-color) !important;
+}
+
+/* 滚动条美化 */
+:deep(.el-scrollbar__bar.is-horizontal),
+:deep(.el-scrollbar__bar.is-vertical) {
+  background-color: rgb(0 0 0 / 30%);
+}
+
+:deep(.el-scrollbar__thumb) {
+  background-color: rgb(0 229 255 / 20%);
+
+  &:hover {
+    background-color: rgb(0 229 255 / 40%);
+  }
+}
+</style>

+ 279 - 0
src/views/pms/stat/rdkb/exception.vue

@@ -0,0 +1,279 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import dayjs from 'dayjs'
+import CountTo from '@/components/count-to1.vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+// --- 类型定义 ---
+type TimeType = 'quarter' | 'month' | 'year'
+
+// 模拟的生产异常数据接口
+interface ProductionExceptions {
+  standby: number // 井场待命
+  maintenance: number // 维修设备
+  exception: number // 异常/复杂
+  material: number // 物资影响
+  noWorkload: number // 无工作量
+}
+
+// 模拟的巡检异常数据接口
+interface InspectionExceptions {
+  equipment: number // 异常设备
+  point: number // 异常点
+}
+
+// --- 状态管理 ---
+const currentTimeType = ref<TimeType>('month')
+const loading = ref(false)
+
+// 时间选项:日、月、年
+const timeOptions = [
+  { label: '本月', value: 'month' },
+  { label: '本季度', value: 'quarter' },
+  { label: '本年', value: 'year' }
+]
+
+// 数据响应式对象
+const productionData = ref<ProductionExceptions>({
+  standby: 0,
+  maintenance: 0,
+  exception: 0,
+  material: 0,
+  noWorkload: 0
+})
+
+const inspectionData = ref<InspectionExceptions>({
+  equipment: 0,
+  point: 0
+})
+
+// --- 配置项:用于 v-for 循环渲染 ---
+const productionConfig = [
+  { key: 'standby', name: '井场待命' },
+  { key: 'maintenance', name: '维修设备' },
+  { key: 'noWorkload', name: '无工作量' },
+  { key: 'material', name: '物资影响' },
+  { key: 'exception', name: '异常/复杂' }
+]
+
+const inspectionConfig = [
+  { key: 'equipment', name: '异常设备' },
+  { key: 'point', name: '异常点' }
+]
+
+const getDateRange = (type: 'year' | 'quarter' | 'month') => {
+  const now = dayjs()
+  let start: dayjs.Dayjs, end: dayjs.Dayjs
+
+  if (type === 'year') {
+    start = now.startOf('year')
+    end = now.endOf('year')
+  } else if (type === 'quarter') {
+    start = now.startOf('quarter')
+    end = now.endOf('quarter')
+  } else {
+    start = now.startOf('month')
+    end = now.endOf('month')
+  }
+
+  return {
+    start: start.format('YYYY-MM-DD HH:mm:ss'),
+    end: end.format('YYYY-MM-DD HH:mm:ss')
+  }
+}
+
+// --- 模拟 API 请求 ---
+const fetchData = async () => {
+  loading.value = true
+
+  // 模拟获取时间范围
+  const { start, end } = getDateRange(currentTimeType.value as 'year' | 'quarter' | 'month')
+
+  const params = {
+    'createTime[0]': start,
+    'createTime[1]': end
+  }
+
+  const equipmentRes = await IotStatApi.getDeviceException(params)
+  const pointRes = await IotStatApi.getStatusException(params)
+
+  const productionRes = await IotStatApi.getProductionException(params)
+
+  inspectionData.value.equipment = equipmentRes.exceptionNum || 0
+  inspectionData.value.point = pointRes.value || 0
+
+  productionConfig.forEach((item) => {
+    productionData.value[item.key] =
+      productionRes.find((resItem) => resItem.name === item.name)?.count ?? 0
+  })
+
+  console.log('inspectionData :>> ', inspectionData)
+  console.log('productionData :>> ', productionData)
+
+  loading.value = false
+}
+
+const handleTimeChange = () => {
+  fetchData()
+}
+
+onMounted(() => {
+  fetchData()
+})
+</script>
+
+<template>
+  <div
+    class="card size-full rounded-lg p-4 flex flex-col"
+    v-loading="loading"
+    element-loading-background="rgba(0, 0, 0, 0.3)"
+  >
+    <!-- 头部:标题 + 时间切换 -->
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2">
+        <!-- 红色装饰条,代表异常/警示 -->
+        <div class="w-1 h-4 bg-[#FF5252] rounded-full shadow-[0_0_8px_#FF5252]"></div>
+        <div class="text-[#e0e0e0] text-lg font-bold">异常统计看板</div>
+      </div>
+      <el-segmented
+        size="default"
+        v-model="currentTimeType"
+        :options="timeOptions"
+        @change="handleTimeChange"
+        class="dark-segmented w-50!"
+        block
+      />
+    </div>
+
+    <!-- 内容区域:上下布局 -->
+    <div class="flex-1 flex flex-col gap-4 min-h-0 overflow-y-auto pr-1">
+      <!-- 1. 生产异常模块 -->
+      <div class="section-container">
+        <div class="text-[#FFD740] text-sm font-bold mb-3 flex items-center gap-2">
+          <span class="w-1.5 h-1.5 rounded-full bg-[#FFD740]"></span>
+          生产异常
+        </div>
+
+        <!-- 3x3 网格 -->
+        <div class="grid grid-cols-5 gap-3">
+          <div v-for="item in productionConfig" :key="item.key" class="stat-box group">
+            <div class="text-[#9ca3af] text-xs mb-1">{{ item.name }}</div>
+
+            <CountTo
+              class="text-xl font-bold font-mono text-white group-hover:text-[#FFD740] transition-colors"
+              :start-val="0"
+              :end-val="productionData[item.key as keyof ProductionExceptions]"
+            >
+              <!-- <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span> -->
+            </CountTo>
+            <!-- <div
+              class="text-xl font-bold font-mono text-white group-hover:text-[#FFD740] transition-colors"
+            >
+              {{ productionData[item.key as keyof ProductionExceptions] }}
+            </div> -->
+            <!-- 底部微光条 -->
+            <div
+              class="absolute bottom-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-[#FFD740] to-transparent opacity-20 group-hover:opacity-100 transition-opacity"
+            ></div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 2. 巡检异常模块 -->
+      <div class="section-container">
+        <div class="text-[#00E5FF] text-sm font-bold mb-3 flex items-center gap-2">
+          <span class="w-1.5 h-1.5 rounded-full bg-[#00E5FF]"></span>
+          巡检异常
+        </div>
+
+        <!-- 2列 网格 -->
+        <div class="grid grid-cols-2 gap-3">
+          <div v-for="item in inspectionConfig" :key="item.key" class="stat-box group">
+            <div class="flex justify-between items-center">
+              <div class="text-[#9ca3af] text-xs">{{ item.name }}</div>
+              <!-- 可以加个小图标占位 -->
+              <div class="w-1 h-1 bg-[#00E5FF] rounded-full opacity-50"></div>
+            </div>
+
+            <CountTo
+              class="text-2xl font-bold font-mono text-white mt-1 group-hover:text-[#00E5FF] transition-colors"
+              :start-val="0"
+              :end-val="inspectionData[item.key as keyof InspectionExceptions]"
+            >
+              <!-- <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span> -->
+            </CountTo>
+            <!-- <div
+              class="text-2xl font-bold font-mono text-white mt-1 group-hover:text-[#00E5FF] transition-colors"
+            >
+              {{ inspectionData[item.key as keyof InspectionExceptions] }}
+            </div> -->
+            <!-- 底部微光条 -->
+            <div
+              class="absolute bottom-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-[#00E5FF] to-transparent opacity-20 group-hover:opacity-100 transition-opacity"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+/* 主卡片背景 */
+.card {
+  background-color: rgb(0 0 0 / 30%);
+  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%);
+  }
+}
+
+/* 子模块容器 */
+.section-container {
+  padding: 12px;
+  background: rgb(255 255 255 / 2%);
+  border: 1px solid rgb(255 255 255 / 3%);
+  border-radius: 8px;
+}
+
+/* 单个数字盒子 */
+.stat-box {
+  position: relative;
+  display: flex;
+  padding: 8px 12px;
+  overflow: hidden;
+  cursor: default;
+  background: rgb(0 0 0 / 20%);
+  border-radius: 6px;
+  transition: all 0.3s ease;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.stat-box:hover {
+  background: rgb(255 255 255 / 5%);
+  transform: translateY(-2px);
+}
+
+/* 分段控制器样式覆盖 */
+.dark-segmented {
+  --el-segmented-item-selected-color: #e5eaf3;
+  --el-border-radius-base: 16px;
+  --el-segmented-color: #cfd3dc;
+  --el-segmented-bg-color: #262727;
+  --el-segmented-item-selected-bg-color: #ff5252;
+  --el-segmented-item-selected-disabled-bg-color: rgb(42 89 138);
+  --el-segmented-item-hover-color: #e5eaf3;
+  --el-segmented-item-hover-bg-color: #39393a;
+  --el-segmented-item-active-bg-color: #424243;
+  --el-segmented-item-disabled-color: #8d9095;
+}
+
+:deep(.el-segmented__item) {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 218 - 0
src/views/pms/stat/rdkb/utilization.vue

@@ -0,0 +1,218 @@
+<script lang="ts" setup>
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
+import { IotStatApi } from '@/api/pms/stat'
+import dayjs from 'dayjs'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import * as echarts from 'echarts'
+
+dayjs.extend(quarterOfYear)
+
+const chartRef = ref(null)
+let myChart: echarts.ECharts | null = null
+const currentTimeType = ref('month')
+
+const timeOptions = [
+  { label: '本月', value: 'month' },
+  { label: '本季度', value: 'quarter' },
+  { label: '本年', value: 'year' }
+]
+
+const getDateRange = (type: 'year' | 'quarter' | 'month') => {
+  const now = dayjs()
+  let start: dayjs.Dayjs, end: dayjs.Dayjs
+
+  if (type === 'year') {
+    start = now.startOf('year')
+    end = now.endOf('year')
+  } else if (type === 'quarter') {
+    start = now.startOf('quarter')
+    end = now.endOf('quarter')
+  } else {
+    start = now.startOf('month')
+    end = now.endOf('month')
+  }
+
+  return {
+    start: start.format('YYYY-MM-DD HH:mm:ss'),
+    end: end.format('YYYY-MM-DD HH:mm:ss')
+  }
+}
+
+const fetchData = async () => {
+  if (myChart) {
+    myChart.showLoading({
+      text: '加载中 ...',
+      color: '#409eff',
+      textColor: '#B6C8DA',
+      maskColor: 'rgba(0, 0, 0, 0.2)'
+    })
+  }
+
+  const { start, end } = getDateRange(currentTimeType.value as 'year' | 'quarter' | 'month')
+
+  const params = {
+    'createTime[0]': start,
+    'createTime[1]': end,
+    timeType: currentTimeType.value
+  }
+
+  try {
+    let list: any[] = []
+
+    const res = await IotStatApi.getUtilization(params)
+    if (res && Array.isArray(res)) list = res
+
+    renderChart(list)
+  } catch (error) {
+    console.error('Workload API Error:', error)
+  } finally {
+    myChart?.hideLoading()
+  }
+}
+
+const renderChart = (data: any[]) => {
+  if (!myChart) return
+
+  const xAxisData = data.map((item) => item.projectDeptName.replace('项目部', ''))
+
+  const seriesData = data.map((item) => {
+    const val = item.utilizationRate
+    if (val === null || val === undefined || isNaN(val)) return 0
+    return parseFloat((val * 100).toFixed(2))
+  })
+
+  const option: echarts.EChartsOption = {
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(0,0,0,0.7)',
+      borderColor: '#409eff',
+      textStyle: { color: '#fff' },
+      axisPointer: {
+        type: 'shadow',
+        shadowStyle: { color: 'rgba(255, 255, 255, 0.1)' }
+      },
+      // 自定义 Tooltip 内容,加上 %
+      formatter: (params: any) => {
+        const item = params[0]
+        return `${item.name}<br/>
+                <span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${item.color.colorStops ? item.color.colorStops[0].color : item.color};"></span>
+                ${item.seriesName}: <span style="font-weight:bold; color: #fff">${item.value}%</span>`
+      }
+    },
+    grid: { top: '15%', left: '3%', right: '3%', bottom: '5%', containLabel: true },
+    xAxis: {
+      data: xAxisData,
+      type: 'category',
+      boundaryGap: true,
+      axisLabel: { color: '#B6C8DA', interval: 0, fontSize: 12 },
+      axisLine: { lineStyle: { color: '#B6C8DA' } },
+      axisTick: { show: false }
+    },
+    yAxis: {
+      type: 'value',
+      name: '利用率 (%)',
+      axisLabel: { color: '#B6C8DA', formatter: '{value}' },
+      nameTextStyle: { color: '#B6C8DA', padding: [0, 0, 0, 10] },
+      axisLine: { lineStyle: { color: '#B6C8DA' } },
+      splitLine: { lineStyle: { color: '#457794', type: 'dashed' } }
+    },
+    series: {
+      name: '设备利用率',
+      type: 'bar',
+      showBackground: true,
+      backgroundStyle: {
+        color: 'rgba(180, 180, 180, 0.1)',
+        borderRadius: [4, 4, 0, 0]
+      },
+      itemStyle: {
+        borderRadius: [4, 4, 0, 0],
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#23D0F6' },
+          { offset: 1, color: '#1A7BF8' }
+        ])
+      },
+      emphasis: {
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#4FFBDF' },
+            { offset: 1, color: '#23D0F6' }
+          ])
+        }
+      },
+      data: seriesData
+    }
+  }
+
+  myChart.setOption(option)
+}
+
+const handleTimeChange = () => {
+  fetchData()
+}
+
+const resizeChart = () => myChart?.resize()
+
+onMounted(() => {
+  nextTick(() => {
+    myChart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+    fetchData()
+    window.addEventListener('resize', resizeChart)
+  })
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  myChart?.dispose()
+})
+</script>
+
+<template>
+  <div class="card size-full rounded-lg p-4 flex flex-col">
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2 items-center">
+        <div class="w-1 h-4 bg-[#00E5FF] rounded-full shadow-[0_0_8px_#00E5FF]"></div>
+        <div class="text-[#e0e0e0] text-lg font-bold">设备利用率</div>
+      </div>
+      <el-segmented
+        size="default"
+        v-model="currentTimeType"
+        :options="timeOptions"
+        @change="handleTimeChange"
+        class="dark-segmented w-50!"
+        block
+      />
+    </div>
+    <div ref="chartRef" class="flex-1 w-full min-h-0"></div>
+  </div>
+</template>
+
+<style scoped>
+.card {
+  background-color: rgb(0 0 0 / 30%);
+  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%);
+  }
+}
+
+.dark-segmented {
+  --el-segmented-item-selected-color: #e5eaf3;
+  --el-border-radius-base: 16px;
+  --el-segmented-color: #cfd3dc;
+  --el-segmented-bg-color: #262727;
+  --el-segmented-item-selected-bg-color: #409eff;
+  --el-segmented-item-selected-disabled-bg-color: rgb(42 89 138);
+  --el-segmented-item-hover-color: #e5eaf3;
+  --el-segmented-item-hover-bg-color: #39393a;
+  --el-segmented-item-active-bg-color: #424243;
+  --el-segmented-item-disabled-color: #8d9095;
+}
+
+:deep(.el-segmented__item) {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 361 - 0
src/views/pms/stat/rdkb/workload.vue

@@ -0,0 +1,361 @@
+<script lang="ts" setup>
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
+import { IotStatApi } from '@/api/pms/stat'
+import dayjs from 'dayjs'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import * as echarts from 'echarts'
+
+dayjs.extend(quarterOfYear)
+
+const chartRef = ref(null)
+let myChart: echarts.ECharts | null = null
+const currentTimeType = ref('month')
+
+const timeOptions = [
+  { label: '本月', value: 'month' },
+  { label: '本季度', value: 'quarter' },
+  { label: '本年', value: 'year' }
+]
+
+const fieldConfig = [
+  { key: 'ylWellCount', name: '压裂井数' },
+  { key: 'lyWellCount', name: '连油井数' },
+  { key: 'cumulativePumpTrips', name: '泵车台次' },
+  { key: 'cumulativeWorkingLayers', name: '压裂层数' }
+]
+
+const getDateRange = (type: 'year' | 'quarter' | 'month') => {
+  const now = dayjs()
+  let start: dayjs.Dayjs, end: dayjs.Dayjs
+
+  if (type === 'year') {
+    start = now.startOf('year')
+    end = now.endOf('year')
+  } else if (type === 'quarter') {
+    start = now.startOf('quarter')
+    end = now.endOf('quarter')
+  } else {
+    start = now.startOf('month')
+    end = now.endOf('month')
+  }
+
+  return {
+    start: start.format('YYYY-MM-DD HH:mm:ss'),
+    end: end.format('YYYY-MM-DD HH:mm:ss')
+  }
+}
+
+const fetchData = async () => {
+  if (myChart) {
+    myChart.showLoading({
+      text: '加载中 ...',
+      color: '#409eff',
+      textColor: '#B6C8DA',
+      maskColor: 'rgba(0, 0, 0, 0.2)'
+    })
+  }
+
+  const { start, end } = getDateRange(currentTimeType.value as 'year' | 'quarter' | 'month')
+
+  const params = {
+    deptId: 163,
+    'createTime[0]': start,
+    'createTime[1]': end,
+    timeType: currentTimeType.value
+  }
+
+  try {
+    let list: any[] = []
+
+    if (currentTimeType.value === 'year') {
+      const res = await IotStatApi.getRdWorkloadYear(params)
+      if (res && Array.isArray(res)) list = res
+    } else {
+      const res = await IotStatApi.getRdWorkload(params)
+      if (res && res.list) list = res.list
+    }
+
+    renderChart(list)
+  } catch (error) {
+    console.error('Workload API Error:', error)
+  } finally {
+    myChart?.hideLoading()
+  }
+}
+
+const renderChart = (data: any[]) => {
+  if (!myChart) return
+  const isYear = currentTimeType.value === 'year'
+
+  // --- 高亮配色方案 ---
+  const colorPalettes = [
+    // 1. 冰蓝霓虹 (压裂井数) - 极亮青色 -> 深蓝
+    {
+      line: '#00E5FF',
+      start: '#00E5FF',
+      end: '#2979FF'
+    },
+    // 2. 日落流金 (连油井数) - 亮黄 -> 亮橙
+    {
+      line: '#FFD740',
+      start: '#FFD740',
+      end: '#FF6D00'
+    },
+    // 3. 赛博紫 (泵车台次) - 亮粉紫 -> 深紫
+    {
+      line: '#EA80FC',
+      start: '#EA80FC',
+      end: '#651FFF'
+    },
+    // 4. 极光绿 (压裂层数) - 荧光绿 -> 青绿
+    {
+      line: '#69F0AE',
+      start: '#69F0AE',
+      end: '#00C853'
+    }
+  ]
+
+  const xAxisData = data.map((item) =>
+    isYear ? item.reportDate : item.projectDeptName.replace('项目部', '')
+  )
+
+  const option: echarts.EChartsOption = {
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(18, 26, 44, 0.9)', // 更深色的背景,对比更强
+      borderColor: '#409eff',
+      textStyle: { color: '#fff' },
+      padding: [10, 15],
+      axisPointer: {
+        type: isYear ? 'line' : 'shadow',
+        lineStyle: { color: '#fff', type: 'dashed' },
+        shadowStyle: { color: 'rgba(255, 255, 255, 0.1)' }
+      }
+    },
+    legend: {
+      data: fieldConfig.map((item) => item.name),
+      textStyle: { color: '#E0E0E0' }, // 图例文字调亮
+      top: 0,
+      itemWidth: 14,
+      itemHeight: 14
+    },
+    grid: {
+      top: '18%', // 留出更多空间给图例
+      left: '2%',
+      right: '2%',
+      bottom: '2%',
+      containLabel: true
+    },
+    xAxis: {
+      data: xAxisData,
+      type: 'category',
+      boundaryGap: !isYear,
+      axisLabel: {
+        color: '#D1D5DB', // X轴文字调亮
+        interval: 0,
+        fontSize: 12,
+        formatter: (value: string) => {
+          // 如果名字太长换行显示
+          return value.length > 4 ? value.slice(0, 4) + '\n' + value.slice(4) : value
+        }
+      },
+      axisLine: { lineStyle: { color: '#4B5563' } },
+      axisTick: { show: false }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { color: '#D1D5DB' },
+      splitLine: {
+        lineStyle: { color: '#457794', type: 'dashed' } // 网格线稍微亮一点
+      }
+    },
+    series: fieldConfig.map((item, index) => {
+      const palette = colorPalettes[index % colorPalettes.length]
+
+      const seriesBase = {
+        name: item.name,
+        data: data.map((d) => {
+          const val = d[item.key]
+          return val === null || val === undefined || isNaN(val) ? 0 : val
+        })
+      }
+
+      if (isYear) {
+        // --- 折线图 (年) ---
+        return {
+          ...seriesBase,
+          type: 'line',
+          smooth: true,
+          showSymbol: false,
+          symbol: 'circle',
+          symbolSize: 8,
+          // 线条非常亮
+          lineStyle: { width: 3, color: palette.line, shadowColor: palette.line, shadowBlur: 10 },
+          itemStyle: { color: palette.line, borderColor: '#fff', borderWidth: 2 },
+          areaStyle: {
+            opacity: 0.5, // 区域透明度适中
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: palette.start }, // 顶部颜色同线条
+              { offset: 1, color: 'rgba(0,0,0,0)' }
+            ])
+          },
+          emphasis: { focus: 'series' }
+        }
+      } else {
+        // --- 柱状图 (月/季) ---
+        return {
+          ...seriesBase,
+          type: 'bar',
+          barMaxWidth: 14,
+          barGap: '30%',
+          itemStyle: {
+            borderRadius: [4, 4, 0, 0],
+            // 柱子使用实色渐变,不再过度透明,显得“脏”
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: palette.start }, // 100% 亮色
+              { offset: 1, color: palette.end } // 底部颜色,透明度由 hex 决定(这里是实色)
+            ]),
+            // 给柱子加一点光晕
+            shadowColor: palette.end,
+            shadowBlur: 5
+          },
+          emphasis: {
+            focus: 'series',
+            itemStyle: {
+              // 鼠标悬停时变得更亮
+              color: palette.line
+            }
+          }
+        }
+      }
+    }) as any
+  }
+
+  myChart.setOption(option)
+}
+// const renderChart = (data: any[]) => {
+//   if (!myChart) return
+//   const isYear = currentTimeType.value === 'year'
+
+//   const color = ['#5470c6', '#f1d209', '#e14f0f', '#91cc75']
+
+//   const xAxisData = data.map((item) =>
+//     isYear ? item.reportDate : item.projectDeptName.replace('项目部', '')
+//   )
+
+//   const option: echarts.EChartsOption = {
+//     tooltip: {
+//       trigger: 'axis',
+//       axisPointer: {
+//         type: isYear ? 'cross' : 'shadow',
+//         label: { backgroundColor: '#6a7985' }
+//       }
+//     },
+//     legend: {
+//       data: fieldConfig.map((item) => item.name),
+//       textStyle: { color: '#B6C8DA' }
+//     },
+//     grid: { top: '15%', left: '2%', right: '2%', bottom: '2%', containLabel: true },
+//     xAxis: {
+//       data: xAxisData,
+//       type: 'category',
+//       boundaryGap: !isYear,
+//       axisLabel: { color: '#B6C8DA', interval: 0 },
+//       axisLine: { lineStyle: { color: '#B6C8DA' } }
+//     },
+//     yAxis: {
+//       type: 'value',
+//       axisLabel: { color: '#B6C8DA' },
+//       splitLine: { lineStyle: { color: '#457794', type: 'dashed' } },
+//       axisLine: { lineStyle: { color: '#B6C8DA' } }
+//     },
+//     series: fieldConfig.map((item, index) => ({
+//       name: item.name,
+//       type: isYear ? 'line' : 'bar',
+//       smooth: true,
+//       barMaxWidth: 30,
+//       symbol: 'circle',
+//       symbolSize: 8,
+//       itemStyle: { color: color[index] },
+//       emphasis: { focus: 'series' },
+//       lineStyle: { width: 3 },
+//       data: data.map((d) => {
+//         const val = d[item.key]
+//         return val === null || val === undefined || isNaN(val) ? 0 : val
+//       })
+//     }))
+//   }
+
+//   myChart.setOption(option)
+// }
+
+const handleTimeChange = () => {
+  fetchData()
+}
+
+const resizeChart = () => myChart?.resize()
+
+onMounted(() => {
+  nextTick(() => {
+    myChart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+    fetchData()
+    window.addEventListener('resize', resizeChart)
+  })
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeChart)
+  myChart?.dispose()
+})
+</script>
+
+<template>
+  <div class="card size-full rounded-lg p-4 flex flex-col">
+    <div class="flex justify-between items-center mb-4">
+      <div class="flex items-center gap-2 items-center">
+        <div class="w-1 h-4 bg-[#00E5FF] rounded-full shadow-[0_0_8px_#00E5FF]"></div>
+        <div class="text-[#e0e0e0] text-lg font-bold">工作量汇总</div>
+      </div>
+      <el-segmented
+        size="default"
+        v-model="currentTimeType"
+        :options="timeOptions"
+        @change="handleTimeChange"
+        class="dark-segmented w-50!"
+        block
+      />
+    </div>
+    <div ref="chartRef" class="flex-1 w-full min-h-0"></div>
+  </div>
+</template>
+
+<style scoped>
+.card {
+  background-color: rgb(0 0 0 / 30%);
+  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%);
+  }
+}
+
+.dark-segmented {
+  --el-segmented-item-selected-color: #e5eaf3;
+  --el-border-radius-base: 16px;
+  --el-segmented-color: #cfd3dc;
+  --el-segmented-bg-color: #262727;
+  --el-segmented-item-selected-bg-color: #409eff;
+  --el-segmented-item-selected-disabled-bg-color: rgb(42 89 138);
+  --el-segmented-item-hover-color: #e5eaf3;
+  --el-segmented-item-hover-bg-color: #39393a;
+  --el-segmented-item-active-bg-color: #424243;
+  --el-segmented-item-disabled-color: #8d9095;
+}
+
+:deep(.el-segmented__item) {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 36 - 11
src/views/report-statistics/costs.vue

@@ -5,6 +5,7 @@ import CountTo from '@/components/count-to1.vue'
 import { IotReportApi } from '@/api/pms/report'
 import { useDebounceFn } from '@vueuse/core'
 import download from '@/utils/download'
+import { rangeShortcuts } from '@/utils/formatTime'
 
 // 定义时间类型
 type TimeType = 'year' | 'month' | 'day'
@@ -24,7 +25,7 @@ const timeOptions: { label: string; value: TimeType }[] = [
   { label: '日', value: 'day' }
 ]
 
-const activeTimeType = ref<TimeType>('year')
+const activeTimeType = ref<TimeType | undefined>('year')
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10
@@ -214,6 +215,14 @@ const handleExport = async () => {
     exportLoading.value = false
   }
 }
+
+const handleClear = () => {
+  handleTimeChange('year')
+}
+
+const handleChange = () => {
+  activeTimeType.value = undefined
+}
 </script>
 
 <template>
@@ -303,16 +312,32 @@ const handleExport = async () => {
         </section>
       </div>
       <div class="flex justify-between gap-4">
-        <el-button-group size="default">
-          <el-button
-            v-for="item in timeOptions"
-            :key="item.value"
-            :type="activeTimeType === item.value ? 'primary' : ''"
-            @click="handleTimeChange(item.value)"
-          >
-            {{ item.label }}
-          </el-button>
-        </el-button-group>
+        <div class="flex items-center gap-4">
+          <el-date-picker
+            size="default"
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            class="!w-220px"
+            @clear="handleClear"
+            @change="handleChange"
+            :clearable="false"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          />
+          <el-button-group size="default">
+            <el-button
+              v-for="item in timeOptions"
+              :key="item.value"
+              :type="activeTimeType === item.value ? 'primary' : ''"
+              @click="handleTimeChange(item.value)"
+            >
+              {{ item.label }}
+            </el-button>
+          </el-button-group>
+        </div>
         <div class="flex items-center gap-2">
           <el-button size="default" @click="handleReset">重置</el-button>
           <el-button

+ 21 - 0
src/views/report-statistics/daily-report.vue

@@ -6,6 +6,7 @@ import dayjs from 'dayjs'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
 import { useUserStore } from '@/store/modules/user'
+import download from '@/utils/download'
 
 defineOptions({ name: 'DailyReport' })
 
@@ -429,6 +430,23 @@ watch(
 const expandRowKeys = computed(() => {
   return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
 })
+
+const exportLoading = ref(false)
+
+const handleExport = () => {
+  exportLoading.value = true
+  if (tab.value === '井') {
+    IotRhDailyReportApi.exportIotRhDailyReportWell(query.value).then((data) => {
+      download.excel(data, '瑞恒井日报统计.xls')
+      exportLoading.value = false
+    })
+  } else {
+    IotRhDailyReportApi.exportIotRhDailyReportTeam(query.value).then((data) => {
+      download.excel(data, '瑞恒队伍日报统计.xls')
+      exportLoading.value = false
+    })
+  }
+}
 </script>
 
 <template>
@@ -479,6 +497,9 @@ const expandRowKeys = computed(() => {
           <Icon icon="ep:search" class="mr-5px" /> 搜索
         </el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
+        <el-button plain type="success" @click="handleExport" :loading="exportLoading">
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
       </el-form-item>
     </el-form>
 

+ 31 - 2
src/views/report-statistics/work-order-completion.vue

@@ -5,6 +5,7 @@ import { useDebounceFn } from '@vueuse/core'
 import MiniBarChart from '@/components/WorkOrderCompletionBar/index.vue'
 import CountTo from '@/components/count-to1.vue'
 import { IotReportApi } from '@/api/pms/report'
+import { rangeShortcuts } from '@/utils/formatTime'
 
 // 定义时间类型
 type TimeType = 'year' | 'month' | 'day'
@@ -40,7 +41,7 @@ const timeOptions: { label: string; value: TimeType }[] = [
   { label: '日', value: 'day' }
 ]
 
-const activeTimeType = ref<TimeType>('year')
+const activeTimeType = ref<TimeType | undefined>('year')
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10
@@ -247,6 +248,14 @@ function handleReset() {
   handleTimeChange('year')
   selectType(undefined)
 }
+
+const handleClear = () => {
+  handleTimeChange('year')
+}
+
+const handleChange = () => {
+  activeTimeType.value = undefined
+}
 </script>
 
 <template>
@@ -346,6 +355,20 @@ function handleReset() {
       </div>
       <div class="flex justify-between">
         <div class="flex gap-4">
+          <el-date-picker
+            size="default"
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            class="!w-220px"
+            @clear="handleClear"
+            @change="handleChange"
+            :clearable="false"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          />
           <el-button-group size="default">
             <el-button
               v-for="item in timeOptions"
@@ -356,9 +379,15 @@ function handleReset() {
               {{ item.label }}
             </el-button>
           </el-button-group>
+        </div>
+        <div class="flex gap-2">
           <el-button size="default" @click="handleReset">重置</el-button>
+          <!-- @click="handleExport"
+            :loading="exportLoading" -->
+          <el-button size="default" plain type="success">
+            <Icon icon="ep:download" class="mr-5px" /> 导出
+          </el-button>
         </div>
-        <el-button size="default" type="primary">导出</el-button>
       </div>
     </div>
     <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">