yanghao 4 dienas atpakaļ
vecāks
revīzija
5f6fee6cbc

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

@@ -159,6 +159,9 @@ export const IotStatApi = {
   getRyTeamRate: async (params: any) => {
   getRyTeamRate: async (params: any) => {
     return await request.get({ url: `rq/stat/ry/device/teamUtilizationRate`, params })
     return await request.get({ url: `rq/stat/ry/device/teamUtilizationRate`, params })
   },
   },
+  getRyProductionBriefs: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/productionBriefs`, params })
+  },
   getRdTeamRate: async (params: any) => {
   getRdTeamRate: async (params: any) => {
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
     return await request.get({ url: `rq/stat/rd/device/teamUtilizationRate`, params })
   },
   },
@@ -184,6 +187,9 @@ export const IotStatApi = {
   getSafeCount: async () => {
   getSafeCount: async () => {
     return await request.get({ url: `/rq/stat/home/safe` })
     return await request.get({ url: `/rq/stat/home/safe` })
   },
   },
+  getSafeCount1: async () => {
+    return await request.get({ url: `/rq/qhse-safe-day/get/dept/` + 158, params: { deptId: 158 } })
+  },
   getMttr: async () => {
   getMttr: async () => {
     return await request.get({ url: `/rq/stat/mttr` })
     return await request.get({ url: `/rq/stat/mttr` })
   },
   },

+ 7 - 13
src/layout/components/Menu/src/components/useRenderMenuItem.tsx

@@ -16,16 +16,11 @@ export const useRenderMenuItem = () =>
         .filter((v) => {
         .filter((v) => {
           if (currentSource === 'zhly') {
           if (currentSource === 'zhly') {
             return (
             return (
-              !v.meta?.hidden &&
-              [
-                '智慧连油',
-                '连油监控',
-                '监控查询',
-                '视频告警',
-                '分屏管理',
-                '告警配置',
-                '连油看板'
-              ].includes(v.meta?.title)
+              (!v.meta?.hidden &&
+                ['智慧连油', '连油监控', '监控查询', '视频告警', '告警配置', '连油看板'].includes(
+                  v.meta?.title
+                )) ||
+              (!v.meta?.hidden && ['/oli-connection/split_view1'].includes(v.path))
             )
             )
           } else if (currentSource === 'znzq') {
           } else if (currentSource === 'znzq') {
             return !v.meta?.hidden && ['/device_monitor'].includes(v.path)
             return !v.meta?.hidden && ['/device_monitor'].includes(v.path)
@@ -52,7 +47,7 @@ export const useRenderMenuItem = () =>
                 '仪器检测',
                 '仪器检测',
                 '台账管理',
                 '台账管理',
                 '证书管理',
                 '证书管理',
-                '危险源管理',
+                '危险源辨识',
                 '环境因素识别',
                 '环境因素识别',
                 '事故事件上报',
                 '事故事件上报',
                 '隐患排查',
                 '隐患排查',
@@ -82,8 +77,7 @@ export const useRenderMenuItem = () =>
           ) {
           ) {
             return (
             return (
               <ElMenuItem
               <ElMenuItem
-                index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
-              >
+                index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
                 {{
                 {{
                   default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
                   default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
                 }}
                 }}

+ 9 - 0
src/styles/kb.scss

@@ -91,6 +91,15 @@
   }
   }
 }
 }
 
 
+.kb-panel-title-text {
+  font-family: YouSheBiaoTiHei, sans-serif;
+  font-size: 24px;
+  font-weight: normal;
+  line-height: 1;
+  letter-spacing: 1px;
+  color: #03409b;
+}
+
 .summary-card {
 .summary-card {
   position: relative;
   position: relative;
   cursor: default;
   cursor: default;

+ 2 - 3
src/views/Home/kb/dayfinish.vue

@@ -214,7 +214,7 @@ onUnmounted(() => {
 <template>
 <template>
   <div class="panel flex flex-col">
   <div class="panel flex flex-col">
     <div class="panel-title h-12 flex items-center justify-between">
     <div class="panel-title h-12 flex items-center justify-between">
-      <div class="flex items-center">
+      <div class="kb-panel-title-text flex items-center">
         <div class="icon-decorator">
         <div class="icon-decorator">
           <span></span>
           <span></span>
           <span></span>
           <span></span>
@@ -232,8 +232,7 @@ onUnmounted(() => {
           :shortcuts="rangeShortcuts"
           :shortcuts="rangeShortcuts"
           :clearable="false"
           :clearable="false"
           class="w-260px!"
           class="w-260px!"
-          @change="handleDateChange"
-        />
+          @change="handleDateChange" />
       </div>
       </div>
     </div>
     </div>
     <div ref="chartRef" class="flex-1 min-h-0"></div>
     <div ref="chartRef" class="flex-1 min-h-0"></div>

+ 69 - 10
src/views/oli-connection/monitoring-board/chart.vue

@@ -142,11 +142,7 @@ const handleMessageUpdate = (_topic: string, data: any) => {
   if (!updatedNames.size) return
   if (!updatedNames.size) return
 
 
   updateDimensionValues(valueMap)
   updateDimensionValues(valueMap)
-
-  const latestTs = getLatestTimestamp() ?? Date.now()
-  trimChartDataToRealtimeWindow(latestTs)
-  applyRealtimeWindow(latestTs)
-  updateSeriesByNames(Array.from(updatedNames))
+  scheduleRealtimeChartUpdate(Array.from(updatedNames))
 }
 }
 
 
 watch(isConnected, (newVal) => {
 watch(isConnected, (newVal) => {
@@ -205,6 +201,8 @@ let chart: echarts.ECharts | null = null
 
 
 const chartRealtimeMode = ref(true)
 const chartRealtimeMode = ref(true)
 let chartLoadVersion = 0
 let chartLoadVersion = 0
+let realtimeUpdateFrame: number | null = null
+const pendingRealtimeSeriesNames = new Set<string>()
 
 
 const TREND_AXIS_MIN = 0
 const TREND_AXIS_MIN = 0
 const TREND_AXIS_MAX = 100
 const TREND_AXIS_MAX = 100
@@ -407,12 +405,23 @@ function updateDimensionValues(valueMap: Map<string, number>) {
   })
   })
 }
 }
 
 
+function getChartAnimationOptions() {
+  return {
+    animation: !chartRealtimeMode.value,
+    animationDuration: chartRealtimeMode.value ? 0 : 200,
+    animationEasing: 'linear',
+    animationDurationUpdate: chartRealtimeMode.value ? 0 : 200,
+    animationEasingUpdate: 'linear'
+  }
+}
+
 function updateSeriesByNames(names: string[]) {
 function updateSeriesByNames(names: string[]) {
   if (!chart) render()
   if (!chart) render()
   if (!chart || !names.length) return
   if (!chart || !names.length) return
 
 
   chart.setOption(
   chart.setOption(
     {
     {
+      ...getChartAnimationOptions(),
       legend: {
       legend: {
         selected: selectedDimension.value
         selected: selectedDimension.value
       },
       },
@@ -429,6 +438,58 @@ function updateSeriesByNames(names: string[]) {
   )
   )
 }
 }
 
 
+function flushRealtimeChartUpdate() {
+  realtimeUpdateFrame = null
+
+  const names = Array.from(pendingRealtimeSeriesNames)
+  pendingRealtimeSeriesNames.clear()
+
+  if (!chart) render()
+  if (!chart || !names.length) return
+
+  const latestTs = getLatestTimestamp() ?? Date.now()
+  trimChartDataToRealtimeWindow(latestTs)
+
+  chart.setOption(
+    {
+      ...getChartAnimationOptions(),
+      xAxis: {
+        min: latestTs - REALTIME_WINDOW_MS,
+        max: getRealtimeXAxisMax(latestTs)
+      },
+      legend: {
+        selected: selectedDimension.value
+      },
+      yAxis: getTrendYAxisOptions(),
+      series: names.map((name) => ({
+        id: name,
+        name,
+        data: buildSeriesData(name)
+      }))
+    },
+    {
+      lazyUpdate: true
+    }
+  )
+}
+
+function scheduleRealtimeChartUpdate(names: string[]) {
+  names.forEach((name) => pendingRealtimeSeriesNames.add(name))
+
+  if (realtimeUpdateFrame !== null) return
+
+  realtimeUpdateFrame = window.requestAnimationFrame(flushRealtimeChartUpdate)
+}
+
+function cancelRealtimeChartUpdate() {
+  if (realtimeUpdateFrame !== null) {
+    window.cancelAnimationFrame(realtimeUpdateFrame)
+    realtimeUpdateFrame = null
+  }
+
+  pendingRealtimeSeriesNames.clear()
+}
+
 function handleClickSpec(modelName: string) {
 function handleClickSpec(modelName: string) {
   selectedDimension.value[modelName] = !selectedDimension.value[modelName]
   selectedDimension.value[modelName] = !selectedDimension.value[modelName]
   chart?.setOption({
   chart?.setOption({
@@ -465,11 +526,7 @@ function render() {
   chart.setOption(
   chart.setOption(
     {
     {
       color: dimensions.value.map((item) => item.color),
       color: dimensions.value.map((item) => item.color),
-      animation: true,
-      animationDuration: 200,
-      animationEasing: 'linear',
-      animationDurationUpdate: 200,
-      animationEasingUpdate: 'linear',
+      ...getChartAnimationOptions(),
       grid: {
       grid: {
         left: '2%',
         left: '2%',
         top: '5%',
         top: '5%',
@@ -692,6 +749,7 @@ watch(
     await cancelAllRequests()
     await cancelAllRequests()
 
 
     destroy()
     destroy()
+    cancelRealtimeChartUpdate()
     chartLoadVersion += 1
     chartLoadVersion += 1
 
 
     if (chart) chart.clear()
     if (chart) chart.clear()
@@ -703,6 +761,7 @@ watch(
 onUnmounted(() => {
 onUnmounted(() => {
   chartLoadVersion += 1
   chartLoadVersion += 1
   destroy()
   destroy()
+  cancelRealtimeChartUpdate()
 
 
   window.removeEventListener('resize', resizeChart)
   window.removeEventListener('resize', resizeChart)
   chart?.dispose()
   chart?.dispose()

+ 78 - 11
src/views/oli-connection/monitoring/detail.vue

@@ -105,6 +105,13 @@ function getInitialDateRange() {
   return getDateRangeByWindow()
   return getDateRangeByWindow()
 }
 }
 
 
+function isRangeGreaterThan120Minutes(range: string[]) {
+  const begin = dayjs(range[0])
+  const end = dayjs(range[1])
+
+  return begin.isValid() && end.isValid() && end.diff(begin, 'minute', true) > 120
+}
+
 const REALTIME_RIGHT_BLANK_MS = 60 * 1000
 const REALTIME_RIGHT_BLANK_MS = 60 * 1000
 const selectedDate = ref<string[]>(getInitialDateRange())
 const selectedDate = ref<string[]>(getInitialDateRange())
 const chartRef = ref<HTMLDivElement | null>(null)
 const chartRef = ref<HTMLDivElement | null>(null)
@@ -115,6 +122,8 @@ const chartRealtimeMode = ref(true)
 const token = ref('')
 const token = ref('')
 let chart: echarts.ECharts | null = null
 let chart: echarts.ECharts | null = null
 let chartLoadVersion = 0
 let chartLoadVersion = 0
+let realtimeUpdateFrame: number | null = null
+const pendingRealtimeSeriesNames = new Set<string>()
 const { toggle, isFullscreen } = useFullscreen(targetArea)
 const { toggle, isFullscreen } = useFullscreen(targetArea)
 
 
 interface ChartPoint {
 interface ChartPoint {
@@ -379,11 +388,22 @@ function appendChartPoint(name: string, point: ChartPoint) {
   chartData.value[name] = series
   chartData.value[name] = series
 }
 }
 
 
+function getChartAnimationOptions() {
+  return {
+    animation: !chartRealtimeMode.value,
+    animationDuration: chartRealtimeMode.value ? 0 : 200,
+    animationEasing: 'linear',
+    animationDurationUpdate: chartRealtimeMode.value ? 0 : 200,
+    animationEasingUpdate: 'linear'
+  }
+}
+
 function updateSingleSeries(name: string) {
 function updateSingleSeries(name: string) {
   if (!chart) renderChart()
   if (!chart) renderChart()
   if (!chart) return
   if (!chart) return
 
 
   chart.setOption({
   chart.setOption({
+    ...getChartAnimationOptions(),
     legend: {
     legend: {
       selected: selectedDimension.value
       selected: selectedDimension.value
     },
     },
@@ -402,6 +422,37 @@ function updateSeriesByNames(names: string[]) {
   if (!chart) return
   if (!chart) return
 
 
   chart.setOption({
   chart.setOption({
+    ...getChartAnimationOptions(),
+    legend: {
+      selected: selectedDimension.value
+    },
+    yAxis: getTrendYAxisOptions(),
+    series: names.map((name) => ({
+      id: name,
+      name,
+      data: chartData.value[name]?.map((item) => mapChartPoint(item, name)) ?? []
+    }))
+  })
+}
+
+function flushRealtimeChartUpdate() {
+  realtimeUpdateFrame = null
+
+  const names = Array.from(pendingRealtimeSeriesNames)
+  pendingRealtimeSeriesNames.clear()
+
+  if (!chart) renderChart()
+  if (!chart || !names.length) return
+
+  const latestTs = getLatestTimestamp() ?? Date.now()
+  trimChartDataToRealtimeWindow(latestTs)
+
+  chart.setOption({
+    ...getChartAnimationOptions(),
+    xAxis: {
+      min: latestTs - chartWindowMs.value,
+      max: getRealtimeXAxisMax(latestTs)
+    },
     legend: {
     legend: {
       selected: selectedDimension.value
       selected: selectedDimension.value
     },
     },
@@ -414,6 +465,23 @@ function updateSeriesByNames(names: string[]) {
   })
   })
 }
 }
 
 
+function scheduleRealtimeChartUpdate(names: string[]) {
+  names.forEach((name) => pendingRealtimeSeriesNames.add(name))
+
+  if (realtimeUpdateFrame !== null) return
+
+  realtimeUpdateFrame = window.requestAnimationFrame(flushRealtimeChartUpdate)
+}
+
+function cancelRealtimeChartUpdate() {
+  if (realtimeUpdateFrame !== null) {
+    window.cancelAnimationFrame(realtimeUpdateFrame)
+    realtimeUpdateFrame = null
+  }
+
+  pendingRealtimeSeriesNames.clear()
+}
+
 function renderChart() {
 function renderChart() {
   if (!chartRef.value) return
   if (!chartRef.value) return
 
 
@@ -426,11 +494,7 @@ function renderChart() {
 
 
   chart.setOption(
   chart.setOption(
     {
     {
-      animation: true,
-      animationDuration: 200,
-      animationEasing: 'linear',
-      animationDurationUpdate: 200,
-      animationEasingUpdate: 'linear',
+      ...getChartAnimationOptions(),
       color: dimensions.value.map((item) => item.color),
       color: dimensions.value.map((item) => item.color),
       grid: {
       grid: {
         left: '2%',
         left: '2%',
@@ -581,11 +645,7 @@ function handleMessageUpdate(_topic: string, payload: any) {
   if (!updatedNames.size) return
   if (!updatedNames.size) return
 
 
   updateDimensionValues(valueMap)
   updateDimensionValues(valueMap)
-
-  const latestTs = getLatestTimestamp() ?? Date.now()
-  trimChartDataToRealtimeWindow(latestTs)
-  applyRealtimeWindow(latestTs)
-  updateSeriesByNames(Array.from(updatedNames))
+  scheduleRealtimeChartUpdate(Array.from(updatedNames))
 }
 }
 
 
 async function ensureToken() {
 async function ensureToken() {
@@ -622,7 +682,10 @@ async function initLoadChartData({
       dimensions.value.map(async (item) => {
       dimensions.value.map(async (item) => {
         item.response = true
         item.response = true
         try {
         try {
-          const res = await IotStatApi.getDeviceInfoChartly(
+          const getChartData = isRangeGreaterThan120Minutes(selectedDate.value)
+            ? IotStatApi.getDeviceInfoChart
+            : IotStatApi.getDeviceInfoChartly
+          const res = await getChartData(
             data.value.deviceCode,
             data.value.deviceCode,
             item.identifier,
             item.identifier,
             selectedDate.value[0],
             selectedDate.value[0],
@@ -673,6 +736,7 @@ async function resetChart() {
   selectedDate.value = getInitialDateRange()
   selectedDate.value = getInitialDateRange()
   await cancelAllRequests()
   await cancelAllRequests()
   destroy()
   destroy()
+  cancelRealtimeChartUpdate()
   chart?.clear()
   chart?.clear()
   await initPage(false)
   await initPage(false)
 }
 }
@@ -692,6 +756,7 @@ async function handleChartWindowMinutesChange(value?: number) {
   chartLoadVersion += 1
   chartLoadVersion += 1
   await cancelAllRequests()
   await cancelAllRequests()
   destroy()
   destroy()
+  cancelRealtimeChartUpdate()
   chart?.clear()
   chart?.clear()
   await nextTick()
   await nextTick()
   renderChart()
   renderChart()
@@ -708,6 +773,7 @@ async function handleDateChange() {
   chartRealtimeMode.value = false
   chartRealtimeMode.value = false
   await cancelAllRequests()
   await cancelAllRequests()
   destroy()
   destroy()
+  cancelRealtimeChartUpdate()
   chart?.clear()
   chart?.clear()
   await nextTick()
   await nextTick()
   renderChart()
   renderChart()
@@ -852,6 +918,7 @@ onMounted(() => {
 onUnmounted(() => {
 onUnmounted(() => {
   chartLoadVersion += 1
   chartLoadVersion += 1
   destroy()
   destroy()
+  cancelRealtimeChartUpdate()
   window.removeEventListener('resize', resizeChart)
   window.removeEventListener('resize', resizeChart)
   chart?.dispose()
   chart?.dispose()
   chart = null
   chart = null

+ 48 - 78
src/views/pms/iotopeationfill/index1.vue

@@ -4,8 +4,7 @@
       <el-tab-pane
       <el-tab-pane
         style="height: 100%"
         style="height: 100%"
         v-for="(deviceItem, deviceIndex) in list"
         v-for="(deviceItem, deviceIndex) in list"
-        :key="deviceIndex"
-      >
+        :key="deviceIndex">
         <template #label>
         <template #label>
           <span
           <span
             :class="['custom-label', { 'has-border': deviceItem.deviceName === '生产日报' }]"
             :class="['custom-label', { 'has-border': deviceItem.deviceName === '生产日报' }]"
@@ -18,8 +17,7 @@
                 deviceItem.deviceName,
                 deviceItem.deviceName,
                 deviceItem.deviceCode
                 deviceItem.deviceCode
               )
               )
-            "
-          >
+            ">
             {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
             {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
           </span>
           </span>
           <span
           <span
@@ -33,8 +31,7 @@
                 deviceItem.deviceName,
                 deviceItem.deviceName,
                 deviceItem.deviceCode
                 deviceItem.deviceCode
               )
               )
-            "
-          >
+            ">
             {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
             {{ deviceItem.deviceCode }} ({{ deviceItem.deviceName }})
           </span>
           </span>
         </template>
         </template>
@@ -44,8 +41,7 @@
             size="default"
             size="default"
             label-width="120px"
             label-width="120px"
             class="scrollable-form"
             class="scrollable-form"
-            :model="{ attrList: attrList, reportDetails, taskId }"
-          >
+            :model="{ attrList: attrList, reportDetails, taskId }">
             <div style="margin-left: 24px">
             <div style="margin-left: 24px">
               <el-form class="demo-form-inline" :inline="true">
               <el-form class="demo-form-inline" :inline="true">
                 <el-form-item :label="t('common.createTime')" class="custom-label1">
                 <el-form-item :label="t('common.createTime')" class="custom-label1">
@@ -65,8 +61,7 @@
                     companyName !== 'rh'
                     companyName !== 'rh'
                   "
                   "
                   label="井号"
                   label="井号"
-                  class="custom-label1"
-                >
+                  class="custom-label1">
                   <span style="text-decoration: underline">
                   <span style="text-decoration: underline">
                     {{ deviceItem.wellName }}
                     {{ deviceItem.wellName }}
                   </span>
                   </span>
@@ -79,20 +74,17 @@
                   "
                   "
                   label="井号"
                   label="井号"
                   class="custom-label1"
                   class="custom-label1"
-                  prop="taskId"
-                >
+                  prop="taskId">
                   <el-select
                   <el-select
                     v-model="taskId"
                     v-model="taskId"
                     placeholder="请选择井号"
                     placeholder="请选择井号"
                     :options="taskOptions"
                     :options="taskOptions"
-                    class="w-40!"
-                  />
+                    class="w-40!" />
                 </el-form-item>
                 </el-form-item>
                 <el-form-item
                 <el-form-item
                   v-else-if="deviceItem.deviceName === '生产日报'"
                   v-else-if="deviceItem.deviceName === '生产日报'"
                   label="井号"
                   label="井号"
-                  class="custom-label1"
-                >
+                  class="custom-label1">
                   <span style="text-decoration: underline">
                   <span style="text-decoration: underline">
                     {{ deviceItem.wellName }}
                     {{ deviceItem.wellName }}
                   </span>
                   </span>
@@ -101,8 +93,7 @@
                   <el-col
                   <el-col
                     v-for="(summaryItem, summaryIndex) in attrList1"
                     v-for="(summaryItem, summaryIndex) in attrList1"
                     :key="summaryIndex"
                     :key="summaryIndex"
-                    :span="24"
-                  >
+                    :span="24">
                     <el-form-item :label="summaryItem.name" class="custom-label1">
                     <el-form-item :label="summaryItem.name" class="custom-label1">
                       <span style="text-decoration: underline">
                       <span style="text-decoration: underline">
                         {{ summaryItem.totalRunTime }}
                         {{ summaryItem.totalRunTime }}
@@ -123,8 +114,7 @@
                 (item) => !keys.includes(item.description)
                 (item) => !keys.includes(item.description)
               )"
               )"
               :key="attrIndex"
               :key="attrIndex"
-              style="margin-left: 24px"
-            >
+              style="margin-left: 24px">
               <!-- 添加提示文字 -->
               <!-- 添加提示文字 -->
               <div v-if="attrItem.isCollection === 1" class="plc-tip">
               <div v-if="attrItem.isCollection === 1" class="plc-tip">
                 <el-alert
                 <el-alert
@@ -133,8 +123,7 @@
                   :closable="false"
                   :closable="false"
                   center
                   center
                   show-icon
                   show-icon
-                  style="width: 320px"
-                />
+                  style="width: 320px" />
               </div>
               </div>
 
 
               <el-form-item
               <el-form-item
@@ -146,15 +135,13 @@
                   '.fillContent'
                   '.fillContent'
                 "
                 "
                 label-position="top"
                 label-position="top"
-                :rules="rules[attrItem.description]"
-              >
+                :rules="rules[attrItem.description]">
                 <div v-if="fillStatus === '1'">
                 <div v-if="fillStatus === '1'">
                   <el-select
                   <el-select
                     disabled
                     disabled
                     v-model="attrItem.fillContent"
                     v-model="attrItem.fillContent"
                     v-if="attrItem.type === 'enum' && attrItem.description !== null"
                     v-if="attrItem.type === 'enum' && attrItem.description !== null"
-                    style="width: 200px"
-                  >
+                    style="width: 200px">
                     <el-option
                     <el-option
                       v-for="dict in attrItem.name === '非生产原因'
                       v-for="dict in attrItem.name === '非生产原因'
                         ? getIntDictOptions(attrItem.description)
                         ? getIntDictOptions(attrItem.description)
@@ -163,16 +150,14 @@
                       :label="dict.label"
                       :label="dict.label"
                       :value="
                       :value="
                         attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
                         attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
-                      "
-                    />
+                      " />
                   </el-select>
                   </el-select>
                   <el-input
                   <el-input
                     v-else
                     v-else
                     v-model="attrItem.fillContent"
                     v-model="attrItem.fillContent"
                     clearable
                     clearable
                     style="width: 200px; margin-right: 10px"
                     style="width: 200px; margin-right: 10px"
-                    disabled
-                  />
+                    disabled />
                 </div>
                 </div>
 
 
                 <el-input
                 <el-input
@@ -180,15 +165,13 @@
                   v-model="attrItem.fillContent"
                   v-model="attrItem.fillContent"
                   type="textarea"
                   type="textarea"
                   clearable
                   clearable
-                  style="width: 200px"
-                />
+                  style="width: 200px" />
                 <el-select
                 <el-select
                   v-model="attrItem.fillContent"
                   v-model="attrItem.fillContent"
                   clearable
                   clearable
                   v-else-if="attrItem.type === 'enum' && attrItem.description !== null"
                   v-else-if="attrItem.type === 'enum' && attrItem.description !== null"
                   style="width: 200px"
                   style="width: 200px"
-                  filterable
-                >
+                  filterable>
                   <el-option
                   <el-option
                     v-for="dict in attrItem.name === '非生产原因'
                     v-for="dict in attrItem.name === '非生产原因'
                       ? getIntDictOptions(attrItem.description)
                       ? getIntDictOptions(attrItem.description)
@@ -197,8 +180,7 @@
                     :label="dict.label"
                     :label="dict.label"
                     :value="
                     :value="
                       attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
                       attrItem.name === '非生产原因' ? Number(dict.value) : dict.value.toString()
-                    "
-                  />
+                    " />
                 </el-select>
                 </el-select>
                 <el-input
                 <el-input
                   v-else
                   v-else
@@ -211,16 +193,16 @@
                       : t('operationFillForm.enterContent')
                       : t('operationFillForm.enterContent')
                   "
                   "
                   @input="handleInput(attrItem)"
                   @input="handleInput(attrItem)"
-                  :maxlength="attrItem.type === 'double' ? calculateMaxLength(attrItem) : undefined"
-                />
+                  :maxlength="
+                    attrItem.type === 'double' ? calculateMaxLength(attrItem) : undefined
+                  " />
               </el-form-item>
               </el-form-item>
             </div>
             </div>
 
 
             <div
             <div
               v-for="(attrItem, attrIndex) in attrList"
               v-for="(attrItem, attrIndex) in attrList"
               :key="attrIndex"
               :key="attrIndex"
-              style="margin-left: 24px"
-            >
+              style="margin-left: 24px">
               <el-divider v-if="attrItem.description === 'repairTime'" content-position="left"
               <el-divider v-if="attrItem.description === 'repairTime'" content-position="left"
                 >非生产时间</el-divider
                 >非生产时间</el-divider
               >
               >
@@ -231,8 +213,7 @@
                 label-position="top"
                 label-position="top"
                 :label="attrItem.name"
                 :label="attrItem.name"
                 :prop="'attrList.' + attrIndex + '.fillContent'"
                 :prop="'attrList.' + attrIndex + '.fillContent'"
-                :rules="rules[attrItem.description]"
-              >
+                :rules="rules[attrItem.description]">
                 <el-input-number
                 <el-input-number
                   class="w-80!"
                   class="w-80!"
                   :min="0"
                   :min="0"
@@ -241,8 +222,7 @@
                   :controls="false"
                   :controls="false"
                   align="left"
                   align="left"
                   placeholder="请输入数字"
                   placeholder="请输入数字"
-                  :disabled="fillStatus === '1'"
-                />
+                  :disabled="fillStatus === '1'" />
               </el-form-item>
               </el-form-item>
 
 
               <el-form-item
               <el-form-item
@@ -250,14 +230,12 @@
                 label-position="top"
                 label-position="top"
                 :label="attrItem.name"
                 :label="attrItem.name"
                 :prop="'attrList.' + attrIndex + '.fillContent'"
                 :prop="'attrList.' + attrIndex + '.fillContent'"
-                :rules="rules[attrItem.description]"
-              >
+                :rules="rules[attrItem.description]">
                 <el-input
                 <el-input
                   class="w-80!"
                   class="w-80!"
                   v-model="attrItem.fillContent"
                   v-model="attrItem.fillContent"
                   placeholder="请输入其他非生产原因"
                   placeholder="请输入其他非生产原因"
-                  :disabled="fillStatus === '1'"
-                />
+                  :disabled="fillStatus === '1'" />
               </el-form-item>
               </el-form-item>
             </div>
             </div>
 
 
@@ -274,8 +252,7 @@
                   type="primary"
                   type="primary"
                   link
                   link
                   :icon="Plus"
                   :icon="Plus"
-                  @click="addProductionStatusRow"
-                >
+                  @click="addProductionStatusRow">
                   添加一行
                   添加一行
                 </el-button>
                 </el-button>
               </div>
               </div>
@@ -292,8 +269,7 @@
                           message: '请选择日期',
                           message: '请选择日期',
                           trigger: ['change', 'blur'],
                           trigger: ['change', 'blur'],
                           type: 'number'
                           type: 'number'
-                        }"
-                      >
+                        }">
                         <el-date-picker
                         <el-date-picker
                           v-model="row.reportDate"
                           v-model="row.reportDate"
                           placeholder="选择日期"
                           placeholder="选择日期"
@@ -301,8 +277,7 @@
                           class="w-full!"
                           class="w-full!"
                           value-format="x"
                           value-format="x"
                           :disabled="fillStatus === '1'"
                           :disabled="fillStatus === '1'"
-                          @change="inputCurrentDepth()"
-                        />
+                          @change="inputCurrentDepth()" />
                       </el-form-item>
                       </el-form-item>
                     </template>
                     </template>
                   </ZmTableColumn>
                   </ZmTableColumn>
@@ -317,8 +292,7 @@
                           required: true,
                           required: true,
                           message: '请选择开始时间',
                           message: '请选择开始时间',
                           trigger: ['change', 'blur']
                           trigger: ['change', 'blur']
-                        }"
-                      >
+                        }">
                         <el-time-picker
                         <el-time-picker
                           v-model="row.startTime"
                           v-model="row.startTime"
                           placeholder="选择开始时间"
                           placeholder="选择开始时间"
@@ -327,8 +301,7 @@
                           value-format="HH:mm"
                           value-format="HH:mm"
                           class="w-full!"
                           class="w-full!"
                           @change="acalculateDuration(row)"
                           @change="acalculateDuration(row)"
-                          :disabled="fillStatus === '1'"
-                        />
+                          :disabled="fillStatus === '1'" />
                       </el-form-item>
                       </el-form-item>
                     </template>
                     </template>
                   </ZmTableColumn>
                   </ZmTableColumn>
@@ -342,8 +315,7 @@
                           required: true,
                           required: true,
                           message: '请选择结束时间',
                           message: '请选择结束时间',
                           trigger: ['change', 'blur']
                           trigger: ['change', 'blur']
-                        }"
-                      >
+                        }">
                         <el-time-picker
                         <el-time-picker
                           v-model="row.endTime"
                           v-model="row.endTime"
                           placeholder="选择结束时间"
                           placeholder="选择结束时间"
@@ -352,8 +324,7 @@
                           value-format="HH:mm"
                           value-format="HH:mm"
                           class="w-full!"
                           class="w-full!"
                           @change="acalculateDuration(row)"
                           @change="acalculateDuration(row)"
-                          :disabled="fillStatus === '1'"
-                        />
+                          :disabled="fillStatus === '1'" />
                       </el-form-item>
                       </el-form-item>
                     </template>
                     </template>
                   </ZmTableColumn>
                   </ZmTableColumn>
@@ -369,8 +340,7 @@
                           message: '请输入工况',
                           message: '请输入工况',
                           trigger: ['change', 'blur']
                           trigger: ['change', 'blur']
                         }"
                         }"
-                        class="mb-0!"
-                      >
+                        class="mb-0!">
                         <el-input
                         <el-input
                           v-model="row.currentOperation"
                           v-model="row.currentOperation"
                           type="textarea"
                           type="textarea"
@@ -378,8 +348,7 @@
                           show-word-limit
                           show-word-limit
                           :maxlength="1000"
                           :maxlength="1000"
                           placeholder="请输入工况"
                           placeholder="请输入工况"
-                          :disabled="fillStatus === '1'"
-                        />
+                          :disabled="fillStatus === '1'" />
                       </el-form-item>
                       </el-form-item>
                     </template>
                     </template>
                   </ZmTableColumn>
                   </ZmTableColumn>
@@ -397,8 +366,7 @@
                           }
                           }
                           // { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
                           // { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
                         ]"
                         ]"
-                        class="mb-0!"
-                      >
+                        class="mb-0!">
                         <el-input-number
                         <el-input-number
                           v-model="row.currentDepth"
                           v-model="row.currentDepth"
                           :min="0"
                           :min="0"
@@ -407,8 +375,7 @@
                           align="left"
                           align="left"
                           placeholder="请输入结束井深"
                           placeholder="请输入结束井深"
                           @input="() => inputCurrentDepth()"
                           @input="() => inputCurrentDepth()"
-                          :disabled="fillStatus === '1'"
-                        >
+                          :disabled="fillStatus === '1'">
                           <template #suffix> m </template>
                           <template #suffix> m </template>
                         </el-input-number>
                         </el-input-number>
                       </el-form-item>
                       </el-form-item>
@@ -421,8 +388,7 @@
                         v-if="$index >= 0"
                         v-if="$index >= 0"
                         :prop="`reportDetails.${$index}.constructionDetail`"
                         :prop="`reportDetails.${$index}.constructionDetail`"
                         :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
                         :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
-                        class="mb-0!"
-                      >
+                        class="mb-0!">
                         <el-input
                         <el-input
                           v-model="row.constructionDetail"
                           v-model="row.constructionDetail"
                           type="textarea"
                           type="textarea"
@@ -430,8 +396,7 @@
                           show-word-limit
                           show-word-limit
                           :maxlength="1000"
                           :maxlength="1000"
                           placeholder="请输入详细描述"
                           placeholder="请输入详细描述"
-                          :disabled="fillStatus === '1'"
-                        />
+                          :disabled="fillStatus === '1'" />
                       </el-form-item>
                       </el-form-item>
                     </template>
                     </template>
                   </ZmTableColumn>
                   </ZmTableColumn>
@@ -443,8 +408,7 @@
                         type="danger"
                         type="danger"
                         :icon="Delete"
                         :icon="Delete"
                         @click="removeProductionStatusRow($index)"
                         @click="removeProductionStatusRow($index)"
-                        :disabled="fillStatus === '1'"
-                      >
+                        :disabled="fillStatus === '1'">
                         删除
                         删除
                       </el-button>
                       </el-button>
                     </template>
                     </template>
@@ -747,8 +711,14 @@ const rules = reactive<FormRules>({
         const dailyInjectGasTime =
         const dailyInjectGasTime =
           attrList.value.find((item) => item.description === 'dailyInjectGasTime')?.fillContent || 0
           attrList.value.find((item) => item.description === 'dailyInjectGasTime')?.fillContent || 0
 
 
-        if (dailyInjectGasTime > 0 && !(value > 0)) {
-          return callback(new Error('当日运转时间大于0,注气量也需要大于0'))
+        // if (dailyInjectGasTime > 0 && !(value > 0)) {
+        //   return callback(new Error('当日运转时间大于0,注气量也需要大于0'))
+        // }
+
+        const hasDailyGasInjection = value !== undefined && value !== null && value !== ''
+
+        if (dailyInjectGasTime > 0 && !hasDailyGasInjection) {
+          return callback(new Error('当日运转时间大于0,需要填写当日注气量'))
         }
         }
 
 
         callback()
         callback()

+ 15 - 15
src/views/pms/operation-meeting/components/meeting-detail-drawer.vue

@@ -260,10 +260,9 @@ const isConstructionEquipmentCountExtProperty = (item: ExtPropertyItem) => {
   const text = getExtPropertySearchText(item)
   const text = getExtPropertySearchText(item)
 
 
   return (
   return (
-    text.includes('施工设备数量') ||
-    text.includes('施工设备数') ||
-    text.includes('constructionequipmentcount') ||
-    text.includes('constructiondevicecount')
+    text.includes('施工设备(台*天)') ||
+    text.includes('施工设备') ||
+    text.includes('constructionDeviceNum')
   )
   )
 }
 }
 
 
@@ -271,11 +270,9 @@ const isOperatingEquipmentCountExtProperty = (item: ExtPropertyItem) => {
   const text = getExtPropertySearchText(item)
   const text = getExtPropertySearchText(item)
 
 
   return (
   return (
-    text.includes('投运设备数量') ||
-    text.includes('投运设备数') ||
-    text.includes('operatingequipmentcount') ||
-    text.includes('operationequipmentcount') ||
-    text.includes('runningequipmentcount')
+    text.includes('投运设备(台*天)') ||
+    text.includes('投运设备') ||
+    text.includes('inServiceDeviceNum')
   )
   )
 }
 }
 
 
@@ -300,10 +297,13 @@ const updateEquipmentUtilizationExtProperty = () => {
   const constructionCount = parseNumberValue(constructionItem.actualValue)
   const constructionCount = parseNumberValue(constructionItem.actualValue)
   const operatingCount = parseNumberValue(operatingItem.actualValue)
   const operatingCount = parseNumberValue(operatingItem.actualValue)
 
 
-  utilizationItem.actualValue =
-    constructionCount === undefined || operatingCount === undefined || operatingCount === 0
-      ? undefined
-      : Number(((constructionCount / operatingCount) * 100).toFixed(2))
+  if (constructionCount === undefined || operatingCount === undefined) {
+    utilizationItem.actualValue = undefined
+  } else if (operatingCount === 0) {
+    utilizationItem.actualValue = 0
+  } else {
+    utilizationItem.actualValue = Number(((constructionCount / operatingCount) * 100).toFixed(2))
+  }
 
 
   nextTick(() => detailFormRef.value?.clearValidate(getExtPropertyProp(utilizationItem)))
   nextTick(() => detailFormRef.value?.clearValidate(getExtPropertyProp(utilizationItem)))
 }
 }
@@ -573,7 +573,7 @@ onMounted(() => {
             :rules="getExtPropertyRules(item)">
             :rules="getExtPropertyRules(item)">
             <el-input-number
             <el-input-number
               v-if="isDoubleExtProperty(item)"
               v-if="isDoubleExtProperty(item)"
-              v-model="item.actualValue"
+              v-model="item.actualValue as any"
               class="w-full!"
               class="w-full!"
               :controls="false"
               :controls="false"
               :disabled="isReadonly || isEquipmentUtilizationExtProperty(item)"
               :disabled="isReadonly || isEquipmentUtilizationExtProperty(item)"
@@ -649,7 +649,7 @@ onMounted(() => {
             :rules="getExtPropertyRules(item)">
             :rules="getExtPropertyRules(item)">
             <el-input-number
             <el-input-number
               v-if="isDoubleExtProperty(item)"
               v-if="isDoubleExtProperty(item)"
-              v-model="item.actualValue"
+              v-model="item.actualValue as any"
               class="w-full!"
               class="w-full!"
               :controls="false"
               :controls="false"
               :disabled="isReadonly"
               :disabled="isReadonly"

+ 5 - 9
src/views/pms/stat/rhkb/deviceList.vue

@@ -96,7 +96,7 @@ onMounted(() => {
 <template>
 <template>
   <div class="panel w-full h-[280px] flex flex-col mt-3">
   <div class="panel w-full h-[280px] flex flex-col mt-3">
     <div class="panel-title h-9 flex items-center justify-between">
     <div class="panel-title h-9 flex items-center justify-between">
-      <div class="flex items-center">
+      <div class="kb-panel-title-text flex items-center">
         <div class="icon-decorator">
         <div class="icon-decorator">
           <span></span>
           <span></span>
           <span></span>
           <span></span>
@@ -114,8 +114,7 @@ onMounted(() => {
           :clearable="false"
           :clearable="false"
           :shortcuts="rangeShortcuts"
           :shortcuts="rangeShortcuts"
           class="w-260px!"
           class="w-260px!"
-          @change="handleDateChange"
-        />
+          @change="handleDateChange" />
       </div>
       </div>
     </div>
     </div>
     <!-- v-loading="loading" -->
     <!-- v-loading="loading" -->
@@ -124,8 +123,7 @@ onMounted(() => {
         :data="tableData"
         :data="tableData"
         :height="TABLE_HEIGHT"
         :height="TABLE_HEIGHT"
         class="device-list-table"
         class="device-list-table"
-        @row-click="handleRowClick"
-      >
+        @row-click="handleRowClick">
         <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
         <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
         <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
         <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="120" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="120" align="center" />
@@ -155,14 +153,12 @@ onMounted(() => {
       destroy-on-close
       destroy-on-close
       class="device-list-dialog"
       class="device-list-dialog"
       @closed="handleDialogClosed"
       @closed="handleDialogClosed"
-      append-to-body
-    >
+      append-to-body>
       <el-table
       <el-table
         v-loading="teamLoading"
         v-loading="teamLoading"
         :data="teamList"
         :data="teamList"
         :height="TEAM_TABLE_HEIGHT"
         :height="TEAM_TABLE_HEIGHT"
-        class="team-list-table"
-      >
+        class="team-list-table">
         <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
         <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
         <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />
         <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />

+ 7 - 7
src/views/pms/stat/rykb.vue

@@ -3,11 +3,11 @@ import { DESIGN_HEIGHT, DESIGN_WIDTH } from '@/utils/kb'
 import rysummary from './rykb/rysummary.vue'
 import rysummary from './rykb/rysummary.vue'
 import safeday from './rykb/safeday.vue'
 import safeday from './rykb/safeday.vue'
 import rydeviceStatus from './rykb/rydeviceStatus.vue'
 import rydeviceStatus from './rykb/rydeviceStatus.vue'
-import ryorderTrend from './rykb/ryorderTrend.vue'
-import zjfinish from './rykb/zjfinish.vue'
-import zjwork from './rykb/zjwork.vue'
+import zjStatsSwitch from './rykb/zjStatsSwitch.vue'
 import xjwork from './rykb/xjwork.vue'
 import xjwork from './rykb/xjwork.vue'
 import rydeviceList from './rykb/rydeviceList.vue'
 import rydeviceList from './rykb/rydeviceList.vue'
+import ryProductionBriefs from './rykb/ryProductionBriefs.vue'
+import rydeviceType from './rykb/rydeviceType.vue'
 
 
 defineOptions({
 defineOptions({
   name: 'IotRyStatt'
   name: 'IotRyStatt'
@@ -83,13 +83,13 @@ onUnmounted(() => {
           <rysummary class="kb-stage-card kb-stage-card--1" />
           <rysummary class="kb-stage-card kb-stage-card--1" />
           <div class="w-full h-148 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
           <div class="w-full h-148 grid grid-rows-2 grid-cols-3 gap-3 mt-3">
             <rydeviceStatus class="kb-stage-card kb-stage-card--2" />
             <rydeviceStatus class="kb-stage-card kb-stage-card--2" />
+            <rydeviceType class="kb-stage-card kb-stage-card--6" />
+            <rydeviceList class="kb-stage-card kb-stage-card--4 kb-stage-card--list" />
             <safeday class="kb-stage-card kb-stage-card--3" />
             <safeday class="kb-stage-card kb-stage-card--3" />
-            <ryorderTrend class="kb-stage-card kb-stage-card--4" />
-            <zjfinish class="kb-stage-card kb-stage-card--5" />
-            <zjwork class="kb-stage-card kb-stage-card--6" />
+            <zjStatsSwitch class="kb-stage-card kb-stage-card--5" />
             <xjwork class="kb-stage-card kb-stage-card--7" />
             <xjwork class="kb-stage-card kb-stage-card--7" />
           </div>
           </div>
-          <rydeviceList class="kb-stage-card kb-stage-card--8 kb-stage-card--list" />
+          <ryProductionBriefs class="kb-stage-card kb-stage-card--8 kb-stage-card--list" />
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>

+ 255 - 0
src/views/pms/stat/rykb/ryProductionBriefs.vue

@@ -0,0 +1,255 @@
+<script lang="ts" setup>
+import { IotStatApi } from '@/api/pms/stat'
+import dayjs from 'dayjs'
+
+interface RyProductionBriefRow {
+  id: number
+  projectClassification: string
+  projectName: string
+  deptName: string
+  taskName: string
+  constructionStatusName: string
+  dailyFootage: number | null
+  completedWells: number | null
+  dailyPowerUsage: number | null
+  dailyFuel: number | null
+  nonProductionTime: number | null
+  nextPlan: string
+  constructionBrief: string
+  projectSort?: number | null
+  teamSort?: number | null
+}
+
+interface SpanMethodProps {
+  rowIndex: number
+  columnIndex: number
+}
+
+const TABLE_HEIGHT = 220
+const MERGE_COLUMN_INDEXES = [0, 1]
+const COMPANY_ORDER = ['钻井', '修井']
+const DEFAULT_DATE = dayjs().format('YYYY-MM-DD')
+
+const selectedDate = ref(DEFAULT_DATE)
+const loading = ref(false)
+const list = ref<RyProductionBriefRow[]>([])
+
+const tableData = computed(() => {
+  return [...list.value].sort((a, b) => {
+    const companySort =
+      getCompanyOrder(a.projectClassification) - getCompanyOrder(b.projectClassification)
+    if (companySort !== 0) return companySort
+
+    const projectSort = Number(a.projectSort ?? 9999) - Number(b.projectSort ?? 9999)
+    if (projectSort !== 0) return projectSort
+
+    const projectNameSort = (a.projectName || '').localeCompare(b.projectName || '', 'zh-Hans-CN')
+    if (projectNameSort !== 0) return projectNameSort
+
+    return Number(a.teamSort ?? 9999) - Number(b.teamSort ?? 9999)
+  })
+})
+
+const spanMaps = computed(() => {
+  return {
+    company: createSpanMap(tableData.value, (row) => row.projectClassification || '-'),
+    project: createSpanMap(
+      tableData.value,
+      (row) => `${row.projectClassification || '-'}__${row.projectName || '-'}`
+    )
+  }
+})
+
+function getCompanyOrder(value?: string) {
+  const index = COMPANY_ORDER.indexOf(value || '')
+  return index === -1 ? COMPANY_ORDER.length : index
+}
+
+function createSpanMap(
+  rows: RyProductionBriefRow[],
+  getKey: (row: RyProductionBriefRow) => 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 (!MERGE_COLUMN_INDEXES.includes(columnIndex)) {
+    return {
+      rowspan: 1,
+      colspan: 1
+    }
+  }
+
+  const rowspan =
+    columnIndex === 0 ? spanMaps.value.company[rowIndex] : spanMaps.value.project[rowIndex]
+
+  return {
+    rowspan,
+    colspan: rowspan > 0 ? 1 : 0
+  }
+}
+
+function formatNumber(value?: number | null, fractionDigits = 1) {
+  const numberValue = Number(value ?? 0)
+  return Number.isInteger(numberValue) ? `${numberValue}` : numberValue.toFixed(fractionDigits)
+}
+
+function formatFootageOrWell(row: RyProductionBriefRow) {
+  if (row.projectClassification === '钻井') {
+    return `${formatNumber(row.dailyFootage)}m`
+  }
+
+  return formatNumber(row.completedWells, 0)
+}
+
+function normalizeList(res: any): RyProductionBriefRow[] {
+  if (Array.isArray(res)) return res
+  if (Array.isArray(res?.list)) return res.list
+  if (Array.isArray(res?.records)) return res.records
+  if (Array.isArray(res?.data)) return res.data
+  return []
+}
+
+function handleDateChange() {
+  getList()
+}
+
+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')
+  ]
+}
+
+async function getList() {
+  loading.value = true
+
+  try {
+    const res = await IotStatApi.getRyProductionBriefs({ createTime: getCreateTimeRange() })
+    list.value = normalizeList(res).filter((row) =>
+      COMPANY_ORDER.includes(row.projectClassification || '')
+    )
+  } catch (error) {
+    console.error('获取瑞鹰生产简报失败:', error)
+    list.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div class="panel w-full h-[280px] flex flex-col mt-3">
+    <div class="panel-title h-9 flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        生产简报
+      </div>
+      <div class="w-120px! -translate-y-[4px]">
+        <el-date-picker
+          v-model="selectedDate"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择日期"
+          :clearable="false"
+          class="w-120px!"
+          @change="handleDateChange" />
+      </div>
+    </div>
+    <div class="flex-1 min-h-0 px-4 py-2">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        :height="TABLE_HEIGHT"
+        :span-method="tableSpanMethod"
+        class="device-list-table production-brief-table">
+        <el-table-column prop="projectClassification" label="公司" min-width="72" align="center" />
+        <el-table-column prop="projectName" label="项目" min-width="150" align="center" />
+        <el-table-column prop="deptName" label="队伍" min-width="94" align="center" />
+        <el-table-column prop="taskName" label="生产任务" min-width="130" align="center" />
+        <el-table-column
+          prop="constructionStatusName"
+          label="运行状态"
+          min-width="88"
+          align="center" />
+
+        <el-table-column prop="nextPlan" label="下步任务" min-width="130" align="center" />
+        <el-table-column
+          prop="constructionBrief"
+          label="当日生产简况"
+          min-width="160"
+          align="center" />
+
+        <el-table-column label="当日进尺(m)/当日井次" min-width="150" align="center">
+          <template #default="{ row }">
+            {{ formatFootageOrWell(row) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="当日电耗(kwh)" min-width="120" align="center">
+          <template #default="{ row }">
+            {{ formatNumber(row.dailyPowerUsage) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="当日油耗(升)" min-width="112" align="center">
+          <template #default="{ row }">
+            {{ formatNumber(row.dailyFuel) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="当日非生产时间" min-width="126" align="center">
+          <template #default="{ row }">
+            {{ formatNumber(row.nonProductionTime) }}
+          </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');
+
+.production-brief-table {
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-size: 16px;
+    line-height: 1.2;
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    padding: 7px 0;
+    font-size: 14px;
+  }
+}
+</style>

+ 25 - 18
src/views/pms/stat/rykb/rydeviceList.vue

@@ -94,16 +94,16 @@ onMounted(() => {
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="panel w-full h-[280px] flex flex-col mt-3">
+  <div class="panel w-full h-full flex flex-col">
     <div class="panel-title h-9 flex items-center justify-between">
     <div class="panel-title h-9 flex items-center justify-between">
-      <div class="flex items-center">
+      <div class="kb-panel-title-text flex items-center">
         <div class="icon-decorator">
         <div class="icon-decorator">
           <span></span>
           <span></span>
           <span></span>
           <span></span>
         </div>
         </div>
-        项目部统计
+        设备利用率
       </div>
       </div>
-      <div class="w-260px! -translate-y-[4px]">
+      <div class="w-220px! -translate-y-[4px]">
         <el-date-picker
         <el-date-picker
           v-model="createTime"
           v-model="createTime"
           value-format="YYYY-MM-DD HH:mm:ss"
           value-format="YYYY-MM-DD HH:mm:ss"
@@ -112,9 +112,8 @@ onMounted(() => {
           end-placeholder="结束日期"
           end-placeholder="结束日期"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
           :clearable="false"
           :clearable="false"
-          class="w-260px!"
-          @change="handleDateChange"
-        />
+          class="w-220px!"
+          @change="handleDateChange" />
       </div>
       </div>
     </div>
     </div>
     <!-- v-loading="loading" -->
     <!-- v-loading="loading" -->
@@ -123,18 +122,17 @@ onMounted(() => {
         :data="tableData"
         :data="tableData"
         :height="TABLE_HEIGHT"
         :height="TABLE_HEIGHT"
         class="device-list-table"
         class="device-list-table"
-        @row-click="handleRowClick"
-      >
-        <el-table-column prop="projectDeptName" label="项目部" min-width="220" align="center" />
-        <el-table-column prop="teamCount" label="队伍数量" min-width="120" align="center" />
-        <el-table-column prop="cumulativeDays" label="累计天数" min-width="120" align="center" />
-        <el-table-column prop="constructionDays" label="施工天数" min-width="120" align="center" />
+        @row-click="handleRowClick">
+        <el-table-column prop="projectDeptName" label="项目部" min-width="150" align="center" />
+        <el-table-column prop="teamCount" label="队伍数量" min-width="88" align="center" />
+        <el-table-column prop="cumulativeDays" label="累计天数" min-width="92" align="center" />
+        <el-table-column prop="constructionDays" label="施工天数" min-width="92" align="center" />
         <!-- <el-table-column label="注气量(万方)" min-width="150" align="center">
         <!-- <el-table-column label="注气量(万方)" min-width="150" align="center">
           <template #default="{ row }">
           <template #default="{ row }">
             {{ formatGasInjection(row.gasInjection) }}
             {{ formatGasInjection(row.gasInjection) }}
           </template>
           </template>
         </el-table-column> -->
         </el-table-column> -->
-        <el-table-column label="设备利用率" min-width="140" align="center">
+        <el-table-column label="设备利用率" min-width="110" align="center">
           <template #default="{ row }">
           <template #default="{ row }">
             {{ formatRate(row.utilizationRate) }}
             {{ formatRate(row.utilizationRate) }}
           </template>
           </template>
@@ -154,14 +152,12 @@ onMounted(() => {
       destroy-on-close
       destroy-on-close
       class="device-list-dialog"
       class="device-list-dialog"
       @closed="handleDialogClosed"
       @closed="handleDialogClosed"
-      append-to-body
-    >
+      append-to-body>
       <el-table
       <el-table
         v-loading="teamLoading"
         v-loading="teamLoading"
         :data="teamList"
         :data="teamList"
         :height="TEAM_TABLE_HEIGHT"
         :height="TEAM_TABLE_HEIGHT"
-        class="team-list-table"
-      >
+        class="team-list-table">
         <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
         <el-table-column prop="teamName" label="队伍名称" min-width="220" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
         <el-table-column prop="cumulativeDays" label="累计天数" min-width="140" align="center" />
         <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />
         <el-table-column prop="constructionDays" label="施工天数" min-width="140" align="center" />
@@ -183,6 +179,17 @@ onMounted(() => {
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 @import url('@/styles/kb.scss');
 @import url('@/styles/kb.scss');
+
+.device-list-table {
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-size: 17px;
+    line-height: 1.25;
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    font-size: 15px;
+  }
+}
 </style>
 </style>
 
 
 <style>
 <style>

+ 178 - 0
src/views/pms/stat/rykb/rydeviceType.vue

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

+ 4 - 4
src/views/pms/stat/rykb/safeday.vue

@@ -8,11 +8,12 @@ const safeDays = ref(FIXED_SAFE_DAYS)
 
 
 async function loadSafeDays() {
 async function loadSafeDays() {
   try {
   try {
-    await IotStatApi.getSafeCount()
+    const res = await IotStatApi.getSafeCount1()
+    safeDays.value = res
   } catch (error) {
   } catch (error) {
     console.error('获取安全生产天数失败:', error)
     console.error('获取安全生产天数失败:', error)
   } finally {
   } finally {
-    safeDays.value = FIXED_SAFE_DAYS
+    // safeDays.value = FIXED_SAFE_DAYS
   }
   }
 }
 }
 
 
@@ -45,8 +46,7 @@ onMounted(() => {
             class="safe-day-panel__value"
             class="safe-day-panel__value"
             :start-val="0"
             :start-val="0"
             :end-val="safeDays"
             :end-val="safeDays"
-            :duration="1200"
-          />
+            :duration="1200" />
           <span class="safe-day-panel__unit">天</span>
           <span class="safe-day-panel__unit">天</span>
         </div>
         </div>
         <div class="safe-day-panel__subline">截至当前未发生安全生产事故</div>
         <div class="safe-day-panel__subline">截至当前未发生安全生产事故</div>

+ 66 - 0
src/views/pms/stat/rykb/zjStatsSwitch.vue

@@ -0,0 +1,66 @@
+<script lang="ts" setup>
+import zjfinish from './zjfinish.vue'
+import zjwork from './zjwork.vue'
+
+type ActivePanel = 'finish' | 'work'
+
+const activePanel = ref<ActivePanel>('finish')
+
+const panelOptions: Array<{ label: string; value: ActivePanel }> = [
+  {
+    label: '完成情况',
+    value: 'finish'
+  },
+  {
+    label: '工作量',
+    value: 'work'
+  }
+]
+
+const activeTitle = computed(() =>
+  activePanel.value === 'finish' ? '钻井完成情况' : '钻井工作量情况'
+)
+</script>
+
+<template>
+  <div class="panel flex flex-col">
+    <div class="panel-title h-9 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="zj-switch" />
+    </div>
+    <div class="flex-1 min-h-0">
+      <zjfinish v-if="activePanel === 'finish'" embedded />
+      <zjwork v-else embedded />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.zj-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: 26px;
+  padding: 2px;
+  border: 1px solid rgb(31 91 184 / 12%);
+  transform: translateY(-2px);
+
+  :deep(.el-segmented__item) {
+    min-height: 22px;
+    padding: 0 8px;
+    font-size: 13px;
+    font-weight: 600;
+    color: #29527f;
+  }
+}
+</style>

+ 6 - 2
src/views/pms/stat/rykb/zjfinish.vue

@@ -11,6 +11,10 @@ import {
 } from '@/utils/kb'
 } from '@/utils/kb'
 import { IotStatApi } from '@/api/pms/stat'
 import { IotStatApi } from '@/api/pms/stat'
 
 
+defineProps<{
+  embedded?: boolean
+}>()
+
 const chartData = ref<ChartData>({
 const chartData = ref<ChartData>({
   xAxis: [],
   xAxis: [],
   series: []
   series: []
@@ -207,8 +211,8 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="panel flex flex-col">
-    <div class="panel-title h-9">
+  <div :class="embedded ? 'h-full flex flex-col' : 'panel flex flex-col'">
+    <div v-if="!embedded" class="panel-title h-9">
       <div class="icon-decorator">
       <div class="icon-decorator">
         <span></span>
         <span></span>
         <span></span>
         <span></span>

+ 6 - 2
src/views/pms/stat/rykb/zjwork.vue

@@ -11,6 +11,10 @@ import {
 } from '@/utils/kb'
 } from '@/utils/kb'
 import { IotStatApi } from '@/api/pms/stat'
 import { IotStatApi } from '@/api/pms/stat'
 
 
+defineProps<{
+  embedded?: boolean
+}>()
+
 const chartData = ref<ChartData>({
 const chartData = ref<ChartData>({
   xAxis: [],
   xAxis: [],
   series: []
   series: []
@@ -250,8 +254,8 @@ onUnmounted(() => {
 </script>
 </script>
 
 
 <template>
 <template>
-  <div class="panel flex flex-col">
-    <div class="panel-title h-9">
+  <div :class="embedded ? 'h-full flex flex-col' : 'panel flex flex-col'">
+    <div v-if="!embedded" class="panel-title h-9">
       <div class="icon-decorator">
       <div class="icon-decorator">
         <span></span>
         <span></span>
         <span></span>
         <span></span>