Zimo 1 день назад
Родитель
Сommit
867b1a5e54

+ 1 - 1
.env.local

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

+ 9 - 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 })
   },
@@ -183,5 +189,8 @@ export const IotStatApi = {
   },
   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` })
   }
 }

+ 20 - 3
src/views/pms/stat/rdkb.vue

@@ -189,11 +189,19 @@
         </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-card class="chart-card" shadow="never">
+      </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">{{
@@ -218,7 +226,6 @@
             <div ref="utilizationRef" style="width: 100%; height: 360px"></div>
           </div>
         </el-card> -->
-      </el-col>
       <!-- 添加两个卡片 -->
       <!--          <div class="flex justify-between mb-2">-->
       <!--            <el-card class="stat-card">-->
@@ -513,6 +520,8 @@ 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'
 
 /** 会员统计 */
 defineOptions({ name: 'IotRdStat' })
@@ -642,6 +651,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

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

@@ -0,0 +1,143 @@
+<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: '压裂设备', color: '#00E5FF' }, // 亮青
+  { key: 'b', name: '连油设备', color: '#FFD740' }, // 亮黄
+  { key: 'c', name: '辅助设备', color: '#69F0AE' } // 荧光绿
+] as const
+
+const fetchData = async () => {
+  loading.value = true
+  try {
+    // 假设接口直接返回 data 对象,或者是 res.data
+    // 请根据实际 axios 拦截器的封装情况调整
+    const res = await IotStatApi.getWhl()
+
+    // 容错处理,防止接口返回空
+    if (res) {
+      // 如果拦截器直接返回了 data 下面的内容
+      stats.value = res
+      // 如果拦截器没拆包,可能是 stats.value = res.data
+    }
+  } 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>

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

@@ -0,0 +1,287 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import dayjs from 'dayjs'
+import CountTo from '@/components/count-to1.vue'
+
+// --- 类型定义 ---
+type TimeType = 'day' | 'month' | 'year'
+
+// 模拟的生产异常数据接口
+interface ProductionExceptions {
+  standby: number // 待命
+  maintenance: number // 维修
+  exception: number // 异常
+  material: number // 物资
+  personnel: number // 人员
+  weather: number // 天气
+  thirdParty: number // 三方
+  client: number // 甲方
+  wellhead: number // 井口
+}
+
+// 模拟的巡检异常数据接口
+interface InspectionExceptions {
+  equipment: number // 异常设备
+  point: number // 异常点
+}
+
+// --- 状态管理 ---
+const currentTimeType = ref<TimeType>('month')
+const loading = ref(false)
+
+// 时间选项:日、月、年
+const timeOptions = [
+  { label: '今日', value: 'day' },
+  { label: '本月', value: 'month' },
+  { label: '本年', value: 'year' }
+]
+
+// 数据响应式对象
+const productionData = ref<ProductionExceptions>({
+  standby: 0,
+  maintenance: 0,
+  exception: 0,
+  material: 0,
+  personnel: 0,
+  weather: 0,
+  thirdParty: 0,
+  client: 0,
+  wellhead: 0
+})
+
+const inspectionData = ref<InspectionExceptions>({
+  equipment: 0,
+  point: 0
+})
+
+// --- 配置项:用于 v-for 循环渲染 ---
+const productionConfig = [
+  { key: 'standby', name: '待命' },
+  { key: 'maintenance', name: '维修' },
+  { key: 'exception', name: '异常' },
+  { key: 'material', name: '物资' },
+  { key: 'personnel', name: '人员' },
+  { key: 'weather', name: '天气' },
+  { key: 'thirdParty', name: '三方' },
+  { key: 'client', name: '甲方' },
+  { key: 'wellhead', name: '井口' }
+]
+
+const inspectionConfig = [
+  { key: 'equipment', name: '异常设备' },
+  { key: 'point', name: '异常点' }
+]
+
+// --- 模拟 API 请求 ---
+const fetchData = async () => {
+  loading.value = true
+
+  // 模拟获取时间范围
+  const now = dayjs()
+  let start = '',
+    end = ''
+  if (currentTimeType.value === 'day') {
+    start = now.startOf('day').format('YYYY-MM-DD HH:mm:ss')
+    end = now.endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  } else if (currentTimeType.value === 'month') {
+    start = now.startOf('month').format('YYYY-MM-DD HH:mm:ss')
+    end = now.endOf('month').format('YYYY-MM-DD HH:mm:ss')
+  } else {
+    start = now.startOf('year').format('YYYY-MM-DD HH:mm:ss')
+    end = now.endOf('year').format('YYYY-MM-DD HH:mm:ss')
+  }
+
+  console.log(`查询时间范围: ${start} - ${end}`)
+
+  // 模拟网络延迟
+  setTimeout(() => {
+    // 生成随机数模拟数据
+    const random = (max: number) => Math.floor(Math.random() * max)
+
+    // 假设这是 API 1: 获取生产异常
+    productionData.value = {
+      standby: random(20),
+      maintenance: random(10),
+      exception: random(5),
+      material: random(15),
+      personnel: random(5),
+      weather: random(8),
+      thirdParty: random(3),
+      client: random(5),
+      wellhead: random(10)
+    }
+
+    // 假设这是 API 2: 获取巡检异常
+    inspectionData.value = {
+      equipment: random(50),
+      point: random(100)
+    }
+
+    loading.value = false
+  }, 500)
+}
+
+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>

+ 4 - 1
src/views/pms/stat/rdkb/utilization.vue

@@ -169,7 +169,10 @@ onUnmounted(() => {
 <template>
   <div class="card size-full rounded-lg p-4 flex flex-col">
     <div class="flex justify-between items-center mb-4">
-      <div class="text-[#b6c8da] text-lg font-bold">设备利用率</div>
+      <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"

+ 4 - 1
src/views/pms/stat/rdkb/workload.vue

@@ -312,7 +312,10 @@ onUnmounted(() => {
 <template>
   <div class="card size-full rounded-lg p-4 flex flex-col">
     <div class="flex justify-between items-center mb-4">
-      <div class="text-[#b6c8da] text-lg font-bold">工作量汇总</div>
+      <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"