Przeglądaj źródła

瑞都日报拆分生产动态

Zimo 2 dni temu
rodzic
commit
c07d5c0f4f

+ 2 - 2
.vscode/settings.json

@@ -83,8 +83,8 @@
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "editor.codeActionsOnSave": {
-    "source.fixAll.eslint": "explicit"
-    // "source.fixAll.stylelint": "explicit"
+    "source.fixAll.eslint": "explicit",
+    "source.fixAll.stylelint": "explicit"
   },
   "[vue]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode"

+ 2 - 0
package.json

@@ -38,6 +38,7 @@
     "@number-flow/vue": "^0.4.8",
     "@riophae/vue-treeselect": "^0.4.0",
     "@types/echarts": "^5.0.0",
+    "@types/js-base64": "^3.3.1",
     "@videojs-player/vue": "^1.0.0",
     "@vueuse/core": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
@@ -65,6 +66,7 @@
     "file-save": "^0.2.0",
     "file-saver": "^2.0.5",
     "highlight.js": "^11.9.0",
+    "js-base64": "^3.7.8",
     "jsencrypt": "^3.3.2",
     "lodash-es": "^4.17.21",
     "markdown-it": "^14.1.0",

Plik diff jest za duży
+ 353 - 939
pnpm-lock.yaml


+ 2 - 1
src/api/pms/iotrddailyreport/index.ts

@@ -42,6 +42,7 @@ export interface IotRdDailyReportVO {
   status: number // 状态(0启用 1禁用)
   processInstanceId: string // 流程实例id
   auditStatus: number // 审批状态 未提交、审批中、审批通过、审批不通过、已取消
+  opinion: string // 审批意见
 }
 
 // 瑞都日报 API
@@ -85,7 +86,7 @@ export const IotRdDailyReportApi = {
   },
 
   // 审批瑞都日报
-  approveRdDailyReport: async (data: IotRdDailyReportVO) => {
+  approveRdDailyReport: async (data: any) => {
     return await request.put({ url: `/pms/iot-rd-daily-report/approval`, data })
   },
 

+ 5 - 0
src/utils/formatTime.ts

@@ -438,3 +438,8 @@ export function getDateRange(
     dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
   ]
 }
+
+export function formatDateNoTime(timestamp?: number) {
+  if (!timestamp) return ''
+  return dayjs(timestamp).format('YYYY-MM-DD')
+}

+ 37 - 48
src/views/pms/iotrddailyreport/fillDailyReport.vue

@@ -5,18 +5,12 @@ import { useUserStore } from '@/store/modules/user'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
-import IotRdDailyReportForm from './IotRdDailyReportForm.vue'
 import dayjs from 'dayjs'
-// import download from '@/utils/download'
+import rdForm from './rd-form.vue'
 
 defineOptions({ name: 'FillDailyReport' })
 
-// const { t } = useI18n()
-
-const router = useRouter()
-// const route = useRoute()
-
-// const message = useMessage()
+// const router = useRouter()
 
 const id = useUserStore().getUser.deptId
 
@@ -119,28 +113,23 @@ watch(
   { immediate: true }
 )
 
-const openForm = (_type: string, id?: number, istime: string = 'false') => {
-  router.push({
-    name: 'FillDailyReportForm',
-    params: { id: id, mode: 'fill' },
-    query: { istime: istime }
-  })
-}
-
-// const exportLoading = ref(false)
+// const openForm = (_type: string, id?: number, istime: string = 'false') => {
+//   router.push({
+//     name: 'FillDailyReportForm',
+//     params: { id: id, mode: 'fill' },
+//     query: { istime: istime }
+//   })
+// }
 
-// async function handleExport() {
-//   try {
-//     await message.exportConfirm()
+const visible = ref(false)
 
-//     exportLoading.value = true
-//     const res = await IotRdDailyReportApi.exportIotRdDailyReportDetails(query.value)
+const formRef = ref()
 
-//     download.excel(res, '瑞都日报明细.xlsx')
-//   } finally {
-//     exportLoading.value = false
-//   }
-// }
+function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
 
 const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
 
@@ -153,19 +142,19 @@ function realValue(type: any, value: string) {
   return option?.label || value
 }
 
-function handleDetail(id: number) {
-  try {
-    router.push({
-      name: 'FillDailyReportForm',
-      params: {
-        id: id.toString(),
-        mode: 'detail'
-      }
-    })
-  } catch (error) {
-    console.error('跳转详情页面失败:', error)
-  }
-}
+// function handleDetail(id: number) {
+//   try {
+//     router.push({
+//       name: 'FillDailyReportForm',
+//       params: {
+//         id: id.toString(),
+//         mode: 'detail'
+//       }
+//     })
+//   } catch (error) {
+//     console.error('跳转详情页面失败:', error)
+//   }
+// }
 </script>
 
 <template>
@@ -216,14 +205,14 @@ function handleDetail(id: number) {
           <Icon icon="ep:search" class="mr-5px" /> 搜索
         </el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
-        <el-button
+        <!-- <el-button
           type="primary"
           plain
           @click="openForm('create')"
           v-hasPermi="['pms:iot-rd-daily-report:create']"
         >
           <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
+        </el-button> -->
         <!-- <el-button type="success" plain @click="handleExport" :loading="exportLoading">
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button> -->
@@ -241,12 +230,12 @@ function handleDetail(id: number) {
               :height="height"
               show-border
             >
-              <zm-table-column label="操作" width="120" fixed="left">
+              <zm-table-column label="操作" width="140" fixed="left">
                 <template #default="scope">
                   <el-button
                     link
                     type="primary"
-                    @click="openForm('fill', scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'edit')"
                     v-hasPermi="['pms:iot-rd-daily-report:update']"
                     v-if="scope.row.status === 0"
                   >
@@ -255,7 +244,7 @@ function handleDetail(id: number) {
                   <el-button
                     link
                     type="success"
-                    @click="handleDetail(scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'detail')"
                     v-hasPermi="['pms:iot-rd-daily-report:query']"
                   >
                     查看
@@ -267,7 +256,7 @@ function handleDetail(id: number) {
                         ? 'success'
                         : 'warning'
                     "
-                    @click="openForm('fill', scope.row.id, 'true')"
+                    @click="handleOpenForm(scope.row.id, 'time')"
                     v-hasPermi="['pms:iot-rd-daily-report:non-productive']"
                     v-if="scope.row.auditStatus === 20"
                   >
@@ -359,8 +348,8 @@ function handleDetail(id: number) {
       </div>
     </div>
   </div>
-
-  <IotRdDailyReportForm ref="formRef" @success="loadList" />
+  <rd-form ref="formRef" :load-list="loadList" v-model:visible="visible" />
+  <!-- <IotRdDailyReportForm ref="formRef" @success="loadList" /> -->
 </template>
 
 <style scoped>

+ 46 - 35
src/views/pms/iotrddailyreport/index.vue

@@ -5,15 +5,15 @@ import { useUserStore } from '@/store/modules/user'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
-import IotRdDailyReportForm from './IotRdDailyReportForm.vue'
 import dayjs from 'dayjs'
 import download from '@/utils/download'
+import rdForm from './rd-form.vue'
 
 defineOptions({ name: 'IotRdDailyReport' })
 
 const { t } = useI18n()
 
-const router = useRouter()
+// const router = useRouter()
 const route = useRoute()
 
 const message = useMessage()
@@ -135,10 +135,10 @@ watch(
   { immediate: true }
 )
 
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
+// const formRef = ref()
+// const openForm = (type: string, id?: number) => {
+//   formRef.value.open(type, id)
+// }
 
 const exportLoading = ref(false)
 
@@ -166,31 +166,41 @@ function realValue(type: any, value: string) {
   return option?.label || value
 }
 
-function handleDetail(id: number) {
-  try {
-    router.push({
-      name: 'FillDailyReportForm',
-      params: {
-        id: id.toString(),
-        mode: 'detail'
-      }
-    })
-  } catch (error) {
-    console.error('跳转详情页面失败:', error)
-  }
-}
+// function handleDetail(id: number) {
+//   try {
+//     router.push({
+//       name: 'FillDailyReportForm',
+//       params: {
+//         id: id.toString(),
+//         mode: 'detail'
+//       }
+//     })
+//   } catch (error) {
+//     console.error('跳转详情页面失败:', error)
+//   }
+// }
+
+// function handleApprove(id: number) {
+//   try {
+//     router.push({
+//       name: 'FillDailyReportForm',
+//       params: {
+//         id: id.toString(),
+//         mode: 'approval'
+//       }
+//     })
+//   } catch (error) {
+//     console.error('跳转审批页面失败:', error)
+//   }
+// }
+
+const visible = ref(false)
 
-function handleApprove(id: number) {
-  try {
-    router.push({
-      name: 'FillDailyReportForm',
-      params: {
-        id: id.toString(),
-        mode: 'approval'
-      }
-    })
-  } catch (error) {
-    console.error('跳转审批页面失败:', error)
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
   }
 }
 </script>
@@ -243,14 +253,14 @@ function handleApprove(id: number) {
           <Icon icon="ep:search" class="mr-5px" /> 搜索
         </el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
-        <el-button
+        <!-- <el-button
           type="primary"
           plain
           @click="openForm('create')"
           v-hasPermi="['pms:iot-rd-daily-report:create']"
         >
           <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
+        </el-button> -->
         <el-button type="success" plain @click="handleExport" :loading="exportLoading">
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
@@ -367,7 +377,7 @@ function handleApprove(id: number) {
                   <el-button
                     link
                     type="success"
-                    @click="handleDetail(scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'detail')"
                     v-hasPermi="['pms:iot-rd-daily-report:query']"
                   >
                     查看
@@ -375,7 +385,7 @@ function handleApprove(id: number) {
                   <el-button
                     link
                     type="warning"
-                    @click="handleApprove(scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'approval')"
                     v-hasPermi="['pms:iot-rd-daily-report:update']"
                     v-if="scope.row.auditStatus === 10"
                   >
@@ -404,7 +414,8 @@ function handleApprove(id: number) {
     </div>
   </div>
 
-  <IotRdDailyReportForm ref="formRef" @success="loadList" />
+  <rd-form ref="formRef" :load-list="loadList" v-model:visible="visible" />
+  <!-- <IotRdDailyReportForm ref="formRef" @success="loadList" /> -->
 </template>
 
 <style scoped>

+ 1537 - 0
src/views/pms/iotrddailyreport/rd-form.vue

@@ -0,0 +1,1537 @@
+<script lang="ts" setup>
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { FormInstance, FormRules } from 'element-plus'
+import { Close, CircleCheck, Plus, Delete } from '@element-plus/icons-vue'
+import { formatDateNoTime } from '@/utils/formatTime'
+import { getStrDictOptions } from '@/utils/dict'
+import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import dayjs from 'dayjs'
+import { useDebounceFn } from '@vueuse/core'
+import { cloneDeep } from 'lodash-es'
+import { Base64 } from 'js-base64'
+
+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: '其他非生产时间' }
+]
+
+const message = useMessage()
+
+const { t } = useI18n()
+
+interface Props {
+  visible: boolean
+  loadList: () => void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  visible: false
+})
+
+const emits = defineEmits(['update:visible'])
+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' }]
+})
+
+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 handleRowValidate = (pid: number, key: string) => {
+  if (!formRef.value) return
+
+  const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
+
+  if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
+
+  formRef.value.validateField(propsToValidate)
+}
+
+const data = ref<Partial<Data>>({})
+
+const original = (): Form => ({
+  timeRange: [],
+  deviceIds: [],
+  dailyFuel: 0,
+  nextPlan: '',
+  externalRental: '',
+  malfunction: '',
+  faultDowntime: 0,
+  platformIds: [],
+  reportDetails: [],
+  reportFuels: [],
+  constructionBrief: '',
+  attachments: []
+})
+
+const opinion = ref('')
+
+const form = ref<Form>(original())
+
+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
+  })
+}
+
+// const dailyFuel = ref(0)
+
+const initDailyFuel = () => {
+  const propVal = data.value.dailyFuel
+
+  const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
+
+  if (hasPropValue) {
+    // dailyFuel.value = propVal
+    form.value.dailyFuel = propVal
+  } else {
+    const list1 = data.value.reportFuels || []
+    const list2 = data.value.reportedFuels || []
+
+    const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
+
+    form.value.reportFuels = validList.map((v) => ({
+      ...v,
+      customFuel: Number(
+        Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
+      )
+    }))
+
+    let total = 0
+    form.value.reportFuels.forEach((item) => {
+      total += item.customFuel
+    })
+    // dailyFuel.value = total
+    form.value.dailyFuel = total
+  }
+}
+
+const formatT = (arr: number[]) =>
+  `${arr[0].toString().padStart(2, '0')}:${arr[1].toString().padStart(2, '0')}`
+
+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()
+    }
+
+    if (data.value.startTime && data.value.endTime) {
+      form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
+    }
+
+    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 formLoading = ref(false)
+
+const submitForm = useDebounceFn(async function submitForm() {
+  try {
+    formLoading.value = true
+
+    const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
+
+    deleteId.forEach((o) => {
+      delete form.value[o.value]
+    })
+
+    await formRef.value?.validate()
+
+    const copyForm = cloneDeep(form.value)
+
+    const responseData: any[] = []
+
+    form.value.platformIds.forEach((pid) => {
+      const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
+        item.bizId = pid
+        return item
+      })
+
+      let platformWell = data.value.platformWell
+
+      if (platformWell === 1) {
+        platformWell = pid === data.value.id ? 1 : 2
+      }
+
+      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: data.value.auditStatus === 20 ? 'Y' : ''
+      }
+
+      responseData.push(platformData)
+    })
+
+    await IotRdDailyReportApi.saveBatch(responseData)
+    message.success(t('common.updateSuccess'))
+    emits('update:visible', false)
+    props.loadList()
+  } catch (error) {
+    console.log('提交失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+})
+
+async function submitApprovalForm(auditStatus: number) {
+  try {
+    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('审批驳回')
+    }
+    emits('update:visible', false)
+    props.loadList()
+  } catch (error) {
+    console.log('审批失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
+  formType.value = type
+  form.value = original()
+  emits('update:visible', true)
+  loadDetail(id).then(() => {
+    nextTick(() => formRef.value?.clearValidate())
+  })
+}
+
+function handleCloseForm() {
+  emits('update:visible', false)
+}
+
+defineExpose({ handleOpenForm })
+
+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
+    }
+
+    if (formType.value === 'approval' && key === 'button') {
+      return data.value.auditStatus !== 10
+    }
+
+    if (
+      formType.value === 'time' &&
+      (NON_PROD_FIELDS.some((field) => field.key === key) ||
+        key === 'constructionBrief' ||
+        key === 'otherNptReason')
+    ) {
+      return false
+    }
+
+    if (formType.value === 'time' && key === 'button') {
+      return false
+    }
+
+    return true
+  }
+
+  if (formType.value === 'edit') {
+    return data.value.status !== 0
+  }
+
+  return false
+})
+
+const header = computed(function () {
+  const suffix =
+    formType.value === 'edit'
+      ? '日报填报'
+      : formType.value === 'approval'
+        ? '日报审批'
+        : formType.value === 'time'
+          ? '日报时效'
+          : '日报详情'
+
+  let title: string = ''
+
+  if (data.value.platformWell === 1) {
+    const platformSource: Platform[] = data.value.platforms || data.value.finishedPlatforms || []
+
+    if (platformSource.length > 0) {
+      const isMainWellInPlatforms = platformSource.find(
+        (platform) => platform.id === data.value.taskId
+      )
+
+      if (!isMainWellInPlatforms) {
+        title = platformSource[0].wellName ?? ''
+      } else title = isMainWellInPlatforms.wellName ?? ''
+    }
+  } else title = data.value.wellName ?? ''
+
+  return { title, suffix, date: formatDateNoTime(data.value.constructionStartDate) }
+})
+
+const modeNotice = computed(() => {
+  if (formType.value === 'approval') {
+    return '审批模式:所有字段均为只读'
+  } else if (formType.value === 'detail') {
+    return '详情模式:所有字段均为只读'
+  } else if (formType.value === 'time') {
+    return '时效模式:非生产时间、当日生产简报可编辑'
+  }
+  return ''
+})
+
+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 progressList = computed(() => {
+  return data.value.taskProgresses ?? []
+})
+
+const deviceOptions = computed(() => {
+  return data.value.selectedDevices ?? []
+})
+
+const noSelectedDevices = computed(() => {
+  if (!deviceOptions.value) return []
+
+  return deviceOptions.value.filter((item) => !form.value.deviceIds.includes(item.id))
+})
+
+const wellOptions = computed(() => {
+  return (
+    data.value.platforms?.map((v) => ({
+      label: v.wellName,
+      value: v.reportId
+    })) ?? []
+  )
+})
+
+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
+})
+
+function handleTechniqueChange(val: string[], platformId: number) {
+  if (!val || val.length === 0) {
+    if (form.value[platformId]) form.value[platformId].extProperty = []
+    return
+  }
+
+  IotDailyReportAttrsApi.dailyReportAttrs({ techniqueIds: val.join(',') }).then((res) => {
+    const newData = res || []
+    const currentExtProps = form.value[platformId].extProperty
+
+    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
+      }
+    })
+
+    form.value[platformId].extProperty = mergedData
+  })
+}
+
+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: ''
+  })
+}
+
+const removeReportDetailRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+  }
+
+  form.value.reportDetails?.splice(index, 1)
+}
+
+const calculateDuration = (row: any) => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0
+    return
+  }
+
+  const todayStr = dayjs().format('YYYY-MM-DD')
+  const start = dayjs(`${todayStr} ${row.startTime}`)
+  const end = dayjs(`${todayStr} ${row.endTime}`)
+
+  let diffMinutes = end.diff(start, 'minute')
+
+  if (diffMinutes < 0) {
+    diffMinutes += 1440
+  }
+
+  row.duration = Number((diffMinutes / 60).toFixed(2))
+}
+
+const handleListChange = useDebounceFn(() => {
+  let total = 0
+  form.value.reportFuels.forEach((item) => {
+    total += item.customFuel
+  })
+  form.value.dailyFuel = total
+}, 500)
+
+const platformWorkloadData = computed(() => {
+  if (!data.value) return []
+  // 需要调整
+  return data.value.platforms || data.value.finishedPlatforms || []
+})
+
+const getWorkloadColumns = () => {
+  const dataSource = platformWorkloadData.value
+  if (!dataSource?.length) return []
+
+  const columnMap = new Map()
+
+  dataSource.forEach((platform) => {
+    platform.extProperty?.forEach((extProp) => {
+      const { identifier, name, unit } = extProp
+
+      if (!columnMap.has(identifier)) {
+        columnMap.set(identifier, {
+          prop: identifier,
+          label: unit ? `${name}(${unit})` : name
+        })
+      }
+    })
+  })
+
+  return Array.from(columnMap.values())
+}
+
+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 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 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: data.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath, //使用正确的 filePath
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
+        }
+
+        // 添加到附件列表
+        if (!form.value.attachments) {
+          form.value.attachments = []
+        }
+        form.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 (form.value.attachments && form.value.attachments.length > index) {
+    form.value.attachments.splice(index, 1)
+  }
+}
+
+const inContent = async (attachment) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
+    return
+  }
+
+  try {
+    const filePath = attachment.filePath
+    const encodedPath = encodeURIComponent(Base64.encode(filePath))
+
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+  } catch (error) {
+    console.error('预览附件失败:', error)
+    message.error('预览附件失败')
+  }
+}
+</script>
+
+<template>
+  <el-drawer
+    ref="DrawerRef"
+    :model-value="visible"
+    @update:model-value="emits('update:visible', $event)"
+    header-class="mb-0!"
+    :with-header="false"
+    size="50%"
+    :close-on-press-escape="false"
+    :close-on-click-modal="false"
+  >
+    <template #default>
+      <div id="rd-form-content" v-loading="loading" class="size-full flex flex-col gap-4">
+        <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>
+
+          <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>
+
+          <div class="info-item">
+            <label>合同号</label>
+            <div>{{ data.contractName || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>井号</label>
+            <div class="text-primary font-bold">
+              {{ data.wellName || data.taskName || '-' }}
+            </div>
+          </div>
+
+          <!-- 第二行 -->
+          <div class="info-item">
+            <label>施工队伍</label>
+            <div>{{ data.deptName || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>施工地点</label>
+            <div>{{ data.location || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>工艺</label>
+            <div>{{ data.techniqueNames || '-' }}</div>
+          </div>
+
+          <!-- 第三行 -->
+          <div class="info-item">
+            <label>设计工作量</label>
+            <div class="font-mono text-gray-700">{{ data.workloadDesign || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>开工日期</label>
+            <div class="font-mono text-gray-700">{{ data.commencementDate || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>完工日期</label>
+            <div class="font-mono text-gray-700">{{ data.completionDate || '-' }}</div>
+          </div>
+
+          <!-- 第四行 -->
+          <div class="info-item">
+            <label>施工周期 (D)</label>
+            <div>{{ data.constructionPeriod ?? '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>停待时间 (D)</label>
+            <div>
+              {{ data.idleTime ?? '-' }}
+            </div>
+          </div>
+
+          <div class="info-item">
+            <label>带班干部</label>
+            <div>{{ data.responsiblePersonNames || '-' }}</div>
+          </div>
+
+          <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>
+
+              <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>
+
+              <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>
+
+              <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>
+        <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"
+          :rules="rules"
+          label-position="top"
+          :model="form"
+          require-asterisk-position="right"
+          class="flex flex-col"
+          :disabled="formDisabled()"
+        >
+          <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>
+            <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>
+
+            <el-form-item label="附件">
+              <FileUpload
+                v-if="formType === 'edit'"
+                ref="fileUploadRef"
+                :device-id="undefined"
+                :show-folder-button="false"
+                @upload-success="handleUploadSuccess"
+              />
+
+              <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 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 }"
+                        resize="none"
+                        show-word-limit
+                        :maxlength="1000"
+                        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="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>
+          </div>
+          <el-form-item
+            class="mt-4 mb-0!"
+            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 in form.platformIds" :key="pid">
+            <el-divider 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-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>
+        <el-backtop target=".el-drawer__body" :right="100" :bottom="100" :visibility-height="40" />
+      </div>
+    </template>
+    <template #footer>
+      <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="emits('update:visible', false)">取 消</el-button>
+      </div>
+      <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="emits('update:visible', false)">取 消</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<style scoped>
+.info-item {
+  display: flex;
+  flex-direction: column;
+  gap: 0.25rem;
+
+  label {
+    font-size: 0.75rem;
+    font-weight: 500;
+    line-height: 1rem;
+    color: #9ca3af;
+  }
+
+  > 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__label) {
+  font-weight: 500;
+}
+
+:deep(.el-scrollbar__bar.is-horizontal) {
+  height: 4px;
+}
+</style>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików