yanghao před 11 hodinami
rodič
revize
f7eab38ca5

+ 1 - 0
package.json

@@ -43,6 +43,7 @@
     "@vueuse/core": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
+    "@zumer/snapdom": "^2.0.2",
     "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
     "axios": "^1.6.8",

+ 27 - 0
pnpm-lock.yaml

@@ -62,6 +62,9 @@ importers:
       '@wangeditor/editor-for-vue':
         specifier: ^5.1.10
         version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.12(typescript@5.3.3))
+      '@zumer/snapdom':
+        specifier: ^2.0.2
+        version: 2.0.2
       '@zxcvbn-ts/core':
         specifier: ^3.0.4
         version: 3.0.4
@@ -1533,36 +1536,42 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm-musl@2.5.0':
     resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-arm64-glibc@2.5.0':
     resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.5.0':
     resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.5.0':
     resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.5.0':
     resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-win32-arm64@2.5.0':
     resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@@ -1671,46 +1680,55 @@ packages:
     resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.27.4':
     resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.27.4':
     resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.27.4':
     resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
     resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.27.4':
     resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.27.4':
     resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.27.4':
     resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.27.4':
     resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.27.4':
     resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
@@ -1756,24 +1774,28 @@ packages:
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@swc/core-linux-arm64-musl@1.9.3':
     resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@swc/core-linux-x64-gnu@1.9.3':
     resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@swc/core-linux-x64-musl@1.9.3':
     resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@swc/core-win32-arm64-msvc@1.9.3':
     resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==}
@@ -2456,6 +2478,9 @@ packages:
     resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
     engines: {node: '>=10.0.0'}
 
+  '@zumer/snapdom@2.0.2':
+    resolution: {integrity: sha512-W6quT4lMcPu8Q9O/Q6witSfc6/+xuY8C8yDoHug/+o7zYKCNE/e0I3//XsWDkyq9C0mDE0TAWF/8bwCR7x3gHQ==}
+
   '@zxcvbn-ts/core@3.0.4':
     resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==}
 
@@ -8088,6 +8113,8 @@ snapshots:
 
   '@xmldom/xmldom@0.8.10': {}
 
+  '@zumer/snapdom@2.0.2': {}
+
   '@zxcvbn-ts/core@3.0.4':
     dependencies:
       fastest-levenshtein: 1.0.16

+ 1 - 0
src/api/pms/iotopeationfill/index.ts

@@ -38,6 +38,7 @@ export interface IotOpeationFillVO {
   maxAllowedValue: any
   isCollection: any
   description: any
+  wellNamePair: any
 }
 
 // 运行记录填报 API

+ 2 - 0
src/components/Backtop/src/Backtop.vue

@@ -13,5 +13,7 @@ const prefixCls = getPrefixCls('backtop')
   <ElBacktop
     :class="`${prefixCls}-backtop`"
     :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
+    :right="50"
+    :bottom="80"
   />
 </template>

+ 2 - 1
src/layout/components/Menu/src/components/useRenderMenuItem.tsx

@@ -33,7 +33,8 @@ export const useRenderMenuItem = () =>
                 '产品管理',
                 '视频设备',
                 '视频配置',
-                '通道管理'
+                '通道管理',
+                '监控中心'
               ].includes(v.meta?.title)
             )
           } else if (currentSource === 'qhse') {

+ 14 - 8
src/layout/components/Message/src/Message.vue

@@ -64,13 +64,18 @@ const routerDetail = (item) => {
     })
   } else if (item.businessType === 'rdDailyReport') {
     push({
-      name: 'FillDailyReportForm',
-      params: { id: id, mode: 'fill' }
+      path: '/iotdayilyreport/FillDailyReport',
+      query: { id: id, mode: 'edit' }
     })
   } else if (item.businessType === 'rdReportApproval') {
     push({
-      name: 'FillDailyReportForm',
-      params: { id: id, mode: 'approval' }
+      path: '/iotdayilyreport/IotRdDailyReport',
+      query: { id: id, mode: 'approval' }
+    })
+  } else if (item.businessType === 'rdReportDetail') {
+    push({
+      path: '/iotdayilyreport/IotRdDailyReport',
+      query: { id: id, mode: 'detail' }
     })
   } else if (item.businessType === 'generateOperation') {
     const param = item.templateParams
@@ -176,10 +181,10 @@ onMounted(() => {
 
   .message-item {
     display: flex;
-    align-items: center;
     padding: 20px 0;
-    border-bottom: 1px solid var(--el-border-color-light);
     cursor: pointer;
+    border-bottom: 1px solid var(--el-border-color-light);
+    align-items: center;
 
     &:last-child {
       border: none;
@@ -205,10 +210,11 @@ onMounted(() => {
       }
     }
   }
+
   .message-item:hover {
-    transform: scale(0.95);
     background-color: #dcf8e4;
-    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+    transform: scale(0.95);
+    box-shadow: 0 2px 5px rgb(0 0 0 / 20%);
   }
 }
 </style>

+ 2 - 15
src/router/modules/remaining.ts

@@ -1026,7 +1026,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         }
       },
       {
-        path: 'dailyReport/fill/:id(\\d+)/:mode',
+        path: 'dailyReport-form',
         component: () => import('@/views/pms/iotrddailyreport/FillDailyReportForm.vue'),
         name: 'FillDailyReportForm',
         meta: {
@@ -1035,20 +1035,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
           canTo: true,
           icon: 'ep:add',
           title: t('project.fillReport'),
-          activeMenu: '/dailyReport/fill'
-        }
-      },
-      {
-        path: 'dailyReport/approval/:id(\\d+)',
-        component: () => import('@/views/pms/iotrddailyreport/DailyReportApprovalForm.vue'),
-        name: 'DailyReportApprovalForm',
-        meta: {
-          noCache: false,
-          hidden: true,
-          canTo: true,
-          icon: 'ep:add',
-          title: t('project.fillReport'),
-          activeMenu: '/dailyReport/approval'
+          activeMenu: '/iotdayilyreport/FillDailyReport'
         }
       }
     ]

+ 193 - 166
src/views/oli-connection/monitoring/detail.vue

@@ -7,7 +7,8 @@ import {
   CircleCheckFilled,
   CircleCloseFilled,
   DataLine,
-  TrendCharts
+  Crop,
+  FullScreen
 } from '@element-plus/icons-vue'
 import { AnimatedCountTo } from '@/components/AnimatedCountTo'
 import { neonColors } from '@/utils/td-color'
@@ -16,6 +17,8 @@ import * as echarts from 'echarts'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
 import { Dimensions, formatIotValue, HeaderItem, useSocketBus } from '@/utils/useSocketBus'
 import { rangeShortcuts } from '@/utils/formatTime'
+import { useFullscreen } from '@vueuse/core'
+import { snapdom } from '@zumer/snapdom'
 
 const { query } = useRoute()
 
@@ -146,16 +149,17 @@ const dimensionsContent = computed(() => [
     icon: DataLine,
     value: gatewayDimensions.value,
     countColor: 'text-blue-600',
-    countBg: 'bg-blue-50'
-  },
-  {
-    label: '中航北斗',
-    icon: TrendCharts,
-    value: carDimensions.value,
-    countColor: 'text-indigo-600',
-    countBg: 'bg-indigo-50',
-    judgment: true
+    countBg: 'bg-blue-50',
+    judgment: false
   }
+  // {
+  //   label: '中航北斗',
+  //   icon: TrendCharts,
+  //   value: carDimensions.value,
+  //   countColor: 'text-indigo-600',
+  //   countBg: 'bg-indigo-50',
+  //   judgment: true
+  // }
 ])
 
 const disabledDimensions = ref<string[]>(['online', 'vehicle_name'])
@@ -377,10 +381,10 @@ function render() {
     animationDurationUpdate: 200,
     animationEasingUpdate: 'linear',
     grid: {
-      left: '5%',
+      left: '0%',
       top: '5%',
       right: '5%',
-      bottom: '8%'
+      bottom: '12%'
     },
     tooltip: {
       trigger: 'axis',
@@ -407,6 +411,7 @@ function render() {
     },
     xAxis: {
       type: 'time',
+      boundaryGap: ['0%', '25%'],
       axisLabel: {
         formatter: (v) => dayjs(v).format('YYYY-MM-DD\nHH:mm:ss'),
         rotate: 0,
@@ -659,30 +664,24 @@ function handleClickSpec(modelName: string) {
   })
 }
 
-const exportChart = () => {
-  if (!chart) return
-  let img = new Image()
-  img.src = chart.getDataURL({
-    type: 'png',
-    pixelRatio: 1,
-    backgroundColor: '#fff'
-  })
+const downloadRef = ref(null)
 
-  img.onload = function () {
-    let canvas = document.createElement('canvas')
-    canvas.width = img.width
-    canvas.height = img.height
-    let ctx = canvas.getContext('2d')
-    ctx?.drawImage(img, 0, 0)
-    let dataURL = canvas.toDataURL('image/png')
-
-    let a = document.createElement('a')
+const exportChart = async () => {
+  try {
+    if (!downloadRef.value) return
 
-    let event = new MouseEvent('click')
+    const result = await snapdom(downloadRef.value, {
+      scale: 2,
+      backgroundColor: '#fff'
+    })
 
-    a.href = dataURL
-    a.download = `${data.value.deviceName}-设备监控-${dayjs().format('YYYY-MM-DD HH:mm:ss')}.png`
-    a.dispatchEvent(event)
+    await result.download({
+      filename: `${data.value.deviceName}-设备监控`,
+      type: 'png'
+    })
+  } catch (error) {
+    console.error(error)
+    ElMessage.error('导出图表失败')
   }
 }
 
@@ -707,14 +706,18 @@ onUnmounted(() => {
     if (chart) chart.resize()
   })
 })
+
+const targetArea = ref(null)
+
+const { toggle, isFullscreen } = useFullscreen(targetArea)
 </script>
 
 <template>
   <div
-    class="grid grid-cols-[260px_1fr] grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
     <div
-      class="grid-col-span-2 rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
+      class="rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
     >
       <div class="flex items-center gap-4">
         <div
@@ -752,151 +755,165 @@ onUnmounted(() => {
         </template>
       </div>
     </div>
+    <div ref="targetArea" class="h-full min-h-0 relative">
+      <div class="grid grid-cols-[260px_1fr] gap-4 h-full">
+        <el-scrollbar
+          class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
+          view-class="flex flex-col min-h-full"
+          v-loading="dimensionLoading"
+        >
+          <template v-for="citem in dimensionsContent" :key="citem.label">
+            <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
+              <div
+                class="sticky-title bg-blue-100 z-10 flex justify-between items-center py-3 px-4 border-0 border-solid"
+              >
+                <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
+                  <el-icon><component :is="citem.icon" /></el-icon>
+                  {{ citem.label }}
+                </span>
+                <span
+                  class="text-xs px-2 py-0.5 rounded-full font-mono"
+                  :class="[citem.countBg, citem.countColor]"
+                >
+                  {{ citem.value.length }}
+                </span>
+              </div>
 
-    <el-scrollbar
-      class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
-      view-class="flex flex-col min-h-full"
-      v-loading="dimensionLoading"
-    >
-      <template v-for="citem in dimensionsContent" :key="citem.label">
-        <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
-          <div
-            class="sticky-title bg-blue-100 z-88 flex justify-between items-center py-3 px-4 border-0 border-solid"
-          >
-            <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
-              <el-icon><component :is="citem.icon" /></el-icon>
-              {{ citem.label }}
-            </span>
-            <span
-              class="text-xs px-2 py-0.5 rounded-full font-mono"
-              :class="[citem.countBg, citem.countColor]"
-            >
-              {{ citem.value.length }}
-            </span>
-          </div>
+              <div class="px-3 pb-4 pt-2 space-y-3">
+                <div
+                  :data-disabled="disabledDimensions.includes(item.identifier)"
+                  v-for="item in citem.value"
+                  :key="item.identifier"
+                  @click="handleClickSpec(item.name)"
+                  class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
+                  :class="{ 'is-active': selectedDimension[item.name] }"
+                  :style="{
+                    '--theme-color': item.color,
+                    '--theme-bg-hover': item.bgHover,
+                    '--theme-bg-active': item.bgActive
+                  }"
+                >
+                  <div class="flex justify-between items-center mb-1">
+                    <span
+                      class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
+                      :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
+                    >
+                      {{ item.name }}
+                    </span>
+                    <div
+                      class="size-2 rounded-full transition-all duration-300 shadow-sm"
+                      :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
+                      :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
+                    ></div>
+                  </div>
+
+                  <div class="flex items-baseline justify-between relative z-9">
+                    <animated-count-to
+                      v-if="!item.isText"
+                      :value="Number(item.value)"
+                      :duration="500"
+                      :suffix="item.suffix"
+                      class="text-lg font-bold font-mono tracking-tight text-slate-800"
+                    />
+                    <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
+                      {{ item.value }}
+                    </span>
+                  </div>
+                  <div
+                    class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
+                    :class="
+                      selectedDimension[item.name]
+                        ? 'opacity-100 shadow-[0_0_8px_currentColor]'
+                        : 'opacity-0'
+                    "
+                    :style="{ backgroundColor: item.color, color: item.color }"
+                  >
+                  </div>
+                </div>
+              </div>
+            </template>
+          </template>
+        </el-scrollbar>
 
-          <div class="px-3 pb-4 pt-2 space-y-3">
-            <div
-              :data-disabled="disabledDimensions.includes(item.identifier)"
-              v-for="item in citem.value"
-              :key="item.identifier"
-              @click="handleClickSpec(item.name)"
-              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
-              :class="{ 'is-active': selectedDimension[item.name] }"
-              :style="{
-                '--theme-color': item.color,
-                '--theme-bg-hover': item.bgHover,
-                '--theme-bg-active': item.bgActive
-              }"
-            >
-              <div class="flex justify-between items-center mb-1">
+        <div
+          class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
+        >
+          <header class="flex items-center justify-between mb-4">
+            <h3 class="flex items-center gap-2">
+              <div
+                class="i-material-symbols:area-chart-outline-rounded text-sky size-6"
+                text-sky
+              ></div>
+              数据趋势
+            </h3>
+            <div class="flex gap-4">
+              <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
+              <el-button size="default" @click="reset">重置</el-button>
+              <el-date-picker
+                v-model="selectedDate"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="datetimerange"
+                unlink-panels
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                size="default"
+                class="w-100!"
+                placement="bottom-end"
+                @change="handleDateChange"
+              />
+              <el-button
+                size="default"
+                :type="isFullscreen ? 'info' : 'primary'"
+                :icon="isFullscreen ? Crop : FullScreen"
+                @click="toggle"
+              >
+                {{ isFullscreen ? '退出全屏' : '全屏' }}
+              </el-button>
+            </div>
+          </header>
+
+          <div ref="downloadRef" class="flex flex-1">
+            <div class="flex gap-1 select-none">
+              <div
+                v-for="item of maxmin"
+                :key="item.name"
+                :style="{
+                  '--theme-bg-hover': item.bgHover
+                }"
+                class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
+                @click="handleClickSpec(item.name)"
+              >
+                <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
+                <div
+                  class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
+                  :style="{ backgroundColor: item.color }"
+                ></div>
                 <span
-                  class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
-                  :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
+                  class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
+                  :style="{ color: item.color }"
                 >
                   {{ item.name }}
                 </span>
                 <div
-                  class="size-2 rounded-full transition-all duration-300 shadow-sm"
-                  :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
-                  :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
+                  class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
+                  :style="{ backgroundColor: item.color }"
                 ></div>
+                <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
               </div>
-
-              <div class="flex items-baseline justify-between relative z-10">
-                <animated-count-to
-                  v-if="!item.isText"
-                  :value="Number(item.value)"
-                  :duration="500"
-                  :suffix="item.suffix"
-                  class="text-lg font-bold font-mono tracking-tight text-slate-800"
-                />
-                <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
-                  {{ item.value }}
-                </span>
-              </div>
+            </div>
+            <div
+              class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:20px_20px]"
+            >
               <div
-                class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
-                :class="
-                  selectedDimension[item.name]
-                    ? 'opacity-100 shadow-[0_0_8px_currentColor]'
-                    : 'opacity-0'
-                "
-                :style="{ backgroundColor: item.color, color: item.color }"
+                v-loading="chartLoading"
+                element-loading-background="transparent"
+                ref="chartRef"
+                class="w-full h-full"
               >
               </div>
             </div>
           </div>
-        </template>
-      </template>
-    </el-scrollbar>
-
-    <div
-      class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
-    >
-      <header class="flex items-center justify-between mb-4">
-        <h3 class="flex items-center gap-2">
-          <div class="i-material-symbols:area-chart-outline-rounded text-sky size-6" text-sky></div>
-          数据趋势
-        </h3>
-        <div class="flex gap-4">
-          <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
-          <el-button size="default" @click="reset">重置</el-button>
-          <el-date-picker
-            v-model="selectedDate"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            type="datetimerange"
-            unlink-panels
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            :shortcuts="rangeShortcuts"
-            size="default"
-            class="w-100!"
-            placement="bottom-end"
-            @change="handleDateChange"
-          />
-        </div>
-      </header>
-
-      <div class="flex flex-1">
-        <div class="flex gap-1 select-none">
-          <div
-            v-for="item of maxmin"
-            :key="item.name"
-            :style="{
-              '--theme-bg-hover': item.bgHover
-            }"
-            class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
-            @click="handleClickSpec(item.name)"
-          >
-            <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
-            <div
-              class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
-              :style="{ backgroundColor: item.color }"
-            ></div>
-            <span
-              class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
-              :style="{ color: item.color }"
-            >
-              {{ item.name }}
-            </span>
-            <div
-              class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
-              :style="{ backgroundColor: item.color }"
-            ></div>
-            <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
-          </div>
-        </div>
-        <div
-          class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden"
-        >
-          <div
-            v-loading="chartLoading"
-            element-loading-background="transparent"
-            ref="chartRef"
-            class="w-full h-full"
-          >
-          </div>
         </div>
       </div>
     </div>
@@ -943,4 +960,14 @@ onUnmounted(() => {
   background-color: #94a3b8;
   opacity: 1;
 }
+
+:fullscreen {
+  padding: 16px;
+  background-color: #fff;
+}
+
+/* 兼容写法 */
+::backdrop {
+  background-color: #fff;
+}
 </style>

+ 181 - 154
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -7,6 +7,8 @@ import {
   CircleCheckFilled,
   CircleCloseFilled,
   DataLine,
+  Crop,
+  FullScreen,
   TrendCharts
 } from '@element-plus/icons-vue'
 import { AnimatedCountTo } from '@/components/AnimatedCountTo'
@@ -16,6 +18,8 @@ import * as echarts from 'echarts'
 import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { Dimensions, formatIotValue, HeaderItem } from '@/utils/useSocketBus'
+import { useFullscreen } from '@vueuse/core'
+import { snapdom } from '@zumer/snapdom'
 
 const { query } = useRoute()
 
@@ -292,7 +296,7 @@ function render() {
       left: '5%',
       top: '5%',
       right: '5%',
-      bottom: '8%'
+      bottom: '12%'
     },
     tooltip: {
       trigger: 'axis',
@@ -563,30 +567,25 @@ function handleClickSpec(modelName: string) {
     chart?.resize()
   })
 }
-const exportChart = () => {
-  if (!chart) return
-  let img = new Image()
-  img.src = chart.getDataURL({
-    type: 'png',
-    pixelRatio: 1,
-    backgroundColor: '#fff'
-  })
 
-  img.onload = function () {
-    let canvas = document.createElement('canvas')
-    canvas.width = img.width
-    canvas.height = img.height
-    let ctx = canvas.getContext('2d')
-    ctx?.drawImage(img, 0, 0)
-    let dataURL = canvas.toDataURL('image/png')
+const downloadRef = ref(null)
 
-    let a = document.createElement('a')
+const exportChart = async () => {
+  try {
+    if (!downloadRef.value) return
 
-    let event = new MouseEvent('click')
+    const result = await snapdom(downloadRef.value, {
+      scale: 2,
+      backgroundColor: '#fff'
+    })
 
-    a.href = dataURL
-    a.download = `${data.value.deviceName}-设备监控-${dayjs().format('YYYY-MM-DD HH:mm:ss')}.png`
-    a.dispatchEvent(event)
+    await result.download({
+      filename: `${data.value.deviceName}-设备监控`,
+      type: 'png'
+    })
+  } catch (error) {
+    console.error(error)
+    ElMessage.error('导出图表失败')
   }
 }
 
@@ -610,14 +609,18 @@ onUnmounted(() => {
     if (chart) chart.resize()
   })
 })
+
+const targetArea = ref(null)
+
+const { toggle, isFullscreen } = useFullscreen(targetArea)
 </script>
 
 <template>
   <div
-    class="grid grid-cols-[260px_1fr] grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-rows-[80px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
     <div
-      class="grid-col-span-2 rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
+      class="rounded-xl shadow-sm border border-gray-100 border-solid px-6 flex items-center justify-between shrink-0 bg-gradient-to-r from-blue-100 to-white"
     >
       <div class="flex items-center gap-4">
         <div
@@ -655,151 +658,165 @@ onUnmounted(() => {
         </template>
       </div>
     </div>
+    <div ref="targetArea" class="h-full min-h-0 relative">
+      <div class="grid grid-cols-[260px_1fr] gap-4 h-full">
+        <el-scrollbar
+          class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
+          view-class="flex flex-col min-h-full"
+          v-loading="dimensionLoading"
+        >
+          <template v-for="citem in dimensionsContent" :key="citem.label">
+            <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
+              <div
+                class="sticky-title bg-blue-100 z-10 flex justify-between items-center py-3 px-4 border-0 border-solid"
+              >
+                <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
+                  <el-icon><component :is="citem.icon" /></el-icon>
+                  {{ citem.label }}
+                </span>
+                <span
+                  class="text-xs px-2 py-0.5 rounded-full font-mono"
+                  :class="[citem.countBg, citem.countColor]"
+                >
+                  {{ citem.value.length }}
+                </span>
+              </div>
 
-    <el-scrollbar
-      class="rounded-xl shadow-sm border border-gray-100 border-solid overflow-hidden bg-gradient-to-b from-blue-100 to-white"
-      view-class="flex flex-col min-h-full"
-      v-loading="dimensionLoading"
-    >
-      <template v-for="citem in dimensionsContent" :key="citem.label">
-        <template v-if="citem.judgment ? Boolean(citem.value.length) : true">
-          <div
-            class="sticky-title bg-blue-100 z-88 flex justify-between items-center py-3 px-4 border-0 border-solid"
-          >
-            <span class="font-bold text-sm text-gray-700! flex items-center gap-2">
-              <el-icon><component :is="citem.icon" /></el-icon>
-              {{ citem.label }}
-            </span>
-            <span
-              class="text-xs px-2 py-0.5 rounded-full font-mono"
-              :class="[citem.countBg, citem.countColor]"
-            >
-              {{ citem.value.length }}
-            </span>
-          </div>
+              <div class="px-3 pb-4 pt-2 space-y-3">
+                <div
+                  :data-disabled="disabledDimensions.includes(item.identifier)"
+                  v-for="item in citem.value"
+                  :key="item.identifier"
+                  @click="handleClickSpec(item.name)"
+                  class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
+                  :class="{ 'is-active': selectedDimension[item.name] }"
+                  :style="{
+                    '--theme-color': item.color,
+                    '--theme-bg-hover': item.bgHover,
+                    '--theme-bg-active': item.bgActive
+                  }"
+                >
+                  <div class="flex justify-between items-center mb-1">
+                    <span
+                      class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
+                      :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
+                    >
+                      {{ item.name }}
+                    </span>
+                    <div
+                      class="size-2 rounded-full transition-all duration-300 shadow-sm"
+                      :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
+                      :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
+                    ></div>
+                  </div>
+
+                  <div class="flex items-baseline justify-between relative z-19">
+                    <animated-count-to
+                      v-if="!item.isText"
+                      :value="Number(item.value)"
+                      :duration="500"
+                      :suffix="item.suffix"
+                      class="text-lg font-bold font-mono tracking-tight text-slate-800"
+                    />
+                    <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
+                      {{ item.value }}
+                    </span>
+                  </div>
+                  <div
+                    class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
+                    :class="
+                      selectedDimension[item.name]
+                        ? 'opacity-100 shadow-[0_0_8px_currentColor]'
+                        : 'opacity-0'
+                    "
+                    :style="{ backgroundColor: item.color, color: item.color }"
+                  >
+                  </div>
+                </div>
+              </div>
+            </template>
+          </template>
+        </el-scrollbar>
 
-          <div class="px-3 pb-4 pt-2 space-y-3">
-            <div
-              :data-disabled="disabledDimensions.includes(item.identifier)"
-              v-for="item in citem.value"
-              :key="item.identifier"
-              @click="handleClickSpec(item.name)"
-              class="dimension-card group relative p-3 rounded-lg border border-solid bg-transparent border-gray-300 transition-all duration-300 cursor-pointer select-none data-[disabled=true]:pointer-events-none"
-              :class="{ 'is-active': selectedDimension[item.name] }"
-              :style="{
-                '--theme-color': item.color,
-                '--theme-bg-hover': item.bgHover,
-                '--theme-bg-active': item.bgActive
-              }"
-            >
-              <div class="flex justify-between items-center mb-1">
+        <div
+          class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
+        >
+          <header class="flex items-center justify-between mb-4">
+            <h3 class="flex items-center gap-2">
+              <div
+                class="i-material-symbols:area-chart-outline-rounded text-sky size-6"
+                text-sky
+              ></div>
+              数据趋势
+            </h3>
+            <div class="flex gap-4">
+              <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
+              <el-button size="default" @click="reset">重置</el-button>
+              <el-date-picker
+                v-model="selectedDate"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="datetimerange"
+                unlink-panels
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                size="default"
+                class="w-100!"
+                placement="bottom-end"
+                @change="handleDateChange"
+              />
+              <el-button
+                size="default"
+                :type="isFullscreen ? 'info' : 'primary'"
+                :icon="isFullscreen ? Crop : FullScreen"
+                @click="toggle"
+              >
+                {{ isFullscreen ? '退出全屏' : '全屏' }}
+              </el-button>
+            </div>
+          </header>
+
+          <div ref="downloadRef" class="flex flex-1">
+            <div class="flex gap-1 select-none">
+              <div
+                v-for="item of maxmin"
+                :key="item.name"
+                :style="{
+                  '--theme-bg-hover': item.bgHover
+                }"
+                class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
+                @click="handleClickSpec(item.name)"
+              >
+                <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
+                <div
+                  class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
+                  :style="{ backgroundColor: item.color }"
+                ></div>
                 <span
-                  class="text-xs font-medium text-gray-500 transition-colors truncate pr-2 group-hover:text-[var(--theme-color)]"
-                  :class="{ 'text-[var(--theme-color)]!': selectedDimension[item.name] }"
+                  class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
+                  :style="{ color: item.color }"
                 >
                   {{ item.name }}
                 </span>
                 <div
-                  class="size-2 rounded-full transition-all duration-300 shadow-sm"
-                  :class="selectedDimension[item.name] ? 'scale-100' : 'scale-0'"
-                  :style="{ backgroundColor: item.color, boxShadow: `0 0 6px ${item.color}` }"
+                  class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
+                  :style="{ backgroundColor: item.color }"
                 ></div>
+                <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
               </div>
-
-              <div class="flex items-baseline justify-between relative z-10">
-                <animated-count-to
-                  v-if="!item.isText"
-                  :value="Number(item.value)"
-                  :duration="500"
-                  :suffix="item.suffix"
-                  class="text-lg font-bold font-mono tracking-tight text-slate-800"
-                />
-                <span v-else class="text-lg font-bold font-mono tracking-tight text-slate-800">
-                  {{ item.value }}
-                </span>
-              </div>
+            </div>
+            <div
+              class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:20px_20px]"
+            >
               <div
-                class="absolute left-0 top-3 bottom-3 w-1 rounded-r transition-all duration-300"
-                :class="
-                  selectedDimension[item.name]
-                    ? 'opacity-100 shadow-[0_0_8px_currentColor]'
-                    : 'opacity-0'
-                "
-                :style="{ backgroundColor: item.color, color: item.color }"
+                v-loading="chartLoading"
+                element-loading-background="transparent"
+                ref="chartRef"
+                class="w-full h-full"
               >
               </div>
             </div>
           </div>
-        </template>
-      </template>
-    </el-scrollbar>
-
-    <div
-      class="rounded-xl shadow-sm border border-gray-100 border-solid p-4 flex flex-col bg-gradient-to-b from-blue-100 to-white"
-    >
-      <header class="flex items-center justify-between mb-4">
-        <h3 class="flex items-center gap-2">
-          <div class="i-material-symbols:area-chart-outline-rounded text-sky size-6" text-sky></div>
-          数据趋势
-        </h3>
-        <div class="flex gap-4">
-          <el-button type="primary" size="default" @click="exportChart">导出为图片</el-button>
-          <el-button size="default" @click="reset">重置</el-button>
-          <el-date-picker
-            v-model="selectedDate"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            type="datetimerange"
-            unlink-panels
-            start-placeholder="开始日期"
-            end-placeholder="结束日期"
-            :shortcuts="rangeShortcuts"
-            size="default"
-            class="w-100!"
-            placement="bottom-end"
-            @change="handleDateChange"
-          />
-        </div>
-      </header>
-
-      <div class="flex flex-1">
-        <div class="flex gap-1 select-none">
-          <div
-            v-for="item of maxmin"
-            :key="item.name"
-            :style="{
-              '--theme-bg-hover': item.bgHover
-            }"
-            class="w-8 h-full flex flex-col items-center justify-between py-2 gap-1 rounded-full group relative bg-transparent border border-solid border-transparent transition-all duration-300 hover:bg-[var(--theme-bg-hover)] hover-border-gray-200 hover:shadow-md cursor-pointer active:scale-95"
-            @click="handleClickSpec(item.name)"
-          >
-            <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.max }}</span>
-            <div
-              class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
-              :style="{ backgroundColor: item.color }"
-            ></div>
-            <span
-              class="[writing-mode:sideways-lr] text-sm font-bold tracking-widest"
-              :style="{ color: item.color }"
-            >
-              {{ item.name }}
-            </span>
-            <div
-              class="flex-1 w-0.5 rounded-full opacity-40 group-hover:opacity-100 transition-opacity duration-300"
-              :style="{ backgroundColor: item.color }"
-            ></div>
-            <span class="[writing-mode:sideways-lr] text-xs text-gray-400">{{ item.min }}</span>
-          </div>
-        </div>
-        <div
-          class="flex flex-1 min-w-0 bg-gray-50/30 rounded-lg border border-dashed border-gray-200 ml-2 relative overflow-hidden"
-        >
-          <div
-            v-loading="chartLoading"
-            element-loading-background="transparent"
-            ref="chartRef"
-            class="w-full h-full"
-          >
-          </div>
         </div>
       </div>
     </div>
@@ -846,4 +863,14 @@ onUnmounted(() => {
   background-color: #94a3b8;
   opacity: 1;
 }
+
+:fullscreen {
+  padding: 16px;
+  background-color: #fff;
+}
+
+/* 兼容写法 */
+::backdrop {
+  background-color: #fff;
+}
 </style>

+ 17 - 4
src/views/pms/dingding.vue

@@ -50,6 +50,8 @@ const businessRoutes: Record<string, string> = {
   generateOperation: '',
   generateMaintenance: '',
   generateMaintain: '',
+  rdDailyReport: 'rdDailyReport',
+  rdReportDetail: 'rdReportDetail',
   rdReportApproval: 'rdReportApproval',
   rhDailyReport: 'rhDailyReport',
   rhReportApproval: 'rhReportApproval',
@@ -103,6 +105,12 @@ onMounted(async () => {
     } else if (type === 'rdReportApproval') {
       window.location.href =
         import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiDu/approval?id=' + id
+    } else if (type === 'rdReportDetail') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiDu/detail?id=' + id
+    } else if (type === 'rdDailyReport') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiDu/edit?id=' + id + '&istime=false'
     } else if (type === 'rhDailyReport') {
       window.location.href = import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruihen/edit?id=' + id
     } else if (type === 'rhReportApproval') {
@@ -183,13 +191,18 @@ onMounted(async () => {
       })
     } else if (type === 'rdDailyReport') {
       push({
-        name: 'FillDailyReportForm',
-        params: { id: id, mode: 'fill' }
+        path: '/iotdayilyreport/FillDailyReport',
+        query: { id: id, mode: 'edit' }
       })
     } else if (type === 'rdReportApproval') {
       push({
-        name: 'DailyReportApprovalForm',
-        params: { id }
+        path: '/iotdayilyreport/IotRdDailyReport',
+        query: { id: id, mode: 'approval' }
+      })
+    } else if (type === 'rdReportDetail') {
+      push({
+        path: '/iotdayilyreport/IotRdDailyReport',
+        query: { id: id, mode: 'detail' }
       })
     } else if (type === 'rhDailyReport') {
       push({ path: '/iotdayilyreport/IotRhDailyReport/fill', query: { id: id } })

+ 77 - 10
src/views/pms/iotopeationfill/index1.vue

@@ -44,7 +44,7 @@
             size="default"
             label-width="120px"
             class="scrollable-form"
-            :model="{ attrList: attrList, reportDetails }"
+            :model="{ attrList: attrList, reportDetails, taskId }"
           >
             <div style="margin-left: 24px">
               <el-form class="demo-form-inline" :inline="true">
@@ -59,7 +59,33 @@
                   </span>
                 </el-form-item>
                 <el-form-item
-                  v-if="deviceItem.deviceName === '生产日报'"
+                  v-if="deviceItem.deviceName === '生产日报' && companyName !== 'ry'"
+                  label="井号"
+                  class="custom-label1"
+                >
+                  <span style="text-decoration: underline">
+                    {{ deviceItem.wellName }}
+                  </span>
+                </el-form-item>
+                <el-form-item
+                  v-else-if="
+                    deviceItem.deviceName === '生产日报' &&
+                    companyName === 'ry' &&
+                    taskOptions.length > 0
+                  "
+                  label="井号"
+                  class="custom-label1"
+                  prop="taskId"
+                >
+                  <el-select
+                    v-model="taskId"
+                    placeholder="请选择井号"
+                    :options="taskOptions"
+                    class="w-40!"
+                  />
+                </el-form-item>
+                <el-form-item
+                  v-else-if="deviceItem.deviceName === '生产日报'"
                   label="井号"
                   class="custom-label1"
                 >
@@ -108,7 +134,7 @@
               </div>
 
               <el-form-item
-                v-if="attrItem.description !== 'productionStatus'"
+                v-if="companyName === 'ry' ? attrItem.description !== 'productionStatus' : true"
                 :label="attrItem.name"
                 prop="deviceId"
                 label-position="top"
@@ -246,12 +272,30 @@
               </div>
               <el-form-item prop="reportDetails" class="table-form-item">
                 <ZmTable :data="reportDetails" :loading="false" class="mb-4">
-                  <ZmTableColumn
-                    label="日期"
-                    :width="105"
-                    cover-formatter
-                    :real-value="() => createTime"
-                  />
+                  <ZmTableColumn label="日期" :width="180" prop="reportDate">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        class="mb-0!"
+                        :prop="`reportDetails.${$index}.reportDate`"
+                        :rules="{
+                          required: true,
+                          message: '请选择日期',
+                          trigger: ['change', 'blur'],
+                          type: 'number'
+                        }"
+                      >
+                        <el-date-picker
+                          v-model="row.reportDate"
+                          placeholder="选择日期"
+                          clearable
+                          class="w-full!"
+                          value-format="x"
+                          :disabled="fillStatus === '1'"
+                        />
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
 
                   <ZmTableColumn :width="130" label="开始时间" prop="startTime">
                     <template #default="{ row, $index }">
@@ -427,6 +471,7 @@ import { useRoute } from 'vue-router'
 import { calculateDuration, formatT } from '@/utils/formatTime'
 import { Delete, Plus } from '@element-plus/icons-vue'
 import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
 
 /** 运行记录填报 列表 */
 defineOptions({ name: 'FillOrderInfo' })
@@ -474,6 +519,7 @@ const queryParams = reactive<any>({
 })
 
 interface ReportDetail {
+  reportDate: number
   startTime: string
   endTime: string
   duration: number
@@ -482,6 +528,9 @@ interface ReportDetail {
   constructionDetail: string
 }
 
+const taskId = ref<number | undefined>(undefined)
+const taskOptions = ref<{ label: string; value: number }[]>([])
+
 const reportDetails = ref<ReportDetail[]>([])
 
 const addProductionStatusRow = () => {
@@ -489,6 +538,7 @@ const addProductionStatusRow = () => {
     reportDetails.value = []
   }
   reportDetails.value.push({
+    reportDate: createTime ? dayjs(createTime).valueOf() : dayjs().valueOf(),
     startTime: '',
     endTime: '',
     duration: 0,
@@ -730,8 +780,19 @@ const getList = async () => {
       queryParams.deviceId = list.value[0].deviceId
     }
     await getAttrList()
+
+    const daily = list.value.find((item) => item.deviceName === '生产日报') ?? { wellNamePair: {} }
+
+    taskOptions.value = Object.keys(daily.wellNamePair).map((key) => ({
+      label: daily.wellNamePair[key],
+      value: Number(key)
+    }))
+
+    taskId.value = taskOptions.value[0].value
+
     IotOpeationFillApi.getReportDetails(deptId.split(',')[3]).then((res) => {
       reportDetails.value = (res ? (res as any[]) : []).map((item) => ({
+        reportDate: item.reportDate ?? dayjs(createTime).valueOf(),
         startTime: formatT(item.startTime),
         endTime: formatT(item.endTime),
         duration: item.duration,
@@ -1009,7 +1070,13 @@ const getFillInfo = async () => {
     })
     const data = attrList2.value as unknown as IotOpeationFillVO
 
-    const reqData = { createReqVO: data, reportDetails: reportDetails.value }
+    const reqData = {
+      createReqVO: data,
+      reportDetails: reportDetails.value.map((item) => ({
+        ...item,
+        taskId: taskId.value
+      }))
+    }
 
     await IotOpeationFillApi.insertLog(reqData)
     message.success(t('common.createSuccess'))

+ 1299 - 3062
src/views/pms/iotrddailyreport/FillDailyReportForm.vue

@@ -1,782 +1,15 @@
-<template>
-  <ContentWrap v-loading="formLoading">
-    <!-- 第一部分:日报标题 -->
-    <div class="daily-report-title">
-      <h2>{{ pageTitle }}</h2>
-      <!-- 在审批模式下显示审批状态提示 -->
-      <div v-if="isReadonlyMode" class="approval-notice">
-        <el-alert :title="modeNotice" type="info" :closable="false" />
-      </div>
-    </div>
-
-    <!-- 第二部分:项目/任务信息 -->
-    <ContentWrap>
-      <div class="info-table" style="margin-top: 1em">
-        <!-- 表格行 -->
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">甲方:</span>
-              <!-- 甲方字段:添加 single-line-ellipsis 类 + title 绑定完整内容 -->
-              <span
-                class="cell-value single-line-ellipsis"
-                :title="dailyReportData.manufactureName || '-'"
-              >
-                {{ dailyReportData.manufactureName || '-' }}
-              </span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">合同号:</span>
-              <span class="cell-value">{{ dailyReportData.contractName || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">井号:</span>
-              <span class="cell-value">{{
-                displayWellName || dailyReportData.taskName || '-'
-              }}</span>
-            </div>
-          </div>
-        </div>
-
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">施工队伍:</span>
-              <span class="cell-value">{{ dailyReportData.deptName || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">施工地点:</span>
-              <span class="cell-value">{{ dailyReportData.location || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">工艺:</span>
-              <span class="cell-value">{{ dailyReportData.techniqueNames || '-' }}</span>
-            </div>
-          </div>
-        </div>
-
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">设计工作量:</span>
-              <span class="cell-value">{{ dailyReportData.workloadDesign || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">开工日期:</span>
-              <span class="cell-value">{{ dailyReportData.commencementDate || '-' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">完工日期:</span>
-              <span class="cell-value">{{ dailyReportData.completionDate || '-' }}</span>
-            </div>
-          </div>
-        </div>
-
-        <div class="table-row">
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">施工周期D:</span>
-              <span class="cell-value">{{ dailyReportData.constructionPeriod || '' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">停待时间D:</span>
-              <span class="cell-value">{{ dailyReportData.idleTime || '' }}</span>
-            </div>
-          </div>
-          <div class="table-cell">
-            <div class="cell-content">
-              <span class="cell-label">带班干部:</span>
-              <span class="cell-value">{{ dailyReportData.responsiblePersonNames || '-' }}</span>
-            </div>
-          </div>
-        </div>
-
-        <!-- 第五行:设备配置(单独一行) -->
-        <div class="table-row">
-          <div class="table-cell full-width">
-            <div class="cell-content">
-              <span class="cell-label">设备配置:</span>
-              <span class="cell-value indent-multiline">{{
-                dailyReportData.deviceNames || '-'
-              }}</span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </ContentWrap>
-
-    <!-- 实际进度显示区域(新增) -->
-    <ContentWrap class="section-padding" v-if="showActualProgress">
-      <h3 class="progress-title">任务进度</h3>
-      <div class="actual-progress-container">
-        <div v-if="actualProgressData.length > 0">
-          <el-steps
-            direction="horizontal"
-            :active="actualProgressData.length - 1"
-            finish-status="success"
-          >
-            <el-step
-              v-for="(step, index) in actualProgressData"
-              :key="index"
-              :title="step.title"
-              :description="step.description"
-              :status="step.status"
-            />
-          </el-steps>
-        </div>
-        <div v-else class="no-progress-data"> 暂无实际进度数据 </div>
-      </div>
-    </ContentWrap>
-
-    <!-- 第三部分:日报填报表单 -->
-    <ContentWrap class="section-padding">
-      <el-form
-        ref="formRef"
-        :model="formData"
-        :rules="isReadonlyMode ? {} : formRules"
-        v-loading="formLoading"
-        style="margin-top: 1em"
-        label-width="200px"
-        :disabled="isReadonlyMode"
-      >
-        <!-- 第一行:时间节点、施工状态 -->
-        <el-row :gutter="30">
-          <el-col :span="12">
-            <el-form-item label="时间节点" prop="timeRange">
-              <el-time-picker
-                is-range
-                v-model="formData.timeRange"
-                range-separator="至"
-                start-placeholder="开始时间"
-                end-placeholder="结束时间"
-                placeholder="选择时间范围"
-                style="width: 100%"
-                :readonly="isReadonlyMode"
-                :disabled="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="施工状态" prop="rdStatus">
-              <el-select
-                v-model="formData.rdStatus"
-                placeholder="请选择施工状态"
-                style="width: 100%"
-                :disabled="isReadonlyMode"
-              >
-                <el-option
-                  v-for="dict in rdStatusOptions"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 平台井 -->
-        <el-row v-if="showPlatformField">
-          <el-col :span="24">
-            <el-form-item label="平台井" prop="platformId">
-              <el-select
-                v-model="formData.platformId"
-                placeholder="请选择平台井"
-                style="width: 100%"
-                :disabled="isReadonlyMode && query.istime !== 'true'"
-              >
-                <el-option
-                  v-for="platform in platformOptions"
-                  :key="platform.id"
-                  :label="platform.wellName"
-                  :value="platform.id"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 施工设备字段 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="施工设备" prop="deviceIds">
-              <!-- 编辑模式:显示选择按钮 -->
-              <template v-if="isEditMode">
-                <el-button
-                  @click="openDeviceDialog"
-                  type="primary"
-                  size="small"
-                  :disabled="formLoading || isReadonlyMode"
-                >
-                  选择设备
-                </el-button>
-                <el-tooltip
-                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
-                  :content="getAllDeviceNamesForDisplay"
-                  placement="top"
-                >
-                  <span class="device-display-container">
-                    {{ formatDevicesForDisplay }}
-                  </span>
-                </el-tooltip>
-                <span v-else class="no-device"> 未选择设备 </span>
-              </template>
-
-              <!-- 只读模式:只显示设备信息 -->
-              <template v-else>
-                <el-tooltip
-                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
-                  :content="getAllDeviceNamesForDisplay"
-                  placement="top"
-                >
-                  <span class="device-display-container">
-                    {{ formatDevicesForDisplay }}
-                  </span>
-                </el-tooltip>
-                <span v-else class="no-device">-</span>
-              </template>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 未施工设备 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="未施工设备" prop="unSelectedDeviceNames">
-              <el-input
-                v-model="unSelectedDeviceNames"
-                type="textarea"
-                :rows="2"
-                placeholder="未施工的设备将显示在这里"
-                :readonly="true"
-                class="unselected-device"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 第二行:施工工艺 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="施工工艺" prop="techniqueIds">
-              <el-select
-                v-model="formData.techniqueIds"
-                placeholder="请选择施工工艺"
-                style="width: 100%"
-                multiple
-                :disabled="isReadonlyMode"
-              >
-                <el-option
-                  v-for="dict in techniqueOptions"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 动态属性区域:施工工艺与当日生产动态之间 -->
-        <el-row v-if="dynamicAttrs.length > 0" :gutter="30">
-          <el-col
-            v-for="attr in dynamicAttrs"
-            :key="attr.id"
-            :span="attr.dataType === 'textarea' ? 24 : 12"
-          >
-            <el-form-item
-              :label="attr.name + (attr.unit ? `(${attr.unit})` : '')"
-              :prop="'dynamicFields.' + attr.identifier"
-              :rules="isReadonlyMode ? [] : getDynamicAttrRules(attr)"
-            >
-              <!-- 文本类型 -->
-              <el-input
-                v-if="attr.dataType === 'text'"
-                v-model="formData.dynamicFields[attr.identifier]"
-                :placeholder="`请输入${attr.name}`"
-                :readonly="isReadonlyMode"
-              />
-
-              <!-- 文本域类型 -->
-              <el-input
-                v-else-if="attr.dataType === 'textarea'"
-                v-model="formData.dynamicFields[attr.identifier]"
-                :placeholder="`请输入${attr.name}`"
-                type="textarea"
-                :rows="3"
-                :readonly="isReadonlyMode"
-              />
-
-              <!-- 数字类型 -->
-              <el-input
-                v-else-if="attr.dataType === 'double'"
-                v-model="formData.dynamicFields[attr.identifier]"
-                :placeholder="`请输入${attr.name}`"
-                type="number"
-                :min="attr.minValue || undefined"
-                :max="attr.maxValue || undefined"
-                :readonly="isReadonlyMode"
-              />
-
-              <!-- 日期类型 -->
-              <el-date-picker
-                v-else-if="attr.dataType === 'date'"
-                v-model="formData.dynamicFields[attr.identifier]"
-                type="date"
-                value-format="x"
-                :placeholder="`选择${attr.name}`"
-                style="width: 100%"
-                :readonly="isReadonlyMode"
-              />
-
-              <!-- 默认文本输入 -->
-              <el-input
-                v-else
-                v-model="formData.dynamicFields[attr.identifier]"
-                :placeholder="`请输入${attr.name}`"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="当日油耗(L)" prop="dailyFuel">
-              <el-input
-                v-model="formData.dailyFuel"
-                type="text"
-                :min="0"
-                placeholder="自动计算当日油耗"
-                :readonly="isReadonlyMode"
-                @blur="formatDailyFuel"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 第三行:当日生产动态 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="当日生产动态" prop="productionStatus">
-              <el-input
-                v-model="formData.productionStatus"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入当日生产动态"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 第四行:下步工作计划 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="下步工作计划" prop="nextPlan">
-              <el-input
-                v-model="formData.nextPlan"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入下步工作计划"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 第五行:外租设备 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="外租设备" prop="externalRental">
-              <el-input
-                v-model="formData.externalRental"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入外租设备信息"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 故障情况 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="故障情况" prop="malfunction">
-              <el-input
-                v-model="formData.malfunction"
-                type="textarea"
-                :rows="3"
-                placeholder="请输入故障情况"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 故障误工H -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="故障误工H" prop="faultDowntime">
-              <el-input
-                v-model="formData.faultDowntime"
-                type="number"
-                :rows="3"
-                placeholder="请输入故障误工H"
-                :readonly="isReadonlyMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <div class="grid grid-cols-3 gao-4">
-          <el-form-item
-            v-for="field in NON_PROD_FIELDS"
-            :key="field.key"
-            :label="field.label + '(H)'"
-            :prop="field.key"
-          >
-            <el-input-number
-              class="!w-full"
-              :min="0"
-              :max="24"
-              v-model="formData[field.key]"
-              :controls="false"
-              align="left"
-              :disabled="query.istime !== 'true'"
-            />
-          </el-form-item>
-        </div>
-
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="其他非生产原因" prop="otherNptReason">
-              <el-input
-                v-model="formData.otherNptReason"
-                placeholder="请输入其他非生产原因"
-                :disabled="query.istime !== 'true'"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <!-- 第六行:上传附件 -->
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="附件">
-              <!-- 文件上传组件 -->
-              <FileUpload
-                v-if="!isReadonlyMode"
-                ref="fileUploadRef"
-                :device-id="deviceId"
-                :show-folder-button="false"
-                @upload-success="handleUploadSuccess"
-              />
-
-              <!-- 已上传附件列表显示 -->
-              <div
-                v-if="formData.attachments && formData.attachments.length > 0"
-                class="attachment-container"
-              >
-                <div class="attachment-list">
-                  <div
-                    v-for="(attachment, index) in formData.attachments"
-                    :key="attachment.id || index"
-                    class="attachment-item"
-                  >
-                    <!-- 为附件名称添加点击事件,传递整个附件对象 -->
-                    <a class="attachment-name" @click="inContent(attachment)">
-                      {{ attachment.filename }}
-                    </a>
-                    <el-button
-                      v-if="!isReadonlyMode"
-                      type="danger"
-                      link
-                      size="small"
-                      @click="removeAttachment(index)"
-                    >
-                      删除
-                    </el-button>
-                  </div>
-                </div>
-              </div>
-
-              <!-- 审批模式下只显示附件列表 -->
-              <div
-                v-else-if="
-                  isApprovalMode && (!formData.attachments || formData.attachments.length === 0)
-                "
-                class="no-attachment"
-              >
-                无附件
-              </div>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-    </ContentWrap>
-
-    <!-- 油耗信息区域 - 当有油耗数据时显示 -->
-    <ContentWrap class="fuel-consumption-section" v-if="showFuelConsumption">
-      <h2 class="text-lg font-semibold mb-4">油耗信息</h2>
-
-      <div class="fuel-consumption-table">
-        <el-table
-          :data="fuelConsumptionData"
-          border
-          style="width: 100%"
-          class="fuel-consumption-el-table"
-          table-layout="fixed"
-          :key="fuelTableKey"
-          row-key="deviceId"
-        >
-          <!-- 车辆编码 -->
-          <el-table-column
-            prop="deviceCode"
-            label="车辆编码"
-            align="center"
-            :show-overflow-tooltip="false"
-            width="120"
-          />
-
-          <!-- 车辆名称 -->
-          <el-table-column
-            prop="deviceName"
-            label="车辆名称"
-            align="center"
-            :show-overflow-tooltip="false"
-            width="150"
-          />
-
-          <!-- 发生日期 -->
-          <el-table-column
-            label="发生日期"
-            align="center"
-            :show-overflow-tooltip="false"
-            width="120"
-          >
-            <template #default="scope">
-              {{ formatDate(scope.row.queryDate) }}
-            </template>
-          </el-table-column>
-
-          <!-- 中航北斗油耗 -->
-          <el-table-column label="中航北斗油耗(L)" align="center" width="140">
-            <template #default="scope">
-              {{ formatNumber(scope.row.zhbdFuel, 2) }}
-            </template>
-          </el-table-column>
-
-          <!-- 实际油耗 -->
-          <el-table-column label="实际油耗(L)" align="center" width="140">
-            <template #default="scope">
-              <!-- 编辑模式下显示输入框 -->
-              <el-input
-                v-if="!isReadonlyMode"
-                v-model="scope.row.customFuel"
-                type="text"
-                :min="0"
-                placeholder="请输入实际油耗"
-                @blur="handleCustomFuelChange(scope.row)"
-                style="width: 100%"
-                size="small"
-                @focus="handleFuelInputFocus(scope.row)"
-                :key="scope.row.deviceId"
-              />
-              <!-- 只读模式下显示数值 -->
-              <span v-else>
-                {{ formatNumber(scope.row.customFuel, 2) }}
-              </span>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-    </ContentWrap>
-
-    <!-- 平台井工作量区域 - 只在平台井的详情或审批模式下显示 -->
-    <ContentWrap
-      class="platform-workload-section"
-      v-if="(isDetailMode || isApprovalMode) && dailyReportData?.platformWell === 1"
-    >
-      <h2 class="text-lg font-semibold mb-4">平台井工作量</h2>
-
-      <div
-        class="platform-workload-table"
-        v-if="platformWorkloadData && platformWorkloadData.length > 0"
-      >
-        <el-table
-          :data="platformWorkloadData"
-          border
-          style="width: 100%"
-          class="platform-workload-el-table"
-          table-layout="fixed"
-        >
-          <!-- 固定列 -->
-          <el-table-column
-            prop="wellName"
-            label="井号"
-            align="center"
-            :show-overflow-tooltip="true"
-          />
-          <el-table-column label="施工状态" align="center" :show-overflow-tooltip="true">
-            <template #default="scope">
-              {{ scope.row.rdStatusLabel || '' }}
-            </template>
-          </el-table-column>
-          <el-table-column label="施工工艺" align="center" :show-overflow-tooltip="true">
-            <template #default="scope">
-              {{ scope.row.techniqueNames || '' }}
-            </template>
-          </el-table-column>
-
-          <!-- 动态工作量列 -->
-          <el-table-column
-            v-for="workloadColumn in getWorkloadColumns()"
-            :key="workloadColumn.key"
-            :label="workloadColumn.label"
-            align="center"
-            :show-overflow-tooltip="true"
-          >
-            <template #default="scope">
-              {{ getWorkloadValue(scope.row, workloadColumn.identifier) }}
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-
-      <div v-else class="text-center text-gray-500 py-4"> 暂无平台井工作量数据 </div>
-    </ContentWrap>
-
-    <!-- 第四部分:审批意见 - 只在审批模式下显示 -->
-    <ContentWrap class="section-padding" v-if="isApprovalMode || isEditMode || isDetailMode">
-      <el-form
-        ref="approvalFormRef"
-        :model="approvalForm"
-        style="margin-top: 1em"
-        label-width="200px"
-      >
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="审批意见" prop="opinion">
-              <el-input
-                v-model="approvalForm.opinion"
-                type="textarea"
-                :rows="4"
-                placeholder="请输入审批意见"
-                maxlength="500"
-                show-word-limit
-                :readonly="!isApprovalMode"
-                :disabled="!isApprovalMode"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-    </ContentWrap>
-
-    <!-- 操作按钮 -->
-    <ContentWrap class="section-padding" v-if="isEditMode">
-      <el-form>
-        <el-form-item style="float: right">
-          <el-button @click="submitForm" type="primary" :disabled="formLoading">
-            {{ t('common.save') }}
-          </el-button>
-          <el-button @click="close">{{ t('common.cancel') }}</el-button>
-        </el-form-item>
-      </el-form>
-    </ContentWrap>
-
-    <!-- 审批模式下的操作按钮 -->
-    <ContentWrap class="section-padding" v-if="isApprovalMode">
-      <el-form>
-        <el-form-item style="float: right">
-          <el-button @click="handleApprove('pass')" type="success"> 审批通过 </el-button>
-          <el-button @click="handleApprove('reject')" type="danger"> 审批驳回 </el-button>
-          <el-button @click="close">{{ t('common.close') }}</el-button>
-        </el-form-item>
-      </el-form>
-    </ContentWrap>
-
-    <!-- 详情模式下的操作按钮 - 只有关闭按钮 -->
-    <ContentWrap class="section-padding" v-if="isDetailMode">
-      <el-form>
-        <el-form-item style="float: right">
-          <el-button @click="close">{{ t('common.close') }}</el-button>
-        </el-form-item>
-      </el-form>
-    </ContentWrap>
-  </ContentWrap>
-
-  <!-- 设备选择对话框 -->
-  <el-dialog
-    v-model="deviceDialogVisible"
-    title="选择施工设备"
-    width="1000px"
-    :before-close="handleDeviceDialogClose"
-    class="device-select-dialog"
-  >
-    <div class="transfer-container">
-      <el-transfer
-        v-model="selectedDeviceIds"
-        :data="filteredDeviceList"
-        :titles="['可选设备', '已选设备']"
-        :props="{ key: 'id', label: 'deviceCode' }"
-        filterable
-        class="transfer-component"
-        @change="handleTransferChange"
-      >
-        <template #default="{ option }">
-          <el-tooltip
-            effect="dark"
-            placement="top"
-            :content="`${option.deviceCode || ''} - ${option.deviceName || ''}`"
-            :disabled="!option.deviceCode && !option.deviceName"
-            transition="fade-in-linear"
-          >
-            <span class="transfer-option-text">
-              {{ option.deviceCode }} - {{ option.deviceName }}
-            </span>
-          </el-tooltip>
-        </template>
-      </el-transfer>
-    </div>
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="handleDeviceDialogClose">取消</el-button>
-        <el-button type="primary" @click="confirmDeviceSelection">确定</el-button>
-      </span>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useMessage } from '@/hooks/web/useMessage'
-import { useTagsViewStore } from '@/store/modules/tagsView'
-import { useRouter } from 'vue-router'
-import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+<script lang="ts" setup>
 import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { FormInstance, FormRules } from 'element-plus'
+import { CircleCheck, Plus, Delete } from '@element-plus/icons-vue'
+import { calculateDuration, formatDateNoTime, formatT } from '@/utils/formatTime'
+import { getStrDictOptions } from '@/utils/dict'
 import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
-import * as DeptApi from '@/api/system/dept'
-import { useUserStore } from '@/store/modules/user'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import dayjs from 'dayjs'
-import FileUpload from '@/components/UploadFile/src/FileUpload.vue'
+import { useDebounceFn } from '@vueuse/core'
+import { cloneDeep } from 'lodash-es'
+import { Base64 } from 'js-base64'
 
 const NON_PROD_FIELDS = [
   { key: 'repairTime', label: '设备故障' },
@@ -791,2481 +24,1485 @@ const NON_PROD_FIELDS = [
   { key: 'relocationTime', label: '生产配合' },
   { key: 'winterBreakTime', label: '待命' },
   { key: 'otherNptTime', label: '其他非生产时间' }
-] as const
+]
 
-const { t } = useI18n()
 const message = useMessage()
-const { delView } = useTagsViewStore()
-const { push, currentRoute } = useRouter()
-const { params, query } = useRoute()
-const userStore = useUserStore()
-
-/** 填报日报 表单 */
-defineOptions({ name: 'FillDailyReportForm' })
 
-const formLoading = ref(false)
-const formRef = ref()
-const id = params.id // 瑞都日报id
-
-const fuelTableKey = ref(0) // 用于强制重新渲染表格
-
-// 添加一个新的响应式变量用于输入
-const dailyFuelInput = ref('')
-
-// 日报数据
-const dailyReportData = ref<any>({})
-
-// 修改 reportFuels 的 watch,添加标志位避免自动覆盖
-const dailyFuelManuallyModified = ref(false)
-
-// 添加模式判断计算属性
-const isApprovalMode = computed(() => params.mode === 'approval')
-const isDetailMode = computed(() => params.mode === 'detail')
-const isEditMode = computed(() => params.mode === 'fill' || !params.mode) // 默认为编辑模式
-
-// 只读模式判断:审批模式或详情模式都为只读
-const isReadonlyMode = computed(
-  () => isApprovalMode.value || isDetailMode.value || formData.value.auditStatus === 20
-)
-
-// 在表单数据定义附近添加
-const platformWellPairs = ref<any[]>([]) // 存储各平台井的工作量数据
-const currentPlatformId = ref<number>() // 当前选中的平台井ID
-
-// 计算属性:显示井名(统一处理主井和平台井逻辑)
-const displayWellName = computed(() => {
-  // 如果是平台井模式
-  if (dailyReportData.value.platformWell === 1) {
-    // 确定平台井数据源:优先使用 platforms,不存在则使用 finishedPlatforms
-    const platformSource =
-      dailyReportData.value.platforms || dailyReportData.value.finishedPlatforms
-
-    // 如果有平台井数据
-    if (platformSource && platformSource.length > 0) {
-      // 检查主井是否在平台井列表中
-      const isMainWellInPlatforms = platformSource.some(
-        (platform: any) => platform.id === dailyReportData.value.taskId
-      )
+const { t } = useI18n()
 
-      // 如果主井不在平台井列表中(说明主井已施工完成),使用第一个平台井的名称
-      if (!isMainWellInPlatforms) {
-        const firstPlatformWellName = platformSource[0].wellName
-        console.log(`主井已施工完成,井号显示使用平台井名称: ${firstPlatformWellName}`)
-        return firstPlatformWellName
-      }
+const formType = ref<'edit' | 'approval' | 'detail' | 'time'>('edit')
+
+interface ExtProperty {
+  name: string
+  unit: string
+  dataType: 'double' | 'string'
+  actualValue: string | number
+  identifier: string
+  required?: number
+}
+
+interface Platform {
+  id: number
+  wellName: string
+  reportId: number
+  rdStatusLabel: string
+  techniqueNames: string
+  extProperty: Array<ExtProperty>
+}
+
+interface PlatformData {
+  rdStatus: string
+  techniqueIds: string[]
+  extProperty: Array<ExtProperty>
+  [key: (typeof NON_PROD_FIELDS)[number]['key']]: any
+}
+
+interface Fule {
+  deviceCode: string
+  deviceName: string
+  queryDate: number
+  zhbdFuel: number
+  customFuel: number
+}
+
+interface Data {
+  id: number
+  platformWell: number
+  platforms: Platform[]
+  finishedPlatforms: Platform[]
+  taskId: number
+  wellName: string
+  constructionStartDate: number
+  manufactureName: string
+  contractName: string
+  deptName: string
+  location: string
+  techniqueNames: string
+  workloadDesign: string
+  commencementDate: string
+  completionDate: string
+  constructionPeriod: string
+  idleTime: string
+  responsiblePersonNames: string
+  deviceNames: string
+  taskName: string
+  taskProgresses: {
+    createTime: string
+    rdStatusLabel: string
+  }[]
+  selectedDevices: {
+    deviceName: string
+    deviceCode: string
+    id: number
+  }[]
+  deviceIds: number[]
+  nextPlan: string
+  externalRental: string
+  malfunction: string
+  faultDowntime: number
+  startTime: number[]
+  endTime: number[]
+  dailyFuel: number
+  reportFuels: Fule[]
+  reportedFuels: Fule[]
+  createTime: number
+  auditStatus: number
+  status: number
+  opinion: string
+  companyId: number
+  deptId: number
+  reportDetails: Omit<ReportDetail, 'startTime' | 'endTime'> &
+    {
+      startTime: number[]
+      endTime: number[]
+    }[]
+  attachments: any[]
+  constructionBrief: string
+}
+
+interface ReportDetail {
+  startTime: string
+  endTime: string
+  constructionDetail: string
+  duration: number
+}
+
+interface Form {
+  timeRange: string[]
+  deviceIds: number[]
+  dailyFuel: number
+  nextPlan: string
+  externalRental: string
+  malfunction: string
+  faultDowntime: number
+  platformIds: number[]
+  reportFuels: Fule[]
+  reportDetails: ReportDetail[]
+  constructionBrief: string
+  attachments: any[]
+  [key: number]: PlatformData | any
+}
+
+const formRef = ref<FormInstance>()
+const rules = ref<FormRules<Form>>({
+  timeRange: [{ required: true, message: '请选择时间节点', trigger: 'change', type: 'array' }],
+  dailyFuel: [{ required: true, message: '请输入当日油耗', trigger: 'change' }],
+  nextPlan: [{ required: true, message: '请输入下计划', trigger: 'change' }],
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    {
+      required: formType.value === 'time',
+      message: '请填写施工简报',
+      type: 'string',
+      trigger: ['blur', 'change']
     }
-  }
-
-  // 其他情况使用原来的井名
-  return dailyReportData.value.wellName
-})
-
-// 页面标题计算
-const pageTitle = computed(() => {
-  const displayWellNameValue = displayWellName.value
-  const constructionDate = dailyReportData.value.constructionStartDate
-
-  if (isApprovalMode.value) {
-    return displayWellNameValue && constructionDate
-      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 日报审批`
-      : '日报审批'
-  } else if (isDetailMode.value) {
-    return displayWellNameValue && constructionDate
-      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 日报详情`
-      : '日报详情'
-  } else {
-    return displayWellNameValue && constructionDate
-      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 生产日报`
-      : '日报填报'
-  }
+  ]
 })
 
-// 处理输入框获取焦点事件
-const handleFuelInputFocus = (fuelItem: any) => {
-  // 如果 customFuel 是空的,确保它显示默认值
-  if (!fuelItem.customFuel || fuelItem.customFuel === '') {
-    const zhbdValue = parseFloat(fuelItem.zhbdFuel)
-    fuelItem.customFuel = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
+function noProductionTimeRule(id: number) {
+  const wellName =
+    wellOptions.value.find((item) => item.value === id)?.label ?? data.value.wellName ?? ''
+  return {
+    validator: (_rule: any, _value: any, callback: any) => {
+      const currentRow = form.value[id]
+      if (!currentRow) {
+        callback()
+        return
+      }
+      let totalTime = 0
+      NON_PROD_FIELDS.forEach((field) => {
+        const val = parseFloat(currentRow[field.key])
+        if (!isNaN(val)) {
+          totalTime += val
+        }
+      })
+      const fixedTotal = Number(totalTime.toFixed(2))
+      if (fixedTotal > 24) {
+        callback(new Error(`【${wellName}】总时间(${fixedTotal}h)不能超过 24 小时`))
+      } else {
+        callback()
+      }
+    },
+    trigger: 'blur'
   }
 }
 
-// 模式提示信息
-const modeNotice = computed(() => {
-  if (isApprovalMode.value) {
-    return '审批模式:所有字段均为只读'
-  } else if (isDetailMode.value) {
-    return '详情模式:所有字段均为只读'
-  }
-  return ''
-})
+const handleRowValidate = (pid: number, key: string) => {
+  if (!formRef.value) return
 
-// 动态属性相关变量
-const dynamicAttrs = ref<any[]>([]) // 存储动态属性列表
+  const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
 
-// 添加设备选择相关变量
-const deviceDialogVisible = ref(false)
-const filteredDeviceList = ref<any[]>([])
-const selectedDeviceIds = ref<number[]>([])
-const deviceMap = ref<Record<number, any>>({})
+  if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
 
-// 添加平台井相关响应式数据
-const platformOptions = ref<any[]>([]) // 平台井下拉选项
-const showPlatformField = ref(false) // 是否显示平台井字段
+  formRef.value.validateField(propsToValidate)
+}
+
+const data = ref<Partial<Data>>({})
 
-// 计算属性:是否显示平台井字段
-const shouldShowPlatformField = computed(() => {
-  return dailyReportData.value.platformWell === 1
+const original = (): Form => ({
+  timeRange: [],
+  deviceIds: [],
+  dailyFuel: 0,
+  nextPlan: '',
+  externalRental: '',
+  malfunction: '',
+  faultDowntime: 0,
+  platformIds: [],
+  reportDetails: [],
+  reportFuels: [],
+  constructionBrief: '',
+  attachments: []
 })
 
-// 初始化平台井数据
-const initPlatformData = (reportData: any) => {
-  // 设置是否显示平台井字段
-  showPlatformField.value = reportData.platformWell === 1
+const opinion = ref('')
 
-  // 设置平台井下拉选项 - 修改后的逻辑
-  let platformSource = reportData.platforms
+const form = ref<Form>(original())
 
-  // 在详情或审批模式下,如果 platforms 不存在,则使用 finishedPlatforms
-  if (
-    (isDetailMode.value || isApprovalMode.value) &&
-    (!platformSource || platformSource.length === 0)
-  ) {
-    platformSource = reportData.finishedPlatforms
+function initPlatformData(reportId: number, sourceData: any) {
+  form.value[reportId] = {
+    rdStatus: sourceData.rdStatus,
+    techniqueIds: sourceData.techniqueIds || [],
+    extProperty: (sourceData.extProperty || []).map((item) => {
+      if (item.dataType === 'double') {
+        item.actualValue = Number(item.actualValue)
+      }
+      return item
+    }),
+    otherNptReason: sourceData.otherNptReason || ''
   }
+  NON_PROD_FIELDS.forEach((field) => {
+    form.value[reportId][field.key] = sourceData[field.key] || 0
+  })
+}
 
-  // 设置平台井下拉选项
-  if (platformSource && Array.isArray(platformSource)) {
-    platformOptions.value = platformSource
-
-    // 初始化 platformWellPairs,确保包含所有平台井的完整数据
-    if (reportData.platformWell === 1) {
-      // 初始化 platformWellPairs,包含所有平台井的完整数据
-      platformWellPairs.value = platformSource.map((platform: any) => {
-        // 查找是否已有该平台井的数据
-        const existingData = reportData.platformWellPairs?.find(
-          (p: any) => p.taskId === platform.id
-        )
-
-        // 确保 techniqueIds 是字符串数组格式
-        let techniqueIds = []
-        if (existingData && existingData.techniqueIds) {
-          techniqueIds = existingData.techniqueIds.map((id: any) => id.toString())
-        } else if (platform.techniqueIds) {
-          techniqueIds = platform.techniqueIds.map((id: any) => id.toString())
-        }
+// const dailyFuel = ref(0)
 
-        return (
-          existingData || {
-            taskId: platform.id,
-            dailyFuel: platform.dailyFuel || '',
-            reportId: platform.reportId, // 使用接口返回的 reportId
-            wellName: platform.wellName,
-            rdStatus: platform.rdStatus || '', // 初始为空
-            techniqueIds: techniqueIds, // 初始为空数组
-            extProperty: platform.extProperty || [], // 初始为空数组
-            repairTime: platform.repairTime ?? 0,
-            selfStopTime: platform.selfStopTime ?? 0,
-            accidentTime: platform.accidentTime ?? 0,
-            complexityTime: platform.complexityTime ?? 0,
-            rectificationTime: platform.rectificationTime ?? 0,
-            waitingStopTime: platform.waitingStopTime ?? 0,
-            partyaDesign: platform.partyaDesign ?? 0,
-            partyaPrepare: platform.partyaPrepare ?? 0,
-            partyaResource: platform.partyaResource ?? 0,
-            relocationTime: platform.relocationTime ?? 0,
-            winterBreakTime: platform.winterBreakTime ?? 0,
-            otherNptTime: platform.otherNptTime ?? 0,
-            otherNptReason: platform.otherNptReason || ''
-          }
-        )
-      })
-    }
-  } else {
-    platformOptions.value = []
-    platformWellPairs.value = []
-  }
+const initDailyFuel = () => {
+  const propVal = data.value.dailyFuel
 
-  // 设置默认选中的平台井 - 修改后的逻辑
-  if (platformOptions.value.length > 0) {
-    let selectedPlatform = null
+  const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
 
-    // 首先尝试查找与 taskId 匹配的平台井
-    if (reportData.taskId) {
-      selectedPlatform = platformOptions.value.find(
-        (platform: any) => platform.id === reportData.taskId
-      )
-    }
+  if (hasPropValue) {
+    // dailyFuel.value = propVal
+    form.value.dailyFuel = propVal
+  } else {
+    const list1 = data.value.reportFuels || []
+    const list2 = data.value.reportedFuels || []
 
-    // 如果没有找到匹配的平台井,选择第一个平台井
-    if (!selectedPlatform) {
-      selectedPlatform = platformOptions.value[0]
-    }
+    const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
 
-    // 设置选中的平台井
-    if (selectedPlatform) {
-      formData.value.platformId = selectedPlatform.id
-      currentPlatformId.value = selectedPlatform.id
-      // 加载平台井的数据到表单
-      loadPlatformData(selectedPlatform.id)
+    form.value.reportFuels = validList.map((v) => ({
+      ...v,
+      customFuel: Number(
+        Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
+      )
+    }))
 
-      // 可选:在控制台输出提示信息
-      if (reportData.taskId && selectedPlatform.id !== reportData.taskId) {
-        console.log(`主井已施工完成,已自动选择第一个平台井: ${selectedPlatform.wellName}`)
-      }
-    }
+    let total = 0
+    form.value.reportFuels.forEach((item) => {
+      total += item.customFuel
+    })
+    // dailyFuel.value = total
+    form.value.dailyFuel = total
   }
 }
 
-// 添加审批表单相关变量
-const approvalFormRef = ref()
-const approvalForm = reactive({
-  opinion: '' // 审批意见
-})
+const loading = ref(false)
+async function loadDetail(id: number) {
+  loading.value = true
+  try {
+    const res = await IotRdDailyReportApi.getIotRdDailyReport(id)
+    data.value = res
+
+    opinion.value = data.value.opinion || ''
+
+    form.value.deviceIds = data.value.deviceIds || []
+    form.value.attachments = data.value.attachments || []
+    form.value.nextPlan = data.value.nextPlan || ''
+    form.value.externalRental = data.value.externalRental || ''
+    form.value.malfunction = data.value.malfunction || ''
+    form.value.faultDowntime = data.value.faultDowntime || 0
+    form.value.constructionBrief = data.value.constructionBrief || ''
+
+    form.value.reportDetails = (data.value.reportDetails || []).map((item) => ({
+      duration: item.duration || 0,
+      constructionDetail: item.constructionDetail || '',
+      startTime: formatT(item.startTime),
+      endTime: formatT(item.endTime)
+    }))
+
+    if (!form.value.reportDetails.length) {
+      addReportDetailRow()
+    }
 
-// 审批表单验证规则(可选,根据需求添加)
-const approvalFormRules = reactive({
-  opinion: [
-    { required: false, message: '请输入审批意见', trigger: 'blur' },
-    { min: 0, max: 500, message: '审批意见长度不能超过500个字符', trigger: 'blur' }
-  ]
-})
+    if (data.value.startTime && data.value.endTime) {
+      form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
+    }
 
-// 将时分秒数组转换为Date对象(基于constructionStartDate的日期)
-const parseTimeArrayToDate = (timeArray: number[], baseDate: number) => {
-  if (!Array.isArray(timeArray) || !baseDate) {
-    return null
+    if (data.value.platformWell === 1) {
+      form.value.platformIds = data.value.platforms?.map((v) => v.reportId) ?? []
+      data.value.platforms?.forEach((p) => {
+        initPlatformData(p.reportId, p)
+      })
+    } else {
+      form.value.platformIds = [data.value.id!]
+      initPlatformData(data.value.id!, data.value)
+    }
+
+    initDailyFuel()
+  } finally {
+    loading.value = false
   }
-  const hour = timeArray[0] || 0
-  const minute = timeArray[1] || 0
-  const second = timeArray[2] || 0
-  // 基于日报日期(constructionStartDate)设置时分秒
-  return dayjs(baseDate).hour(hour).minute(minute).second(second).toDate()
 }
 
-// 添加文件上传组件的引用
-const fileUploadRef = ref()
-
-// 表单数据
-const formData = ref<any>({
-  id: undefined,
-  deptId: undefined,
-  taskId: undefined,
-  platformWell: undefined,
-  companyId: undefined,
-  deptName: undefined,
-  constructionStartDate: undefined,
-  contractName: undefined,
-  projectDepartment: '',
-  costCenterId: undefined,
-  costCenter: '',
-  platformId: undefined, // 平台井ID
-  // 日报填报字段
-  timeRange: [
-    // 设置默认时间范围 8:00 - 8:00
-    dayjs().hour(8).minute(0).second(0).toDate(),
-    dayjs().hour(8).minute(0).second(0).toDate()
-  ],
-  startTime: undefined, // 开始时间
-  endTime: undefined, // 结束时间
-  rdStatus: '', // 施工状态
-  deviceIds: [] as number[], // 设备ID数组
-  techniqueIds: [], // 施工工艺
-  dailyFuel: '', // 当日油耗
-  productionStatus: '', // 当日生产动态
-  nextPlan: '', // 下步工作计划
-  externalRental: '', // 外租设备
-  malfunction: '', // 故障情况
-  faultDowntime: '', // 故障误工
-  // 添加动态字段对象
-  dynamicFields: {} as Record<string, any>,
-  // 附件列表
-  attachments: [] as any[],
-  reportFuels: [] as any[], // 油耗信息数组
-  repairTime: 0,
-  selfStopTime: 0,
-  accidentTime: 0,
-  complexityTime: 0,
-  rectificationTime: 0,
-  waitingStopTime: 0,
-  partyaDesign: 0,
-  partyaPrepare: 0,
-  partyaResource: 0,
-  relocationTime: 0,
-  winterBreakTime: 0,
-  otherNptTime: 0,
-  otherNptReason: ''
-})
-
-// 添加上传成功处理函数
-const handleUploadSuccess = (result: any) => {
-  console.log('上传成功', result)
+const formLoading = ref(false)
 
+const submitForm = useDebounceFn(async function submitForm() {
   try {
-    // 检查响应是否成功
-    if (!result.response) {
-      message.error('上传响应数据异常')
-      return
-    }
+    formLoading.value = true
 
-    if (result.response.code !== 0) {
-      message.error(result.response.msg || '文件上传失败')
-      return
-    }
+    const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
 
-    const responseData = result.response.data
+    deleteId.forEach((o) => {
+      delete form.value[o.value]
+    })
 
-    if (!responseData) {
-      message.error('上传数据为空')
-      return
-    }
+    await formRef.value?.validate()
 
-    // 处理返回的文件列表
-    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
-      responseData.files.forEach((file: any) => {
-        if (!file.filePath) {
-          console.warn('文件缺少 filePath:', file)
-          return
-        }
+    const copyForm = cloneDeep(form.value)
 
-        // 根据后端返回的数据结构构建附件对象
-        const attachment = {
-          id: undefined,
-          category: 'daily_report',
-          bizId: formData.value.id,
-          type: 'attachment',
-          filename: file.name || '未知文件',
-          fileType: getFileType(file.name),
-          filePath: file.filePath, //使用正确的 filePath
-          fileSize: formatFileSize(file.size || 0),
-          remark: ''
-        }
+    const responseData: any[] = []
 
-        // 添加到附件列表
-        if (!formData.value.attachments) {
-          formData.value.attachments = []
-        }
-        formData.value.attachments.push(attachment)
+    form.value.platformIds.forEach((pid) => {
+      const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
+        item.bizId = pid
+        return item
       })
 
-      message.success(`成功上传 ${responseData.files.length} 个文件`)
-    } else {
-      console.warn('上传成功但没有返回文件信息')
-      message.warning('上传成功但未获取到文件信息')
-    }
-  } catch (error) {
-    console.error('处理上传结果时发生错误:', error)
-    message.error('处理上传结果失败')
-  }
-}
+      let platformWell = data.value.platformWell
 
-// 删除附件
-const removeAttachment = (index: number) => {
-  if (formData.value.attachments && formData.value.attachments.length > index) {
-    formData.value.attachments.splice(index, 1)
-  }
-}
+      if (platformWell === 1) {
+        platformWell = pid === data.value.id ? 1 : 2
+      }
 
-// 计算属性:未施工设备名称
-const unSelectedDeviceNames = computed(() => {
-  const selectedDevices = dailyReportData.value.selectedDevices || []
-  const selectedDeviceIds = formData.value.deviceIds || []
+      const platformData = {
+        id: pid,
+        timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
+        projectDepartment: '',
+        costCenter: '',
+        dynamicFields: {},
+        platformWell,
+        companyId: data.value.companyId,
+        deptId: data.value.deptId,
+        startTime: copyForm.timeRange[0],
+        endTime: copyForm.timeRange[1],
+        deviceIds: copyForm.deviceIds,
+        dailyFuel: copyForm.dailyFuel,
+        nextPlan: copyForm.nextPlan,
+        externalRental: copyForm.externalRental,
+        malfunction: copyForm.malfunction,
+        faultDowntime: copyForm.faultDowntime,
+        reportFuels: copyForm.reportFuels.map((item) => ({
+          ...item,
+          reportId: pid
+        })),
+        reportDetails: copyForm.reportDetails,
+        constructionBrief: copyForm.constructionBrief,
+        attachments: platformAttachments,
+        ...(data.value.platformWell === 1
+          ? {
+              platformId: data.value.platforms?.find((v) => v.reportId === pid)?.id
+            }
+          : {}),
+        extProperty: copyForm[pid].extProperty,
+        rdStatus: copyForm[pid].rdStatus,
+        techniqueIds: copyForm[pid].techniqueIds,
+        ...NON_PROD_FIELDS.reduce(
+          (acc, field) => {
+            acc[field.key] = copyForm[pid][field.key] ?? 0
+            return acc
+          },
+          {} as Record<string, number>
+        ),
+        otherNptReason: copyForm[pid].otherNptReason || '',
+        nonProduct: formType.value === 'time' ? 'Y' : ''
+      }
 
-  if (selectedDevices.length === 0) {
-    return '无可用设备'
-  }
+      responseData.push(platformData)
+    })
 
-  // 筛选出未选择的设备
-  const unselectedDevices = selectedDevices.filter(
-    (device: any) => !selectedDeviceIds.includes(device.id)
-  )
+    await IotRdDailyReportApi.saveBatch(responseData)
+    message.success(t('common.updateSuccess'))
 
-  if (unselectedDevices.length === 0) {
-    return '所有设备都已施工'
+    handleCancel()
+  } catch (error) {
+    console.log('提交失败:', error)
+  } finally {
+    formLoading.value = false
   }
-
-  // 提取设备名称并用逗号分隔
-  const deviceNames = unselectedDevices
-    .map((device: any) => device.deviceName || device.deviceCode || '未知设备')
-    .filter((name: string) => name !== '未知设备')
-
-  return deviceNames.join(', ') || '无未选择设备'
 })
 
-// 附件名称点击事件
-const inContent = async (attachment) => {
-  if (!attachment || !attachment.filePath) {
-    message.error('附件路径不存在')
-    return
-  }
-
+async function submitApprovalForm(auditStatus: number) {
   try {
-    // 直接使用 attachment.filePath
-    const filePath = attachment.filePath
-    // 确保 filePath 是编码后的格式
-    const encodedPath = encodeURIComponent(Base64.encode(filePath))
+    formLoading.value = true
+    await IotRdDailyReportApi.approveRdDailyReport({
+      ...data.value,
+      startTime: form.value.timeRange[0],
+      endTime: form.value.timeRange[1],
+      reportDetails: data.value.reportDetails?.map((item) => ({
+        ...item,
+        startTime: formatT(item.startTime),
+        endTime: formatT(item.endTime)
+      })),
+      id: data.value.id!,
+      auditStatus,
+      opinion: opinion.value
+    })
+    if (auditStatus === 20) {
+      message.success('审批通过')
+    } else {
+      message.success('审批驳回')
+    }
 
-    // 打开预览窗口
-    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+    handleCancel()
   } catch (error) {
-    console.error('预览附件失败:', error)
-    message.error('预览附件失败')
-  }
-}
-
-// 获取文件类型辅助函数
-const getFileType = (filename: string) => {
-  const ext = filename.split('.').pop()?.toLowerCase()
-  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
-    return 'image'
-  } else if (['pdf'].includes(ext || '')) {
-    return 'pdf'
-  } else if (['doc', 'docx'].includes(ext || '')) {
-    return 'word'
-  } else if (['xls', 'xlsx'].includes(ext || '')) {
-    return 'excel'
-  } else {
-    return 'other'
+    console.log('审批失败:', error)
+  } finally {
+    formLoading.value = false
   }
 }
 
-// 格式化文件大小辅助函数
-const formatFileSize = (bytes: number) => {
-  if (bytes === 0) return '0 Bytes'
-  const k = 1024
-  const sizes = ['Bytes', 'KB', 'MB', 'GB']
-  const i = Math.floor(Math.log(bytes) / Math.log(k))
-  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
+  console.log('id :>> ', id)
+  formType.value = type
+  form.value = original()
+  loadDetail(id).then(() => {
+    nextTick(() => formRef.value?.clearValidate())
+  })
 }
 
-// 计算属性:格式化设备显示
-const formatDevicesForDisplay = computed(() => {
-  const deviceIds = formData.value.deviceIds
-  if (!deviceIds || deviceIds.length === 0) {
-    return '无设备'
-  }
-
-  const deviceNames = deviceIds
-    .map((id) => deviceMap.value[id]?.deviceName)
-    .filter((name) => name !== undefined && name !== '')
+const route = useRoute()
+const router = useRouter()
 
-  if (deviceNames.length === 0) return '无设备'
-
-  // 如果设备数量超过2个,显示前两个加省略号
-  /* if (deviceNames.length > 2) {
-    return `${deviceNames[0]}, ${deviceNames[1]}...`
-  } */
-
-  return deviceNames.join(', ')
-})
+function handleCancel() {
+  router.push({
+    path: (route.query.backpath ?? '') as any
+  })
+}
 
-// 计算属性:获取所有设备名称(用于tooltip)
-const getAllDeviceNamesForDisplay = computed(() => {
-  const deviceIds = formData.value.deviceIds
-  if (!deviceIds || deviceIds.length === 0) {
-    return '无设备'
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(
+      Number(route.query.id),
+      route.query.mode as 'edit' | 'approval' | 'detail' | 'time'
+    )
   }
-
-  const deviceNames = deviceIds
-    .map((id) => deviceMap.value[id]?.deviceCode || '未知设备')
-    .filter((name) => name !== '未知设备')
-
-  return deviceNames.join(', ') || '无有效设备'
 })
 
-// 打开设备选择对话框
-const openDeviceDialog = async () => {
-  if (!dailyReportData.value.deptId) {
-    message.error('请先加载项目信息')
-    return
-  }
-
-  try {
-    formLoading.value = true
-    selectedDeviceIds.value = [...(formData.value.deviceIds || [])]
+const formDisabled = computed(() => (key?: string) => {
+  if (formType.value === 'approval' || formType.value === 'detail' || formType.value === 'time') {
+    if (formType.value === 'approval' && key === 'opinion') {
+      return data.value.auditStatus !== 10
+    }
 
-    // 直接从日报数据的 selectedDevices 中获取设备列表
-    const selectedDevices = dailyReportData.value.selectedDevices || []
+    if (formType.value === 'approval' && key === 'button') {
+      return data.value.auditStatus !== 10
+    }
 
-    // 更新设备映射表
-    const newDeviceMap = { ...deviceMap.value }
-    selectedDevices.forEach((device: any) => {
-      if (device.id) {
-        newDeviceMap[device.id] = device
-      }
-    })
-    deviceMap.value = newDeviceMap
+    if (
+      formType.value === 'time' &&
+      (NON_PROD_FIELDS.some((field) => field.key === key) ||
+        key === 'constructionBrief' ||
+        key === 'otherNptReason')
+    ) {
+      return false
+    }
 
-    filteredDeviceList.value = selectedDevices
-    deviceDialogVisible.value = true
-  } catch (error) {
-    console.error('获取设备列表失败:', error)
-    message.error('获取设备列表失败')
-  } finally {
-    formLoading.value = false
-  }
-}
+    if (formType.value === 'time' && key === 'button') {
+      return false
+    }
 
-// 修改格式化函数,改为失去焦点时触发
-const formatDailyFuel = () => {
-  if (!dailyFuelInput.value || dailyFuelInput.value.trim() === '') {
-    formData.value.dailyFuel = ''
-    dailyFuelInput.value = ''
-    return
+    return true
   }
 
-  // 移除非数字字符(除了小数点)
-  const cleaned = dailyFuelInput.value.replace(/[^\d.]/g, '')
+  if (formType.value === 'edit') {
+    if (
+      NON_PROD_FIELDS.some((field) => field.key === key) ||
+      key === 'constructionBrief' ||
+      key === 'otherNptReason'
+    ) {
+      return true
+    }
 
-  // 确保只有一个小数点
-  const parts = cleaned.split('.')
-  if (parts.length > 2) {
-    dailyFuelInput.value = parts[0] + '.' + parts.slice(1).join('')
+    return data.value.status !== 0
   }
 
-  const numValue = parseFloat(dailyFuelInput.value)
-  if (!isNaN(numValue)) {
-    // 限制到两位小数
-    formData.value.dailyFuel = formatNumber(numValue, 2)
-    dailyFuelInput.value = formData.value.dailyFuel
-  } else {
-    formData.value.dailyFuel = ''
-    dailyFuelInput.value = ''
-  }
-}
+  return false
+})
 
-// 处理穿梭框变化
-const handleTransferChange = (value: number[], direction: string, movedKeys: number[]) => {
-  // 可以添加额外的处理逻辑
-}
+const header = computed(function () {
+  const suffix =
+    formType.value === 'edit'
+      ? '日报填报'
+      : formType.value === 'approval'
+        ? '日报审批'
+        : formType.value === 'time'
+          ? '日报时效'
+          : '日报详情'
 
-// 确认设备选择
-const confirmDeviceSelection = () => {
-  formData.value.deviceIds = [...selectedDeviceIds.value]
-  deviceDialogVisible.value = false
-  message.success(`已选择 ${selectedDeviceIds.value.length} 台设备`)
-}
+  let title: string = ''
 
-// 关闭设备选择对话框
-const handleDeviceDialogClose = () => {
-  deviceDialogVisible.value = false
-}
+  if (data.value.platformWell === 1) {
+    const platformSource: Platform[] = data.value.platforms || data.value.finishedPlatforms || []
 
-// 初始化设备数据
-const initDeviceData = (reportData: any) => {
-  // 初始化设备ID
-  if (reportData.deviceIds && Array.isArray(reportData.deviceIds)) {
-    formData.value.deviceIds = [...reportData.deviceIds]
-  } else {
-    formData.value.deviceIds = []
-  }
+    if (platformSource.length > 0) {
+      const isMainWellInPlatforms = platformSource.find(
+        (platform) => platform.id === data.value.taskId
+      )
 
-  // 初始化设备映射表(用于显示设备名称)
-  if (reportData.selectedDevices && Array.isArray(reportData.selectedDevices)) {
-    const newDeviceMap = { ...deviceMap.value }
-    reportData.selectedDevices.forEach((device: any) => {
-      if (device.id) {
-        newDeviceMap[device.id] = device
-      }
-    })
-    deviceMap.value = newDeviceMap
-  }
-}
+      if (!isMainWellInPlatforms) {
+        title = platformSource[0].wellName ?? ''
+      } else title = isMainWellInPlatforms.wellName ?? ''
+    }
+  } else title = data.value.wellName ?? ''
 
-// const validateOtherReason = (_rule: any, value: any, callback: any) => {
-//   const time = formData.value.otherNptTime || 0
-//   if (time > 0 && !value) {
-//     callback(new Error('填写了其他时间,必须说明原因'))
-//   } else {
-//     callback()
-//   }
-// }
-
-const formRules = computed(() => {
-  // 判断是否为虚拟项目
-  const isVirtualProject = dailyReportData.value.virtualProject === 'Y'
-
-  // 基础校验规则(时间节点、当日生产动态始终必填)
-  const rules = {
-    timeRange: [{ required: true, message: '时间节点不能为空', trigger: 'change' }],
-    productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }],
-    nextPlan: [{ required: true, message: '下步工作计划不能为空', trigger: 'blur' }],
-    // otherNptReason: [{ validator: validateOtherReason, trigger: ['blur', 'change'] }],
-    dailyFuel: [
-      {
-        required: true,
-        message: '当日油耗不能为空',
-        trigger: 'blur'
-      },
-      {
-        validator: (rule: any, value: any, callback: any) => {
-          if (value === '' || value === null || value === undefined) {
-            callback()
-            return
-          }
-
-          const numValue = Number(value)
-          if (isNaN(numValue)) {
-            callback(new Error('当日油耗必须是数字'))
-          } else if (numValue < 0) {
-            callback(new Error('当日油耗不能小于0'))
-          } else {
-            callback()
-          }
-        },
-        trigger: 'blur'
-      }
-    ]
-  }
+  return { title, suffix, date: formatDateNoTime(data.value.constructionStartDate) }
+})
 
-  // 非虚拟项目时,添加施工状态、施工工艺的必填校验
-  if (!isVirtualProject) {
-    rules.rdStatus = [{ required: true, message: '施工状态不能为空', trigger: 'change' }]
-    rules.techniqueIds = [{ required: true, message: '施工工艺不能为空', trigger: 'change' }]
+const modeNotice = computed(() => {
+  if (formType.value === 'approval') {
+    return '审批模式:所有字段均为只读'
+  } else if (formType.value === 'detail') {
+    return '详情模式:所有字段均为只读'
+  } else if (formType.value === 'time') {
+    return '时效模式:非生产时间、当日生产简报可编辑'
   }
-
-  return rules
+  return ''
 })
 
-const queryParams = reactive({
-  deptId: undefined,
-  techniqueIds: []
+const statusClass = computed(() => {
+  return formType.value === 'edit'
+    ? 'bg-blue-50 text-blue-500 border-blue-200'
+    : formType.value === 'approval'
+      ? 'bg-orange-50 text-orange-600 border-orange-200'
+      : formType.value === 'time'
+        ? 'bg-green-50 text-green-600 border-green-200'
+        : 'bg-gray-100 text-gray-500 border-gray-200'
 })
 
-// 下拉选项
-const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS) // 施工状态
-const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY) // 瑞都施工工艺
-
-// 计算属性:日报标题
-const dailyReportTitle = computed(() => {
-  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
-    return '日报填报'
-  }
-  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
-  return `${dailyReportData.value.wellName} - ${dateStr} 生产日报`
+const progressList = computed(() => {
+  return data.value.taskProgresses ?? []
 })
 
-// 日报审批:日报标题
-const dailyReportApprovalTitle = computed(() => {
-  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
-    return '日报审批'
-  }
-  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
-  return `${dailyReportData.value.wellName} - ${dateStr} 日报审批`
+const deviceOptions = computed(() => {
+  return data.value.selectedDevices ?? []
 })
 
-// 计算属性:施工周期
-const constructionPeriod = computed(() => {
-  const start = dailyReportData.value.constructionStartDate
-  const end = dailyReportData.value.constructionEndDate
-  if (!start || !end) return 0
+const noSelectedDevices = computed(() => {
+  if (!deviceOptions.value) return []
 
-  const startDate = dayjs(start)
-  const endDate = dayjs(end)
-  return endDate.diff(startDate, 'day')
+  return deviceOptions.value.filter((item) => !form.value.deviceIds.includes(item.id))
 })
 
-// 日期格式化函数
-const formatDate = (timestamp: number) => {
-  if (!timestamp) return ''
-  return dayjs(timestamp).format('YYYY-MM-DD')
-}
+const wellOptions = computed(() => {
+  return (
+    data.value.platforms?.map((v) => ({
+      label: v.wellName,
+      value: v.reportId
+    })) ?? []
+  )
+})
 
-const close = () => {
-  delView(unref(currentRoute))
-  push({ name: 'FillDailyReport', params: {} })
-}
+const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS)
+const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY).map((v) => {
+  v.value = Number(v.value) as any
+  return v
+})
 
-/** 提交表单 */
-const emit = defineEmits(['success'])
-const submitForm = async () => {
-  // 验证表单
-  try {
-    await formRef.value.validate()
-  } catch (error) {
+function handleTechniqueChange(val: string[], platformId: number) {
+  if (!val || val.length === 0) {
+    if (form.value[platformId]) form.value[platformId].extProperty = []
     return
   }
 
-  // 保存当前平台井的数据
-  if (currentPlatformId.value) {
-    saveCurrentPlatformData(currentPlatformId.value)
-  }
+  IotDailyReportAttrsApi.dailyReportAttrs({ techniqueIds: val.join(',') }).then((res) => {
+    const newData = res || []
+    const currentExtProps = form.value[platformId].extProperty
 
-  let validationPassed = true
-  const validationErrors: string[] = []
-
-  // 检查所有平台井数据
-  for (const pair of platformWellPairs.value) {
-    // 计算所有时间字段的总和
-    const totalTime =
-      (pair.repairTime || 0) +
-      (pair.selfStopTime || 0) +
-      (pair.accidentTime || 0) +
-      (pair.complexityTime || 0) +
-      (pair.rectificationTime || 0) +
-      (pair.waitingStopTime || 0) +
-      (pair.partyaDesign || 0) +
-      (pair.partyaPrepare || 0) +
-      (pair.partyaResource || 0) +
-      (pair.relocationTime || 0) +
-      (pair.winterBreakTime || 0) +
-      (pair.otherNptTime || 0)
-
-    // 检查总和是否超过24小时
-    if (totalTime > 24) {
-      validationPassed = false
-      validationErrors.push(`${pair.wellName || '平台井'}的时间总和不能超过24小时`)
-    }
+    const uniqueMap = new Map()
+    newData.forEach((item: any) => {
+      const key =
+        item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString()
+      uniqueMap.set(key, item)
+    })
+    const uniqueData = Array.from(uniqueMap.values())
+
+    const mergedData = uniqueData.map((newItem: any) => {
+      const newKey =
+        newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : ''
+      const oldItem = currentExtProps.find((old: any) => {
+        const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : ''
+        return newKey && oldKey && newKey === oldKey
+      })
+      return {
+        ...newItem,
+        actualValue: oldItem?.actualValue ?? newItem.actualValue
+      }
+    })
 
-    // 检查otherNptTime>0时是否填写了otherNptReason
-    if ((pair.otherNptTime || 0) > 0 && !pair.otherNptReason) {
-      validationPassed = false
-      validationErrors.push(`${pair.wellName || '平台井'}的其他时间大于0时必须填写原因`)
-    }
-  }
+    form.value[platformId].extProperty = mergedData
+  })
+}
 
-  // 检查非平台井模式
-  if (dailyReportData.value.platformWell !== 1) {
-    // 计算所有时间字段的总和
-    const totalTime =
-      (formData.value.repairTime || 0) +
-      (formData.value.selfStopTime || 0) +
-      (formData.value.accidentTime || 0) +
-      (formData.value.complexityTime || 0) +
-      (formData.value.rectificationTime || 0) +
-      (formData.value.waitingStopTime || 0) +
-      (formData.value.partyaDesign || 0) +
-      (formData.value.partyaPrepare || 0) +
-      (formData.value.partyaResource || 0) +
-      (formData.value.relocationTime || 0) +
-      (formData.value.winterBreakTime || 0) +
-      (formData.value.otherNptTime || 0)
-
-    // 检查总和是否超过24小时
-    if (totalTime > 24) {
-      validationPassed = false
-      validationErrors.push('时间总和不能超过24小时')
-    }
+const { ZmTable, ZmTableColumn } = useTableComponents<Fule | ReportDetail | Platform>()
+
+const addReportDetailRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = []
   }
+  form.value.reportDetails.push({
+    startTime: '',
+    endTime: '',
+    duration: 0,
+    constructionDetail: ''
+  })
+}
 
-  // 如果验证失败,显示错误信息
-  if (!validationPassed) {
-    validationErrors.forEach((error) => {
-      message.error(error)
-    })
+const removeReportDetailRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
     return
   }
 
-  // 打印 platformWellPairs
-  console.log('platformWellPairs:', JSON.stringify(platformWellPairs.value, null, 2))
+  form.value.reportDetails?.splice(index, 1)
+}
 
-  // 处理时间范围数据
-  if (formData.value.timeRange && formData.value.timeRange.length === 2) {
-    // 将时间范围转换为 LocalTime 格式的字符串
-    const startDate = dayjs(formData.value.timeRange[0])
-    const endDate = dayjs(formData.value.timeRange[1])
+const handleListChange = useDebounceFn(() => {
+  let total = 0
+  form.value.reportFuels.forEach((item) => {
+    total += item.customFuel
+  })
+  form.value.dailyFuel = total
+}, 500)
 
-    // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
-    formData.value.startTime = startDate.format('HH:mm:ss')
-    formData.value.endTime = endDate.format('HH:mm:ss')
-  }
+const platformWorkloadData = computed(() => {
+  if (!data.value) return []
+  // 需要调整
+  return data.value.platforms || data.value.finishedPlatforms || []
+})
 
-  // 构建动态属性 extProperty 数组
-  const extProperties = dynamicAttrs.value.map((attr) => {
-    return {
-      name: attr.name,
-      sort: attr.sort,
-      unit: attr.unit,
-      actualValue: formData.value.dynamicFields[attr.identifier] || '', // 从 dynamicFields 中获取用户填写的值
-      dataType: attr.dataType,
-      maxValue: attr.maxValue,
-      minValue: attr.minValue,
-      required: attr.required,
-      accessMode: attr.accessMode,
-      identifier: attr.identifier,
-      defaultValue: attr.defaultValue
-    }
-  })
+const getWorkloadColumns = () => {
+  const dataSource = platformWorkloadData.value
+  if (!dataSource?.length) return []
 
-  // 准备提交数据,包含动态字段
-  const baseSubmitData = {
-    ...formData.value,
-    // 将动态字段组装成 extProperty 数组
-    extProperty: extProperties,
-    deviceIds: formData.value.deviceIds, // 设备ID集合
-    // 在填报模式下也提交审批意见字段
-    opinion: isEditMode.value ? approvalForm.opinion : undefined,
-    // 将油耗数据格式化为后端需要的格式
-    /* reportFuels: formData.value.reportFuels.map(fuel => ({
-      ...fuel,
-      // 确保 customFuel 是数字格式
-      customFuel: fuel.customFuel ? parseFloat(fuel.customFuel) : null
-    })), */
-    // 确保当日油耗是数字格式
-    dailyFuel: formData.value.dailyFuel ? parseFloat(formData.value.dailyFuel) : 0,
-    nonProduct: query.istime === 'true' ? 'Y' : ''
-  }
+  const columnMap = new Map()
+
+  dataSource.forEach((platform) => {
+    platform.extProperty?.forEach((extProp) => {
+      const { identifier, name, unit } = extProp
 
-  console.log('baseSubmitData:', baseSubmitData)
-
-  // 删除不需要复制的字段
-  const {
-    id: currentId,
-    platformId,
-    platformWell,
-    taskId,
-    rdStatus,
-    techniqueIds,
-    extProperty,
-    ...baseData
-  } = baseSubmitData
-
-  const submitDatas: any[] = []
-
-  if (dailyReportData.value.platformWell === 1 && platformWellPairs.value.length > 0) {
-    // 平台井模式:处理所有平台井数据
-    platformWellPairs.value.forEach((pair) => {
-      console.log('pair:', pair)
-      // 复制基础数据
-      const platformData: any = { ...baseData }
-
-      // 设置平台井特定字段
-      platformData.id = pair.reportId // 使用 platformWellPairs 中的 reportId
-      platformData.platformId = pair.taskId // 使用 platformWellPairs 中的 taskId
-      platformData.rdStatus = pair.rdStatus || ''
-      platformData.techniqueIds = pair.techniqueIds || []
-      platformData.extProperty = pair.extProperty || []
-
-      platformData.repairTime = pair.repairTime || 0
-      platformData.selfStopTime = pair.selfStopTime || 0
-      platformData.accidentTime = pair.accidentTime || 0
-      platformData.complexityTime = pair.complexityTime || 0
-      platformData.rectificationTime = pair.rectificationTime || 0
-      platformData.waitingStopTime = pair.waitingStopTime || 0
-      platformData.partyaDesign = pair.partyaDesign || 0
-      platformData.partyaPrepare = pair.partyaPrepare || 0
-      platformData.partyaResource = pair.partyaResource || 0
-      platformData.relocationTime = pair.relocationTime || 0
-      platformData.winterBreakTime = pair.winterBreakTime || 0
-      platformData.otherNptTime = pair.otherNptTime || 0
-      platformData.otherNptReason = pair.otherNptReason || ''
-
-      // 处理附件:复制并修改 bizId 为当前 pair 的 reportId
-      platformData.attachments = (baseData.attachments || []).map((attachment) => ({
-        ...attachment, // 深拷贝单个附件
-        bizId: pair.reportId // 替换 bizId 为当前平台井的 reportId
-      }))
-
-      // 车辆油耗:复制并修改 reportId 为当前 pair 的 reportId
-      platformData.reportFuels = (baseData.reportFuels || []).map((reportFuel) => ({
-        ...reportFuel, // 深拷贝单个油耗
-        reportId: pair.reportId // 替换 reportId 为当前平台井的 reportId
-      }))
-
-      // 重新构建 dynamicFields(如果需要)
-      const dynamicFields = {}
-      if (platformData.extProperty && platformData.extProperty.length > 0) {
-        platformData.extProperty.forEach((prop) => {
-          if (prop.identifier) {
-            dynamicFields[prop.identifier] = prop.actualValue || ''
-          }
+      if (!columnMap.has(identifier)) {
+        columnMap.set(identifier, {
+          prop: identifier,
+          label: unit ? `${name}(${unit})` : name
         })
       }
-      platformData.dynamicFields = dynamicFields
-
-      submitDatas.push(platformData)
     })
+  })
 
-    console.log('平台井模式提交数据:', JSON.stringify(submitDatas, null, 2))
-  } else {
-    // 非平台井模式:只提交当前数据
-    submitDatas.push(baseSubmitData)
-  }
+  return Array.from(columnMap.values())
+}
 
-  // 提交请求
-  formLoading.value = true
-  try {
-    // 调用更新接口
-    await IotRdDailyReportApi.saveBatch(submitDatas)
-    message.success(t('common.updateSuccess'))
-    close()
-    // 发送操作成功的事件
-    emit('success')
-  } catch (error) {
-    console.error('提交失败:', error)
-  } finally {
-    formLoading.value = false
-  }
+const getWorkloadValue = (platform: Platform, identifier: string) => {
+  if (!platform || !platform.extProperty) return ''
+  const prop = platform.extProperty.find((item) => item.identifier === identifier)
+  return prop ? prop.actualValue || '' : ''
 }
 
-/** 重置表单 */
-const resetForm = () => {
-  formRef.value?.resetFields()
+const getFileType = (filename: string) => {
+  const ext = filename.split('.').pop()?.toLowerCase()
+  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
+    return 'image'
+  } else if (['pdf'].includes(ext || '')) {
+    return 'pdf'
+  } else if (['doc', 'docx'].includes(ext || '')) {
+    return 'word'
+  } else if (['xls', 'xlsx'].includes(ext || '')) {
+    return 'excel'
+  } else {
+    return 'other'
+  }
 }
 
-// 初始化动态属性
-const initDynamicAttrs = (reportData: any) => {
-  if (reportData.dailyReportAttrs && reportData.dailyReportAttrs.length > 0) {
-    dynamicAttrs.value = reportData.dailyReportAttrs
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
 
-    // 初始化动态字段的值
-    const initialDynamicFields: Record<string, any> = {}
+const handleUploadSuccess = (result: any) => {
+  console.log('上传成功', result)
 
-    // 优先从 extProperty 中获取实际值(编辑时)
-    if (reportData.extProperty && reportData.extProperty.length > 0) {
-      reportData.extProperty.forEach((extProp: any) => {
-        if (extProp.identifier) {
-          initialDynamicFields[extProp.identifier] = extProp.actualValue || ''
-        }
-      })
+  try {
+    if (!result.response) {
+      message.error('上传响应数据异常')
+      return
     }
 
-    reportData.dailyReportAttrs.forEach((attr: any) => {
-      if (!initialDynamicFields.hasOwnProperty(attr.identifier)) {
-        // 优先使用实际值,如果没有则使用默认值
-        const value =
-          attr.extProperty &&
-          attr.extProperty.actualValue !== undefined &&
-          attr.extProperty.actualValue !== null &&
-          attr.extProperty.actualValue !== ''
-            ? attr.extProperty.actualValue
-            : attr.defaultValue || attr.extProperty?.defaultValue || ''
-
-        initialDynamicFields[attr.identifier] = value
-      }
-    })
+    if (result.response.code !== 0) {
+      message.error(result.response.msg || '文件上传失败')
+      return
+    }
 
-    formData.value.dynamicFields = initialDynamicFields
-  }
-}
+    const responseData = result.response.data
 
-// 获取动态字段的验证规则
-const getDynamicAttrRules = (attr: any) => {
-  const rules = []
-  if (attr.required === 1) {
-    rules.push({
-      required: true,
-      message: `${attr.name}不能为空`,
-      trigger: 'blur'
-    })
-  }
+    if (!responseData) {
+      message.error('上传数据为空')
+      return
+    }
 
-  // 数字类型验证
-  if (attr.dataType === 'double') {
-    rules.push({
-      validator: (rule: any, value: any, callback: any) => {
-        if (value === '' || value === null || value === undefined) {
-          callback()
+    // 处理返回的文件列表
+    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
+      responseData.files.forEach((file: any) => {
+        if (!file.filePath) {
+          console.warn('文件缺少 filePath:', file)
           return
         }
 
-        const numValue = Number(value)
-        if (isNaN(numValue)) {
-          callback(new Error(`${attr.name}必须是数字`))
-        } else if (attr.minValue && numValue < Number(attr.minValue)) {
-          callback(new Error(`${attr.name}不能小于${attr.minValue}`))
-        } else if (attr.maxValue && numValue > Number(attr.maxValue)) {
-          callback(new Error(`${attr.name}不能大于${attr.maxValue}`))
-        } else {
-          callback()
+        // 根据后端返回的数据结构构建附件对象
+        const attachment = {
+          id: undefined,
+          category: 'daily_report',
+          bizId: data.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath, //使用正确的 filePath
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
         }
-      },
-      trigger: 'blur'
-    })
-  }
 
-  return rules
-}
-
-// 更新动态属性(处理交集、新增和删除)
-const updateDynamicAttrs = async (
-  newAttrs: any[],
-  newTechniqueIds: string[],
-  oldTechniqueIds?: string[]
-) => {
-  const oldAttrs = [...dynamicAttrs.value]
-  const oldDynamicFields = { ...formData.value.dynamicFields }
-
-  // 计算需要保留的字段(交集)
-  const commonAttrs = oldAttrs.filter((oldAttr) =>
-    newAttrs.some((newAttr) => newAttr.identifier === oldAttr.identifier)
-  )
-
-  // 计算需要新增的字段
-  const addedAttrs = newAttrs.filter(
-    (newAttr) => !oldAttrs.some((oldAttr) => oldAttr.identifier === newAttr.identifier)
-  )
-
-  // 计算需要删除的字段
-  const removedAttrs = oldAttrs.filter(
-    (oldAttr) => !newAttrs.some((newAttr) => newAttr.identifier === oldAttr.identifier)
-  )
-
-  // 构建新的动态属性数组
-  const updatedAttrs = [...commonAttrs, ...addedAttrs]
-
-  // 构建新的动态字段对象
-  const updatedDynamicFields = { ...oldDynamicFields }
-
-  // 移除已删除的字段
-  removedAttrs.forEach((attr) => {
-    delete updatedDynamicFields[attr.identifier]
-  })
-
-  // 初始化新增字段的值
-  addedAttrs.forEach((attr) => {
-    if (!updatedDynamicFields[attr.identifier]) {
-      // 如果有默认值使用默认值,否则为空
-      updatedDynamicFields[attr.identifier] =
-        attr.defaultValue || attr.extProperty?.defaultValue || ''
-    }
-  })
-
-  // 更新响应式数据
-  dynamicAttrs.value = updatedAttrs
-  formData.value.dynamicFields = updatedDynamicFields
-}
-
-// 加载动态属性
-const loadDynamicAttrs = async (newTechniqueIds: string[], oldTechniqueIds?: string[]) => {
-  try {
-    formLoading.value = true
+        // 添加到附件列表
+        if (!form.value.attachments) {
+          form.value.attachments = []
+        }
+        form.value.attachments.push(attachment)
+      })
 
-    const queryParams = {
-      techniqueIds: newTechniqueIds.join(',')
+      message.success(`成功上传 ${responseData.files.length} 个文件`)
+    } else {
+      console.warn('上传成功但没有返回文件信息')
+      message.warning('上传成功但未获取到文件信息')
     }
-
-    const response = await IotDailyReportAttrsApi.dailyReportAttrs(queryParams)
-    const newAttrs = response || []
-
-    // 处理动态属性更新
-    await updateDynamicAttrs(newAttrs, newTechniqueIds, oldTechniqueIds)
   } catch (error) {
-    console.error('加载动态属性失败:', error)
-    message.error('加载动态属性失败')
-  } finally {
-    formLoading.value = false
+    console.error('处理上传结果时发生错误:', error)
+    message.error('处理上传结果失败')
   }
 }
 
-// 计算属性:获取平台井工作量数据(在详情/审批模式下优先使用 platforms,不存在则使用 finishedPlatforms)
-const platformWorkloadData = computed(() => {
-  if (!dailyReportData.value) return []
-
-  // 在详情或审批模式下
-  if (isDetailMode.value || isApprovalMode.value) {
-    // 优先使用 platforms,如果不存在则使用 finishedPlatforms
-    return dailyReportData.value.platforms || dailyReportData.value.finishedPlatforms || []
-  }
-
-  // 其他模式只使用 platforms
-  return dailyReportData.value.platforms || []
-})
-
-// 在 watch 监听平台井选择变化的部分附近,添加施工工艺转换函数
-// 添加施工工艺数值到标签的转换函数
-const convertTechniqueIdsToLabels = (techniqueIds: number[]): string[] => {
-  if (!techniqueIds || !Array.isArray(techniqueIds)) {
-    return []
-  }
-
-  return techniqueIds.map((id) => {
-    const dict = techniqueOptions.value.find((option) => option.value === id.toString())
-    return dict ? dict.label : id.toString()
-  })
-}
-
-// 监听施工工艺变化
-watch(
-  () => formData.value.techniqueIds,
-  async (newTechniqueIds, oldTechniqueIds) => {
-    if (newTechniqueIds && newTechniqueIds.length > 0) {
-      await loadDynamicAttrs(newTechniqueIds, oldTechniqueIds)
-
-      // 动态属性加载完成后,更新当前平台井的 extProperty
-      if (currentPlatformId.value) {
-        updateCurrentPlatformExtProperty()
-      }
-    } else {
-      dynamicAttrs.value = []
-      formData.value.dynamicFields = {}
-
-      // 清空当前平台井的 extProperty
-      if (currentPlatformId.value) {
-        updateCurrentPlatformExtProperty()
-      }
-    }
-  },
-  { deep: true }
-)
-
-// 监听 formData.dailyFuel 变化,同步到输入变量
-watch(
-  () => formData.value.dailyFuel,
-  (newVal) => {
-    if (newVal !== null && newVal !== undefined && newVal !== '') {
-      // 将数字转换为字符串显示,但不干扰输入
-      dailyFuelInput.value = String(newVal)
-    } else {
-      dailyFuelInput.value = ''
-    }
-    dailyFuelManuallyModified.value = true
-  },
-  { immediate: true }
-)
-
-// 监听reportFuels的变化,自动更新当日油耗
-watch(
-  () => formData.value.reportFuels,
-  (newFuels) => {
-    // 只有在编辑模式且用户没有手动修改过当日油耗时才自动计算
-    if (!isReadonlyMode.value) {
-      calculateAndUpdateDailyFuel()
-    }
-  },
-  { deep: true }
-)
-
-// 更新当前平台井的 extProperty
-const updateCurrentPlatformExtProperty = () => {
-  if (!currentPlatformId.value) return
-
-  const index = platformWellPairs.value.findIndex((item) => item.taskId === currentPlatformId.value)
-  if (index !== -1) {
-    platformWellPairs.value[index].extProperty = getCurrentExtProperties()
-  }
-}
-
-// 监听平台井选择变化
-watch(
-  () => formData.value.platformId,
-  (newPlatformId, oldPlatformId) => {
-    if (newPlatformId && newPlatformId !== oldPlatformId) {
-      // 保存当前平台井的数据到 platformWellPairs
-      if (oldPlatformId) {
-        saveCurrentPlatformData(oldPlatformId)
-      }
-
-      // 加载新平台井的数据到表单
-      loadPlatformData(newPlatformId)
-      currentPlatformId.value = newPlatformId
-    }
-  }
-)
-
-// 监听动态字段变化,实时更新到 platformWellPairs
-watch(
-  () => formData.value.dynamicFields,
-  (newFields) => {
-    if (currentPlatformId.value) {
-      updateCurrentPlatformExtProperty()
-    }
-  },
-  { deep: true }
-)
-
-// 监听施工状态变化
-watch(
-  () => formData.value.rdStatus,
-  (newStatus) => {
-    if (currentPlatformId.value) {
-      const index = platformWellPairs.value.findIndex(
-        (item) => item.taskId === currentPlatformId.value
-      )
-      if (index !== -1) {
-        platformWellPairs.value[index].rdStatus = newStatus
-      }
-    }
-  }
-)
-
-// 保存当前平台井数据到 platformWellPairs
-const saveCurrentPlatformData = (platformId: number) => {
-  const index = platformWellPairs.value.findIndex((item) => item.taskId === platformId)
-  if (index !== -1) {
-    platformWellPairs.value[index] = {
-      ...platformWellPairs.value[index],
-      rdStatus: formData.value.rdStatus,
-      techniqueIds: [...formData.value.techniqueIds],
-      extProperty: getCurrentExtProperties(),
-      repairTime: formData.value.repairTime ?? 0,
-      selfStopTime: formData.value.selfStopTime ?? 0,
-      accidentTime: formData.value.accidentTime ?? 0,
-      complexityTime: formData.value.complexityTime ?? 0,
-      rectificationTime: formData.value.rectificationTime ?? 0,
-      waitingStopTime: formData.value.waitingStopTime ?? 0,
-      partyaDesign: formData.value.partyaDesign ?? 0,
-      partyaPrepare: formData.value.partyaPrepare ?? 0,
-      partyaResource: formData.value.partyaResource ?? 0,
-      relocationTime: formData.value.relocationTime ?? 0,
-      winterBreakTime: formData.value.winterBreakTime ?? 0,
-      otherNptTime: formData.value.otherNptTime ?? 0,
-      otherNptReason: formData.value.otherNptReason || ''
-    }
-  } else {
-    // 如果找不到对应的平台井,添加新的记录
-    const platform = platformOptions.value.find((p) => p.id === platformId)
-    if (platform) {
-      platformWellPairs.value.push({
-        taskId: platformId,
-        reportId: undefined, // 新记录没有 reportId
-        wellName: platform.wellName,
-        rdStatus: formData.value.rdStatus,
-        techniqueIds: [...formData.value.techniqueIds],
-        extProperty: getCurrentExtProperties(),
-        repairTime: formData.value.repairTime ?? 0,
-        selfStopTime: formData.value.selfStopTime ?? 0,
-        accidentTime: formData.value.accidentTime ?? 0,
-        complexityTime: formData.value.complexityTime ?? 0,
-        rectificationTime: formData.value.rectificationTime ?? 0,
-        waitingStopTime: formData.value.waitingStopTime ?? 0,
-        partyaDesign: formData.value.partyaDesign ?? 0,
-        partyaPrepare: formData.value.partyaPrepare ?? 0,
-        partyaResource: formData.value.partyaResource ?? 0,
-        relocationTime: formData.value.relocationTime ?? 0,
-        winterBreakTime: formData.value.winterBreakTime ?? 0,
-        otherNptTime: formData.value.otherNptTime ?? 0,
-        otherNptReason: formData.value.otherNptReason || ''
-      })
-    }
-  }
-}
-
-// 从 platformWellPairs 加载平台井数据到表单
-const loadPlatformData = (platformId: number) => {
-  console.log('11 :>> ', 11)
-  const platformData = platformWellPairs.value.find((item) => item.taskId === platformId)
-  if (platformData) {
-    // 更新表单字段
-    formData.value.rdStatus = platformData.rdStatus || ''
-    // formData.value.dailyFuel = platformData.dailyFuel ? [...platformData.dailyFuel] : []
-    // 将施工工艺数值转换为对应的标签
-    if (platformData.techniqueIds && Array.isArray(platformData.techniqueIds)) {
-      // 如果是数字数组,转换为字符串数组(与数据字典格式匹配)
-      formData.value.techniqueIds = platformData.techniqueIds.map((id) => id.toString())
-    } else {
-      formData.value.techniqueIds = platformData.techniqueIds ? [...platformData.techniqueIds] : []
-    }
-
-    // 在详情或审批模式下,更新 dailyFuel 为当前平台井的值
-    if (isDetailMode.value || isApprovalMode.value) {
-      // 使用平台井的 dailyFuel 值
-      const platformDailyFuel = platformData.dailyFuel || ''
-      formData.value.dailyFuel = platformDailyFuel ? formatNumber(platformDailyFuel, 2) : ''
-      // 同步更新输入框
-      dailyFuelInput.value = formData.value.dailyFuel
-    }
-
-    // 更新动态属性
-    if (platformData.extProperty && platformData.extProperty.length > 0) {
-      const dynamicFields: Record<string, any> = {}
-      platformData.extProperty.forEach((prop: any) => {
-        if (prop.identifier) {
-          dynamicFields[prop.identifier] = prop.actualValue || ''
-        }
-      })
-      formData.value.dynamicFields = dynamicFields
-    } else {
-      formData.value.dynamicFields = {}
-    }
-
-    formData.value.repairTime = platformData.repairTime ?? 0
-    formData.value.selfStopTime = platformData.selfStopTime ?? 0
-    formData.value.accidentTime = platformData.accidentTime ?? 0
-    formData.value.complexityTime = platformData.complexityTime ?? 0
-    formData.value.rectificationTime = platformData.rectificationTime ?? 0
-    formData.value.waitingStopTime = platformData.waitingStopTime ?? 0
-    formData.value.partyaDesign = platformData.partyaDesign ?? 0
-    formData.value.partyaPrepare = platformData.partyaPrepare ?? 0
-    formData.value.partyaResource = platformData.partyaResource ?? 0
-    formData.value.relocationTime = platformData.relocationTime ?? 0
-    formData.value.winterBreakTime = platformData.winterBreakTime ?? 0
-    formData.value.otherNptTime = platformData.otherNptTime ?? 0
-    formData.value.otherNptReason = platformData.otherNptReason || ''
-  } else {
-    // 如果没有找到数据,初始化默认值
-    formData.value.rdStatus = ''
-    formData.value.techniqueIds = []
-    formData.value.dynamicFields = {}
-    // 初始化其他时间字段
-    formData.value.repairTime = 0
-    formData.value.selfStopTime = 0
-    formData.value.accidentTime = 0
-    formData.value.complexityTime = 0
-    formData.value.rectificationTime = 0
-    formData.value.waitingStopTime = 0
-    formData.value.partyaDesign = 0
-    formData.value.partyaPrepare = 0
-    formData.value.partyaResource = 0
-    formData.value.relocationTime = 0
-    formData.value.winterBreakTime = 0
-    formData.value.otherNptTime = 0
-    formData.value.otherNptReason = ''
-
-    // 在详情或审批模式下,清空 dailyFuel
-    if (isDetailMode.value || isApprovalMode.value) {
-      formData.value.dailyFuel = ''
-      dailyFuelInput.value = ''
-    }
-  }
-}
-
-// 获取当前动态属性数据
-const getCurrentExtProperties = () => {
-  return dynamicAttrs.value.map((attr) => {
-    return {
-      name: attr.name,
-      sort: attr.sort,
-      unit: attr.unit,
-      actualValue: formData.value.dynamicFields[attr.identifier] || '',
-      dataType: attr.dataType,
-      maxValue: attr.maxValue,
-      minValue: attr.minValue,
-      required: attr.required,
-      accessMode: attr.accessMode,
-      identifier: attr.identifier,
-      defaultValue: attr.defaultValue
-    }
-  })
-}
-
-// 是否显示实际进度
-const showActualProgress = computed(() => {
-  // 在所有模式下都显示,如果有数据就显示
-  return dailyReportData.value?.taskProgresses && dailyReportData.value.taskProgresses.length > 0
-})
-
-// 实际进度数据
-const actualProgressData = computed(() => {
-  if (
-    !dailyReportData.value?.taskProgresses ||
-    !Array.isArray(dailyReportData.value.taskProgresses)
-  ) {
-    return []
-  }
-
-  // 将 taskProgresses 转换为 el-steps 需要的格式
-  return dailyReportData.value.taskProgresses.map((progress: any, index: number) => {
-    // 格式化日期:如果只有日期部分,使用日期格式;如果有时间,使用日期时间格式
-    let formattedDate = ''
-    if (progress.createTime) {
-      // 判断日期格式,如果包含时间则显示完整时间,否则只显示日期
-      if (progress.createTime.includes(' ')) {
-        // 已经是完整的日期时间格式
-        formattedDate = progress.createTime
-      } else {
-        // 只有日期部分
-        formattedDate = progress.createTime
-      }
-    }
-
-    // 构建标题:日期 + 状态
-    const title =
-      formattedDate && progress.rdStatusLabel
-        ? `${formattedDate} ${progress.rdStatusLabel}`
-        : progress.rdStatusLabel || '未知状态'
-
-    return {
-      title: title,
-      description: '', // 可以根据需要添加描述信息
-      status: undefined, // el-steps 会自动计算状态
-      // 保留原始数据,便于调试
-      rawData: progress
-    }
-  })
-})
-
-// 初始化表单数据
-const initFormData = (reportData: any) => {
-  // 处理附件数据格式转换
-  const formattedAttachments = (reportData.attachments || []).map((attachment: any) => ({
-    id: attachment.id,
-    category: attachment.category?.toLowerCase() || 'daily_report',
-    bizId: attachment.bizId,
-    type: attachment.type?.toLowerCase() || 'attachment',
-    filename: attachment.filename,
-    fileType: attachment.fileType, // 使用辅助函数获取文件类型
-    filePath: attachment.filePath,
-    fileSize: attachment.fileSize,
-    remark: attachment.remark || ''
-  }))
-
-  // 确保 techniqueIds 是字符串数组格式
-  let techniqueIds = []
-  if (reportData.techniqueIds && Array.isArray(reportData.techniqueIds)) {
-    techniqueIds = reportData.techniqueIds.map((id: number) => id.toString())
-  }
-
-  formData.value = {
-    ...formData.value,
-    id: reportData.id,
-    deptId: reportData.deptId,
-    taskId: reportData.taskId,
-    platformWell: reportData.platformWell,
-    rdStatus: reportData.rdStatus || '',
-    techniqueIds: techniqueIds,
-    dailyFuel: reportData.dailyFuel, // 当日油耗默认值
-    productionStatus: reportData.productionStatus || '',
-    nextPlan: reportData.nextPlan || '',
-    externalRental: reportData.externalRental || '',
-    malfunction: reportData.malfunction || '',
-    faultDowntime: reportData.faultDowntime || '',
-    startTime: reportData.startTime || undefined,
-    endTime: reportData.endTime || undefined,
-    companyId: reportData.companyId || '',
-    dynamicFields: {}, // 确保有初始值
-    auditStatus: reportData.auditStatus,
-    // 初始化附件数据
-    attachments: formattedAttachments,
-    repairTime: reportData.repairTime ?? 0,
-    selfStopTime: reportData.selfStopTime ?? 0,
-    accidentTime: reportData.accidentTime ?? 0,
-    complexityTime: reportData.complexityTime ?? 0,
-    rectificationTime: reportData.rectificationTime ?? 0,
-    waitingStopTime: reportData.waitingStopTime ?? 0,
-    partyaDesign: reportData.partyaDesign ?? 0,
-    partyaPrepare: reportData.partyaPrepare ?? 0,
-    partyaResource: reportData.partyaResource ?? 0,
-    relocationTime: reportData.relocationTime ?? 0,
-    winterBreakTime: reportData.winterBreakTime ?? 0,
-    otherNptTime: reportData.otherNptTime ?? 0,
-    otherNptReason: reportData.otherNptReason || ''
-  }
-
-  // 初始化审批意见数据
-  approvalForm.opinion = reportData.auditOpinion || reportData.opinion || ''
-
-  queryParams.deptId = reportData.companyId
-  // 设置时间范围选择器
-  if (
-    reportData.startTime &&
-    Array.isArray(reportData.startTime) &&
-    reportData.endTime &&
-    Array.isArray(reportData.endTime)
-  ) {
-    // 基于日报的施工开始日期作为基准日期
-    const baseDate = reportData.constructionStartDate || Date.now()
-    const startTime = parseTimeArrayToDate(reportData.startTime, baseDate)
-    const endTime = parseTimeArrayToDate(reportData.endTime, baseDate)
-
-    if (startTime && endTime) {
-      formData.value.timeRange = [startTime, endTime]
-    }
-  }
-
-  // 初始化平台井数据
-  initPlatformData(reportData)
-
-  // 初始化动态属性
-  initDynamicAttrs(reportData)
-
-  // 初始化设备数据
-  initDeviceData(reportData)
-
-  // 初始化油耗数据 - 根据模式选择数据源
-  if (isDetailMode.value || isApprovalMode.value) {
-    // 详情或审批模式:优先使用 reportedFuels
-    let fuelSource = reportData.reportedFuels
-
-    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
-      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
-      const processedFuels = fuelSource.map((fuel: any) => {
-        // 创建全新的对象,避免引用共享
-        const newFuel = {
-          ...fuel, // 使用展开运算符创建浅拷贝
-          // 确保每个字段都有独立的值
-          createTime: fuel.createTime,
-          updateTime: fuel.updateTime,
-          creator: fuel.creator,
-          updater: fuel.updater,
-          deleted: fuel.deleted,
-          id: fuel.id,
-          type: fuel.type,
-          reportId: fuel.reportId,
-          deviceId: fuel.deviceId,
-          deviceCode: fuel.deviceCode,
-          yfDeviceCode: fuel.yfDeviceCode,
-          deviceName: fuel.deviceName,
-          carId: fuel.carId,
-          zhbdFuel: fuel.zhbdFuel,
-          customFuel: null, // 初始化为null
-          queryDate: fuel.queryDate,
-          remark: fuel.remark
-        }
-
-        let customFuelValue
-
-        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
-        if (fuel.customFuel !== null && fuel.customFuel !== undefined) {
-          const numValue = parseFloat(fuel.customFuel)
-          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00'
-        } else {
-          // 如果 customFuel 为空,则使用 zhbdFuel 的值
-          const zhbdValue = parseFloat(fuel.zhbdFuel)
-          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
-        }
-
-        return {
-          ...newFuel,
-          customFuel: customFuelValue
-        }
-      })
-
-      formData.value.reportFuels = processedFuels
-
-      // 计算初始的当日油耗
-      calculateTotalDailyFuel()
-    } else {
-      // 如果 reportedFuels 不存在或为空,设置空数组
-      formData.value.reportFuels = []
-    }
-  } else {
-    // 编辑模式:优先使用 reportedFuels,不存在则使用 reportFuels
-    let fuelSource = reportData.reportedFuels || reportData.reportFuels || []
-
-    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
-      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
-      const processedFuels = fuelSource.map((fuel: any) => {
-        // 创建全新的对象,避免引用共享
-        const newFuel = {
-          ...fuel, // 使用展开运算符创建浅拷贝
-          // 确保每个字段都有独立的值
-          createTime: fuel.createTime,
-          updateTime: fuel.updateTime,
-          creator: fuel.creator,
-          updater: fuel.updater,
-          deleted: fuel.deleted,
-          id: fuel.id,
-          type: fuel.type,
-          reportId: fuel.reportId,
-          deviceId: fuel.deviceId,
-          deviceCode: fuel.deviceCode,
-          yfDeviceCode: fuel.yfDeviceCode,
-          deviceName: fuel.deviceName,
-          carId: fuel.carId,
-          zhbdFuel: fuel.zhbdFuel,
-          customFuel: null, // 初始化为null
-          queryDate: fuel.queryDate,
-          remark: fuel.remark
-        }
-
-        let customFuelValue
-
-        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
-        if (fuel.customFuel !== null && fuel.customFuel !== undefined && fuel.customFuel !== '') {
-          const numValue = parseFloat(fuel.customFuel)
-          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00'
-        } else {
-          // 如果 customFuel 为空,则使用 zhbdFuel 的值
-          const zhbdValue = parseFloat(fuel.zhbdFuel)
-          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
-        }
-
-        return {
-          ...newFuel,
-          customFuel: customFuelValue
-        }
-      })
-
-      formData.value.reportFuels = processedFuels
-
-      // 计算初始的当日油耗
-      calculateTotalDailyFuel()
-    } else {
-      formData.value.reportFuels = []
-    }
-  }
-}
-
-onMounted(async () => {
-  formLoading.value = true
-  try {
-    // 加载当前登录人所属部门
-    const deptId = userStore.getUser.deptId
-    const dept = await DeptApi.getDept(deptId)
-
-    // 查询瑞都日报详情
-    if (id) {
-      const response = await IotRdDailyReportApi.getIotRdDailyReport(id)
-      console.log('response :>> ', response)
-      dailyReportData.value = response || {}
-      initFormData(dailyReportData.value)
-
-      // 确保油耗数据在初始化后立即渲染
-      await nextTick()
-      // 强制更新油耗数据
-      if (formData.value.reportFuels && formData.value.reportFuels.length > 0) {
-        formData.value.reportFuels = [...formData.value.reportFuels]
-      }
-    }
-  } catch (error) {
-    console.error('初始化数据失败:', error)
-    message.error('数据加载失败')
-  } finally {
-    formLoading.value = false
-  }
-})
-
-// 详细 审批 平台井 获取工作量列配置
-const getWorkloadColumns = () => {
-  const dataSource = platformWorkloadData.value
-  if (!dataSource || dataSource.length === 0) return []
-
-  const columns = []
-  const addedIdentifiers = new Set()
-
-  dataSource.forEach((platform) => {
-    if (platform.extProperty && Array.isArray(platform.extProperty)) {
-      platform.extProperty.forEach((extProp) => {
-        if (!addedIdentifiers.has(extProp.identifier)) {
-          columns.push({
-            key: extProp.identifier,
-            identifier: extProp.identifier,
-            label: `${extProp.name}(${extProp.unit})`
-          })
-          addedIdentifiers.add(extProp.identifier)
-        }
-      })
-    }
-  })
-
-  return columns
-}
-
-// 添加一个深拷贝油耗数据的辅助函数
-const deepCopyFuelData = (fuelData: any) => {
-  if (!fuelData) return null
-
-  return {
-    createTime: fuelData.createTime,
-    updateTime: fuelData.updateTime,
-    creator: fuelData.creator,
-    updater: fuelData.updater,
-    deleted: fuelData.deleted,
-    id: fuelData.id,
-    type: fuelData.type,
-    reportId: fuelData.reportId,
-    deviceId: fuelData.deviceId,
-    deviceCode: fuelData.deviceCode,
-    yfDeviceCode: fuelData.yfDeviceCode,
-    deviceName: fuelData.deviceName,
-    carId: fuelData.carId,
-    zhbdFuel: fuelData.zhbdFuel,
-    customFuel: fuelData.customFuel,
-    queryDate: fuelData.queryDate,
-    remark: fuelData.remark
-  }
-}
-
-// 强制刷新表格
-const refreshFuelTable = () => {
-  fuelTableKey.value += 1
-}
-
-// 计算当日油耗的默认值
-const calculateDailyFuel = (reportData: any) => {
-  let dailyFuelValue = 0
-
-  // 如果有接口返回的dailyFuel,优先使用
-  if (reportData.dailyFuel !== null && reportData.dailyFuel !== undefined) {
-    dailyFuelValue = Number(reportData.dailyFuel)
-  }
-
-  // 如果reportFuels有数据,累加zhbdFuel的值
-  if (
-    reportData.reportFuels &&
-    Array.isArray(reportData.reportFuels) &&
-    reportData.reportFuels.length > 0
-  ) {
-    const totalZhbdFuel = reportData.reportFuels.reduce((sum: number, item: any) => {
-      const zhbdFuelValue = Number(item.zhbdFuel) || 0
-      return sum + zhbdFuelValue
-    }, 0)
-
-    // 只有当累计值大于0时才覆盖原有的dailyFuel值
-    if (totalZhbdFuel > 0) {
-      dailyFuelValue = totalZhbdFuel
-    }
-  }
-
-  return formatNumber(dailyFuelValue, 2)
-}
-
-// 处理当日油耗输入
-const handleDailyFuelInput = () => {
-  // 确保保留两位小数
-  if (formData.value.dailyFuel !== '') {
-    const numValue = parseFloat(formData.value.dailyFuel)
-    if (!isNaN(numValue)) {
-      formData.value.dailyFuel = numValue.toFixed(2)
-    }
-  }
-}
-
-// 添加数字格式化函数
-const formatNumber = (value: any, decimalPlaces: number = 2) => {
-  if (value === null || value === undefined || value === '' || value === 'NaN') {
-    return '0.00'
-  }
-
-  // 如果已经是字符串,尝试转换为数字
-  if (typeof value === 'string') {
-    // 移除可能的非数字字符
-    const cleaned = value.replace(/[^\d.-]/g, '')
-    const num = Number(cleaned)
-    if (isNaN(num)) {
-      return '0.00'
-    }
-    return num.toFixed(decimalPlaces)
-  }
-
-  const num = Number(value)
-  if (isNaN(num)) {
-    return '0.00'
-  }
-
-  return num.toFixed(decimalPlaces)
-}
-
-// 新增:计算并更新当日油耗的方法
-const calculateAndUpdateDailyFuel = () => {
-  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
-    return
-  }
-
-  // 计算所有车辆的实际油耗总和
-  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
-    const customFuelValue = Number(item.customFuel) || 0
-    return sum + customFuelValue
-  }, 0)
-
-  // 只有当累计的实际油耗大于0时,才更新当日油耗
-  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
-    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2)
-    // 更新输入框显示
-    if (dailyFuelInput.value) {
-      dailyFuelInput.value = formData.value.dailyFuel
-    }
-  }
-}
-
-// 添加计算属性:获取油耗数据显示数据源
-const fuelConsumptionData = computed(() => {
-  // 所有模式都统一使用 formData.value.reportFuels 作为数据源
-  // 因为 formData.value.reportFuels 在 initFormData 中已经正确处理了所有情况
-  return formData.value.reportFuels || []
-})
-
-// 判断是否显示油耗信息区域
-const showFuelConsumption = computed(() => {
-  const data = fuelConsumptionData.value
-  return data && Array.isArray(data) && data.length > 0
-})
-
-// 重新计算当日油耗const formatNumber
-const calculateTotalDailyFuel = () => {
-  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
-    return
-  }
-
-  // 计算所有车辆的实际油耗总和
-  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
-    const customFuelValue = Number(item.customFuel) || 0
-    return sum + customFuelValue
-  }, 0)
-
-  // 只有当累计的实际油耗大于0时,才自动更新当日油耗
-  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
-    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2)
+const removeAttachment = (index: number) => {
+  if (form.value.attachments && form.value.attachments.length > index) {
+    form.value.attachments.splice(index, 1)
   }
 }
 
-// 处理实际油耗变化
-const handleCustomFuelChange = (fuelItem: any) => {
-  // 获取当前输入的值
-  let value = fuelItem.customFuel
-
-  // 如果输入为空,则重置为zhbdFuel的值
-  if (value === '' || value === null || value === undefined) {
-    fuelItem.customFuel = formatNumber(fuelItem.zhbdFuel, 2)
+const inContent = async (attachment) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
     return
   }
 
-  // 移除非数字字符(除了小数点)
-  const cleaned = value.toString().replace(/[^\d.]/g, '')
-
-  // 确保只有一个小数点
-  const parts = cleaned.split('.')
-  let formattedValue = cleaned
-  if (parts.length > 2) {
-    formattedValue = parts[0] + '.' + parts.slice(1).join('')
-  }
-
-  // 转换为数字并格式化为两位小数
-  const numValue = parseFloat(formattedValue)
-  if (!isNaN(numValue)) {
-    // 限制到两位小数
-    fuelItem.customFuel = formatNumber(numValue, 2)
-  } else {
-    // 如果转换失败,设置为0.00
-    fuelItem.customFuel = '0.00'
-  }
-
-  // 同步更新 formData.reportFuels 中的数据
-  // 确保通过 deviceId 正确找到并更新对应的记录
-  if (formData.value.reportFuels && fuelItem.deviceId) {
-    const index = formData.value.reportFuels.findIndex(
-      (item) => item.deviceId === fuelItem.deviceId
-    )
-
-    if (index !== -1) {
-      // 创建新对象,避免引用问题
-      const updatedFuel = {
-        ...formData.value.reportFuels[index],
-        customFuel: fuelItem.customFuel
-      }
-
-      // 使用 Vue.set 或直接赋值确保响应性
-      formData.value.reportFuels[index] = updatedFuel
-
-      // 强制刷新表格
-      fuelTableKey.value += 1
-    }
-  }
-
-  // 手动触发当日油耗的重新计算
-  calculateAndUpdateDailyFuel()
-}
-
-// 详情 审批 平台井 获取工作量值
-const getWorkloadValue = (platform, identifier) => {
-  if (!platform.extProperty) return ''
-  const prop = platform.extProperty.find((item) => item.identifier === identifier)
-  return prop ? prop.actualValue || '' : ''
-}
-
-/** 审批操作 */
-const handleApprove = async (action: 'pass' | 'reject') => {
-  // 只有在审批模式下才执行审批操作
-  if (!isApprovalMode.value) {
-    message.warning('当前不是审批模式')
-    return
-  }
   try {
-    // 验证审批表单(如果需要)
-    // await approvalFormRef.value.validate()
-
-    formLoading.value = true
-
-    // 处理时间范围数据
-    if (formData.value.timeRange && formData.value.timeRange.length === 2) {
-      // 将时间范围转换为 LocalTime 格式的字符串
-      const startDate = dayjs(formData.value.timeRange[0])
-      const endDate = dayjs(formData.value.timeRange[1])
-
-      // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
-      formData.value.startTime = startDate.format('HH:mm:ss')
-      formData.value.endTime = endDate.format('HH:mm:ss')
-    }
-
-    // 构建审批数据,包含审批意见
-    const approveData = {
-      ...formData.value,
-      id: Number(id),
-      opinion: approvalForm.opinion,
-      auditStatus: action === 'pass' ? 20 : 30
-    }
+    const filePath = attachment.filePath
+    const encodedPath = encodeURIComponent(Base64.encode(filePath))
 
-    // 这里可以调用审批API
-    if (action === 'pass') {
-      // 审批通过逻辑
-      await IotRdDailyReportApi.approveRdDailyReport(approveData)
-      message.success('审批通过')
-    } else {
-      // 审批驳回逻辑
-      await IotRdDailyReportApi.approveRdDailyReport(approveData)
-      message.success('审批驳回')
-    }
-    close()
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
   } catch (error) {
-    console.error('审批操作失败:', error)
-    message.error('审批操作失败')
-  } finally {
-    formLoading.value = false
+    console.error('预览附件失败:', error)
+    message.error('预览附件失败')
   }
 }
 </script>
 
-<style scoped>
-.info-table {
-  border: 1px solid #e0e0e0;
-  border-radius: 4px;
-  overflow: hidden;
-}
-
-.table-row {
-  display: flex;
-  border-bottom: 1px solid #e0e0e0;
-}
-
-.table-row:last-child {
-  border-bottom: none;
-}
-
-.table-cell {
-  flex: 1;
-  border-right: 1px solid #e0e0e0;
-  padding: 12px 8px;
-  min-height: 44px;
-  display: flex;
-  align-items: center;
-}
-
-.table-cell:last-child {
-  border-right: none;
-}
+<template>
+  <div
+    class="bg-white rounded-xl shadow size-full flex flex-col gap-4 p-4 mb-12"
+    v-loading="loading"
+  >
+    <div class="flex justify-between items-start">
+      <div class="flex flex-col gap-1">
+        <div class="flex items-center gap-3">
+          <span class="text-xl font-bold text-gray-900 leading-tight">
+            {{ header.title ?? header.suffix }}
+          </span>
+          <div
+            v-if="header.title"
+            class="px-2 py-0.5 rounded text-xs font-medium border"
+            :class="statusClass"
+          >
+            {{ header.suffix }}
+          </div>
+        </div>
+        <div v-if="header.date" class="text-sm text-gray-400 font-medium">
+          {{ header.date }}
+        </div>
+      </div>
 
-.table-cell.full-width {
-  flex: 1;
-  border-right: none;
-}
+      <!-- <el-button link @click="handleCloseForm">
+        <el-icon size="24"><Close /></el-icon>
+      </el-button> -->
+    </div>
+    <el-alert
+      class="min-h-12"
+      v-if="formType !== 'edit'"
+      :title="modeNotice"
+      type="info"
+      :closable="false"
+    />
+
+    <el-alert
+      class="min-h-12"
+      v-if="formType !== 'approval' && data.opinion"
+      :title="data.opinion"
+      type="warning"
+      :closable="false"
+    />
+    <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+
+    <div class="grid grid-cols-3 gap-y-8 gap-x-4">
+      <div class="info-item">
+        <label>甲方</label>
+        <div class="truncate">{{ data.manufactureName || '-' }}</div>
+      </div>
 
-.cell-content {
-  display: flex;
-  align-items: center;
-  width: 100%;
-}
+      <div class="info-item">
+        <label>合同号</label>
+        <div>{{ data.contractName || '-' }}</div>
+      </div>
 
-.cell-label {
-  font-weight: 500;
-  /* 统一字体大小为 14px(Element 表单默认) */
-  font-size: 14px;
-  color: #606266;
-  min-width: 80px;
-  margin-right: 8px;
-  flex-shrink: 0;
-}
+      <div class="info-item">
+        <label>井号</label>
+        <div class="text-primary font-bold">
+          {{ data.wellName || data.taskName || '-' }}
+        </div>
+      </div>
 
-.cell-value {
-  /* 统一字体大小为 14px(Element 输入框默认) */
-  font-size: 14px;
-  color: #303133;
-  /* 统一行高为 1.5(Element 组件默认行高) */
-  line-height: 1.5;
-  flex: 1;
-  word-break: break-all;
-}
+      <!-- 第二行 -->
+      <div class="info-item">
+        <label>施工队伍</label>
+        <div>{{ data.deptName || '-' }}</div>
+      </div>
 
-/* 响应式设计 */
-@media (max-width: 768px) {
-  .table-row {
-    flex-direction: column;
-  }
+      <div class="info-item">
+        <label>施工地点</label>
+        <div>{{ data.location || '-' }}</div>
+      </div>
 
-  .table-cell {
-    border-right: none;
-    border-bottom: 1px solid #e0e0e0;
-  }
+      <div class="info-item">
+        <label>工艺</label>
+        <div>{{ data.techniqueNames || '-' }}</div>
+      </div>
 
-  .table-cell:last-child {
-    border-bottom: none;
-  }
-}
+      <!-- 第三行 -->
+      <div class="info-item">
+        <label>设计工作量</label>
+        <div class="font-mono text-gray-700">{{ data.workloadDesign || '-' }}</div>
+      </div>
 
-.daily-report-title {
-  text-align: center;
-  margin: 20px 0;
-  padding: 10px;
-  border-bottom: 2px solid #409eff;
-}
+      <div class="info-item">
+        <label>开工日期</label>
+        <div class="font-mono text-gray-700">{{ data.commencementDate || '-' }}</div>
+      </div>
 
-.daily-report-title h2 {
-  margin: 0;
-  color: #303133;
-  font-size: 16px;
-  font-weight: bold;
-}
+      <div class="info-item">
+        <label>完工日期</label>
+        <div class="font-mono text-gray-700">{{ data.completionDate || '-' }}</div>
+      </div>
 
-/* 为第二、三部分增加左右留白 */
-.section-padding {
-  padding-left: 0px;
-  padding-right: 40px;
-}
+      <!-- 第四行 -->
+      <div class="info-item">
+        <label>施工周期 (D)</label>
+        <div>{{ data.constructionPeriod ?? '-' }}</div>
+      </div>
 
-.project-info-section {
-  margin: 20px 0;
-  padding: 20px;
-  background-color: #f8f9fa;
-  border-radius: 4px;
-  border: 1px solid #e9ecef;
-}
+      <div class="info-item">
+        <label>停待时间 (D)</label>
+        <div>
+          {{ data.idleTime ?? '-' }}
+        </div>
+      </div>
 
-.info-row {
-  padding: 12px 0;
-  border-bottom: 1px solid #e9ecef;
-}
+      <div class="info-item">
+        <label>带班干部</label>
+        <div>{{ data.responsiblePersonNames || '-' }}</div>
+      </div>
 
-.info-row:last-child {
-  border-bottom: none;
-}
+      <div class="info-item col-span-3">
+        <label>设备配置</label>
+        <div>{{ data.deviceNames || '-' }}</div>
+      </div>
+    </div>
+    <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+    <div v-if="progressList.length > 0">
+      <h3 class="text-lg font-bold text-gray-800 mb-6 flex items-center gap-2">
+        <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+        任务进度
+      </h3>
+      <el-scrollbar class="h-24!" view-class="w-full flex items-start px-2">
+        <div
+          v-for="(item, index) in progressList"
+          :key="index"
+          class="group relative flex flex-col items-center flex-1 min-w-[160px] cursor-default select-none"
+        >
+          <div
+            v-if="index !== progressList.length - 1"
+            class="absolute top-[34px] left-1/2 w-full h-[2px] bg-gray-100 group-hover:bg-blue-50 transition-colors duration-300"
+          >
+          </div>
 
-.info-label {
-  font-weight: bold;
-  color: #495057;
-  margin-right: 8px;
-}
+          <span
+            class="text-xs font-medium text-gray-400 mb-3 font-mono transition-colors duration-300 group-hover:text-blue-500"
+          >
+            {{ item.createTime || '--' }}
+          </span>
 
-.info-value {
-  color: #212529;
-}
+          <div
+            class="relative z-10 mb-3 transition-transform duration-300 group-hover:-translate-y-0.5"
+          >
+            <div
+              class="w-4 h-4 rounded-full border-[3px] border-white shadow-[0_0_0_2px_rgba(229,231,235,1)] bg-blue-600 group-hover:shadow-[0_0_0_4px_rgba(219,234,254,1)] group-hover:bg-blue-500 transition-all duration-300"
+            >
+            </div>
+          </div>
 
-:deep(.el-textarea .el-textarea__inner) {
-  min-height: 80px;
-}
+          <span
+            class="text-sm font-bold text-gray-700 px-2 text-center leading-relaxed transition-colors duration-300 group-hover:text-blue-600"
+          >
+            {{ item.rdStatusLabel || '未知状态' }}
+          </span>
+        </div>
+      </el-scrollbar>
+    </div>
 
-/* 确保表单label不换行 */
-:deep(.el-form-item__label) {
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-}
+    <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+
+    <el-form
+      ref="formRef"
+      size="default"
+      :rules="rules"
+      label-position="top"
+      :model="form"
+      require-asterisk-position="right"
+      class="flex flex-col"
+      :disabled="formDisabled()"
+    >
+      <!-- <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+        <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+        基础信息
+      </h3> -->
+      <div
+        class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
+      >
+        <el-form-item label="时间节点" prop="timeRange">
+          <el-time-picker
+            v-model="form.timeRange"
+            is-range
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            placeholder="选择时间范围"
+            clearable
+            format="HH:mm"
+            value-format="HH:mm"
+          />
+        </el-form-item>
+        <el-form-item label="当日油耗(L)" prop="dailyFuel">
+          <el-input-number
+            v-model="form.dailyFuel"
+            :min="0"
+            :controls="false"
+            align="left"
+            class="w-full!"
+            placeholder="请输入当日油耗"
+          >
+            <template #suffix>升(L)</template>
+          </el-input-number>
+        </el-form-item>
+        <el-form-item class="col-span-2" label="施工设备" prop="deviceIds">
+          <el-select
+            v-model="form.deviceIds"
+            multiple
+            placeholder="请选择施工设备"
+            clearable
+            filterable
+            tag-type="primary"
+          >
+            <el-option
+              v-for="item in deviceOptions"
+              :key="item.id"
+              :label="item.deviceName"
+              :value="item.id"
+            >
+              <span class="font-medium">{{ item.deviceCode + ' - ' + item.deviceName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="col-span-2" label="闲置/未施工设备">
+          <div
+            class="w-full min-h-[40px] p-3 rounded bg-gray-50 border border-gray-200 border-dashed transition-all"
+          >
+            <template v-if="noSelectedDevices.length > 0">
+              <div class="flex flex-wrap gap-2">
+                <el-tag
+                  v-for="device in noSelectedDevices"
+                  :key="device.id"
+                  type="info"
+                  effect="plain"
+                  class="!border-gray-300"
+                >
+                  {{ device.deviceName }}
+                </el-tag>
+              </div>
+            </template>
+            <template v-else>
+              <div class="text-gray-400 text-sm flex items-center">
+                <el-icon class="mr-1"><CircleCheck /></el-icon>
+                所有设备均已投入施工
+              </div>
+            </template>
+          </div>
+        </el-form-item>
+        <div class="col-span-2 flex items-center justify-between mb-6">
+          <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+            <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+            生产动态
+          </h3>
+          <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
+            添加一行
+          </el-button>
+        </div>
 
-/* 甲方字段:单行显示+超出省略 */
-.single-line-ellipsis {
-  /* 强制文本单行显示 */
-  white-space: nowrap;
-  /* 超出容器部分隐藏 */
-  overflow: hidden;
-  /* 超出部分显示省略号 */
-  text-overflow: ellipsis;
-  /* 避免文本被截断(可选,根据需求调整) */
-  word-break: normal;
-}
+        <el-form-item prop="reportDetails" class="col-span-2">
+          <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
+            <ZmTableColumn
+              :width="120"
+              label="日期"
+              cover-formatter
+              :real-value="
+                () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
+              "
+            />
+            <ZmTableColumn :width="160" label="开始时间" prop="startTime">
+              <template #default="{ row, $index }">
+                <el-form-item
+                  v-if="$index >= 0"
+                  class="mb-0!"
+                  :prop="`reportDetails.${$index}.startTime`"
+                  :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
+                >
+                  <el-time-picker
+                    v-model="row.startTime"
+                    placeholder="选择开始时间"
+                    clearable
+                    format="HH:mm"
+                    value-format="HH:mm"
+                    class="w-full!"
+                    @change="calculateDuration(row)"
+                  />
+                </el-form-item>
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn :width="160" label="结束时间" prop="endTime">
+              <template #default="{ row, $index }">
+                <el-form-item
+                  v-if="$index >= 0"
+                  class="mb-0!"
+                  :prop="`reportDetails.${$index}.endTime`"
+                  :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
+                >
+                  <el-time-picker
+                    v-model="row.endTime"
+                    placeholder="选择结束时间"
+                    clearable
+                    format="HH:mm"
+                    value-format="HH:mm"
+                    class="w-full!"
+                    @change="calculateDuration(row)"
+                  />
+                </el-form-item>
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+            <ZmTableColumn label="施工详情" prop="constructionDetail">
+              <template #default="{ row, $index }">
+                <el-form-item
+                  v-if="$index >= 0"
+                  class="mb-0!"
+                  :prop="`reportDetails.${$index}.constructionDetail`"
+                  :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
+                >
+                  <el-input
+                    v-model="row.constructionDetail"
+                    placeholder="输入施工详情"
+                    type="textarea"
+                    :autosize="{ minRows: 1 }"
+                    show-word-limit
+                    :maxlength="2000"
+                    class="w-full!"
+                  />
+                </el-form-item>
+              </template>
+            </ZmTableColumn>
+            <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
+              <template #default="{ $index }">
+                <el-button link type="danger" :icon="Delete" @click="removeReportDetailRow($index)">
+                  删除
+                </el-button>
+              </template>
+            </ZmTableColumn>
+          </ZmTable>
+        </el-form-item>
 
-/* 设备配置字段:换行缩进(与首行对齐) */
-.indent-multiline {
-  /* 首行及换行后缩进 2em(与 label 宽度匹配,可根据需求调整) */
-  text-indent: 0em;
-  /* 允许长文本换行(覆盖原有 cell-value 的 break-all,确保中文换行正常) */
-  word-break: break-word;
-  /* 保证换行后文本正常显示(可选,清除可能的 nowrap 影响) */
-  white-space: normal;
-}
+        <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
+          <el-input
+            v-model="form.nextPlan"
+            type="textarea"
+            :autosize="{ minRows: 2 }"
+            resize="none"
+            show-word-limit
+            :maxlength="1000"
+            placeholder="请输入下步工作计划"
+          />
+        </el-form-item>
+        <el-form-item class="col-span-2" label="外组设备" prop="externalRental">
+          <el-input
+            v-model="form.externalRental"
+            type="textarea"
+            :autosize="{ minRows: 2 }"
+            resize="none"
+            show-word-limit
+            :maxlength="1000"
+            placeholder="请输入外组设备"
+          />
+        </el-form-item>
+        <el-form-item class="col-span-2" label="故障情况" prop="malfunction">
+          <el-input
+            v-model="form.malfunction"
+            type="textarea"
+            :autosize="{ minRows: 2 }"
+            show-word-limit
+            resize="none"
+            :maxlength="1000"
+            placeholder="请输入故障情况"
+          />
+        </el-form-item>
+        <el-form-item label="故障误工(H)" prop="faultDowntime">
+          <el-input-number
+            v-model="form.faultDowntime"
+            :min="0"
+            :controls="false"
+            align="left"
+            class="w-full!"
+          >
+            <template #suffix>小时(H)</template>
+          </el-input-number>
+        </el-form-item>
 
-/* 添加审批模式下的样式 */
-.approval-notice {
-  margin-top: 10px;
-}
+        <el-form-item label="附件">
+          <FileUpload
+            v-if="formType === 'edit'"
+            ref="fileUploadRef"
+            :device-id="undefined"
+            :show-folder-button="false"
+            @upload-success="handleUploadSuccess"
+          />
 
-/* 审批模式下表单字段的只读样式 */
-:deep(.el-form-item.is-disabled .el-input__inner),
-:deep(.el-form-item.is-disabled .el-textarea__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #c0c4cc;
-  cursor: not-allowed;
-}
+          <div v-if="form.attachments && form.attachments.length > 0" class="attachment-container">
+            <div class="attachment-list">
+              <div
+                v-for="(attachment, index) in form.attachments"
+                :key="attachment.id || index"
+                class="attachment-item"
+              >
+                <a class="attachment-name" @click="inContent(attachment)">
+                  {{ attachment.filename }}
+                </a>
+                <el-button
+                  :disabled="formDisabled()"
+                  type="danger"
+                  link
+                  size="small"
+                  @click="removeAttachment(index)"
+                >
+                  删除
+                </el-button>
+              </div>
+            </div>
+          </div>
+          <div v-else-if="!form.attachments || form.attachments.length === 0" class="no-attachment">
+            无附件
+          </div>
+        </el-form-item>
+      </div>
 
-:deep(.el-form-item.is-disabled .el-select .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #c0c4cc;
-  cursor: not-allowed;
-}
+      <el-divider class="my-6! border-2! border-[var(--el-color-primary)]!" />
+
+      <el-form-item v-if="data.platformWell === 1" label="平台井" prop="platformIds">
+        <el-select
+          v-model="form.platformIds"
+          multiple
+          :options="wellOptions"
+          placeholder="请选择平台井"
+          clearable
+          filterable
+          collapse-tags
+          collapse-tags-tooltip
+          :max-collapse-tags="5"
+          tag-type="primary"
+        />
+      </el-form-item>
+      <template v-for="(pid, pindex) in form.platformIds" :key="pid">
+        <el-divider v-if="pindex !== 0" class="my-6 border-2! border-[var(--el-color-primary)]!" />
+        <div
+          class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
+        >
+          <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
+            <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+            {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
+          </h3>
+          <el-form-item
+            label="施工状态"
+            :prop="`${pid}.rdStatus`"
+            :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
+            class="col-span-2"
+          >
+            <el-select
+              v-model="form[pid].rdStatus"
+              :options="rdStatusOptions"
+              placeholder="请选择"
+              class="w-full"
+              clearable
+            />
+          </el-form-item>
 
-:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #c0c4cc;
-  cursor: not-allowed;
-}
+          <el-form-item
+            label="施工工艺"
+            :prop="`${pid}.techniqueIds`"
+            :rules="{
+              required: true,
+              message: '请选择施工工艺',
+              trigger: 'change',
+              type: 'array'
+            }"
+            class="col-span-2"
+          >
+            <el-select
+              v-model="form[pid].techniqueIds"
+              :options="techniqueOptions"
+              multiple
+              collapse-tags
+              collapse-tags-tooltip
+              placeholder="请选择"
+              class="w-full"
+              @change="(val) => handleTechniqueChange(val, pid)"
+              clearable
+            />
+          </el-form-item>
 
-/* 只读模式下表单字段的样式 */
-:deep(.el-form-item.is-disabled .el-input__inner),
-:deep(.el-form-item.is-disabled .el-textarea__inner),
-:deep(.el-form-item.is-disabled .el-select .el-input__inner),
-:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #606266; /* 保持文字可读性 */
-  cursor: not-allowed;
-}
+          <template v-if="form[pid] && form[pid].extProperty">
+            <el-form-item
+              v-for="(attr, idx) in form[pid].extProperty"
+              :key="idx"
+              :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
+              :prop="`${pid}.extProperty.${idx}.actualValue`"
+              :rules="
+                attr.required === 1
+                  ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
+                  : []
+              "
+            >
+              <el-input-number
+                v-if="attr.dataType === 'double'"
+                v-model="attr.actualValue"
+                :controls="false"
+                class="w-full!"
+                align="left"
+                placeholder="请输入"
+              />
+              <el-input type="textarea" v-else v-model="attr.actualValue" placeholder="请输入" />
+            </el-form-item>
+          </template>
 
-/* 详情模式下的特殊样式 */
-.detail-mode .cell-value {
-  color: #303133;
-  font-weight: normal;
-}
+          <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
+            非生产时间
+          </el-divider>
 
-/* 添加审批意见区域的样式 */
-.approval-opinion-section {
-  margin-top: 20px;
-  border-top: 2px solid #f0f0f0;
-  padding-top: 20px;
-}
+          <el-form-item
+            v-for="field in NON_PROD_FIELDS"
+            :key="field.key"
+            :label="field.label"
+            :prop="`${pid}.${field.key}`"
+            :rules="noProductionTimeRule(pid)"
+          >
+            <el-input-number
+              v-model="form[pid][field.key]"
+              :min="0"
+              :max="24"
+              :controls="false"
+              class="w-full!"
+              align="left"
+              @blur="handleRowValidate(pid, field.key)"
+              :disabled="formDisabled(field.key)"
+            >
+              <template #suffix>小时(H)</template>
+            </el-input-number>
+          </el-form-item>
 
-/* 审批意见文本域样式 */
-:deep(.approval-opinion .el-textarea__inner) {
-  min-height: 100px;
-  resize: vertical;
-}
+          <el-form-item
+            class="col-span-4"
+            label="其他非生产原因"
+            :prop="`${pid}.otherNptReason`"
+            :rules="
+              form[pid].otherNptTime > 0
+                ? { required: true, message: '请填写原因', trigger: 'change' }
+                : {}
+            "
+          >
+            <el-input
+              v-model="form[pid].otherNptReason"
+              type="textarea"
+              :autosize="{ minRows: 2 }"
+              resize="none"
+              show-word-limit
+              :maxlength="1000"
+              placeholder="当'其他非生产时间'大于0时必填"
+              :disabled="formDisabled('otherNptReason')"
+            />
+          </el-form-item>
+        </div>
+      </template>
+
+      <el-form-item class="mt-4 col-span-2" label="当日施工简报" prop="constructionBrief">
+        <el-input
+          v-model="form.constructionBrief"
+          type="textarea"
+          :autosize="{ minRows: 2 }"
+          show-word-limit
+          resize="none"
+          :maxlength="1000"
+          placeholder="请输入当日施工简报"
+          :disabled="formDisabled('constructionBrief')"
+        />
+      </el-form-item>
+
+      <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+      <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
+        <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+        油耗信息
+      </h3>
+
+      <ZmTable :data="form.reportFuels" :loading="false">
+        <ZmTableColumn label="设备编号" :width="160" prop="deviceCode" />
+        <ZmTableColumn label="设备名称" prop="deviceName" />
+        <ZmTableColumn
+          label="发生日期"
+          prop="queryDate"
+          :width="110"
+          cover-formatter
+          :real-value="(row) => (row.queryDate ? dayjs(row.queryDate).format('YYYY-MM-DD') : '')"
+        />
+        <ZmTableColumn label="中航北斗油耗(L)" :width="140" prop="zhbdFuel" />
+        <ZmTableColumn label="实际油耗(L)" prop="customFuel">
+          <template #default="{ row, $index }">
+            <el-form-item class="mb-0!" :prop="`reportFuels.${$index}.customFuel`">
+              <el-input-number
+                v-model="row.customFuel"
+                :min="0"
+                :controls="false"
+                class="w-full!"
+                align="left"
+                @input="handleListChange"
+              >
+                <template #suffix> L </template>
+              </el-input-number>
+            </el-form-item>
+          </template>
+        </ZmTableColumn>
+      </ZmTable>
+
+      <template v-if="platformWorkloadData.length > 0">
+        <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+        <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
+          <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+          平台井工作量
+        </h3>
+
+        <ZmTable :data="platformWorkloadData" :loading="false">
+          <ZmTableColumn label="井号" prop="wellName" />
+          <ZmTableColumn label="施工状态" prop="rdStatusLabel" />
+          <ZmTableColumn label="施工工艺" prop="techniqueNames" />
+          <template v-for="{ prop, label } in getWorkloadColumns()" :key="prop">
+            <ZmTableColumn
+              :label="label"
+              :prop="prop"
+              cover-formatter
+              :real-value="(row) => getWorkloadValue(row, prop)"
+            />
+          </template>
+        </ZmTable>
+      </template>
+
+      <template v-if="formType === 'approval'">
+        <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+        <el-form-item label="审批意见" prop="opinion">
+          <el-input
+            v-model="opinion"
+            type="textarea"
+            :autosize="{ minRows: 2 }"
+            resize="none"
+            show-word-limit
+            :maxlength="1000"
+            placeholder="请输入审批意见"
+            :disabled="formDisabled('opinion')"
+          />
+        </el-form-item>
+      </template>
+    </el-form>
+  </div>
 
-/* 审批意见标签样式 */
-:deep(.approval-opinion .el-form-item__label) {
-  font-weight: bold;
-  color: #606266;
-}
+  <div
+    class="h-16 z-10 flex items-center justify-end px-6 shadow bg-white absolute bottom-0 left-0 w-full border-solid border-0 border-t-1 border-gray-200"
+  >
+    <div v-if="formType === 'edit' || formType === 'time'">
+      <el-button
+        size="default"
+        type="primary"
+        @click="submitForm"
+        :disabled="formDisabled('button')"
+        :loading="formLoading"
+      >
+        确 定
+      </el-button>
+      <el-button size="default" @click="handleCancel">取 消</el-button>
+    </div>
 
-/* 附件列表样式 */
-.attachment-list {
-  width: 100%;
-  margin-top: 5px;
-  border: 1px solid #e0e0e0;
-  border-radius: 4px;
-  padding: 10px;
-  background-color: #fafafa;
-  box-sizing: border-box;
-}
+    <div v-if="formType === 'approval'">
+      <el-button
+        size="default"
+        type="primary"
+        @click="submitApprovalForm(20)"
+        :disabled="formDisabled('button')"
+        :loading="formLoading"
+      >
+        审批通过
+      </el-button>
+      <el-button
+        size="default"
+        type="danger"
+        @click="submitApprovalForm(30)"
+        :disabled="formDisabled('button')"
+        :loading="formLoading"
+      >
+        审批拒绝
+      </el-button>
+      <el-button size="default" @click="handleCancel">取 消</el-button>
+    </div>
+  </div>
+</template>
 
-.attachment-item {
+<style scoped>
+.info-item {
   display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 8px 12px;
-  border-bottom: 1px solid #f0f0f0;
-}
-
-.attachment-item:last-child {
-  border-bottom: none;
-}
-
-.attachment-name {
-  flex: 1;
-  color: #606266;
-  font-size: 11px;
-}
-
-.no-attachment {
-  color: #909399;
-  font-style: italic;
-  margin-top: 5px;
-  padding: 10px;
-}
-
-/* 附件名称链接样式 */
-.attachment-name {
-  color: #409eff;
-  text-decoration: underline;
-  cursor: pointer;
-}
-
-.attachment-name:hover {
-  color: #66b1ff;
-}
-
-/* 只读模式下的设备显示样式 */
-.device-display-readonly {
-  color: #606266;
-  font-size: 11px;
-  line-height: 1.5;
-  background-color: #f5f7fa;
-  padding: 8px 12px;
-  border-radius: 4px;
-  border: 1px solid #e4e7ed;
-  display: inline-block;
-  min-width: 200px;
-}
-
-.no-device {
-  margin-left: 10px;
-  color: #909399;
-  font-style: italic;
-}
-
-/* 设备选择对话框样式 */
-.transfer-container {
-  text-align: center;
-  padding: 0px;
-}
-
-.transfer-component {
-  width: 100%;
-  min-width: 600px;
-}
-
-:deep(.el-transfer-panel) {
-  width: 40% !important;
-}
-
-:deep(.el-transfer-panel__item) {
-  display: flex !important;
-  align-items: center !important;
-  height: 32px !important;
-  line-height: 32px !important;
-  padding: 0 8px !important;
-  margin: 0 !important;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.transfer-option-text {
-  display: inline-block;
-  max-width: 100%;
-}
-
-:deep(.el-transfer-panel__list) {
-  width: 100% !important;
-}
-
-.device-display-container {
-  /* 与其他文本域保持相同的宽度和样式 */
-  display: inline-block;
-  width: 100%; /* 减去按钮宽度 */
-  min-height: 32px;
-  line-height: 32px;
-  padding: 0 12px;
-  margin-left: 0px;
-  border-radius: 4px;
-  border: 1px solid #e4e7ed;
-  background-color: #fff;
-  font-size: 11px;
-  /* 文本溢出处理 */
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-/* 只读模式下的设备显示样式 */
-:deep(.is-disabled) .device-display-container {
-  background-color: #f5f7fa;
-  color: #606266;
-  cursor: not-allowed;
-}
-
-/* 附件容器样式调整 */
-.attachment-container {
-  /* 与其他文本域保持相同的宽度和边距 */
-  width: 100%;
-  margin-top: 10px;
-}
-
-/* 未选择设备字段的只读样式 */
-:deep(.unselected-device .el-textarea__inner) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #909399;
-  cursor: not-allowed;
-  resize: none;
-}
-
-/* 平台井工作量区域专用样式 */
-.platform-workload-section {
-  padding-left: 0px;
-  padding-right: 0px; /* 去掉右侧间距 */
-}
-
-/* 表格样式优化 */
-.platform-workload-el-table {
-  width: 100%;
-}
-
-/* 表头不换行 */
-:deep(.platform-workload-el-table .el-table__header-wrapper th) {
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-}
-
-/* 单元格内容不换行 */
-:deep(.platform-workload-el-table .el-table__body-wrapper td) {
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-}
-
-/* 强制设置表头宽度为100% */
-:deep(.platform-workload-el-table .el-table__header) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 强制设置表格主体宽度为100% */
-:deep(.platform-workload-el-table .el-table__body) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 确保表格填满容器 */
-:deep(.platform-workload-el-table .el-table) {
-  width: 100% !important;
-}
-
-/* 表格容器填满父容器 */
-.platform-workload-table {
-  width: 100%;
-}
-
-/* 油耗信息区域样式 */
-.fuel-consumption-section {
-  padding-left: 0px;
-  padding-right: 0px;
-  margin-top: 20px;
-}
-
-/* 强制表格宽度为100% */
-:deep(.fuel-consumption-el-table) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 确保表格内部元素也充满宽度 */
-:deep(.fuel-consumption-el-table .el-table) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 强制设置表头宽度为100% */
-:deep(.fuel-consumption-el-table .el-table__header) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 表头不换行 */
-:deep(.fuel-consumption-el-table .el-table__header-wrapper th) {
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  background-color: #f5f7fa;
-  color: #606266;
-  font-weight: bold;
-}
-
-/* 强制设置表格主体宽度为100% */
-:deep(.fuel-consumption-el-table .el-table__body) {
-  width: 100% !important;
-  min-width: 100% !important;
-}
-
-/* 表头和表体都设置为100%宽度 */
-:deep(.fuel-consumption-el-table .el-table__header-wrapper),
-:deep(.fuel-consumption-el-table .el-table__body-wrapper) {
-  width: 100% !important;
-}
-
-/* 单元格内容居中 */
-:deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
-  text-align: center;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-}
-
-/* 实际油耗输入框样式 */
-:deep(.fuel-consumption-el-table .el-input__inner) {
-  text-align: center;
-  padding: 0 5px;
-  height: 28px;
-  line-height: 28px;
-}
-
-/* 只读模式下的数值显示 */
-.fuel-consumption-el-table .readonly-value {
-  color: #606266;
-  font-weight: normal;
-}
+  flex-direction: column;
+  gap: 0.25rem;
 
-/* 强制设置表格宽度为100% */
-:deep(.fuel-consumption-el-table .el-table) {
-  width: 100% !important;
-}
-
-/* 表格容器填满父容器 */
-.fuel-consumption-table {
-  width: 100%;
-  overflow-x: auto;
-}
-
-/* 响应式调整 */
-@media (max-width: 768px) {
-  .fuel-consumption-section {
-    padding-left: 10px;
-    padding-right: 10px;
+  label {
+    font-size: 0.75rem;
+    font-weight: 500;
+    line-height: 1rem;
+    color: #9ca3af;
   }
 
-  :deep(.fuel-consumption-el-table .el-table__header-wrapper th),
-  :deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
-    padding: 8px 5px;
-    font-size: 12px;
+  > div {
+    min-height: 1.25rem;
+    font-size: 0.875rem;
+    font-weight: 600;
+    line-height: 1.25rem;
+    color: #1f2937;
+    word-break: break-all;
   }
 }
 
-/* 当日油耗输入框样式优化 */
-:deep(.el-form-item .el-input-number) {
-  width: 100%;
-}
-
-/* 当日油耗字段在只读模式下的样式 */
-:deep(.is-disabled .el-input__inner[type='number']) {
-  background-color: #f5f7fa;
-  border-color: #e4e7ed;
-  color: #606266;
-  cursor: not-allowed;
-}
-
-/* 实际进度区域样式 */
-.actual-progress-container {
-  margin-top: 10px;
-  padding: 20px;
-  border: 1px solid #e6e6e6;
-  border-radius: 8px;
-  background-color: #fafafa;
-}
-
-.progress-title {
-  margin: 0 0 16px 0;
-  font-size: 16px;
-  font-weight: bold;
-  color: #67c23a; /* 实际进度使用绿色标题 */
-}
-
-.no-progress-data {
-  text-align: center;
-  padding: 20px 0;
-  color: #909399;
-  font-style: italic;
-}
-
-/* 调整步骤组件样式以适应水平布局 */
-:deep(.actual-progress-container .el-steps--horizontal) {
-  flex-wrap: nowrap;
-  overflow-x: auto;
-  padding-bottom: 10px;
-}
-
-:deep(.actual-progress-container .el-step__title) {
-  font-size: 12px;
-  line-height: 1.4;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  max-width: 120px;
-}
-
-/* 确保步骤容器有足够空间 */
-:deep(.actual-progress-container .el-step) {
-  flex-basis: auto;
-  flex-shrink: 0;
+:deep(.el-form-item__label) {
+  font-weight: 500;
 }
 
-/* 响应式调整 */
-@media (max-width: 768px) {
-  :deep(.actual-progress-container .el-step__title) {
-    font-size: 11px;
-    max-width: 100px;
-  }
-
-  .actual-progress-container {
-    padding: 15px;
-  }
+:deep(.el-scrollbar__bar.is-horizontal) {
+  height: 4px;
 }
 </style>

+ 3271 - 0
src/views/pms/iotrddailyreport/FillDailyReportForm1.vue

@@ -0,0 +1,3271 @@
+<template>
+  <ContentWrap v-loading="formLoading">
+    <!-- 第一部分:日报标题 -->
+    <div class="daily-report-title">
+      <h2>{{ pageTitle }}</h2>
+      <!-- 在审批模式下显示审批状态提示 -->
+      <div v-if="isReadonlyMode" class="approval-notice">
+        <el-alert :title="modeNotice" type="info" :closable="false" />
+      </div>
+    </div>
+
+    <!-- 第二部分:项目/任务信息 -->
+    <ContentWrap>
+      <div class="info-table" style="margin-top: 1em">
+        <!-- 表格行 -->
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">甲方:</span>
+              <!-- 甲方字段:添加 single-line-ellipsis 类 + title 绑定完整内容 -->
+              <span
+                class="cell-value single-line-ellipsis"
+                :title="dailyReportData.manufactureName || '-'"
+              >
+                {{ dailyReportData.manufactureName || '-' }}
+              </span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">合同号:</span>
+              <span class="cell-value">{{ dailyReportData.contractName || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">井号:</span>
+              <span class="cell-value">{{
+                displayWellName || dailyReportData.taskName || '-'
+              }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工队伍:</span>
+              <span class="cell-value">{{ dailyReportData.deptName || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工地点:</span>
+              <span class="cell-value">{{ dailyReportData.location || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">工艺:</span>
+              <span class="cell-value">{{ dailyReportData.techniqueNames || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">设计工作量:</span>
+              <span class="cell-value">{{ dailyReportData.workloadDesign || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">开工日期:</span>
+              <span class="cell-value">{{ dailyReportData.commencementDate || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">完工日期:</span>
+              <span class="cell-value">{{ dailyReportData.completionDate || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工周期D:</span>
+              <span class="cell-value">{{ dailyReportData.constructionPeriod || '' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">停待时间D:</span>
+              <span class="cell-value">{{ dailyReportData.idleTime || '' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">带班干部:</span>
+              <span class="cell-value">{{ dailyReportData.responsiblePersonNames || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 第五行:设备配置(单独一行) -->
+        <div class="table-row">
+          <div class="table-cell full-width">
+            <div class="cell-content">
+              <span class="cell-label">设备配置:</span>
+              <span class="cell-value indent-multiline">{{
+                dailyReportData.deviceNames || '-'
+              }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </ContentWrap>
+
+    <!-- 实际进度显示区域(新增) -->
+    <ContentWrap class="section-padding" v-if="showActualProgress">
+      <h3 class="progress-title">任务进度</h3>
+      <div class="actual-progress-container">
+        <div v-if="actualProgressData.length > 0">
+          <el-steps
+            direction="horizontal"
+            :active="actualProgressData.length - 1"
+            finish-status="success"
+          >
+            <el-step
+              v-for="(step, index) in actualProgressData"
+              :key="index"
+              :title="step.title"
+              :description="step.description"
+              :status="step.status"
+            />
+          </el-steps>
+        </div>
+        <div v-else class="no-progress-data"> 暂无实际进度数据 </div>
+      </div>
+    </ContentWrap>
+
+    <!-- 第三部分:日报填报表单 -->
+    <ContentWrap class="section-padding">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="isReadonlyMode ? {} : formRules"
+        v-loading="formLoading"
+        style="margin-top: 1em"
+        label-width="200px"
+        :disabled="isReadonlyMode"
+      >
+        <!-- 第一行:时间节点、施工状态 -->
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="时间节点" prop="timeRange">
+              <el-time-picker
+                is-range
+                v-model="formData.timeRange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                placeholder="选择时间范围"
+                style="width: 100%"
+                :readonly="isReadonlyMode"
+                :disabled="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="施工状态" prop="rdStatus">
+              <el-select
+                v-model="formData.rdStatus"
+                placeholder="请选择施工状态"
+                style="width: 100%"
+                :disabled="isReadonlyMode"
+              >
+                <el-option
+                  v-for="dict in rdStatusOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 平台井 -->
+        <el-row v-if="showPlatformField">
+          <el-col :span="24">
+            <el-form-item label="平台井" prop="platformId">
+              <el-select
+                v-model="formData.platformId"
+                placeholder="请选择平台井"
+                style="width: 100%"
+                :disabled="isReadonlyMode && query.istime !== 'true'"
+              >
+                <el-option
+                  v-for="platform in platformOptions"
+                  :key="platform.id"
+                  :label="platform.wellName"
+                  :value="platform.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 施工设备字段 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="施工设备" prop="deviceIds">
+              <!-- 编辑模式:显示选择按钮 -->
+              <template v-if="isEditMode">
+                <el-button
+                  @click="openDeviceDialog"
+                  type="primary"
+                  size="small"
+                  :disabled="formLoading || isReadonlyMode"
+                >
+                  选择设备
+                </el-button>
+                <el-tooltip
+                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
+                  :content="getAllDeviceNamesForDisplay"
+                  placement="top"
+                >
+                  <span class="device-display-container">
+                    {{ formatDevicesForDisplay }}
+                  </span>
+                </el-tooltip>
+                <span v-else class="no-device"> 未选择设备 </span>
+              </template>
+
+              <!-- 只读模式:只显示设备信息 -->
+              <template v-else>
+                <el-tooltip
+                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
+                  :content="getAllDeviceNamesForDisplay"
+                  placement="top"
+                >
+                  <span class="device-display-container">
+                    {{ formatDevicesForDisplay }}
+                  </span>
+                </el-tooltip>
+                <span v-else class="no-device">-</span>
+              </template>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 未施工设备 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="未施工设备" prop="unSelectedDeviceNames">
+              <el-input
+                v-model="unSelectedDeviceNames"
+                type="textarea"
+                :rows="2"
+                placeholder="未施工的设备将显示在这里"
+                :readonly="true"
+                class="unselected-device"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第二行:施工工艺 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="施工工艺" prop="techniqueIds">
+              <el-select
+                v-model="formData.techniqueIds"
+                placeholder="请选择施工工艺"
+                style="width: 100%"
+                multiple
+                :disabled="isReadonlyMode"
+              >
+                <el-option
+                  v-for="dict in techniqueOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 动态属性区域:施工工艺与当日生产动态之间 -->
+        <el-row v-if="dynamicAttrs.length > 0" :gutter="30">
+          <el-col
+            v-for="attr in dynamicAttrs"
+            :key="attr.id"
+            :span="attr.dataType === 'textarea' ? 24 : 12"
+          >
+            <el-form-item
+              :label="attr.name + (attr.unit ? `(${attr.unit})` : '')"
+              :prop="'dynamicFields.' + attr.identifier"
+              :rules="isReadonlyMode ? [] : getDynamicAttrRules(attr)"
+            >
+              <!-- 文本类型 -->
+              <el-input
+                v-if="attr.dataType === 'text'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 文本域类型 -->
+              <el-input
+                v-else-if="attr.dataType === 'textarea'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                type="textarea"
+                :rows="3"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 数字类型 -->
+              <el-input
+                v-else-if="attr.dataType === 'double'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                type="number"
+                :min="attr.minValue || undefined"
+                :max="attr.maxValue || undefined"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 日期类型 -->
+              <el-date-picker
+                v-else-if="attr.dataType === 'date'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                type="date"
+                value-format="x"
+                :placeholder="`选择${attr.name}`"
+                style="width: 100%"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 默认文本输入 -->
+              <el-input
+                v-else
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="当日油耗(L)" prop="dailyFuel">
+              <el-input
+                v-model="formData.dailyFuel"
+                type="text"
+                :min="0"
+                placeholder="自动计算当日油耗"
+                :readonly="isReadonlyMode"
+                @blur="formatDailyFuel"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第三行:当日生产动态 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="当日生产动态" prop="productionStatus">
+              <el-input
+                v-model="formData.productionStatus"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入当日生产动态"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第四行:下步工作计划 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="下步工作计划" prop="nextPlan">
+              <el-input
+                v-model="formData.nextPlan"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入下步工作计划"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第五行:外租设备 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="外租设备" prop="externalRental">
+              <el-input
+                v-model="formData.externalRental"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入外租设备信息"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 故障情况 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="故障情况" prop="malfunction">
+              <el-input
+                v-model="formData.malfunction"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入故障情况"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 故障误工H -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="故障误工H" prop="faultDowntime">
+              <el-input
+                v-model="formData.faultDowntime"
+                type="number"
+                :rows="3"
+                placeholder="请输入故障误工H"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <div class="grid grid-cols-3 gao-4">
+          <el-form-item
+            v-for="field in NON_PROD_FIELDS"
+            :key="field.key"
+            :label="field.label + '(H)'"
+            :prop="field.key"
+          >
+            <el-input-number
+              class="!w-full"
+              :min="0"
+              :max="24"
+              v-model="formData[field.key]"
+              :controls="false"
+              align="left"
+              :disabled="query.istime !== 'true'"
+            />
+          </el-form-item>
+        </div>
+
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="其他非生产原因" prop="otherNptReason">
+              <el-input
+                v-model="formData.otherNptReason"
+                placeholder="请输入其他非生产原因"
+                :disabled="query.istime !== 'true'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第六行:上传附件 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="附件">
+              <!-- 文件上传组件 -->
+              <FileUpload
+                v-if="!isReadonlyMode"
+                ref="fileUploadRef"
+                :device-id="deviceId"
+                :show-folder-button="false"
+                @upload-success="handleUploadSuccess"
+              />
+
+              <!-- 已上传附件列表显示 -->
+              <div
+                v-if="formData.attachments && formData.attachments.length > 0"
+                class="attachment-container"
+              >
+                <div class="attachment-list">
+                  <div
+                    v-for="(attachment, index) in formData.attachments"
+                    :key="attachment.id || index"
+                    class="attachment-item"
+                  >
+                    <!-- 为附件名称添加点击事件,传递整个附件对象 -->
+                    <a class="attachment-name" @click="inContent(attachment)">
+                      {{ attachment.filename }}
+                    </a>
+                    <el-button
+                      v-if="!isReadonlyMode"
+                      type="danger"
+                      link
+                      size="small"
+                      @click="removeAttachment(index)"
+                    >
+                      删除
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 审批模式下只显示附件列表 -->
+              <div
+                v-else-if="
+                  isApprovalMode && (!formData.attachments || formData.attachments.length === 0)
+                "
+                class="no-attachment"
+              >
+                无附件
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 油耗信息区域 - 当有油耗数据时显示 -->
+    <ContentWrap class="fuel-consumption-section" v-if="showFuelConsumption">
+      <h2 class="text-lg font-semibold mb-4">油耗信息</h2>
+
+      <div class="fuel-consumption-table">
+        <el-table
+          :data="fuelConsumptionData"
+          border
+          style="width: 100%"
+          class="fuel-consumption-el-table"
+          table-layout="fixed"
+          :key="fuelTableKey"
+          row-key="deviceId"
+        >
+          <!-- 车辆编码 -->
+          <el-table-column
+            prop="deviceCode"
+            label="车辆编码"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="120"
+          />
+
+          <!-- 车辆名称 -->
+          <el-table-column
+            prop="deviceName"
+            label="车辆名称"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="150"
+          />
+
+          <!-- 发生日期 -->
+          <el-table-column
+            label="发生日期"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="120"
+          >
+            <template #default="scope">
+              {{ formatDate(scope.row.queryDate) }}
+            </template>
+          </el-table-column>
+
+          <!-- 中航北斗油耗 -->
+          <el-table-column label="中航北斗油耗(L)" align="center" width="140">
+            <template #default="scope">
+              {{ formatNumber(scope.row.zhbdFuel, 2) }}
+            </template>
+          </el-table-column>
+
+          <!-- 实际油耗 -->
+          <el-table-column label="实际油耗(L)" align="center" width="140">
+            <template #default="scope">
+              <!-- 编辑模式下显示输入框 -->
+              <el-input
+                v-if="!isReadonlyMode"
+                v-model="scope.row.customFuel"
+                type="text"
+                :min="0"
+                placeholder="请输入实际油耗"
+                @blur="handleCustomFuelChange(scope.row)"
+                style="width: 100%"
+                size="small"
+                @focus="handleFuelInputFocus(scope.row)"
+                :key="scope.row.deviceId"
+              />
+              <!-- 只读模式下显示数值 -->
+              <span v-else>
+                {{ formatNumber(scope.row.customFuel, 2) }}
+              </span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </ContentWrap>
+
+    <!-- 平台井工作量区域 - 只在平台井的详情或审批模式下显示 -->
+    <ContentWrap
+      class="platform-workload-section"
+      v-if="(isDetailMode || isApprovalMode) && dailyReportData?.platformWell === 1"
+    >
+      <h2 class="text-lg font-semibold mb-4">平台井工作量</h2>
+
+      <div
+        class="platform-workload-table"
+        v-if="platformWorkloadData && platformWorkloadData.length > 0"
+      >
+        <el-table
+          :data="platformWorkloadData"
+          border
+          style="width: 100%"
+          class="platform-workload-el-table"
+          table-layout="fixed"
+        >
+          <!-- 固定列 -->
+          <el-table-column
+            prop="wellName"
+            label="井号"
+            align="center"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="施工状态" align="center" :show-overflow-tooltip="true">
+            <template #default="scope">
+              {{ scope.row.rdStatusLabel || '' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="施工工艺" align="center" :show-overflow-tooltip="true">
+            <template #default="scope">
+              {{ scope.row.techniqueNames || '' }}
+            </template>
+          </el-table-column>
+
+          <!-- 动态工作量列 -->
+          <el-table-column
+            v-for="workloadColumn in getWorkloadColumns()"
+            :key="workloadColumn.key"
+            :label="workloadColumn.label"
+            align="center"
+            :show-overflow-tooltip="true"
+          >
+            <template #default="scope">
+              {{ getWorkloadValue(scope.row, workloadColumn.identifier) }}
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <div v-else class="text-center text-gray-500 py-4"> 暂无平台井工作量数据 </div>
+    </ContentWrap>
+
+    <!-- 第四部分:审批意见 - 只在审批模式下显示 -->
+    <ContentWrap class="section-padding" v-if="isApprovalMode || isEditMode || isDetailMode">
+      <el-form
+        ref="approvalFormRef"
+        :model="approvalForm"
+        style="margin-top: 1em"
+        label-width="200px"
+      >
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="审批意见" prop="opinion">
+              <el-input
+                v-model="approvalForm.opinion"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入审批意见"
+                maxlength="500"
+                show-word-limit
+                :readonly="!isApprovalMode"
+                :disabled="!isApprovalMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 操作按钮 -->
+    <ContentWrap class="section-padding" v-if="isEditMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="submitForm" type="primary" :disabled="formLoading">
+            {{ t('common.save') }}
+          </el-button>
+          <el-button @click="close">{{ t('common.cancel') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 审批模式下的操作按钮 -->
+    <ContentWrap class="section-padding" v-if="isApprovalMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="handleApprove('pass')" type="success"> 审批通过 </el-button>
+          <el-button @click="handleApprove('reject')" type="danger"> 审批驳回 </el-button>
+          <el-button @click="close">{{ t('common.close') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 详情模式下的操作按钮 - 只有关闭按钮 -->
+    <ContentWrap class="section-padding" v-if="isDetailMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="close">{{ t('common.close') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+  </ContentWrap>
+
+  <!-- 设备选择对话框 -->
+  <el-dialog
+    v-model="deviceDialogVisible"
+    title="选择施工设备"
+    width="1000px"
+    :before-close="handleDeviceDialogClose"
+    class="device-select-dialog"
+  >
+    <div class="transfer-container">
+      <el-transfer
+        v-model="selectedDeviceIds"
+        :data="filteredDeviceList"
+        :titles="['可选设备', '已选设备']"
+        :props="{ key: 'id', label: 'deviceCode' }"
+        filterable
+        class="transfer-component"
+        @change="handleTransferChange"
+      >
+        <template #default="{ option }">
+          <el-tooltip
+            effect="dark"
+            placement="top"
+            :content="`${option.deviceCode || ''} - ${option.deviceName || ''}`"
+            :disabled="!option.deviceCode && !option.deviceName"
+            transition="fade-in-linear"
+          >
+            <span class="transfer-option-text">
+              {{ option.deviceCode }} - {{ option.deviceName }}
+            </span>
+          </el-tooltip>
+        </template>
+      </el-transfer>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleDeviceDialogClose">取消</el-button>
+        <el-button type="primary" @click="confirmDeviceSelection">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useMessage } from '@/hooks/web/useMessage'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import { useRouter } from 'vue-router'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
+import * as DeptApi from '@/api/system/dept'
+import { useUserStore } from '@/store/modules/user'
+import dayjs from 'dayjs'
+import FileUpload from '@/components/UploadFile/src/FileUpload.vue'
+
+const NON_PROD_FIELDS = [
+  { key: 'repairTime', label: '设备故障' },
+  { key: 'selfStopTime', label: '设备保养' },
+  { key: 'accidentTime', label: '工程质量' },
+  { key: 'complexityTime', label: '技术受限' },
+  { key: 'rectificationTime', label: '生产组织' },
+  { key: 'waitingStopTime', label: '不可抗力' },
+  { key: 'partyaDesign', label: '甲方设计' },
+  { key: 'partyaPrepare', label: '甲方准备' },
+  { key: 'partyaResource', label: '甲方资源' },
+  { key: 'relocationTime', label: '生产配合' },
+  { key: 'winterBreakTime', label: '待命' },
+  { key: 'otherNptTime', label: '其他非生产时间' }
+] as const
+
+const { t } = useI18n()
+const message = useMessage()
+const { delView } = useTagsViewStore()
+const { push, currentRoute } = useRouter()
+const { params, query } = useRoute()
+const userStore = useUserStore()
+
+/** 填报日报 表单 */
+defineOptions({ name: 'FillDailyReportForm' })
+
+const formLoading = ref(false)
+const formRef = ref()
+const id = params.id // 瑞都日报id
+
+const fuelTableKey = ref(0) // 用于强制重新渲染表格
+
+// 添加一个新的响应式变量用于输入
+const dailyFuelInput = ref('')
+
+// 日报数据
+const dailyReportData = ref<any>({})
+
+// 修改 reportFuels 的 watch,添加标志位避免自动覆盖
+const dailyFuelManuallyModified = ref(false)
+
+// 添加模式判断计算属性
+const isApprovalMode = computed(() => params.mode === 'approval')
+const isDetailMode = computed(() => params.mode === 'detail')
+const isEditMode = computed(() => params.mode === 'fill' || !params.mode) // 默认为编辑模式
+
+// 只读模式判断:审批模式或详情模式都为只读
+const isReadonlyMode = computed(
+  () => isApprovalMode.value || isDetailMode.value || formData.value.auditStatus === 20
+)
+
+// 在表单数据定义附近添加
+const platformWellPairs = ref<any[]>([]) // 存储各平台井的工作量数据
+const currentPlatformId = ref<number>() // 当前选中的平台井ID
+
+// 计算属性:显示井名(统一处理主井和平台井逻辑)
+const displayWellName = computed(() => {
+  // 如果是平台井模式
+  if (dailyReportData.value.platformWell === 1) {
+    // 确定平台井数据源:优先使用 platforms,不存在则使用 finishedPlatforms
+    const platformSource =
+      dailyReportData.value.platforms || dailyReportData.value.finishedPlatforms
+
+    // 如果有平台井数据
+    if (platformSource && platformSource.length > 0) {
+      // 检查主井是否在平台井列表中
+      const isMainWellInPlatforms = platformSource.some(
+        (platform: any) => platform.id === dailyReportData.value.taskId
+      )
+
+      // 如果主井不在平台井列表中(说明主井已施工完成),使用第一个平台井的名称
+      if (!isMainWellInPlatforms) {
+        const firstPlatformWellName = platformSource[0].wellName
+        console.log(`主井已施工完成,井号显示使用平台井名称: ${firstPlatformWellName}`)
+        return firstPlatformWellName
+      }
+    }
+  }
+
+  // 其他情况使用原来的井名
+  return dailyReportData.value.wellName
+})
+
+// 页面标题计算
+const pageTitle = computed(() => {
+  const displayWellNameValue = displayWellName.value
+  const constructionDate = dailyReportData.value.constructionStartDate
+
+  if (isApprovalMode.value) {
+    return displayWellNameValue && constructionDate
+      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 日报审批`
+      : '日报审批'
+  } else if (isDetailMode.value) {
+    return displayWellNameValue && constructionDate
+      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 日报详情`
+      : '日报详情'
+  } else {
+    return displayWellNameValue && constructionDate
+      ? `${displayWellNameValue} - ${formatDate(constructionDate)} 生产日报`
+      : '日报填报'
+  }
+})
+
+// 处理输入框获取焦点事件
+const handleFuelInputFocus = (fuelItem: any) => {
+  // 如果 customFuel 是空的,确保它显示默认值
+  if (!fuelItem.customFuel || fuelItem.customFuel === '') {
+    const zhbdValue = parseFloat(fuelItem.zhbdFuel)
+    fuelItem.customFuel = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
+  }
+}
+
+// 模式提示信息
+const modeNotice = computed(() => {
+  if (isApprovalMode.value) {
+    return '审批模式:所有字段均为只读'
+  } else if (isDetailMode.value) {
+    return '详情模式:所有字段均为只读'
+  }
+  return ''
+})
+
+// 动态属性相关变量
+const dynamicAttrs = ref<any[]>([]) // 存储动态属性列表
+
+// 添加设备选择相关变量
+const deviceDialogVisible = ref(false)
+const filteredDeviceList = ref<any[]>([])
+const selectedDeviceIds = ref<number[]>([])
+const deviceMap = ref<Record<number, any>>({})
+
+// 添加平台井相关响应式数据
+const platformOptions = ref<any[]>([]) // 平台井下拉选项
+const showPlatformField = ref(false) // 是否显示平台井字段
+
+// 计算属性:是否显示平台井字段
+const shouldShowPlatformField = computed(() => {
+  return dailyReportData.value.platformWell === 1
+})
+
+// 初始化平台井数据
+const initPlatformData = (reportData: any) => {
+  // 设置是否显示平台井字段
+  showPlatformField.value = reportData.platformWell === 1
+
+  // 设置平台井下拉选项 - 修改后的逻辑
+  let platformSource = reportData.platforms
+
+  // 在详情或审批模式下,如果 platforms 不存在,则使用 finishedPlatforms
+  if (
+    (isDetailMode.value || isApprovalMode.value) &&
+    (!platformSource || platformSource.length === 0)
+  ) {
+    platformSource = reportData.finishedPlatforms
+  }
+
+  // 设置平台井下拉选项
+  if (platformSource && Array.isArray(platformSource)) {
+    platformOptions.value = platformSource
+
+    // 初始化 platformWellPairs,确保包含所有平台井的完整数据
+    if (reportData.platformWell === 1) {
+      // 初始化 platformWellPairs,包含所有平台井的完整数据
+      platformWellPairs.value = platformSource.map((platform: any) => {
+        // 查找是否已有该平台井的数据
+        const existingData = reportData.platformWellPairs?.find(
+          (p: any) => p.taskId === platform.id
+        )
+
+        // 确保 techniqueIds 是字符串数组格式
+        let techniqueIds = []
+        if (existingData && existingData.techniqueIds) {
+          techniqueIds = existingData.techniqueIds.map((id: any) => id.toString())
+        } else if (platform.techniqueIds) {
+          techniqueIds = platform.techniqueIds.map((id: any) => id.toString())
+        }
+
+        return (
+          existingData || {
+            taskId: platform.id,
+            dailyFuel: platform.dailyFuel || '',
+            reportId: platform.reportId, // 使用接口返回的 reportId
+            wellName: platform.wellName,
+            rdStatus: platform.rdStatus || '', // 初始为空
+            techniqueIds: techniqueIds, // 初始为空数组
+            extProperty: platform.extProperty || [], // 初始为空数组
+            repairTime: platform.repairTime ?? 0,
+            selfStopTime: platform.selfStopTime ?? 0,
+            accidentTime: platform.accidentTime ?? 0,
+            complexityTime: platform.complexityTime ?? 0,
+            rectificationTime: platform.rectificationTime ?? 0,
+            waitingStopTime: platform.waitingStopTime ?? 0,
+            partyaDesign: platform.partyaDesign ?? 0,
+            partyaPrepare: platform.partyaPrepare ?? 0,
+            partyaResource: platform.partyaResource ?? 0,
+            relocationTime: platform.relocationTime ?? 0,
+            winterBreakTime: platform.winterBreakTime ?? 0,
+            otherNptTime: platform.otherNptTime ?? 0,
+            otherNptReason: platform.otherNptReason || ''
+          }
+        )
+      })
+    }
+  } else {
+    platformOptions.value = []
+    platformWellPairs.value = []
+  }
+
+  // 设置默认选中的平台井 - 修改后的逻辑
+  if (platformOptions.value.length > 0) {
+    let selectedPlatform = null
+
+    // 首先尝试查找与 taskId 匹配的平台井
+    if (reportData.taskId) {
+      selectedPlatform = platformOptions.value.find(
+        (platform: any) => platform.id === reportData.taskId
+      )
+    }
+
+    // 如果没有找到匹配的平台井,选择第一个平台井
+    if (!selectedPlatform) {
+      selectedPlatform = platformOptions.value[0]
+    }
+
+    // 设置选中的平台井
+    if (selectedPlatform) {
+      formData.value.platformId = selectedPlatform.id
+      currentPlatformId.value = selectedPlatform.id
+      // 加载平台井的数据到表单
+      loadPlatformData(selectedPlatform.id)
+
+      // 可选:在控制台输出提示信息
+      if (reportData.taskId && selectedPlatform.id !== reportData.taskId) {
+        console.log(`主井已施工完成,已自动选择第一个平台井: ${selectedPlatform.wellName}`)
+      }
+    }
+  }
+}
+
+// 添加审批表单相关变量
+const approvalFormRef = ref()
+const approvalForm = reactive({
+  opinion: '' // 审批意见
+})
+
+// 审批表单验证规则(可选,根据需求添加)
+const approvalFormRules = reactive({
+  opinion: [
+    { required: false, message: '请输入审批意见', trigger: 'blur' },
+    { min: 0, max: 500, message: '审批意见长度不能超过500个字符', trigger: 'blur' }
+  ]
+})
+
+// 将时分秒数组转换为Date对象(基于constructionStartDate的日期)
+const parseTimeArrayToDate = (timeArray: number[], baseDate: number) => {
+  if (!Array.isArray(timeArray) || !baseDate) {
+    return null
+  }
+  const hour = timeArray[0] || 0
+  const minute = timeArray[1] || 0
+  const second = timeArray[2] || 0
+  // 基于日报日期(constructionStartDate)设置时分秒
+  return dayjs(baseDate).hour(hour).minute(minute).second(second).toDate()
+}
+
+// 添加文件上传组件的引用
+const fileUploadRef = ref()
+
+// 表单数据
+const formData = ref<any>({
+  id: undefined,
+  deptId: undefined,
+  taskId: undefined,
+  platformWell: undefined,
+  companyId: undefined,
+  deptName: undefined,
+  constructionStartDate: undefined,
+  contractName: undefined,
+  projectDepartment: '',
+  costCenterId: undefined,
+  costCenter: '',
+  platformId: undefined, // 平台井ID
+  // 日报填报字段
+  timeRange: [
+    // 设置默认时间范围 8:00 - 8:00
+    dayjs().hour(8).minute(0).second(0).toDate(),
+    dayjs().hour(8).minute(0).second(0).toDate()
+  ],
+  startTime: undefined, // 开始时间
+  endTime: undefined, // 结束时间
+  rdStatus: '', // 施工状态
+  deviceIds: [] as number[], // 设备ID数组
+  techniqueIds: [], // 施工工艺
+  dailyFuel: '', // 当日油耗
+  productionStatus: '', // 当日生产动态
+  nextPlan: '', // 下步工作计划
+  externalRental: '', // 外租设备
+  malfunction: '', // 故障情况
+  faultDowntime: '', // 故障误工
+  // 添加动态字段对象
+  dynamicFields: {} as Record<string, any>,
+  // 附件列表
+  attachments: [] as any[],
+  reportFuels: [] as any[], // 油耗信息数组
+  repairTime: 0,
+  selfStopTime: 0,
+  accidentTime: 0,
+  complexityTime: 0,
+  rectificationTime: 0,
+  waitingStopTime: 0,
+  partyaDesign: 0,
+  partyaPrepare: 0,
+  partyaResource: 0,
+  relocationTime: 0,
+  winterBreakTime: 0,
+  otherNptTime: 0,
+  otherNptReason: ''
+})
+
+// 添加上传成功处理函数
+const handleUploadSuccess = (result: any) => {
+  console.log('上传成功', result)
+
+  try {
+    // 检查响应是否成功
+    if (!result.response) {
+      message.error('上传响应数据异常')
+      return
+    }
+
+    if (result.response.code !== 0) {
+      message.error(result.response.msg || '文件上传失败')
+      return
+    }
+
+    const responseData = result.response.data
+
+    if (!responseData) {
+      message.error('上传数据为空')
+      return
+    }
+
+    // 处理返回的文件列表
+    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
+      responseData.files.forEach((file: any) => {
+        if (!file.filePath) {
+          console.warn('文件缺少 filePath:', file)
+          return
+        }
+
+        // 根据后端返回的数据结构构建附件对象
+        const attachment = {
+          id: undefined,
+          category: 'daily_report',
+          bizId: formData.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath, //使用正确的 filePath
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
+        }
+
+        // 添加到附件列表
+        if (!formData.value.attachments) {
+          formData.value.attachments = []
+        }
+        formData.value.attachments.push(attachment)
+      })
+
+      message.success(`成功上传 ${responseData.files.length} 个文件`)
+    } else {
+      console.warn('上传成功但没有返回文件信息')
+      message.warning('上传成功但未获取到文件信息')
+    }
+  } catch (error) {
+    console.error('处理上传结果时发生错误:', error)
+    message.error('处理上传结果失败')
+  }
+}
+
+// 删除附件
+const removeAttachment = (index: number) => {
+  if (formData.value.attachments && formData.value.attachments.length > index) {
+    formData.value.attachments.splice(index, 1)
+  }
+}
+
+// 计算属性:未施工设备名称
+const unSelectedDeviceNames = computed(() => {
+  const selectedDevices = dailyReportData.value.selectedDevices || []
+  const selectedDeviceIds = formData.value.deviceIds || []
+
+  if (selectedDevices.length === 0) {
+    return '无可用设备'
+  }
+
+  // 筛选出未选择的设备
+  const unselectedDevices = selectedDevices.filter(
+    (device: any) => !selectedDeviceIds.includes(device.id)
+  )
+
+  if (unselectedDevices.length === 0) {
+    return '所有设备都已施工'
+  }
+
+  // 提取设备名称并用逗号分隔
+  const deviceNames = unselectedDevices
+    .map((device: any) => device.deviceName || device.deviceCode || '未知设备')
+    .filter((name: string) => name !== '未知设备')
+
+  return deviceNames.join(', ') || '无未选择设备'
+})
+
+// 附件名称点击事件
+const inContent = async (attachment) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
+    return
+  }
+
+  try {
+    // 直接使用 attachment.filePath
+    const filePath = attachment.filePath
+    // 确保 filePath 是编码后的格式
+    const encodedPath = encodeURIComponent(Base64.encode(filePath))
+
+    // 打开预览窗口
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+  } catch (error) {
+    console.error('预览附件失败:', error)
+    message.error('预览附件失败')
+  }
+}
+
+// 获取文件类型辅助函数
+const getFileType = (filename: string) => {
+  const ext = filename.split('.').pop()?.toLowerCase()
+  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
+    return 'image'
+  } else if (['pdf'].includes(ext || '')) {
+    return 'pdf'
+  } else if (['doc', 'docx'].includes(ext || '')) {
+    return 'word'
+  } else if (['xls', 'xlsx'].includes(ext || '')) {
+    return 'excel'
+  } else {
+    return 'other'
+  }
+}
+
+// 格式化文件大小辅助函数
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+// 计算属性:格式化设备显示
+const formatDevicesForDisplay = computed(() => {
+  const deviceIds = formData.value.deviceIds
+  if (!deviceIds || deviceIds.length === 0) {
+    return '无设备'
+  }
+
+  const deviceNames = deviceIds
+    .map((id) => deviceMap.value[id]?.deviceName)
+    .filter((name) => name !== undefined && name !== '')
+
+  if (deviceNames.length === 0) return '无设备'
+
+  // 如果设备数量超过2个,显示前两个加省略号
+  /* if (deviceNames.length > 2) {
+    return `${deviceNames[0]}, ${deviceNames[1]}...`
+  } */
+
+  return deviceNames.join(', ')
+})
+
+// 计算属性:获取所有设备名称(用于tooltip)
+const getAllDeviceNamesForDisplay = computed(() => {
+  const deviceIds = formData.value.deviceIds
+  if (!deviceIds || deviceIds.length === 0) {
+    return '无设备'
+  }
+
+  const deviceNames = deviceIds
+    .map((id) => deviceMap.value[id]?.deviceCode || '未知设备')
+    .filter((name) => name !== '未知设备')
+
+  return deviceNames.join(', ') || '无有效设备'
+})
+
+// 打开设备选择对话框
+const openDeviceDialog = async () => {
+  if (!dailyReportData.value.deptId) {
+    message.error('请先加载项目信息')
+    return
+  }
+
+  try {
+    formLoading.value = true
+    selectedDeviceIds.value = [...(formData.value.deviceIds || [])]
+
+    // 直接从日报数据的 selectedDevices 中获取设备列表
+    const selectedDevices = dailyReportData.value.selectedDevices || []
+
+    // 更新设备映射表
+    const newDeviceMap = { ...deviceMap.value }
+    selectedDevices.forEach((device: any) => {
+      if (device.id) {
+        newDeviceMap[device.id] = device
+      }
+    })
+    deviceMap.value = newDeviceMap
+
+    filteredDeviceList.value = selectedDevices
+    deviceDialogVisible.value = true
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    message.error('获取设备列表失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+// 修改格式化函数,改为失去焦点时触发
+const formatDailyFuel = () => {
+  if (!dailyFuelInput.value || dailyFuelInput.value.trim() === '') {
+    formData.value.dailyFuel = ''
+    dailyFuelInput.value = ''
+    return
+  }
+
+  // 移除非数字字符(除了小数点)
+  const cleaned = dailyFuelInput.value.replace(/[^\d.]/g, '')
+
+  // 确保只有一个小数点
+  const parts = cleaned.split('.')
+  if (parts.length > 2) {
+    dailyFuelInput.value = parts[0] + '.' + parts.slice(1).join('')
+  }
+
+  const numValue = parseFloat(dailyFuelInput.value)
+  if (!isNaN(numValue)) {
+    // 限制到两位小数
+    formData.value.dailyFuel = formatNumber(numValue, 2)
+    dailyFuelInput.value = formData.value.dailyFuel
+  } else {
+    formData.value.dailyFuel = ''
+    dailyFuelInput.value = ''
+  }
+}
+
+// 处理穿梭框变化
+const handleTransferChange = (value: number[], direction: string, movedKeys: number[]) => {
+  // 可以添加额外的处理逻辑
+}
+
+// 确认设备选择
+const confirmDeviceSelection = () => {
+  formData.value.deviceIds = [...selectedDeviceIds.value]
+  deviceDialogVisible.value = false
+  message.success(`已选择 ${selectedDeviceIds.value.length} 台设备`)
+}
+
+// 关闭设备选择对话框
+const handleDeviceDialogClose = () => {
+  deviceDialogVisible.value = false
+}
+
+// 初始化设备数据
+const initDeviceData = (reportData: any) => {
+  // 初始化设备ID
+  if (reportData.deviceIds && Array.isArray(reportData.deviceIds)) {
+    formData.value.deviceIds = [...reportData.deviceIds]
+  } else {
+    formData.value.deviceIds = []
+  }
+
+  // 初始化设备映射表(用于显示设备名称)
+  if (reportData.selectedDevices && Array.isArray(reportData.selectedDevices)) {
+    const newDeviceMap = { ...deviceMap.value }
+    reportData.selectedDevices.forEach((device: any) => {
+      if (device.id) {
+        newDeviceMap[device.id] = device
+      }
+    })
+    deviceMap.value = newDeviceMap
+  }
+}
+
+// const validateOtherReason = (_rule: any, value: any, callback: any) => {
+//   const time = formData.value.otherNptTime || 0
+//   if (time > 0 && !value) {
+//     callback(new Error('填写了其他时间,必须说明原因'))
+//   } else {
+//     callback()
+//   }
+// }
+
+const formRules = computed(() => {
+  // 判断是否为虚拟项目
+  const isVirtualProject = dailyReportData.value.virtualProject === 'Y'
+
+  // 基础校验规则(时间节点、当日生产动态始终必填)
+  const rules = {
+    timeRange: [{ required: true, message: '时间节点不能为空', trigger: 'change' }],
+    productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }],
+    nextPlan: [{ required: true, message: '下步工作计划不能为空', trigger: 'blur' }],
+    // otherNptReason: [{ validator: validateOtherReason, trigger: ['blur', 'change'] }],
+    dailyFuel: [
+      {
+        required: true,
+        message: '当日油耗不能为空',
+        trigger: 'blur'
+      },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          if (value === '' || value === null || value === undefined) {
+            callback()
+            return
+          }
+
+          const numValue = Number(value)
+          if (isNaN(numValue)) {
+            callback(new Error('当日油耗必须是数字'))
+          } else if (numValue < 0) {
+            callback(new Error('当日油耗不能小于0'))
+          } else {
+            callback()
+          }
+        },
+        trigger: 'blur'
+      }
+    ]
+  }
+
+  // 非虚拟项目时,添加施工状态、施工工艺的必填校验
+  if (!isVirtualProject) {
+    rules.rdStatus = [{ required: true, message: '施工状态不能为空', trigger: 'change' }]
+    rules.techniqueIds = [{ required: true, message: '施工工艺不能为空', trigger: 'change' }]
+  }
+
+  return rules
+})
+
+const queryParams = reactive({
+  deptId: undefined,
+  techniqueIds: []
+})
+
+// 下拉选项
+const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS) // 施工状态
+const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY) // 瑞都施工工艺
+
+// 计算属性:日报标题
+const dailyReportTitle = computed(() => {
+  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
+    return '日报填报'
+  }
+  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
+  return `${dailyReportData.value.wellName} - ${dateStr} 生产日报`
+})
+
+// 日报审批:日报标题
+const dailyReportApprovalTitle = computed(() => {
+  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
+    return '日报审批'
+  }
+  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
+  return `${dailyReportData.value.wellName} - ${dateStr} 日报审批`
+})
+
+// 计算属性:施工周期
+const constructionPeriod = computed(() => {
+  const start = dailyReportData.value.constructionStartDate
+  const end = dailyReportData.value.constructionEndDate
+  if (!start || !end) return 0
+
+  const startDate = dayjs(start)
+  const endDate = dayjs(end)
+  return endDate.diff(startDate, 'day')
+})
+
+// 日期格式化函数
+const formatDate = (timestamp: number) => {
+  if (!timestamp) return ''
+  return dayjs(timestamp).format('YYYY-MM-DD')
+}
+
+const close = () => {
+  delView(unref(currentRoute))
+  push({ name: 'FillDailyReport', params: {} })
+}
+
+/** 提交表单 */
+const emit = defineEmits(['success'])
+const submitForm = async () => {
+  // 验证表单
+  try {
+    await formRef.value.validate()
+  } catch (error) {
+    return
+  }
+
+  // 保存当前平台井的数据
+  if (currentPlatformId.value) {
+    saveCurrentPlatformData(currentPlatformId.value)
+  }
+
+  let validationPassed = true
+  const validationErrors: string[] = []
+
+  // 检查所有平台井数据
+  for (const pair of platformWellPairs.value) {
+    // 计算所有时间字段的总和
+    const totalTime =
+      (pair.repairTime || 0) +
+      (pair.selfStopTime || 0) +
+      (pair.accidentTime || 0) +
+      (pair.complexityTime || 0) +
+      (pair.rectificationTime || 0) +
+      (pair.waitingStopTime || 0) +
+      (pair.partyaDesign || 0) +
+      (pair.partyaPrepare || 0) +
+      (pair.partyaResource || 0) +
+      (pair.relocationTime || 0) +
+      (pair.winterBreakTime || 0) +
+      (pair.otherNptTime || 0)
+
+    // 检查总和是否超过24小时
+    if (totalTime > 24) {
+      validationPassed = false
+      validationErrors.push(`${pair.wellName || '平台井'}的时间总和不能超过24小时`)
+    }
+
+    // 检查otherNptTime>0时是否填写了otherNptReason
+    if ((pair.otherNptTime || 0) > 0 && !pair.otherNptReason) {
+      validationPassed = false
+      validationErrors.push(`${pair.wellName || '平台井'}的其他时间大于0时必须填写原因`)
+    }
+  }
+
+  // 检查非平台井模式
+  if (dailyReportData.value.platformWell !== 1) {
+    // 计算所有时间字段的总和
+    const totalTime =
+      (formData.value.repairTime || 0) +
+      (formData.value.selfStopTime || 0) +
+      (formData.value.accidentTime || 0) +
+      (formData.value.complexityTime || 0) +
+      (formData.value.rectificationTime || 0) +
+      (formData.value.waitingStopTime || 0) +
+      (formData.value.partyaDesign || 0) +
+      (formData.value.partyaPrepare || 0) +
+      (formData.value.partyaResource || 0) +
+      (formData.value.relocationTime || 0) +
+      (formData.value.winterBreakTime || 0) +
+      (formData.value.otherNptTime || 0)
+
+    // 检查总和是否超过24小时
+    if (totalTime > 24) {
+      validationPassed = false
+      validationErrors.push('时间总和不能超过24小时')
+    }
+  }
+
+  // 如果验证失败,显示错误信息
+  if (!validationPassed) {
+    validationErrors.forEach((error) => {
+      message.error(error)
+    })
+    return
+  }
+
+  // 打印 platformWellPairs
+  console.log('platformWellPairs:', JSON.stringify(platformWellPairs.value, null, 2))
+
+  // 处理时间范围数据
+  if (formData.value.timeRange && formData.value.timeRange.length === 2) {
+    // 将时间范围转换为 LocalTime 格式的字符串
+    const startDate = dayjs(formData.value.timeRange[0])
+    const endDate = dayjs(formData.value.timeRange[1])
+
+    // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
+    formData.value.startTime = startDate.format('HH:mm:ss')
+    formData.value.endTime = endDate.format('HH:mm:ss')
+  }
+
+  // 构建动态属性 extProperty 数组
+  const extProperties = dynamicAttrs.value.map((attr) => {
+    return {
+      name: attr.name,
+      sort: attr.sort,
+      unit: attr.unit,
+      actualValue: formData.value.dynamicFields[attr.identifier] || '', // 从 dynamicFields 中获取用户填写的值
+      dataType: attr.dataType,
+      maxValue: attr.maxValue,
+      minValue: attr.minValue,
+      required: attr.required,
+      accessMode: attr.accessMode,
+      identifier: attr.identifier,
+      defaultValue: attr.defaultValue
+    }
+  })
+
+  // 准备提交数据,包含动态字段
+  const baseSubmitData = {
+    ...formData.value,
+    // 将动态字段组装成 extProperty 数组
+    extProperty: extProperties,
+    deviceIds: formData.value.deviceIds, // 设备ID集合
+    // 在填报模式下也提交审批意见字段
+    opinion: isEditMode.value ? approvalForm.opinion : undefined,
+    // 将油耗数据格式化为后端需要的格式
+    /* reportFuels: formData.value.reportFuels.map(fuel => ({
+      ...fuel,
+      // 确保 customFuel 是数字格式
+      customFuel: fuel.customFuel ? parseFloat(fuel.customFuel) : null
+    })), */
+    // 确保当日油耗是数字格式
+    dailyFuel: formData.value.dailyFuel ? parseFloat(formData.value.dailyFuel) : 0,
+    nonProduct: query.istime === 'true' ? 'Y' : ''
+  }
+
+  console.log('baseSubmitData:', baseSubmitData)
+
+  // 删除不需要复制的字段
+  const {
+    id: currentId,
+    platformId,
+    platformWell,
+    taskId,
+    rdStatus,
+    techniqueIds,
+    extProperty,
+    ...baseData
+  } = baseSubmitData
+
+  const submitDatas: any[] = []
+
+  if (dailyReportData.value.platformWell === 1 && platformWellPairs.value.length > 0) {
+    // 平台井模式:处理所有平台井数据
+    platformWellPairs.value.forEach((pair) => {
+      console.log('pair:', pair)
+      // 复制基础数据
+      const platformData: any = { ...baseData }
+
+      // 设置平台井特定字段
+      platformData.id = pair.reportId // 使用 platformWellPairs 中的 reportId
+      platformData.platformId = pair.taskId // 使用 platformWellPairs 中的 taskId
+      platformData.rdStatus = pair.rdStatus || ''
+      platformData.techniqueIds = pair.techniqueIds || []
+      platformData.extProperty = pair.extProperty || []
+
+      platformData.repairTime = pair.repairTime || 0
+      platformData.selfStopTime = pair.selfStopTime || 0
+      platformData.accidentTime = pair.accidentTime || 0
+      platformData.complexityTime = pair.complexityTime || 0
+      platformData.rectificationTime = pair.rectificationTime || 0
+      platformData.waitingStopTime = pair.waitingStopTime || 0
+      platformData.partyaDesign = pair.partyaDesign || 0
+      platformData.partyaPrepare = pair.partyaPrepare || 0
+      platformData.partyaResource = pair.partyaResource || 0
+      platformData.relocationTime = pair.relocationTime || 0
+      platformData.winterBreakTime = pair.winterBreakTime || 0
+      platformData.otherNptTime = pair.otherNptTime || 0
+      platformData.otherNptReason = pair.otherNptReason || ''
+
+      // 处理附件:复制并修改 bizId 为当前 pair 的 reportId
+      platformData.attachments = (baseData.attachments || []).map((attachment) => ({
+        ...attachment, // 深拷贝单个附件
+        bizId: pair.reportId // 替换 bizId 为当前平台井的 reportId
+      }))
+
+      // 车辆油耗:复制并修改 reportId 为当前 pair 的 reportId
+      platformData.reportFuels = (baseData.reportFuels || []).map((reportFuel) => ({
+        ...reportFuel, // 深拷贝单个油耗
+        reportId: pair.reportId // 替换 reportId 为当前平台井的 reportId
+      }))
+
+      // 重新构建 dynamicFields(如果需要)
+      const dynamicFields = {}
+      if (platformData.extProperty && platformData.extProperty.length > 0) {
+        platformData.extProperty.forEach((prop) => {
+          if (prop.identifier) {
+            dynamicFields[prop.identifier] = prop.actualValue || ''
+          }
+        })
+      }
+      platformData.dynamicFields = dynamicFields
+
+      submitDatas.push(platformData)
+    })
+
+    console.log('平台井模式提交数据:', JSON.stringify(submitDatas, null, 2))
+  } else {
+    // 非平台井模式:只提交当前数据
+    submitDatas.push(baseSubmitData)
+  }
+
+  // 提交请求
+  formLoading.value = true
+  try {
+    // 调用更新接口
+    await IotRdDailyReportApi.saveBatch(submitDatas)
+    message.success(t('common.updateSuccess'))
+    close()
+    // 发送操作成功的事件
+    emit('success')
+  } catch (error) {
+    console.error('提交失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+
+// 初始化动态属性
+const initDynamicAttrs = (reportData: any) => {
+  if (reportData.dailyReportAttrs && reportData.dailyReportAttrs.length > 0) {
+    dynamicAttrs.value = reportData.dailyReportAttrs
+
+    // 初始化动态字段的值
+    const initialDynamicFields: Record<string, any> = {}
+
+    // 优先从 extProperty 中获取实际值(编辑时)
+    if (reportData.extProperty && reportData.extProperty.length > 0) {
+      reportData.extProperty.forEach((extProp: any) => {
+        if (extProp.identifier) {
+          initialDynamicFields[extProp.identifier] = extProp.actualValue || ''
+        }
+      })
+    }
+
+    reportData.dailyReportAttrs.forEach((attr: any) => {
+      if (!initialDynamicFields.hasOwnProperty(attr.identifier)) {
+        // 优先使用实际值,如果没有则使用默认值
+        const value =
+          attr.extProperty &&
+          attr.extProperty.actualValue !== undefined &&
+          attr.extProperty.actualValue !== null &&
+          attr.extProperty.actualValue !== ''
+            ? attr.extProperty.actualValue
+            : attr.defaultValue || attr.extProperty?.defaultValue || ''
+
+        initialDynamicFields[attr.identifier] = value
+      }
+    })
+
+    formData.value.dynamicFields = initialDynamicFields
+  }
+}
+
+// 获取动态字段的验证规则
+const getDynamicAttrRules = (attr: any) => {
+  const rules = []
+  if (attr.required === 1) {
+    rules.push({
+      required: true,
+      message: `${attr.name}不能为空`,
+      trigger: 'blur'
+    })
+  }
+
+  // 数字类型验证
+  if (attr.dataType === 'double') {
+    rules.push({
+      validator: (rule: any, value: any, callback: any) => {
+        if (value === '' || value === null || value === undefined) {
+          callback()
+          return
+        }
+
+        const numValue = Number(value)
+        if (isNaN(numValue)) {
+          callback(new Error(`${attr.name}必须是数字`))
+        } else if (attr.minValue && numValue < Number(attr.minValue)) {
+          callback(new Error(`${attr.name}不能小于${attr.minValue}`))
+        } else if (attr.maxValue && numValue > Number(attr.maxValue)) {
+          callback(new Error(`${attr.name}不能大于${attr.maxValue}`))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    })
+  }
+
+  return rules
+}
+
+// 更新动态属性(处理交集、新增和删除)
+const updateDynamicAttrs = async (
+  newAttrs: any[],
+  newTechniqueIds: string[],
+  oldTechniqueIds?: string[]
+) => {
+  const oldAttrs = [...dynamicAttrs.value]
+  const oldDynamicFields = { ...formData.value.dynamicFields }
+
+  // 计算需要保留的字段(交集)
+  const commonAttrs = oldAttrs.filter((oldAttr) =>
+    newAttrs.some((newAttr) => newAttr.identifier === oldAttr.identifier)
+  )
+
+  // 计算需要新增的字段
+  const addedAttrs = newAttrs.filter(
+    (newAttr) => !oldAttrs.some((oldAttr) => oldAttr.identifier === newAttr.identifier)
+  )
+
+  // 计算需要删除的字段
+  const removedAttrs = oldAttrs.filter(
+    (oldAttr) => !newAttrs.some((newAttr) => newAttr.identifier === oldAttr.identifier)
+  )
+
+  // 构建新的动态属性数组
+  const updatedAttrs = [...commonAttrs, ...addedAttrs]
+
+  // 构建新的动态字段对象
+  const updatedDynamicFields = { ...oldDynamicFields }
+
+  // 移除已删除的字段
+  removedAttrs.forEach((attr) => {
+    delete updatedDynamicFields[attr.identifier]
+  })
+
+  // 初始化新增字段的值
+  addedAttrs.forEach((attr) => {
+    if (!updatedDynamicFields[attr.identifier]) {
+      // 如果有默认值使用默认值,否则为空
+      updatedDynamicFields[attr.identifier] =
+        attr.defaultValue || attr.extProperty?.defaultValue || ''
+    }
+  })
+
+  // 更新响应式数据
+  dynamicAttrs.value = updatedAttrs
+  formData.value.dynamicFields = updatedDynamicFields
+}
+
+// 加载动态属性
+const loadDynamicAttrs = async (newTechniqueIds: string[], oldTechniqueIds?: string[]) => {
+  try {
+    formLoading.value = true
+
+    const queryParams = {
+      techniqueIds: newTechniqueIds.join(',')
+    }
+
+    const response = await IotDailyReportAttrsApi.dailyReportAttrs(queryParams)
+    const newAttrs = response || []
+
+    // 处理动态属性更新
+    await updateDynamicAttrs(newAttrs, newTechniqueIds, oldTechniqueIds)
+  } catch (error) {
+    console.error('加载动态属性失败:', error)
+    message.error('加载动态属性失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+// 计算属性:获取平台井工作量数据(在详情/审批模式下优先使用 platforms,不存在则使用 finishedPlatforms)
+const platformWorkloadData = computed(() => {
+  if (!dailyReportData.value) return []
+
+  // 在详情或审批模式下
+  if (isDetailMode.value || isApprovalMode.value) {
+    // 优先使用 platforms,如果不存在则使用 finishedPlatforms
+    return dailyReportData.value.platforms || dailyReportData.value.finishedPlatforms || []
+  }
+
+  // 其他模式只使用 platforms
+  return dailyReportData.value.platforms || []
+})
+
+// 在 watch 监听平台井选择变化的部分附近,添加施工工艺转换函数
+// 添加施工工艺数值到标签的转换函数
+const convertTechniqueIdsToLabels = (techniqueIds: number[]): string[] => {
+  if (!techniqueIds || !Array.isArray(techniqueIds)) {
+    return []
+  }
+
+  return techniqueIds.map((id) => {
+    const dict = techniqueOptions.value.find((option) => option.value === id.toString())
+    return dict ? dict.label : id.toString()
+  })
+}
+
+// 监听施工工艺变化
+watch(
+  () => formData.value.techniqueIds,
+  async (newTechniqueIds, oldTechniqueIds) => {
+    if (newTechniqueIds && newTechniqueIds.length > 0) {
+      await loadDynamicAttrs(newTechniqueIds, oldTechniqueIds)
+
+      // 动态属性加载完成后,更新当前平台井的 extProperty
+      if (currentPlatformId.value) {
+        updateCurrentPlatformExtProperty()
+      }
+    } else {
+      dynamicAttrs.value = []
+      formData.value.dynamicFields = {}
+
+      // 清空当前平台井的 extProperty
+      if (currentPlatformId.value) {
+        updateCurrentPlatformExtProperty()
+      }
+    }
+  },
+  { deep: true }
+)
+
+// 监听 formData.dailyFuel 变化,同步到输入变量
+watch(
+  () => formData.value.dailyFuel,
+  (newVal) => {
+    if (newVal !== null && newVal !== undefined && newVal !== '') {
+      // 将数字转换为字符串显示,但不干扰输入
+      dailyFuelInput.value = String(newVal)
+    } else {
+      dailyFuelInput.value = ''
+    }
+    dailyFuelManuallyModified.value = true
+  },
+  { immediate: true }
+)
+
+// 监听reportFuels的变化,自动更新当日油耗
+watch(
+  () => formData.value.reportFuels,
+  (newFuels) => {
+    // 只有在编辑模式且用户没有手动修改过当日油耗时才自动计算
+    if (!isReadonlyMode.value) {
+      calculateAndUpdateDailyFuel()
+    }
+  },
+  { deep: true }
+)
+
+// 更新当前平台井的 extProperty
+const updateCurrentPlatformExtProperty = () => {
+  if (!currentPlatformId.value) return
+
+  const index = platformWellPairs.value.findIndex((item) => item.taskId === currentPlatformId.value)
+  if (index !== -1) {
+    platformWellPairs.value[index].extProperty = getCurrentExtProperties()
+  }
+}
+
+// 监听平台井选择变化
+watch(
+  () => formData.value.platformId,
+  (newPlatformId, oldPlatformId) => {
+    if (newPlatformId && newPlatformId !== oldPlatformId) {
+      // 保存当前平台井的数据到 platformWellPairs
+      if (oldPlatformId) {
+        saveCurrentPlatformData(oldPlatformId)
+      }
+
+      // 加载新平台井的数据到表单
+      loadPlatformData(newPlatformId)
+      currentPlatformId.value = newPlatformId
+    }
+  }
+)
+
+// 监听动态字段变化,实时更新到 platformWellPairs
+watch(
+  () => formData.value.dynamicFields,
+  (newFields) => {
+    if (currentPlatformId.value) {
+      updateCurrentPlatformExtProperty()
+    }
+  },
+  { deep: true }
+)
+
+// 监听施工状态变化
+watch(
+  () => formData.value.rdStatus,
+  (newStatus) => {
+    if (currentPlatformId.value) {
+      const index = platformWellPairs.value.findIndex(
+        (item) => item.taskId === currentPlatformId.value
+      )
+      if (index !== -1) {
+        platformWellPairs.value[index].rdStatus = newStatus
+      }
+    }
+  }
+)
+
+// 保存当前平台井数据到 platformWellPairs
+const saveCurrentPlatformData = (platformId: number) => {
+  const index = platformWellPairs.value.findIndex((item) => item.taskId === platformId)
+  if (index !== -1) {
+    platformWellPairs.value[index] = {
+      ...platformWellPairs.value[index],
+      rdStatus: formData.value.rdStatus,
+      techniqueIds: [...formData.value.techniqueIds],
+      extProperty: getCurrentExtProperties(),
+      repairTime: formData.value.repairTime ?? 0,
+      selfStopTime: formData.value.selfStopTime ?? 0,
+      accidentTime: formData.value.accidentTime ?? 0,
+      complexityTime: formData.value.complexityTime ?? 0,
+      rectificationTime: formData.value.rectificationTime ?? 0,
+      waitingStopTime: formData.value.waitingStopTime ?? 0,
+      partyaDesign: formData.value.partyaDesign ?? 0,
+      partyaPrepare: formData.value.partyaPrepare ?? 0,
+      partyaResource: formData.value.partyaResource ?? 0,
+      relocationTime: formData.value.relocationTime ?? 0,
+      winterBreakTime: formData.value.winterBreakTime ?? 0,
+      otherNptTime: formData.value.otherNptTime ?? 0,
+      otherNptReason: formData.value.otherNptReason || ''
+    }
+  } else {
+    // 如果找不到对应的平台井,添加新的记录
+    const platform = platformOptions.value.find((p) => p.id === platformId)
+    if (platform) {
+      platformWellPairs.value.push({
+        taskId: platformId,
+        reportId: undefined, // 新记录没有 reportId
+        wellName: platform.wellName,
+        rdStatus: formData.value.rdStatus,
+        techniqueIds: [...formData.value.techniqueIds],
+        extProperty: getCurrentExtProperties(),
+        repairTime: formData.value.repairTime ?? 0,
+        selfStopTime: formData.value.selfStopTime ?? 0,
+        accidentTime: formData.value.accidentTime ?? 0,
+        complexityTime: formData.value.complexityTime ?? 0,
+        rectificationTime: formData.value.rectificationTime ?? 0,
+        waitingStopTime: formData.value.waitingStopTime ?? 0,
+        partyaDesign: formData.value.partyaDesign ?? 0,
+        partyaPrepare: formData.value.partyaPrepare ?? 0,
+        partyaResource: formData.value.partyaResource ?? 0,
+        relocationTime: formData.value.relocationTime ?? 0,
+        winterBreakTime: formData.value.winterBreakTime ?? 0,
+        otherNptTime: formData.value.otherNptTime ?? 0,
+        otherNptReason: formData.value.otherNptReason || ''
+      })
+    }
+  }
+}
+
+// 从 platformWellPairs 加载平台井数据到表单
+const loadPlatformData = (platformId: number) => {
+  console.log('11 :>> ', 11)
+  const platformData = platformWellPairs.value.find((item) => item.taskId === platformId)
+  if (platformData) {
+    // 更新表单字段
+    formData.value.rdStatus = platformData.rdStatus || ''
+    // formData.value.dailyFuel = platformData.dailyFuel ? [...platformData.dailyFuel] : []
+    // 将施工工艺数值转换为对应的标签
+    if (platformData.techniqueIds && Array.isArray(platformData.techniqueIds)) {
+      // 如果是数字数组,转换为字符串数组(与数据字典格式匹配)
+      formData.value.techniqueIds = platformData.techniqueIds.map((id) => id.toString())
+    } else {
+      formData.value.techniqueIds = platformData.techniqueIds ? [...platformData.techniqueIds] : []
+    }
+
+    // 在详情或审批模式下,更新 dailyFuel 为当前平台井的值
+    if (isDetailMode.value || isApprovalMode.value) {
+      // 使用平台井的 dailyFuel 值
+      const platformDailyFuel = platformData.dailyFuel || ''
+      formData.value.dailyFuel = platformDailyFuel ? formatNumber(platformDailyFuel, 2) : ''
+      // 同步更新输入框
+      dailyFuelInput.value = formData.value.dailyFuel
+    }
+
+    // 更新动态属性
+    if (platformData.extProperty && platformData.extProperty.length > 0) {
+      const dynamicFields: Record<string, any> = {}
+      platformData.extProperty.forEach((prop: any) => {
+        if (prop.identifier) {
+          dynamicFields[prop.identifier] = prop.actualValue || ''
+        }
+      })
+      formData.value.dynamicFields = dynamicFields
+    } else {
+      formData.value.dynamicFields = {}
+    }
+
+    formData.value.repairTime = platformData.repairTime ?? 0
+    formData.value.selfStopTime = platformData.selfStopTime ?? 0
+    formData.value.accidentTime = platformData.accidentTime ?? 0
+    formData.value.complexityTime = platformData.complexityTime ?? 0
+    formData.value.rectificationTime = platformData.rectificationTime ?? 0
+    formData.value.waitingStopTime = platformData.waitingStopTime ?? 0
+    formData.value.partyaDesign = platformData.partyaDesign ?? 0
+    formData.value.partyaPrepare = platformData.partyaPrepare ?? 0
+    formData.value.partyaResource = platformData.partyaResource ?? 0
+    formData.value.relocationTime = platformData.relocationTime ?? 0
+    formData.value.winterBreakTime = platformData.winterBreakTime ?? 0
+    formData.value.otherNptTime = platformData.otherNptTime ?? 0
+    formData.value.otherNptReason = platformData.otherNptReason || ''
+  } else {
+    // 如果没有找到数据,初始化默认值
+    formData.value.rdStatus = ''
+    formData.value.techniqueIds = []
+    formData.value.dynamicFields = {}
+    // 初始化其他时间字段
+    formData.value.repairTime = 0
+    formData.value.selfStopTime = 0
+    formData.value.accidentTime = 0
+    formData.value.complexityTime = 0
+    formData.value.rectificationTime = 0
+    formData.value.waitingStopTime = 0
+    formData.value.partyaDesign = 0
+    formData.value.partyaPrepare = 0
+    formData.value.partyaResource = 0
+    formData.value.relocationTime = 0
+    formData.value.winterBreakTime = 0
+    formData.value.otherNptTime = 0
+    formData.value.otherNptReason = ''
+
+    // 在详情或审批模式下,清空 dailyFuel
+    if (isDetailMode.value || isApprovalMode.value) {
+      formData.value.dailyFuel = ''
+      dailyFuelInput.value = ''
+    }
+  }
+}
+
+// 获取当前动态属性数据
+const getCurrentExtProperties = () => {
+  return dynamicAttrs.value.map((attr) => {
+    return {
+      name: attr.name,
+      sort: attr.sort,
+      unit: attr.unit,
+      actualValue: formData.value.dynamicFields[attr.identifier] || '',
+      dataType: attr.dataType,
+      maxValue: attr.maxValue,
+      minValue: attr.minValue,
+      required: attr.required,
+      accessMode: attr.accessMode,
+      identifier: attr.identifier,
+      defaultValue: attr.defaultValue
+    }
+  })
+}
+
+// 是否显示实际进度
+const showActualProgress = computed(() => {
+  // 在所有模式下都显示,如果有数据就显示
+  return dailyReportData.value?.taskProgresses && dailyReportData.value.taskProgresses.length > 0
+})
+
+// 实际进度数据
+const actualProgressData = computed(() => {
+  if (
+    !dailyReportData.value?.taskProgresses ||
+    !Array.isArray(dailyReportData.value.taskProgresses)
+  ) {
+    return []
+  }
+
+  // 将 taskProgresses 转换为 el-steps 需要的格式
+  return dailyReportData.value.taskProgresses.map((progress: any, index: number) => {
+    // 格式化日期:如果只有日期部分,使用日期格式;如果有时间,使用日期时间格式
+    let formattedDate = ''
+    if (progress.createTime) {
+      // 判断日期格式,如果包含时间则显示完整时间,否则只显示日期
+      if (progress.createTime.includes(' ')) {
+        // 已经是完整的日期时间格式
+        formattedDate = progress.createTime
+      } else {
+        // 只有日期部分
+        formattedDate = progress.createTime
+      }
+    }
+
+    // 构建标题:日期 + 状态
+    const title =
+      formattedDate && progress.rdStatusLabel
+        ? `${formattedDate} ${progress.rdStatusLabel}`
+        : progress.rdStatusLabel || '未知状态'
+
+    return {
+      title: title,
+      description: '', // 可以根据需要添加描述信息
+      status: undefined, // el-steps 会自动计算状态
+      // 保留原始数据,便于调试
+      rawData: progress
+    }
+  })
+})
+
+// 初始化表单数据
+const initFormData = (reportData: any) => {
+  // 处理附件数据格式转换
+  const formattedAttachments = (reportData.attachments || []).map((attachment: any) => ({
+    id: attachment.id,
+    category: attachment.category?.toLowerCase() || 'daily_report',
+    bizId: attachment.bizId,
+    type: attachment.type?.toLowerCase() || 'attachment',
+    filename: attachment.filename,
+    fileType: attachment.fileType, // 使用辅助函数获取文件类型
+    filePath: attachment.filePath,
+    fileSize: attachment.fileSize,
+    remark: attachment.remark || ''
+  }))
+
+  // 确保 techniqueIds 是字符串数组格式
+  let techniqueIds = []
+  if (reportData.techniqueIds && Array.isArray(reportData.techniqueIds)) {
+    techniqueIds = reportData.techniqueIds.map((id: number) => id.toString())
+  }
+
+  formData.value = {
+    ...formData.value,
+    id: reportData.id,
+    deptId: reportData.deptId,
+    taskId: reportData.taskId,
+    platformWell: reportData.platformWell,
+    rdStatus: reportData.rdStatus || '',
+    techniqueIds: techniqueIds,
+    dailyFuel: reportData.dailyFuel, // 当日油耗默认值
+    productionStatus: reportData.productionStatus || '',
+    nextPlan: reportData.nextPlan || '',
+    externalRental: reportData.externalRental || '',
+    malfunction: reportData.malfunction || '',
+    faultDowntime: reportData.faultDowntime || '',
+    startTime: reportData.startTime || undefined,
+    endTime: reportData.endTime || undefined,
+    companyId: reportData.companyId || '',
+    dynamicFields: {}, // 确保有初始值
+    auditStatus: reportData.auditStatus,
+    // 初始化附件数据
+    attachments: formattedAttachments,
+    repairTime: reportData.repairTime ?? 0,
+    selfStopTime: reportData.selfStopTime ?? 0,
+    accidentTime: reportData.accidentTime ?? 0,
+    complexityTime: reportData.complexityTime ?? 0,
+    rectificationTime: reportData.rectificationTime ?? 0,
+    waitingStopTime: reportData.waitingStopTime ?? 0,
+    partyaDesign: reportData.partyaDesign ?? 0,
+    partyaPrepare: reportData.partyaPrepare ?? 0,
+    partyaResource: reportData.partyaResource ?? 0,
+    relocationTime: reportData.relocationTime ?? 0,
+    winterBreakTime: reportData.winterBreakTime ?? 0,
+    otherNptTime: reportData.otherNptTime ?? 0,
+    otherNptReason: reportData.otherNptReason || ''
+  }
+
+  // 初始化审批意见数据
+  approvalForm.opinion = reportData.auditOpinion || reportData.opinion || ''
+
+  queryParams.deptId = reportData.companyId
+  // 设置时间范围选择器
+  if (
+    reportData.startTime &&
+    Array.isArray(reportData.startTime) &&
+    reportData.endTime &&
+    Array.isArray(reportData.endTime)
+  ) {
+    // 基于日报的施工开始日期作为基准日期
+    const baseDate = reportData.constructionStartDate || Date.now()
+    const startTime = parseTimeArrayToDate(reportData.startTime, baseDate)
+    const endTime = parseTimeArrayToDate(reportData.endTime, baseDate)
+
+    if (startTime && endTime) {
+      formData.value.timeRange = [startTime, endTime]
+    }
+  }
+
+  // 初始化平台井数据
+  initPlatformData(reportData)
+
+  // 初始化动态属性
+  initDynamicAttrs(reportData)
+
+  // 初始化设备数据
+  initDeviceData(reportData)
+
+  // 初始化油耗数据 - 根据模式选择数据源
+  if (isDetailMode.value || isApprovalMode.value) {
+    // 详情或审批模式:优先使用 reportedFuels
+    let fuelSource = reportData.reportedFuels
+
+    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
+      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
+      const processedFuels = fuelSource.map((fuel: any) => {
+        // 创建全新的对象,避免引用共享
+        const newFuel = {
+          ...fuel, // 使用展开运算符创建浅拷贝
+          // 确保每个字段都有独立的值
+          createTime: fuel.createTime,
+          updateTime: fuel.updateTime,
+          creator: fuel.creator,
+          updater: fuel.updater,
+          deleted: fuel.deleted,
+          id: fuel.id,
+          type: fuel.type,
+          reportId: fuel.reportId,
+          deviceId: fuel.deviceId,
+          deviceCode: fuel.deviceCode,
+          yfDeviceCode: fuel.yfDeviceCode,
+          deviceName: fuel.deviceName,
+          carId: fuel.carId,
+          zhbdFuel: fuel.zhbdFuel,
+          customFuel: null, // 初始化为null
+          queryDate: fuel.queryDate,
+          remark: fuel.remark
+        }
+
+        let customFuelValue
+
+        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
+        if (fuel.customFuel !== null && fuel.customFuel !== undefined) {
+          const numValue = parseFloat(fuel.customFuel)
+          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00'
+        } else {
+          // 如果 customFuel 为空,则使用 zhbdFuel 的值
+          const zhbdValue = parseFloat(fuel.zhbdFuel)
+          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
+        }
+
+        return {
+          ...newFuel,
+          customFuel: customFuelValue
+        }
+      })
+
+      formData.value.reportFuels = processedFuels
+
+      // 计算初始的当日油耗
+      calculateTotalDailyFuel()
+    } else {
+      // 如果 reportedFuels 不存在或为空,设置空数组
+      formData.value.reportFuels = []
+    }
+  } else {
+    // 编辑模式:优先使用 reportedFuels,不存在则使用 reportFuels
+    let fuelSource = reportData.reportedFuels || reportData.reportFuels || []
+
+    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
+      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
+      const processedFuels = fuelSource.map((fuel: any) => {
+        // 创建全新的对象,避免引用共享
+        const newFuel = {
+          ...fuel, // 使用展开运算符创建浅拷贝
+          // 确保每个字段都有独立的值
+          createTime: fuel.createTime,
+          updateTime: fuel.updateTime,
+          creator: fuel.creator,
+          updater: fuel.updater,
+          deleted: fuel.deleted,
+          id: fuel.id,
+          type: fuel.type,
+          reportId: fuel.reportId,
+          deviceId: fuel.deviceId,
+          deviceCode: fuel.deviceCode,
+          yfDeviceCode: fuel.yfDeviceCode,
+          deviceName: fuel.deviceName,
+          carId: fuel.carId,
+          zhbdFuel: fuel.zhbdFuel,
+          customFuel: null, // 初始化为null
+          queryDate: fuel.queryDate,
+          remark: fuel.remark
+        }
+
+        let customFuelValue
+
+        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
+        if (fuel.customFuel !== null && fuel.customFuel !== undefined && fuel.customFuel !== '') {
+          const numValue = parseFloat(fuel.customFuel)
+          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00'
+        } else {
+          // 如果 customFuel 为空,则使用 zhbdFuel 的值
+          const zhbdValue = parseFloat(fuel.zhbdFuel)
+          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00'
+        }
+
+        return {
+          ...newFuel,
+          customFuel: customFuelValue
+        }
+      })
+
+      formData.value.reportFuels = processedFuels
+
+      // 计算初始的当日油耗
+      calculateTotalDailyFuel()
+    } else {
+      formData.value.reportFuels = []
+    }
+  }
+}
+
+onMounted(async () => {
+  formLoading.value = true
+  try {
+    // 加载当前登录人所属部门
+    const deptId = userStore.getUser.deptId
+    const dept = await DeptApi.getDept(deptId)
+
+    // 查询瑞都日报详情
+    if (id) {
+      const response = await IotRdDailyReportApi.getIotRdDailyReport(id)
+      console.log('response :>> ', response)
+      dailyReportData.value = response || {}
+      initFormData(dailyReportData.value)
+
+      // 确保油耗数据在初始化后立即渲染
+      await nextTick()
+      // 强制更新油耗数据
+      if (formData.value.reportFuels && formData.value.reportFuels.length > 0) {
+        formData.value.reportFuels = [...formData.value.reportFuels]
+      }
+    }
+  } catch (error) {
+    console.error('初始化数据失败:', error)
+    message.error('数据加载失败')
+  } finally {
+    formLoading.value = false
+  }
+})
+
+// 详细 审批 平台井 获取工作量列配置
+const getWorkloadColumns = () => {
+  const dataSource = platformWorkloadData.value
+  if (!dataSource || dataSource.length === 0) return []
+
+  const columns = []
+  const addedIdentifiers = new Set()
+
+  dataSource.forEach((platform) => {
+    if (platform.extProperty && Array.isArray(platform.extProperty)) {
+      platform.extProperty.forEach((extProp) => {
+        if (!addedIdentifiers.has(extProp.identifier)) {
+          columns.push({
+            key: extProp.identifier,
+            identifier: extProp.identifier,
+            label: `${extProp.name}(${extProp.unit})`
+          })
+          addedIdentifiers.add(extProp.identifier)
+        }
+      })
+    }
+  })
+
+  return columns
+}
+
+// 添加一个深拷贝油耗数据的辅助函数
+const deepCopyFuelData = (fuelData: any) => {
+  if (!fuelData) return null
+
+  return {
+    createTime: fuelData.createTime,
+    updateTime: fuelData.updateTime,
+    creator: fuelData.creator,
+    updater: fuelData.updater,
+    deleted: fuelData.deleted,
+    id: fuelData.id,
+    type: fuelData.type,
+    reportId: fuelData.reportId,
+    deviceId: fuelData.deviceId,
+    deviceCode: fuelData.deviceCode,
+    yfDeviceCode: fuelData.yfDeviceCode,
+    deviceName: fuelData.deviceName,
+    carId: fuelData.carId,
+    zhbdFuel: fuelData.zhbdFuel,
+    customFuel: fuelData.customFuel,
+    queryDate: fuelData.queryDate,
+    remark: fuelData.remark
+  }
+}
+
+// 强制刷新表格
+const refreshFuelTable = () => {
+  fuelTableKey.value += 1
+}
+
+// 计算当日油耗的默认值
+const calculateDailyFuel = (reportData: any) => {
+  let dailyFuelValue = 0
+
+  // 如果有接口返回的dailyFuel,优先使用
+  if (reportData.dailyFuel !== null && reportData.dailyFuel !== undefined) {
+    dailyFuelValue = Number(reportData.dailyFuel)
+  }
+
+  // 如果reportFuels有数据,累加zhbdFuel的值
+  if (
+    reportData.reportFuels &&
+    Array.isArray(reportData.reportFuels) &&
+    reportData.reportFuels.length > 0
+  ) {
+    const totalZhbdFuel = reportData.reportFuels.reduce((sum: number, item: any) => {
+      const zhbdFuelValue = Number(item.zhbdFuel) || 0
+      return sum + zhbdFuelValue
+    }, 0)
+
+    // 只有当累计值大于0时才覆盖原有的dailyFuel值
+    if (totalZhbdFuel > 0) {
+      dailyFuelValue = totalZhbdFuel
+    }
+  }
+
+  return formatNumber(dailyFuelValue, 2)
+}
+
+// 处理当日油耗输入
+const handleDailyFuelInput = () => {
+  // 确保保留两位小数
+  if (formData.value.dailyFuel !== '') {
+    const numValue = parseFloat(formData.value.dailyFuel)
+    if (!isNaN(numValue)) {
+      formData.value.dailyFuel = numValue.toFixed(2)
+    }
+  }
+}
+
+// 添加数字格式化函数
+const formatNumber = (value: any, decimalPlaces: number = 2) => {
+  if (value === null || value === undefined || value === '' || value === 'NaN') {
+    return '0.00'
+  }
+
+  // 如果已经是字符串,尝试转换为数字
+  if (typeof value === 'string') {
+    // 移除可能的非数字字符
+    const cleaned = value.replace(/[^\d.-]/g, '')
+    const num = Number(cleaned)
+    if (isNaN(num)) {
+      return '0.00'
+    }
+    return num.toFixed(decimalPlaces)
+  }
+
+  const num = Number(value)
+  if (isNaN(num)) {
+    return '0.00'
+  }
+
+  return num.toFixed(decimalPlaces)
+}
+
+// 新增:计算并更新当日油耗的方法
+const calculateAndUpdateDailyFuel = () => {
+  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
+    return
+  }
+
+  // 计算所有车辆的实际油耗总和
+  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
+    const customFuelValue = Number(item.customFuel) || 0
+    return sum + customFuelValue
+  }, 0)
+
+  // 只有当累计的实际油耗大于0时,才更新当日油耗
+  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
+    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2)
+    // 更新输入框显示
+    if (dailyFuelInput.value) {
+      dailyFuelInput.value = formData.value.dailyFuel
+    }
+  }
+}
+
+// 添加计算属性:获取油耗数据显示数据源
+const fuelConsumptionData = computed(() => {
+  // 所有模式都统一使用 formData.value.reportFuels 作为数据源
+  // 因为 formData.value.reportFuels 在 initFormData 中已经正确处理了所有情况
+  return formData.value.reportFuels || []
+})
+
+// 判断是否显示油耗信息区域
+const showFuelConsumption = computed(() => {
+  const data = fuelConsumptionData.value
+  return data && Array.isArray(data) && data.length > 0
+})
+
+// 重新计算当日油耗const formatNumber
+const calculateTotalDailyFuel = () => {
+  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
+    return
+  }
+
+  // 计算所有车辆的实际油耗总和
+  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
+    const customFuelValue = Number(item.customFuel) || 0
+    return sum + customFuelValue
+  }, 0)
+
+  // 只有当累计的实际油耗大于0时,才自动更新当日油耗
+  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
+    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2)
+  }
+}
+
+// 处理实际油耗变化
+const handleCustomFuelChange = (fuelItem: any) => {
+  // 获取当前输入的值
+  let value = fuelItem.customFuel
+
+  // 如果输入为空,则重置为zhbdFuel的值
+  if (value === '' || value === null || value === undefined) {
+    fuelItem.customFuel = formatNumber(fuelItem.zhbdFuel, 2)
+    return
+  }
+
+  // 移除非数字字符(除了小数点)
+  const cleaned = value.toString().replace(/[^\d.]/g, '')
+
+  // 确保只有一个小数点
+  const parts = cleaned.split('.')
+  let formattedValue = cleaned
+  if (parts.length > 2) {
+    formattedValue = parts[0] + '.' + parts.slice(1).join('')
+  }
+
+  // 转换为数字并格式化为两位小数
+  const numValue = parseFloat(formattedValue)
+  if (!isNaN(numValue)) {
+    // 限制到两位小数
+    fuelItem.customFuel = formatNumber(numValue, 2)
+  } else {
+    // 如果转换失败,设置为0.00
+    fuelItem.customFuel = '0.00'
+  }
+
+  // 同步更新 formData.reportFuels 中的数据
+  // 确保通过 deviceId 正确找到并更新对应的记录
+  if (formData.value.reportFuels && fuelItem.deviceId) {
+    const index = formData.value.reportFuels.findIndex(
+      (item) => item.deviceId === fuelItem.deviceId
+    )
+
+    if (index !== -1) {
+      // 创建新对象,避免引用问题
+      const updatedFuel = {
+        ...formData.value.reportFuels[index],
+        customFuel: fuelItem.customFuel
+      }
+
+      // 使用 Vue.set 或直接赋值确保响应性
+      formData.value.reportFuels[index] = updatedFuel
+
+      // 强制刷新表格
+      fuelTableKey.value += 1
+    }
+  }
+
+  // 手动触发当日油耗的重新计算
+  calculateAndUpdateDailyFuel()
+}
+
+// 详情 审批 平台井 获取工作量值
+const getWorkloadValue = (platform, identifier) => {
+  if (!platform.extProperty) return ''
+  const prop = platform.extProperty.find((item) => item.identifier === identifier)
+  return prop ? prop.actualValue || '' : ''
+}
+
+/** 审批操作 */
+const handleApprove = async (action: 'pass' | 'reject') => {
+  // 只有在审批模式下才执行审批操作
+  if (!isApprovalMode.value) {
+    message.warning('当前不是审批模式')
+    return
+  }
+  try {
+    // 验证审批表单(如果需要)
+    // await approvalFormRef.value.validate()
+
+    formLoading.value = true
+
+    // 处理时间范围数据
+    if (formData.value.timeRange && formData.value.timeRange.length === 2) {
+      // 将时间范围转换为 LocalTime 格式的字符串
+      const startDate = dayjs(formData.value.timeRange[0])
+      const endDate = dayjs(formData.value.timeRange[1])
+
+      // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
+      formData.value.startTime = startDate.format('HH:mm:ss')
+      formData.value.endTime = endDate.format('HH:mm:ss')
+    }
+
+    // 构建审批数据,包含审批意见
+    const approveData = {
+      ...formData.value,
+      id: Number(id),
+      opinion: approvalForm.opinion,
+      auditStatus: action === 'pass' ? 20 : 30
+    }
+
+    // 这里可以调用审批API
+    if (action === 'pass') {
+      // 审批通过逻辑
+      await IotRdDailyReportApi.approveRdDailyReport(approveData)
+      message.success('审批通过')
+    } else {
+      // 审批驳回逻辑
+      await IotRdDailyReportApi.approveRdDailyReport(approveData)
+      message.success('审批驳回')
+    }
+    close()
+  } catch (error) {
+    console.error('审批操作失败:', error)
+    message.error('审批操作失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+</script>
+
+<style scoped>
+.info-table {
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.table-row {
+  display: flex;
+  border-bottom: 1px solid #e0e0e0;
+}
+
+.table-row:last-child {
+  border-bottom: none;
+}
+
+.table-cell {
+  flex: 1;
+  border-right: 1px solid #e0e0e0;
+  padding: 12px 8px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+}
+
+.table-cell:last-child {
+  border-right: none;
+}
+
+.table-cell.full-width {
+  flex: 1;
+  border-right: none;
+}
+
+.cell-content {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.cell-label {
+  font-weight: 500;
+  /* 统一字体大小为 14px(Element 表单默认) */
+  font-size: 14px;
+  color: #606266;
+  min-width: 80px;
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+
+.cell-value {
+  /* 统一字体大小为 14px(Element 输入框默认) */
+  font-size: 14px;
+  color: #303133;
+  /* 统一行高为 1.5(Element 组件默认行高) */
+  line-height: 1.5;
+  flex: 1;
+  word-break: break-all;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .table-row {
+    flex-direction: column;
+  }
+
+  .table-cell {
+    border-right: none;
+    border-bottom: 1px solid #e0e0e0;
+  }
+
+  .table-cell:last-child {
+    border-bottom: none;
+  }
+}
+
+.daily-report-title {
+  text-align: center;
+  margin: 20px 0;
+  padding: 10px;
+  border-bottom: 2px solid #409eff;
+}
+
+.daily-report-title h2 {
+  margin: 0;
+  color: #303133;
+  font-size: 16px;
+  font-weight: bold;
+}
+
+/* 为第二、三部分增加左右留白 */
+.section-padding {
+  padding-left: 0px;
+  padding-right: 40px;
+}
+
+.project-info-section {
+  margin: 20px 0;
+  padding: 20px;
+  background-color: #f8f9fa;
+  border-radius: 4px;
+  border: 1px solid #e9ecef;
+}
+
+.info-row {
+  padding: 12px 0;
+  border-bottom: 1px solid #e9ecef;
+}
+
+.info-row:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  font-weight: bold;
+  color: #495057;
+  margin-right: 8px;
+}
+
+.info-value {
+  color: #212529;
+}
+
+:deep(.el-textarea .el-textarea__inner) {
+  min-height: 80px;
+}
+
+/* 确保表单label不换行 */
+:deep(.el-form-item__label) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 甲方字段:单行显示+超出省略 */
+.single-line-ellipsis {
+  /* 强制文本单行显示 */
+  white-space: nowrap;
+  /* 超出容器部分隐藏 */
+  overflow: hidden;
+  /* 超出部分显示省略号 */
+  text-overflow: ellipsis;
+  /* 避免文本被截断(可选,根据需求调整) */
+  word-break: normal;
+}
+
+/* 设备配置字段:换行缩进(与首行对齐) */
+.indent-multiline {
+  /* 首行及换行后缩进 2em(与 label 宽度匹配,可根据需求调整) */
+  text-indent: 0em;
+  /* 允许长文本换行(覆盖原有 cell-value 的 break-all,确保中文换行正常) */
+  word-break: break-word;
+  /* 保证换行后文本正常显示(可选,清除可能的 nowrap 影响) */
+  white-space: normal;
+}
+
+/* 添加审批模式下的样式 */
+.approval-notice {
+  margin-top: 10px;
+}
+
+/* 审批模式下表单字段的只读样式 */
+:deep(.el-form-item.is-disabled .el-input__inner),
+:deep(.el-form-item.is-disabled .el-textarea__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+:deep(.el-form-item.is-disabled .el-select .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+/* 只读模式下表单字段的样式 */
+:deep(.el-form-item.is-disabled .el-input__inner),
+:deep(.el-form-item.is-disabled .el-textarea__inner),
+:deep(.el-form-item.is-disabled .el-select .el-input__inner),
+:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266; /* 保持文字可读性 */
+  cursor: not-allowed;
+}
+
+/* 详情模式下的特殊样式 */
+.detail-mode .cell-value {
+  color: #303133;
+  font-weight: normal;
+}
+
+/* 添加审批意见区域的样式 */
+.approval-opinion-section {
+  margin-top: 20px;
+  border-top: 2px solid #f0f0f0;
+  padding-top: 20px;
+}
+
+/* 审批意见文本域样式 */
+:deep(.approval-opinion .el-textarea__inner) {
+  min-height: 100px;
+  resize: vertical;
+}
+
+/* 审批意见标签样式 */
+:deep(.approval-opinion .el-form-item__label) {
+  font-weight: bold;
+  color: #606266;
+}
+
+/* 附件列表样式 */
+.attachment-list {
+  width: 100%;
+  margin-top: 5px;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  padding: 10px;
+  background-color: #fafafa;
+  box-sizing: border-box;
+}
+
+.attachment-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.attachment-item:last-child {
+  border-bottom: none;
+}
+
+.attachment-name {
+  flex: 1;
+  color: #606266;
+  font-size: 11px;
+}
+
+.no-attachment {
+  color: #909399;
+  font-style: italic;
+  margin-top: 5px;
+  padding: 10px;
+}
+
+/* 附件名称链接样式 */
+.attachment-name {
+  color: #409eff;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.attachment-name:hover {
+  color: #66b1ff;
+}
+
+/* 只读模式下的设备显示样式 */
+.device-display-readonly {
+  color: #606266;
+  font-size: 11px;
+  line-height: 1.5;
+  background-color: #f5f7fa;
+  padding: 8px 12px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  display: inline-block;
+  min-width: 200px;
+}
+
+.no-device {
+  margin-left: 10px;
+  color: #909399;
+  font-style: italic;
+}
+
+/* 设备选择对话框样式 */
+.transfer-container {
+  text-align: center;
+  padding: 0px;
+}
+
+.transfer-component {
+  width: 100%;
+  min-width: 600px;
+}
+
+:deep(.el-transfer-panel) {
+  width: 40% !important;
+}
+
+:deep(.el-transfer-panel__item) {
+  display: flex !important;
+  align-items: center !important;
+  height: 32px !important;
+  line-height: 32px !important;
+  padding: 0 8px !important;
+  margin: 0 !important;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.transfer-option-text {
+  display: inline-block;
+  max-width: 100%;
+}
+
+:deep(.el-transfer-panel__list) {
+  width: 100% !important;
+}
+
+.device-display-container {
+  /* 与其他文本域保持相同的宽度和样式 */
+  display: inline-block;
+  width: 100%; /* 减去按钮宽度 */
+  min-height: 32px;
+  line-height: 32px;
+  padding: 0 12px;
+  margin-left: 0px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  background-color: #fff;
+  font-size: 11px;
+  /* 文本溢出处理 */
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 只读模式下的设备显示样式 */
+:deep(.is-disabled) .device-display-container {
+  background-color: #f5f7fa;
+  color: #606266;
+  cursor: not-allowed;
+}
+
+/* 附件容器样式调整 */
+.attachment-container {
+  /* 与其他文本域保持相同的宽度和边距 */
+  width: 100%;
+  margin-top: 10px;
+}
+
+/* 未选择设备字段的只读样式 */
+:deep(.unselected-device .el-textarea__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #909399;
+  cursor: not-allowed;
+  resize: none;
+}
+
+/* 平台井工作量区域专用样式 */
+.platform-workload-section {
+  padding-left: 0px;
+  padding-right: 0px; /* 去掉右侧间距 */
+}
+
+/* 表格样式优化 */
+.platform-workload-el-table {
+  width: 100%;
+}
+
+/* 表头不换行 */
+:deep(.platform-workload-el-table .el-table__header-wrapper th) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 单元格内容不换行 */
+:deep(.platform-workload-el-table .el-table__body-wrapper td) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 强制设置表头宽度为100% */
+:deep(.platform-workload-el-table .el-table__header) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 强制设置表格主体宽度为100% */
+:deep(.platform-workload-el-table .el-table__body) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 确保表格填满容器 */
+:deep(.platform-workload-el-table .el-table) {
+  width: 100% !important;
+}
+
+/* 表格容器填满父容器 */
+.platform-workload-table {
+  width: 100%;
+}
+
+/* 油耗信息区域样式 */
+.fuel-consumption-section {
+  padding-left: 0px;
+  padding-right: 0px;
+  margin-top: 20px;
+}
+
+/* 强制表格宽度为100% */
+:deep(.fuel-consumption-el-table) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 确保表格内部元素也充满宽度 */
+:deep(.fuel-consumption-el-table .el-table) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 强制设置表头宽度为100% */
+:deep(.fuel-consumption-el-table .el-table__header) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 表头不换行 */
+:deep(.fuel-consumption-el-table .el-table__header-wrapper th) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: bold;
+}
+
+/* 强制设置表格主体宽度为100% */
+:deep(.fuel-consumption-el-table .el-table__body) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 表头和表体都设置为100%宽度 */
+:deep(.fuel-consumption-el-table .el-table__header-wrapper),
+:deep(.fuel-consumption-el-table .el-table__body-wrapper) {
+  width: 100% !important;
+}
+
+/* 单元格内容居中 */
+:deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
+  text-align: center;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 实际油耗输入框样式 */
+:deep(.fuel-consumption-el-table .el-input__inner) {
+  text-align: center;
+  padding: 0 5px;
+  height: 28px;
+  line-height: 28px;
+}
+
+/* 只读模式下的数值显示 */
+.fuel-consumption-el-table .readonly-value {
+  color: #606266;
+  font-weight: normal;
+}
+
+/* 强制设置表格宽度为100% */
+:deep(.fuel-consumption-el-table .el-table) {
+  width: 100% !important;
+}
+
+/* 表格容器填满父容器 */
+.fuel-consumption-table {
+  width: 100%;
+  overflow-x: auto;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .fuel-consumption-section {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+
+  :deep(.fuel-consumption-el-table .el-table__header-wrapper th),
+  :deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
+    padding: 8px 5px;
+    font-size: 12px;
+  }
+}
+
+/* 当日油耗输入框样式优化 */
+:deep(.el-form-item .el-input-number) {
+  width: 100%;
+}
+
+/* 当日油耗字段在只读模式下的样式 */
+:deep(.is-disabled .el-input__inner[type='number']) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266;
+  cursor: not-allowed;
+}
+
+/* 实际进度区域样式 */
+.actual-progress-container {
+  margin-top: 10px;
+  padding: 20px;
+  border: 1px solid #e6e6e6;
+  border-radius: 8px;
+  background-color: #fafafa;
+}
+
+.progress-title {
+  margin: 0 0 16px 0;
+  font-size: 16px;
+  font-weight: bold;
+  color: #67c23a; /* 实际进度使用绿色标题 */
+}
+
+.no-progress-data {
+  text-align: center;
+  padding: 20px 0;
+  color: #909399;
+  font-style: italic;
+}
+
+/* 调整步骤组件样式以适应水平布局 */
+:deep(.actual-progress-container .el-steps--horizontal) {
+  flex-wrap: nowrap;
+  overflow-x: auto;
+  padding-bottom: 10px;
+}
+
+:deep(.actual-progress-container .el-step__title) {
+  font-size: 12px;
+  line-height: 1.4;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 120px;
+}
+
+/* 确保步骤容器有足够空间 */
+:deep(.actual-progress-container .el-step) {
+  flex-basis: auto;
+  flex-shrink: 0;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  :deep(.actual-progress-container .el-step__title) {
+    font-size: 11px;
+    max-width: 100px;
+  }
+
+  .actual-progress-container {
+    padding: 15px;
+  }
+}
+</style>

+ 23 - 5
src/views/pms/iotrddailyreport/fillDailyReport.vue

@@ -10,7 +10,7 @@ import rdForm from './rd-form.vue'
 
 defineOptions({ name: 'FillDailyReport' })
 
-// const router = useRouter()
+const router = useRouter()
 
 const id = useUserStore().getUser.deptId
 
@@ -123,14 +123,32 @@ watch(
 
 const visible = ref(false)
 
-const formRef = ref()
+// const formRef = ref()
+
+// function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
+//   if (formRef.value) {
+//     formRef.value.handleOpenForm(id, type)
+//   }
+// }
 
 function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
-  if (formRef.value) {
-    formRef.value.handleOpenForm(id, type)
-  }
+  router.push({
+    name: 'FillDailyReportForm',
+    query: { id: id, mode: type, backpath: route.path }
+  })
 }
 
+const route = useRoute()
+
+onMounted(() => {
+  if (route.query.mode) {
+    handleOpenForm(
+      Number(route.query.id),
+      route.query.mode as 'edit' | 'detail' | 'approval' | 'time'
+    )
+  }
+})
+
 const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
 
 function formCreateTime(row: ListItem) {

+ 50 - 32
src/views/pms/iotrddailyreport/index.vue

@@ -13,7 +13,7 @@ defineOptions({ name: 'IotRdDailyReport' })
 
 const { t } = useI18n()
 
-// const router = useRouter()
+const router = useRouter()
 const route = useRoute()
 
 const message = useMessage()
@@ -196,13 +196,29 @@ function realValue(type: any, value: string) {
 
 const visible = ref(false)
 
-const formRef = ref()
+// const formRef = ref()
+
+// function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
+//   if (formRef.value) {
+//     formRef.value.handleOpenForm(id, type)
+//   }
+// }
 
 function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
-  if (formRef.value) {
-    formRef.value.handleOpenForm(id, type)
-  }
+  router.push({
+    name: 'FillDailyReportForm',
+    query: { id: id, mode: type, backpath: route.path }
+  })
 }
+
+onMounted(() => {
+  if (route.query.mode) {
+    handleOpenForm(
+      Number(route.query.id),
+      route.query.mode as 'edit' | 'detail' | 'approval' | 'time'
+    )
+  }
+})
 </script>
 
 <template>
@@ -312,7 +328,7 @@ function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time
                       placement="right"
                       trigger="hover"
                       width="320"
-                      popper-class="!p-3 flex flex-col gap-y-2"
+                      popper-class="!p-0"
                       :disabled="row.reportDetails.length <= 1"
                     >
                       <template #reference>
@@ -360,38 +376,40 @@ function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time
                           </div>
                         </el-badge>
                       </template>
-                      <div
-                        v-for="(item, index) in row.reportDetails"
-                        :key="index"
-                        class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2 max-w-80"
-                      >
-                        <div class="flex items-center gap-x-2">
-                          <div class="flex items-center">
-                            <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
-                            <div class="font-medium mr-2">{{
-                              dayjs(row.createTime).format('YYYY-MM-DD')
-                            }}</div>
+                      <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                        <div
+                          v-for="(item, index) in row.reportDetails"
+                          :key="index"
+                          class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2 max-w-80"
+                        >
+                          <div class="flex items-center gap-x-2">
                             <div class="flex items-center">
-                              <span>{{ formatT(item.startTime) }}</span>
-                              <span class="mx-1">-</span>
-                              <span>{{ formatT(item.endTime) }}</span>
+                              <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                              <div class="font-medium mr-2">{{
+                                dayjs(row.createTime).format('YYYY-MM-DD')
+                              }}</div>
+                              <div class="flex items-center">
+                                <span>{{ formatT(item.startTime) }}</span>
+                                <span class="mx-1">-</span>
+                                <span>{{ formatT(item.endTime) }}</span>
+                              </div>
                             </div>
-                          </div>
 
-                          <div class="ml-auto group-hover:text-blue-600 font-medium">
-                            {{ item.duration }} H
+                            <div class="ml-auto group-hover:text-blue-600 font-medium">
+                              {{ item.duration }} H
+                            </div>
                           </div>
-                        </div>
 
-                        <div class="flex items-center">
-                          <div class="font-medium flex-shrink-0">施工详情:</div>
-                          <span
-                            class="font-medium group-hover:text-blue-600 transition-colors break-all whitespace-pre-wrap"
-                          >
-                            {{ item.constructionDetail || '-' }}
-                          </span>
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">施工详情:</div>
+                            <span
+                              class="font-medium group-hover:text-blue-600 transition-colors break-all whitespace-pre-wrap"
+                            >
+                              {{ item.constructionDetail || '-' }}
+                            </span>
+                          </div>
                         </div>
-                      </div>
+                      </el-scrollbar>
                     </el-popover>
                   </div>
                   <span v-else class="text-gray-300">-</span>

+ 266 - 7
src/views/pms/iotrddailyreport/rd-form.vue

@@ -791,7 +791,7 @@ const inContent = async (attachment) => {
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
     :with-header="false"
-    size="50%"
+    size="100%"
     :close-on-press-escape="false"
     :close-on-click-modal="false"
   >
@@ -951,11 +951,9 @@ const inContent = async (attachment) => {
             </div>
           </el-scrollbar>
         </div>
+
         <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
-        <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
-          <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
-          基础信息
-        </h3>
+
         <el-form
           ref="formRef"
           size="default"
@@ -966,6 +964,169 @@ const inContent = async (attachment) => {
           class="flex flex-col"
           :disabled="formDisabled()"
         >
+          <el-form-item v-if="data.platformWell === 1" label="平台井" prop="platformIds">
+            <el-select
+              v-model="form.platformIds"
+              multiple
+              :options="wellOptions"
+              placeholder="请选择平台井"
+              clearable
+              filterable
+              collapse-tags
+              collapse-tags-tooltip
+              :max-collapse-tags="5"
+              tag-type="primary"
+            />
+          </el-form-item>
+          <template v-for="(pid, pindex) in form.platformIds" :key="pid">
+            <el-divider
+              v-if="pindex !== 0"
+              class="my-6 border-2! border-[var(--el-color-primary)]!"
+            />
+            <div
+              class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
+            >
+              <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
+                <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+                {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
+              </h3>
+              <el-form-item
+                label="施工状态"
+                :prop="`${pid}.rdStatus`"
+                :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
+                class="col-span-2"
+              >
+                <el-select
+                  v-model="form[pid].rdStatus"
+                  :options="rdStatusOptions"
+                  placeholder="请选择"
+                  class="w-full"
+                  clearable
+                />
+              </el-form-item>
+
+              <el-form-item
+                label="施工工艺"
+                :prop="`${pid}.techniqueIds`"
+                :rules="{
+                  required: true,
+                  message: '请选择施工工艺',
+                  trigger: 'change',
+                  type: 'array'
+                }"
+                class="col-span-2"
+              >
+                <el-select
+                  v-model="form[pid].techniqueIds"
+                  :options="techniqueOptions"
+                  multiple
+                  collapse-tags
+                  collapse-tags-tooltip
+                  placeholder="请选择"
+                  class="w-full"
+                  @change="(val) => handleTechniqueChange(val, pid)"
+                  clearable
+                />
+              </el-form-item>
+
+              <template v-if="form[pid] && form[pid].extProperty">
+                <el-form-item
+                  v-for="(attr, idx) in form[pid].extProperty"
+                  :key="idx"
+                  :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
+                  :prop="`${pid}.extProperty.${idx}.actualValue`"
+                  :rules="
+                    attr.required === 1
+                      ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
+                      : []
+                  "
+                >
+                  <el-input-number
+                    v-if="attr.dataType === 'double'"
+                    v-model="attr.actualValue"
+                    :controls="false"
+                    class="w-full!"
+                    align="left"
+                    placeholder="请输入"
+                  />
+                  <el-input
+                    type="textarea"
+                    v-else
+                    v-model="attr.actualValue"
+                    placeholder="请输入"
+                  />
+                </el-form-item>
+              </template>
+
+              <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
+                非生产时间
+              </el-divider>
+
+              <el-form-item
+                v-for="field in NON_PROD_FIELDS"
+                :key="field.key"
+                :label="field.label"
+                :prop="`${pid}.${field.key}`"
+                :rules="noProductionTimeRule(pid)"
+              >
+                <el-input-number
+                  v-model="form[pid][field.key]"
+                  :min="0"
+                  :max="24"
+                  :controls="false"
+                  class="w-full!"
+                  align="left"
+                  @blur="handleRowValidate(pid, field.key)"
+                  :disabled="formDisabled(field.key)"
+                >
+                  <template #suffix>小时(H)</template>
+                </el-input-number>
+              </el-form-item>
+
+              <el-form-item
+                class="col-span-4"
+                label="其他非生产原因"
+                :prop="`${pid}.otherNptReason`"
+                :rules="
+                  form[pid].otherNptTime > 0
+                    ? { required: true, message: '请填写原因', trigger: 'change' }
+                    : {}
+                "
+              >
+                <el-input
+                  v-model="form[pid].otherNptReason"
+                  type="textarea"
+                  :autosize="{ minRows: 2 }"
+                  resize="none"
+                  show-word-limit
+                  :maxlength="1000"
+                  placeholder="当'其他非生产时间'大于0时必填"
+                  :disabled="formDisabled('otherNptReason')"
+                />
+              </el-form-item>
+            </div>
+          </template>
+
+          <el-form-item class="mt-4 col-span-2" label="当日施工简报" prop="constructionBrief">
+            <el-input
+              v-model="form.constructionBrief"
+              type="textarea"
+              :autosize="{ minRows: 2 }"
+              show-word-limit
+              resize="none"
+              :maxlength="1000"
+              placeholder="请输入当日施工简报"
+              :disabled="formDisabled('constructionBrief')"
+            />
+          </el-form-item>
+
+          <el-divider class="mt-0! border-2! border-[var(--el-color-primary)]!" />
+
+          <!-- <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+            <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+            基础信息
+          </h3> -->
+
           <div
             class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
           >
@@ -1038,6 +1199,103 @@ const inContent = async (attachment) => {
                 </template>
               </div>
             </el-form-item>
+
+            <div class="col-span-2 flex items-center justify-between mb-6">
+              <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+                <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+                生产动态
+              </h3>
+              <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
+                添加一行
+              </el-button>
+            </div>
+
+            <el-form-item prop="reportDetails" class="col-span-2">
+              <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
+                <ZmTableColumn
+                  :width="105"
+                  label="日期"
+                  cover-formatter
+                  :real-value="
+                    () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
+                  "
+                />
+                <ZmTableColumn :width="160" label="开始时间" prop="startTime">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.startTime`"
+                      :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
+                    >
+                      <el-time-picker
+                        v-model="row.startTime"
+                        placeholder="选择开始时间"
+                        clearable
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        class="w-full!"
+                        @change="calculateDuration(row)"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn :width="160" label="结束时间" prop="endTime">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.endTime`"
+                      :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
+                    >
+                      <el-time-picker
+                        v-model="row.endTime"
+                        placeholder="选择结束时间"
+                        clearable
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        class="w-full!"
+                        @change="calculateDuration(row)"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+                <ZmTableColumn label="施工详情" prop="constructionDetail">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.constructionDetail`"
+                      :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
+                    >
+                      <el-input
+                        v-model="row.constructionDetail"
+                        placeholder="输入施工详情"
+                        type="textarea"
+                        :autosize="{ minRows: 1 }"
+                        show-word-limit
+                        :maxlength="2000"
+                        class="w-full!"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
+                  <template #default="{ $index }">
+                    <el-button
+                      link
+                      type="danger"
+                      :icon="Delete"
+                      @click="removeReportDetailRow($index)"
+                    >
+                      删除
+                    </el-button>
+                  </template>
+                </ZmTableColumn>
+              </ZmTable>
+            </el-form-item>
+
             <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
               <el-input
                 v-model="form.nextPlan"
@@ -1124,8 +1382,9 @@ const inContent = async (attachment) => {
                 无附件
               </div>
             </el-form-item>
+          </div>
 
-            <div class="col-span-2 flex items-center justify-between mb-6">
+          <!-- <div class="col-span-2 flex items-center justify-between mb-6">
               <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
                 <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
                 生产动态
@@ -1377,7 +1636,7 @@ const inContent = async (attachment) => {
                 />
               </el-form-item>
             </div>
-          </template>
+          </template> -->
 
           <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
 

+ 39 - 20
src/views/pms/iotrydailyreport/ry-form.vue

@@ -40,6 +40,7 @@ const NON_PROD_FIELDS = [
 ] as const
 
 interface ReportDetail {
+  reportDate: number
   startTime: string
   endTime: string
   duration: number
@@ -92,7 +93,7 @@ interface FormOriginal {
 
   reportDetails: ReportDetail[]
   remark: string
-  createTime: string
+  createTime: number
   opinion: string
 }
 
@@ -187,6 +188,7 @@ async function loadDetail(id: number) {
 
     form.value.reportDetails = (res.reportDetails ? (res.reportDetails as any[]) : []).map(
       (item) => ({
+        reportDate: item.reportDate ?? res.createTime,
         startTime: formatT(item.startTime),
         endTime: formatT(item.endTime),
         duration: item.duration,
@@ -218,6 +220,7 @@ const addProductionStatusRow = () => {
     form.value.reportDetails = []
   }
   form.value.reportDetails.push({
+    reportDate: form.value.createTime ?? dayjs().valueOf(),
     startTime: '',
     endTime: '',
     duration: 0,
@@ -318,7 +321,12 @@ const rules = reactive<FormRules>({
   // productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
   reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
   constructionBrief: [
-    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+    {
+      required: isApproval.value,
+      message: '请填写施工简报',
+      type: 'string',
+      trigger: ['blur', 'change']
+    }
   ],
 
   // 生产时间绑定校验
@@ -432,7 +440,7 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
     :model-value="visible"
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
-    size="60%"
+    size="100%"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
   >
@@ -672,14 +680,30 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
           </div>
           <el-form-item prop="reportDetails">
             <ZmTable :data="form.reportDetails!" :loading="false" class="mb-4">
-              <ZmTableColumn
-                label="日期"
-                :width="105"
-                cover-formatter
-                :real-value="
-                  () => (form.createTime ? dayjs(form.createTime).format('YYYY-MM-DD') : '')
-                "
-              />
+              <ZmTableColumn label="日期" :width="180" prop="reportDate">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.reportDate`"
+                    :rules="{
+                      required: true,
+                      message: '请选择日期',
+                      trigger: ['change', 'blur'],
+                      type: 'number'
+                    }"
+                  >
+                    <el-date-picker
+                      v-model="row.reportDate"
+                      placeholder="选择日期"
+                      clearable
+                      class="w-full!"
+                      value-format="x"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
 
               <ZmTableColumn :width="130" label="开始时间" prop="startTime">
                 <template #default="{ row, $index }">
@@ -733,7 +757,7 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
               </ZmTableColumn>
               <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
 
-              <ZmTableColumn label="工况" min-width="140">
+              <ZmTableColumn label="工况" :width="200">
                 <template #default="{ row, $index }">
                   <el-form-item
                     v-if="$index >= 0"
@@ -754,7 +778,7 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
                 </template>
               </ZmTableColumn>
 
-              <ZmTableColumn label="结束井深(m)" min-width="80">
+              <ZmTableColumn label="结束井深(m)" :width="200">
                 <template #default="{ row, $index }">
                   <el-form-item
                     v-if="$index >= 0"
@@ -823,12 +847,7 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
           </el-form-item>
         </div>
 
-        <el-form-item
-          v-if="isApproval"
-          class="col-span-2"
-          label="当日施工简报"
-          prop="constructionBrief"
-        >
+        <el-form-item class="col-span-2" label="当日施工简报" prop="constructionBrief">
           <el-input
             v-model="form.constructionBrief"
             type="textarea"
@@ -837,7 +856,7 @@ const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, ind
             resize="none"
             :maxlength="1000"
             placeholder="请输入当日施工简报"
-            :disabled="formType === 'readonly'"
+            :disabled="!isApproval"
           />
         </el-form-item>
 

+ 42 - 40
src/views/pms/iotrydailyreport/ry-table.vue

@@ -256,7 +256,7 @@ function handleCurrentChange(val: number) {
                     placement="right"
                     trigger="hover"
                     width="320"
-                    popper-class="!p-3 flex flex-col gap-y-2"
+                    popper-class="!p-0"
                     :disabled="row.reportDetails.length <= 1"
                   >
                     <template #reference>
@@ -324,53 +324,55 @@ function handleCurrentChange(val: number) {
                         </div>
                       </el-badge>
                     </template>
-                    <div
-                      v-for="(item, index) in row.reportDetails"
-                      :key="index"
-                      class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
-                    >
-                      <div class="flex items-center gap-x-2">
-                        <div class="flex items-center">
-                          <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
-                          <div class="font-medium mr-2">{{
-                            dayjs(row.createTime).format('YYYY-MM-DD')
-                          }}</div>
+                    <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                      <div
+                        v-for="(item, index) in row.reportDetails"
+                        :key="index"
+                        class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                      >
+                        <div class="flex items-center gap-x-2">
                           <div class="flex items-center">
-                            <span>{{ formatT(item.startTime) }}</span>
-                            <span class="mx-1">-</span>
-                            <span>{{ formatT(item.endTime) }}</span>
+                            <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                            <div class="font-medium mr-2">{{
+                              dayjs(row.createTime).format('YYYY-MM-DD')
+                            }}</div>
+                            <div class="flex items-center">
+                              <span>{{ formatT(item.startTime) }}</span>
+                              <span class="mx-1">-</span>
+                              <span>{{ formatT(item.endTime) }}</span>
+                            </div>
                           </div>
-                        </div>
 
-                        <div class="ml-auto group-hover:text-blue-600 font-medium">
-                          {{ item.duration }} H
+                          <div class="ml-auto group-hover:text-blue-600 font-medium">
+                            {{ item.duration }} H
+                          </div>
                         </div>
-                      </div>
 
-                      <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">结束井深(m):</div>
-                        <span
-                          class="font-medium truncate group-hover:text-blue-600 transition-colors"
-                          >{{ item.currentDepth || '-' }} m
-                        </span>
-                      </div>
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">结束井深(m):</div>
+                          <span
+                            class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                            >{{ item.currentDepth || '-' }} m
+                          </span>
+                        </div>
 
-                      <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">工况:</div>
-                        <span class="font-medium group-hover:text-blue-600 transition-colors">
-                          {{ item.currentOperation || '-' }}
-                        </span>
-                      </div>
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">工况:</div>
+                          <span class="font-medium group-hover:text-blue-600 transition-colors">
+                            {{ item.currentOperation || '-' }}
+                          </span>
+                        </div>
 
-                      <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">详细描述:</div>
-                        <span
-                          class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
-                        >
-                          {{ item.constructionDetail || '-' }}
-                        </span>
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">详细描述:</div>
+                          <span
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                          >
+                            {{ item.constructionDetail || '-' }}
+                          </span>
+                        </div>
                       </div>
-                    </div>
+                    </el-scrollbar>
                   </el-popover>
                 </div>
                 <span v-else class="text-gray-300">-</span>

+ 38 - 19
src/views/pms/iotrydailyreport/ry-xj-form.vue

@@ -39,6 +39,7 @@ const NON_PROD_FIELDS = [
 ] as const
 
 interface ReportDetail {
+  reportDate: number
   startTime: string
   endTime: string
   duration: number
@@ -89,7 +90,7 @@ interface FormOriginal {
 
   reportDetails: ReportDetail[]
   remark: string
-  createTime: string
+  createTime: number
   opinion: string
 }
 
@@ -183,6 +184,7 @@ async function loadDetail(id: number) {
 
     form.value.reportDetails = (res.reportDetails ? (res.reportDetails as any[]) : []).map(
       (item) => ({
+        reportDate: item.reportDate ?? res.createTime,
         startTime: formatT(item.startTime),
         endTime: formatT(item.endTime),
         duration: item.duration,
@@ -209,6 +211,7 @@ const addProductionStatusRow = () => {
     form.value.reportDetails = []
   }
   form.value.reportDetails.push({
+    reportDate: form.value.createTime ?? dayjs().valueOf(),
     startTime: '',
     endTime: '',
     duration: 0,
@@ -301,7 +304,12 @@ const rules = reactive<FormRules>({
   repairStatus: [{ required: true, message: '请选择施工状态', trigger: ['change', 'blur'] }],
   reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
   constructionBrief: [
-    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+    {
+      required: isApproval.value,
+      message: '请填写施工简报',
+      type: 'string',
+      trigger: ['blur', 'change']
+    }
   ],
 
   // 生产时间绑定校验
@@ -409,7 +417,7 @@ const orange = computed(() => {
     :model-value="visible"
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
-    size="60%"
+    size="100%"
   >
     <template #header>
       <span class="text-xl font-bold text-[var(--el-text-color-primary)]">
@@ -637,14 +645,30 @@ const orange = computed(() => {
           </div>
           <el-form-item prop="reportDetails">
             <ZmTable :data="form.reportDetails!" :loading="false" class="mb-4">
-              <ZmTableColumn
-                label="日期"
-                :width="105"
-                cover-formatter
-                :real-value="
-                  () => (form.createTime ? dayjs(form.createTime).format('YYYY-MM-DD') : '')
-                "
-              />
+              <ZmTableColumn label="日期" :width="180" prop="reportDate">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.reportDate`"
+                    :rules="{
+                      required: true,
+                      message: '请选择日期',
+                      trigger: ['change', 'blur'],
+                      type: 'number'
+                    }"
+                  >
+                    <el-date-picker
+                      v-model="row.reportDate"
+                      placeholder="选择日期"
+                      clearable
+                      class="w-full!"
+                      value-format="x"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
 
               <ZmTableColumn :width="130" label="开始时间" prop="startTime">
                 <template #default="{ row, $index }">
@@ -698,7 +722,7 @@ const orange = computed(() => {
               </ZmTableColumn>
               <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
 
-              <ZmTableColumn label="工况" min-width="140">
+              <ZmTableColumn label="工况" :width="200">
                 <template #default="{ row, $index }">
                   <el-form-item
                     v-if="$index >= 0"
@@ -759,12 +783,7 @@ const orange = computed(() => {
           </el-form-item>
         </div>
 
-        <el-form-item
-          v-if="isApproval"
-          class="col-span-2"
-          label="当日施工简报"
-          prop="constructionBrief"
-        >
+        <el-form-item class="col-span-2" label="当日施工简报" prop="constructionBrief">
           <el-input
             v-model="form.constructionBrief"
             type="textarea"
@@ -773,7 +792,7 @@ const orange = computed(() => {
             resize="none"
             :maxlength="1000"
             placeholder="请输入当日施工简报"
-            :disabled="formType === 'readonly'"
+            :disabled="!isApproval"
           />
         </el-form-item>
 

+ 51 - 57
src/views/pms/iotrydailyreport/ry-xj-table.vue

@@ -317,7 +317,7 @@ function handleCurrentChange(val: number) {
                     placement="right"
                     trigger="hover"
                     width="320"
-                    popper-class="!p-3 flex flex-col gap-y-2"
+                    popper-class="p-0!"
                     :disabled="row.reportDetails.length <= 1"
                   >
                     <template #reference>
@@ -353,87 +353,81 @@ function handleCurrentChange(val: number) {
 
                           <div class="flex items-center">
                             <div class="font-medium flex-shrink-0">工况:</div>
-                            <span
-                              class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].currentOperation"
+                              placement="top"
+                              popper-class="max-w-100"
                             >
-                              {{ row.reportDetails[0].currentOperation || '-' }}
-                            </span>
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].currentOperation || '-' }}
+                              </span>
+                            </el-tooltip>
                           </div>
 
                           <div class="flex items-center">
                             <div class="font-medium flex-shrink-0">详细描述:</div>
-                            <span
-                              class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].constructionDetail"
+                              placement="top"
+                              popper-class="max-w-100"
                             >
-                              {{ row.reportDetails[0].constructionDetail || '-' }}
-                            </span>
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].constructionDetail || '-' }}
+                              </span>
+                            </el-tooltip>
                           </div>
                         </div>
                       </el-badge>
                     </template>
-                    <div
-                      v-for="(item, index) in row.reportDetails"
-                      :key="index"
-                      class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
-                    >
-                      <div class="flex items-center gap-x-2">
-                        <div class="flex items-center">
-                          <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
-                          <div class="font-medium mr-2">{{
-                            dayjs(row.createTime).format('YYYY-MM-DD')
-                          }}</div>
+                    <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                      <div
+                        v-for="(item, index) in row.reportDetails"
+                        :key="index"
+                        class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                      >
+                        <div class="flex items-center gap-x-2">
                           <div class="flex items-center">
-                            <span>{{ formatT(item.startTime) }}</span>
-                            <span class="mx-1">-</span>
-                            <span>{{ formatT(item.endTime) }}</span>
+                            <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                            <div class="font-medium mr-2">{{
+                              dayjs(row.createTime).format('YYYY-MM-DD')
+                            }}</div>
+                            <div class="flex items-center">
+                              <span>{{ formatT(item.startTime) }}</span>
+                              <span class="mx-1">-</span>
+                              <span>{{ formatT(item.endTime) }}</span>
+                            </div>
                           </div>
-                        </div>
 
-                        <div class="ml-auto group-hover:text-blue-600 font-medium">
-                          {{ item.duration }} H
+                          <div class="ml-auto group-hover:text-blue-600 font-medium">
+                            {{ item.duration }} H
+                          </div>
                         </div>
-                      </div>
-
-                      <!-- <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">结束井深(m):</div>
-                        <span
-                          class="font-medium truncate group-hover:text-blue-600 transition-colors"
-                          >{{ item.currentDepth || '-' }} m
-                        </span>
-                      </div> -->
 
-                      <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">工况:</div>
-                        <el-tooltip
-                          effect="dark"
-                          :content="item.currentOperation"
-                          placement="top"
-                          popper-class="max-w-100"
-                        >
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">工况:</div>
                           <span
-                            class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
                           >
                             {{ item.currentOperation || '-' }}
                           </span>
-                        </el-tooltip>
-                      </div>
+                        </div>
 
-                      <div class="flex items-center">
-                        <div class="font-medium flex-shrink-0">详细描述:</div>
-                        <el-tooltip
-                          effect="dark"
-                          :content="item.constructionDetail"
-                          placement="top"
-                          popper-class="max-w-100"
-                        >
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">详细描述:</div>
                           <span
-                            class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
                           >
                             {{ item.constructionDetail || '-' }}
                           </span>
-                        </el-tooltip>
+                        </div>
                       </div>
-                    </div>
+                    </el-scrollbar>
                   </el-popover>
                 </div>
                 <span v-else class="text-gray-300">-</span>

+ 28 - 15
src/views/pms/maintenance/IotMaintenancePlanEdit.vue

@@ -5,7 +5,7 @@
       :model="formData"
       :rules="formRules"
       v-loading="formLoading"
-      style="margin-right: 4em; margin-left: 0.5em; margin-top: 1em"
+      style="margin-top: 1em; margin-right: 4em; margin-left: 0.5em"
       label-width="130px"
     >
       <div class="base-expandable-content">
@@ -316,7 +316,7 @@
           <template #default="scope">
             <div style="display: flex; justify-content: center; align-items: center; width: 100%">
               <div>
-                <Icon style="vertical-align: middle; color: #ea3434" icon="ep:zoom-out" />
+                <Icon style="color: #ea3434; vertical-align: middle" icon="ep:zoom-out" />
                 <el-button
                   style="vertical-align: middle"
                   link
@@ -1788,17 +1788,17 @@ const handleDelete = async (str: string) => {
 }
 
 :deep(.el-input-number .el-input__inner) {
-  text-align: left !important;
   padding-left: 10px; /* 保持左侧间距 */
+  text-align: left !important;
 }
 
 /* 分组容器样式 */
 .form-group {
   position: relative;
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
   padding: 20px 15px 10px;
   margin-bottom: 18px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
   transition: border-color 0.2s;
 }
 
@@ -1807,11 +1807,11 @@ const handleDelete = async (str: string) => {
   position: absolute;
   top: -10px;
   left: 20px;
-  background: white;
   padding: 0 8px;
-  color: #606266;
   font-size: 12px;
   font-weight: 500;
+  color: #606266;
+  background: white;
 }
 
 /* 确保分页组件右对齐 */
@@ -1822,14 +1822,14 @@ const handleDelete = async (str: string) => {
 }
 
 .full-content-cell {
-  white-space: nowrap; /* 禁止换行 */
   overflow: visible; /* 允许内容溢出单元格 */
+  white-space: nowrap; /* 禁止换行 */
 }
 
 /* 确保表格容器可滚动 */
 .table-container {
-  overflow-x: auto;
   width: 100%;
+  overflow-x: auto;
 
   /* 修复固定列错位 */
   :deep(.el-table__fixed-body-wrapper) {
@@ -1839,27 +1839,28 @@ const handleDelete = async (str: string) => {
 
 /* 滚动条调整 */
 :deep(.el-table__body-wrapper) {
-  overflow-x: auto;
   padding-bottom: 12px;
+  overflow-x: auto;
 }
 
 /* 固定表格布局 */
 el-table {
-  table-layout: fixed;
   min-width: 100%;
+  table-layout: fixed;
 }
 
 /* 全局禁止换行和省略号 */
 :deep(.el-table th > .cell),
 :deep(.el-table td > .cell) {
-  white-space: nowrap !important;
   overflow: visible !important;
   text-overflow: clip !important;
+  white-space: nowrap !important;
 }
 
 /* 表头特别处理 */
 :deep(.el-table__header) {
   border: 1px solid #dcdfe6 !important;
+
   /* .cell {
     display: inline-block;
     white-space: nowrap;
@@ -1877,10 +1878,10 @@ el-table {
 }
 
 :deep(.el-table__header .is-group th) {
+  position: relative;
+  font-weight: 600;
   background-color: #f5f7fa !important;
   border-bottom: 1px solid #dcdfe6 !important;
-  font-weight: 600;
-  position: relative;
 }
 
 :deep(.el-table__header .is-group th::after) {
@@ -1909,7 +1910,7 @@ el-table {
   box-shadow: none !important;
 }
 
-:deep(.el-table__fixed:before) {
+:deep(.el-table__fixed::before) {
   background-color: transparent !important;
 }
 
@@ -1932,6 +1933,18 @@ el-table {
   }
 }
 
+:deep(.el-table) {
+  .el-table__row--striped {
+    .el-table__cell {
+      background-color: var(--el-table-current-row-bg-color) !important;
+    }
+
+    .all-filled{
+      background-color: #67c23a !important;
+    }
+  }
+}
+
 /* 已完整填写行的背景色 */
 :deep(.el-table .all-filled) {
   background-color: #67c23a !important; /* 淡绿色背景 */

+ 155 - 88
src/views/pms/video_center/sip/splitview.vue

@@ -7,11 +7,7 @@
       :body-style="{ padding: '0px' }"
       class="border-none"
     >
-      <el-container
-        v-loading="loading"
-        style="height: 100%"
-        :element-loading-text="t('sip.splitview998531-0')"
-      >
+      <el-container style="height: 100%">
         <el-aside width="250px" style="background-color: #ffffff">
           <DeviceTree :click-event="clickEvent" />
         </el-aside>
@@ -56,23 +52,25 @@
             </el-button>
           </div>
           <div style="height: 85vh; display: flex; flex-wrap: wrap">
-            <!-- 只渲染实际可用的通道数量,不超过当前分屏数量 -->
+            <!-- 渲染所有分屏数量的播放框 -->
             <div
-              v-for="i in Math.min(spilt, availableChannels.length || spilt)"
+              v-for="i in spilt"
               :key="i"
               class="play-box"
               :style="liveStyle"
               :class="{ redborder: playerIdx == i - 1 }"
               @click="playerIdx = i - 1"
             >
+              <!-- 显示序号,如果该位置没有视频或不在可用通道范围内 -->
               <div
-                v-if="!videoUrl[i - 1]"
+                v-if="!videoUrl[i - 1] || i - 1 >= availableChannels.length"
                 style="color: #ffffff; font-size: 30px; font-weight: bold"
               >
                 {{ i }}
               </div>
+              <!-- 只有当有视频URL时才渲染播放器 -->
               <player
-                v-else
+                v-if="videoUrl[i - 1]"
                 :ref="(el) => setPlayerRef(el, i - 1)"
                 :videourl="videoUrl[i - 1]"
                 :playerInfo="
@@ -88,28 +86,6 @@
                 @destroy="destroy"
               />
             </div>
-
-            <!-- 如果通道数少于分屏数,显示空白占位 -->
-            <div
-              v-for="i in Math.max(0, spilt - availableChannels.length)"
-              :key="`empty-${i}`"
-              class="play-box"
-              :style="liveStyle"
-            >
-              <div
-                style="
-                  color: #ffffff;
-                  font-size: 30px;
-                  font-weight: bold;
-                  display: flex;
-                  align-items: center;
-                  justify-content: center;
-                  height: 100%;
-                "
-              >
-                {{ availableChannels.length + i }}
-              </div>
-            </div>
           </div>
         </el-main>
       </el-container>
@@ -178,42 +154,119 @@ const liveStyle = computed(() => {
   return style
 })
 
-// 监听分屏变化
+const loadAdditionalChannelsForSplit = async (newSplit, oldSplit) => {
+  loading.value = true
+  try {
+    const currentlyLoaded = Math.min(oldSplit, availableChannels.value.length)
+    const toBeLoaded = Math.min(newSplit, availableChannels.value.length)
+
+    // 使用数组存储所有要执行的异步操作
+    const operations = []
+
+    for (let i = currentlyLoaded; i < toBeLoaded; i++) {
+      const channelData = availableChannels.value[i]
+
+      if (channelData._playUrl) {
+        // 同步操作:立即设置URL
+        setPlayUrl(channelData._playUrl, i)
+      } else {
+        // 异步操作:稍后并行执行
+        operations.push({
+          index: i,
+          channelData,
+          playData: {
+            deviceSipId: channelData.deviceId,
+            channelSipId: channelData.basicData?.channelSipId || channelData.id,
+            name: channelData.name || `通道${i + 1}`,
+            ...channelData.basicData
+          }
+        })
+      }
+    }
+
+    // 并行执行所有sendDevicePush请求
+    if (operations.length > 0) {
+      const promises = operations.map(({ playData, index, channelData }) =>
+        sendDevicePush(playData, index)
+          .then(() => {
+            // 成功后的回调
+            if (videoUrl.value[index]) {
+              channelData._playUrl = videoUrl.value[index]
+            }
+            return { success: true, index }
+          })
+          .catch((error) => {
+            console.error(`通道${index + 1}加载失败:`, error)
+            return { success: false, index, error }
+          })
+      )
+
+      // 使用allSettled而不是all,这样即使有失败也不会中断其他请求
+      await Promise.allSettled(promises)
+    }
+  } catch (error) {
+    console.error('加载新增通道出错:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 方法2:并行销毁,提高效率
+const destroyAllPlayersAsync = async () => {
+  if (!playerRefs.value || playerRefs.value.length === 0) {
+    console.log('没有播放器需要销毁')
+    return true
+  }
+
+  console.log(`开始并行销毁 ${playerRefs.value.length} 个播放器`)
+
+  try {
+    // 收集所有销毁 Promise
+    const destroyPromises = playerRefs.value
+      .filter((player) => player && typeof player.destroy === 'function')
+      .map((player, index) => {
+        return Promise.resolve().then(async () => {
+          try {
+            await player.destroy()
+            console.log(`播放器 ${index} 销毁完成`)
+            return { success: true, index }
+          } catch (error) {
+            console.error(`播放器 ${index} 销毁失败:`, error)
+            return { success: false, index, error }
+          }
+        })
+      })
+
+    // 并行执行所有销毁
+    const results = await Promise.allSettled(destroyPromises)
+
+    // 统计结果
+    const successCount = results.filter((r) => r.status === 'fulfilled' && r.value.success).length
+    const failedCount = results.length - successCount
+
+    console.log(`销毁完成: 成功 ${successCount} 个, 失败 ${failedCount} 个`)
+
+    // 清空引用数组
+    playerRefs.value = []
+
+    return failedCount === 0
+  } catch (error) {
+    console.error('并行销毁过程中出错:', error)
+    return false
+  }
+}
+
 watch(
   () => spilt.value,
   async (newSplit, oldSplit) => {
-    // 只有当分屏数增加(例如从1到4,或从4到9)且有当前选中设备时才执行
-    if (newSplit > oldSplit && currentDevice.value) {
-      console.log(
-        `分屏从 ${oldSplit} 变为 ${newSplit},自动填充设备 ${currentDevice.value.name} 的通道`
-      )
-
-      // 延迟执行,确保DOM更新完成
+    // 先销毁所有现有播放器,确保资源释放
+    await destroyAllPlayersAsync()
+    // 如果当前有选中的设备
+    if (currentDevice.value && availableChannels.value.length > 0) {
       await nextTick()
 
-      loading.value = true
-      try {
-        // 重新分配通道到所有播放器
-        await assignChannelsToPlayers(availableChannels.value)
-      } catch (error) {
-        console.error('分屏变化时填充通道出错:', error)
-        ElMessage.error('自动填充通道失败')
-      } finally {
-        loading.value = false
-      }
-    }
-    // 当分屏数减少时,清空超出当前通道数的播放器
-    else if (newSplit < oldSplit) {
-      // 清空超出当前分屏数或超出通道数的播放器
-      const maxVisible = Math.min(newSplit, availableChannels.value.length)
-      for (let i = maxVisible; i < Math.max(oldSplit, availableChannels.value.length); i++) {
-        const newVideoUrls = [...videoUrl.value]
-        if (newVideoUrls[i]) {
-          newVideoUrls[i] = ''
-          videoUrl.value = newVideoUrls
-          clear(i + 1) // 清理存储的数据
-        }
-      }
+      // 只加载新出现的播放器,不重新加载已有播放器
+      await loadAdditionalChannelsForSplit(newSplit, oldSplit)
     }
   },
   { immediate: false }
@@ -244,45 +297,39 @@ const destroy = (idx) => {
 const currentDevice = ref(null) // 存储当前选中的设备信息
 // 点击事件
 let playerInfo = ref([])
+
 const clickEvent = async (data) => {
   // 情况1:点击的是设备节点 (type === 0)
   if (data.type === 0) {
+    // 重置选中的空闲槽位
+    selectedEmptySlot.value = -1
+
     currentDevice.value = {
       id: data.userData?.serialNumber,
       name: data.name,
       data: data
     }
-    const deviceId = data.userData?.serialNumber // 从树节点数据中提取设备ID
+    const deviceId = data.userData?.serialNumber
     if (!deviceId) return
 
     loading.value = true
     try {
-      // 1. 获取该设备下的所有通道
       const channels = await getDeviceChannels(deviceId)
-
-      // 只保留摄像头类型的通道
       const cameraChannels = channels.filter((channel) => channel.basicData.model === 'Camera')
       availableChannels.value = cameraChannels
 
-      // 清空之前的数据
       playerInfo.value = []
       cameraChannels.forEach((channel) => {
         playerInfo.value.push(channel)
       })
 
-      console.log('playerInfo>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', playerInfo.value)
-      console.log('availableChannels>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', availableChannels.value)
-      console.log('videourl**************************', videoUrl.value)
-
       if (!cameraChannels || cameraChannels.length === 0) {
         console.warn('该设备下没有可用的摄像头通道')
-        // 清空所有播放器
         const newVideoUrls = Array(spilt.value).fill('')
         videoUrl.value = newVideoUrls
         return
       }
 
-      // 2. 将通道智能分配给播放器
       await assignChannelsToPlayers(cameraChannels)
     } catch (error) {
       console.error('处理设备通道时出错:', error)
@@ -290,9 +337,20 @@ const clickEvent = async (data) => {
       loading.value = false
     }
   }
-  // 情况2:点击的是具体的通道节点,保持原有逻辑不变
+  // 情况2:点击的是具体的通道节点
   else if (data.userData?.channelSipId) {
-    sendDevicePush(data.userData)
+    // 检查是否有选中的空闲槽位
+    let targetIndex = selectedEmptySlot.value !== -1 ? selectedEmptySlot.value : playerIdx.value
+
+    // 验证目标索引是否在有效范围内
+    if (targetIndex >= spilt.value) {
+      targetIndex = playerIdx.value // 回退到当前选中的播放器
+    }
+
+    // 重置选中的空闲槽位
+    selectedEmptySlot.value = -1
+
+    await sendDevicePush(data.userData, targetIndex)
   }
 }
 
@@ -310,39 +368,35 @@ const getDeviceChannels = async (deviceId) => {
 const assignChannelsToPlayers = async (channels) => {
   const currentSplit = spilt.value
   const actualChannels = Math.min(channels.length, currentSplit)
-
-  console.log(`分配 ${actualChannels} 个通道到最多 ${currentSplit} 个播放窗口`)
-
-  // 清空当前所有播放器
-  const newVideoUrls = Array(Math.max(currentSplit, channels.length)).fill('')
+  // 初始化所有播放器位置
+  const newVideoUrls = Array(currentSplit).fill('')
   videoUrl.value = newVideoUrls
 
   // 为每个可用通道分配播放器
   for (let i = 0; i < actualChannels; i++) {
-    // 延迟执行,避免同时发起大量请求
     await new Promise((resolve) => setTimeout(resolve, 50))
 
     const channelData = channels[i]
-    // 构建符合 sendDevicePush 要求的参数格式
     const playData = {
       deviceSipId: channelData.deviceId,
       channelSipId: channelData.basicData?.channelSipId || channelData.id,
       name: channelData.name || `通道${i + 1}`,
       ...channelData.basicData
     }
-    console.log(`将通道 ${playData.name} 分配给播放器 ${i}`)
+
     await sendDevicePush(playData, i)
   }
-
-  // 如果通道数少于当前分屏数,不需要做额外处理,因为已经清空了超出部分
 }
 
 // 通知设备上传媒体流
-const sendDevicePush = (itemData, targetIndex = null) => {
+const sendDevicePush = async (itemData, targetIndex = null) => {
   // 关键修改:允许从外部传入 targetIndex,默认使用当前激活的 playerIdx
   const playIndex = targetIndex !== null ? targetIndex : playerIdx.value
 
-  save(itemData, playIndex) // 保存数据也要使用正确的索引
+  // 确保索引在有效范围内
+  const validIndex = Math.min(playIndex, spilt.value - 1)
+
+  save(itemData, validIndex) // 保存数据也要使用正确的索引
 
   let deviceId = itemData.deviceSipId
   let channelId = itemData.channelSipId
@@ -357,7 +411,7 @@ const sendDevicePush = (itemData, targetIndex = null) => {
         itemData.playUrl = res.playurl
       }
       itemData.streamId = res.streamId
-      setPlayUrl(itemData.playUrl, playIndex) // 播放URL设置到指定位置
+      setPlayUrl(itemData.playUrl, validIndex) // 播放URL设置到指定位置
     })
     .finally(() => {
       loading.value = false
@@ -367,6 +421,7 @@ const sendDevicePush = (itemData, targetIndex = null) => {
 let playInfoRes = ref('')
 // 设置播放URL
 const setPlayUrl = (url, idx) => {
+  console.log('设置播放URL:', url, '索引:', idx)
   // 确保数组长度足够
   while (videoUrl.value.length <= idx) {
     videoUrl.value.push('')
@@ -389,6 +444,18 @@ const checkPlayByParam = () => {
   }
 }
 
+// 存储当前选中的空闲槽位
+const selectedEmptySlot = ref(-1)
+
+const handleEmpty = (i) => {
+  // 计算当前点击的空闲槽位的实际索引
+  const slotIndex = availableChannels.value.length + i - 1
+  selectedEmptySlot.value = slotIndex
+  playerIdx.value = slotIndex
+
+  console.log('选中空闲槽位:', slotIndex)
+}
+
 // 截图处理
 const shot = (e) => {
   var base64ToBlob = function (code) {