Zimo hace 3 días
padre
commit
3a913e7f3e

+ 487 - 0
src/components/ZmUpload/index.vue

@@ -0,0 +1,487 @@
+<template>
+  <div class="w-full max-w-1200px mx-auto p-4 flex flex-col h-[85vh] box-border">
+    <div
+      class="relative mb-6 p-8 border-2 border-dashed rounded-xl transition-all duration-300 group"
+      :class="[
+        isDragOver
+          ? 'border-primary bg-primary-50 scale-[1.01]'
+          : 'border-gray-300 bg-white hover:border-primary-300',
+        uploading ? 'opacity-80 pointer-events-none' : ''
+      ]"
+      @dragover.prevent="isDragOver = true"
+      @dragleave.prevent="isDragOver = false"
+      @drop.prevent="handleDrop"
+    >
+      <div class="flex flex-col items-center justify-center gap-4 text-center">
+        <div class="p-4 rounded-full bg-gray-50 group-hover:bg-white transition-colors">
+          <el-icon
+            class="text-5xl text-gray-400 group-hover:text-primary transition-colors duration-300"
+            ><UploadFilled
+          /></el-icon>
+        </div>
+
+        <div class="text-gray-500">
+          <p class="text-lg font-medium mb-1">拖拽文件到此处,或点击下方按钮</p>
+          <p class="text-xs text-gray-400">{{ uploadHintText }}</p>
+        </div>
+
+        <div class="flex gap-4 mt-2">
+          <el-button
+            type="primary"
+            size="large"
+            round
+            :loading="uploading"
+            @click="handleFileUploadClick"
+            class="!px-8 shadow-md hover:shadow-lg transition-shadow"
+          >
+            <el-icon class="mr-2"><Document /></el-icon>
+            {{ uploading ? '上传中...' : '选择文件' }}
+          </el-button>
+
+          <el-button
+            v-if="showFolderButton"
+            type="success"
+            size="large"
+            round
+            plain
+            :loading="uploading"
+            @click="handleFolderUploadClick"
+            class="!px-8 shadow-md hover:shadow-lg transition-shadow"
+          >
+            <el-icon class="mr-2"><Folder /></el-icon>
+            文件夹上传
+          </el-button>
+        </div>
+      </div>
+
+      <input
+        ref="fileInput"
+        type="file"
+        class="hidden"
+        :multiple="true"
+        accept="*"
+        :webkitdirectory="isFolderMode"
+        :directory="isFolderMode"
+        @change="handleFileChange"
+      />
+    </div>
+
+    <div
+      v-if="uploadList.length > 0"
+      class="flex-1 overflow-hidden flex flex-col bg-white rounded-xl shadow-sm border border-gray-100"
+    >
+      <div class="p-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
+        <h3 class="font-bold text-gray-700 flex items-center gap-2">
+          <el-icon><List /></el-icon> 上传列表
+          <span
+            class="text-xs font-normal text-gray-400 bg-white px-2 py-0.5 rounded-full border border-gray-200"
+          >
+            {{ uploadList.length }} 项
+          </span>
+        </h3>
+        <el-button v-if="uploading" link type="danger" @click="cancelAll">全部取消</el-button>
+      </div>
+
+      <div class="flex-1 overflow-y-auto custom-scrollbar p-2">
+        <el-table :data="uploadList" style="width: 100%" :show-header="true" class="custom-table">
+          <el-table-column label="名称" min-width="250">
+            <template #default="{ row }">
+              <div class="flex items-center gap-3">
+                <div
+                  class="w-10 h-10 rounded-lg flex items-center justify-center bg-gray-50 text-xl shrink-0"
+                >
+                  <el-icon v-if="row.type === 'folder'" class="text-yellow-500"><Folder /></el-icon>
+                  <el-icon v-else class="text-blue-500"><Document /></el-icon>
+                </div>
+                <div class="flex flex-col overflow-hidden">
+                  <span class="truncate font-medium text-gray-700" :title="row.name">{{
+                    row.name
+                  }}</span>
+                  <span class="text-xs text-gray-400">{{ formatSize(null, null, row.size) }}</span>
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="状态" width="300">
+            <template #default="{ row }">
+              <div class="pr-6">
+                <div class="flex justify-between text-xs mb-1">
+                  <span :class="getStatusColor(row.status)">{{ getStatusText(row.status) }}</span>
+                  <span class="text-gray-400">{{ row.progress }}%</span>
+                </div>
+                <el-progress
+                  :percentage="row.progress"
+                  :status="getProgressStatus(row.status)"
+                  :stroke-width="6"
+                  :show-text="false"
+                  class="!m-0"
+                />
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="操作" width="100" align="center">
+            <template #default="{ row }">
+              <el-button
+                v-if="row.status === 'uploading' || row.status === 'wait'"
+                circle
+                size="small"
+                type="danger"
+                plain
+                @click.stop="handleCancelUpload(row)"
+              >
+                <el-icon><Close /></el-icon>
+              </el-button>
+
+              <el-icon v-else-if="row.status === 'success'" class="text-green-500 text-lg"
+                ><CircleCheckFilled
+              /></el-icon>
+              <el-icon v-else-if="row.status === 'error'" class="text-red-500 text-lg"
+                ><CircleCloseFilled
+              /></el-icon>
+              <span v-else-if="row.status === 'cancelled'" class="text-xs text-gray-400"
+                >已取消</span
+              >
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, nextTick } from 'vue'
+import axios from 'axios'
+import { ElMessage, ElNotification } from 'element-plus'
+import {
+  UploadFilled,
+  Folder,
+  Document,
+  Close,
+  CircleCheckFilled,
+  CircleCloseFilled,
+  List
+} from '@element-plus/icons-vue'
+
+// --- Props 定义 ---
+const props = defineProps({
+  deviceId: { type: String, default: '' },
+  allowFolderUpload: { type: Boolean, default: true },
+  showFolderButton: { type: Boolean, default: true },
+  maxFolderSize: { type: Number, default: 300 }, // MB
+  uploadUrl: {
+    type: String,
+    default: import.meta.env.VITE_BASE_URL + '/admin-api/rq/file/upload'
+  }
+})
+
+// --- Events ---
+const emit = defineEmits(['uploadSuccess', 'uploadError', 'uploadComplete'])
+
+// --- State ---
+const fileInput = ref(null)
+const uploading = ref(false)
+const uploadList = ref([])
+const isFolderMode = ref(false)
+const isDragOver = ref(false)
+const uploadControllers = ref(new Map()) // 存储 AbortController 用于取消 axios 请求
+
+// --- Computed ---
+const uploadHintText = computed(() => {
+  return props.showFolderButton
+    ? '支持单个文件 (Max 50MB) 或整个文件夹上传 (Max 500MB)'
+    : '单个文件大小不能超过 50MB'
+})
+
+// --- Helpers ---
+const getProgressStatus = (status) => {
+  const map = { success: 'success', error: 'exception', uploading: '', wait: 'warning' }
+  return map[status] || ''
+}
+
+const getStatusColor = (status) => {
+  const map = {
+    success: 'text-green-500',
+    error: 'text-red-500',
+    uploading: 'text-primary',
+    wait: 'text-gray-400',
+    cancelled: 'text-gray-400'
+  }
+  return map[status]
+}
+
+const getStatusText = (status) => {
+  const map = {
+    success: '上传成功',
+    error: '上传失败',
+    uploading: '正在上传...',
+    wait: '等待中',
+    cancelled: '已取消'
+  }
+  return map[status]
+}
+
+const formatSize = (row, column, bytes) => {
+  if (bytes === 0) return '0 B'
+  const k = 1024
+  const sizes = ['B', '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 calculateFolderSize = (files) => {
+  return files.reduce((acc, file) => acc + file.size, 0)
+}
+
+// --- Actions ---
+
+// 点击上传文件
+const handleFileUploadClick = () => {
+  if (uploading.value) return
+  isFolderMode.value = false
+  triggerInput()
+}
+
+// 点击上传文件夹
+const handleFolderUploadClick = () => {
+  if (uploading.value) return
+  if (!props.allowFolderUpload) {
+    ElMessage.warning('当前不允许上传文件夹')
+    return
+  }
+  isFolderMode.value = true
+  triggerInput()
+}
+
+const triggerInput = () => {
+  if (fileInput.value) fileInput.value.value = ''
+  nextTick(() => fileInput.value.click())
+}
+
+// 拖拽处理 (默认作为普通文件处理,浏览器对于拖拽文件夹有安全限制,这里简化处理)
+const handleDrop = (e) => {
+  isDragOver.value = false
+  if (uploading.value) return
+
+  const files = Array.from(e.dataTransfer.files)
+  if (files.length === 0) return
+
+  // 拖拽模式默认为文件模式处理
+  isFolderMode.value = false
+  processFiles(files)
+}
+
+// Input Change 处理
+const handleFileChange = (e) => {
+  const files = Array.from(e.target.files)
+  if (files.length === 0) return
+  e.target.value = '' // Reset input
+  processFiles(files)
+}
+
+// 统一文件处理逻辑
+const processFiles = async (files) => {
+  // 判断是否包含文件夹路径 (通过 input webkitdirectory 获取的文件会有 webkitRelativePath)
+  const hasRelativePath =
+    isFolderMode.value &&
+    files.some((f) => f.webkitRelativePath && f.webkitRelativePath.includes('/'))
+
+  if (hasRelativePath) {
+    // --- 文件夹模式 ---
+    const folderMap = new Map()
+
+    files.forEach((file) => {
+      const path = file.webkitRelativePath
+      const rootFolder = path.substring(0, path.indexOf('/'))
+      if (!folderMap.has(rootFolder)) {
+        folderMap.set(rootFolder, [])
+      }
+      folderMap.get(rootFolder).push(file)
+    })
+
+    // 校验大小并加入列表
+    for (const [folderName, folderFiles] of folderMap) {
+      const size = calculateFolderSize(folderFiles)
+      if (size / 1024 / 1024 > props.maxFolderSize) {
+        ElNotification({
+          title: '超限警告',
+          message: `文件夹 "${folderName}" 超过 ${props.maxFolderSize}MB`,
+          type: 'warning'
+        })
+        continue
+      }
+
+      addToUploadList({
+        name: folderName,
+        size: size,
+        type: 'folder',
+        files: folderFiles
+      })
+    }
+  } else {
+    // --- 普通文件模式 ---
+    files.forEach((file) => {
+      addToUploadList({
+        name: file.name,
+        size: file.size,
+        type: 'file',
+        files: [file]
+      })
+    })
+  }
+
+  await startUploadQueue()
+}
+
+const addToUploadList = (item) => {
+  uploadList.value.push({
+    uid: Date.now() + Math.random().toString(36).substr(2, 9),
+    progress: 0,
+    status: 'wait',
+    ...item
+  })
+}
+
+// --- Upload Logic (Axios) ---
+const startUploadQueue = async () => {
+  if (uploading.value) return // 避免重复触发
+  uploading.value = true
+
+  // 获取所有等待中的任务
+  const pendingItems = uploadList.value.filter((item) => item.status === 'wait')
+
+  // 并发上传控制(这里简化为全部并发,实际生产环境可能需要 p-limit)
+  const promises = pendingItems.map((item) => uploadSingleItem(item))
+
+  await Promise.allSettled(promises)
+
+  // 检查是否全部结束
+  checkAllComplete()
+}
+
+const uploadSingleItem = async (item) => {
+  const index = uploadList.value.findIndex((x) => x.uid === item.uid)
+  if (index === -1 || item.status === 'cancelled') return
+
+  // 更新状态
+  uploadList.value[index].status = 'uploading'
+
+  // 构建 FormData
+  const formData = new FormData()
+  item.files.forEach((file) => {
+    // 如果是文件夹上传,保留相对路径,否则使用文件名
+    const filename =
+      item.type === 'folder' && file.webkitRelativePath ? file.webkitRelativePath : file.name
+    formData.append('files', file, filename)
+  })
+
+  if (item.type === 'folder') {
+    formData.append('isFolder', 'true')
+    formData.append('folderPath', item.name)
+  }
+
+  // 创建 CancelToken
+  const controller = new AbortController()
+  uploadControllers.value.set(item.uid, controller)
+
+  try {
+    const response = await axios.post(props.uploadUrl, formData, {
+      headers: {
+        'tenant-id': 1,
+        'device-id': props.deviceId,
+        'Content-Type': 'multipart/form-data'
+      },
+      signal: controller.signal,
+      onUploadProgress: (progressEvent) => {
+        if (progressEvent.total) {
+          const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
+          // 保持响应式更新
+          if (uploadList.value[index]) {
+            uploadList.value[index].progress = percent
+          }
+        }
+      }
+    })
+
+    // 成功处理
+    if (uploadList.value[index]) {
+      uploadList.value[index].status = 'success'
+      uploadList.value[index].progress = 100
+      emit('uploadSuccess', { uid: item.uid, name: item.name, response: response.data })
+    }
+  } catch (error) {
+    if (axios.isCancel(error)) {
+      if (uploadList.value[index]) uploadList.value[index].status = 'cancelled'
+    } else {
+      console.error('Upload Error:', error)
+      if (uploadList.value[index]) uploadList.value[index].status = 'error'
+      emit('uploadError', { uid: item.uid, name: item.name, error })
+    }
+  } finally {
+    uploadControllers.value.delete(item.uid)
+  }
+}
+
+const handleCancelUpload = (item) => {
+  const controller = uploadControllers.value.get(item.uid)
+  if (controller) {
+    controller.abort()
+  } else {
+    // 如果还没开始上传(在wait状态),直接标记取消
+    const index = uploadList.value.findIndex((x) => x.uid === item.uid)
+    if (index !== -1) uploadList.value[index].status = 'cancelled'
+  }
+}
+
+const cancelAll = () => {
+  uploadList.value.forEach((item) => {
+    if (item.status === 'uploading' || item.status === 'wait') {
+      handleCancelUpload(item)
+    }
+  })
+}
+
+const checkAllComplete = () => {
+  const hasPending = uploadList.value.some((item) => ['wait', 'uploading'].includes(item.status))
+  if (!hasPending) {
+    uploading.value = false
+    emit('uploadComplete')
+  }
+}
+</script>
+
+<style scoped>
+/* 自定义滚动条样式 */
+.custom-scrollbar::-webkit-scrollbar {
+  width: 6px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+  background: #d1d5db;
+  border-radius: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+  background: #9ca3af;
+}
+
+/* 覆盖 Element 表格的一些默认样式以适配 UnoCSS 风格 */
+:deep(.custom-table .el-table__inner-wrapper::before) {
+  display: none; /* 移除表格底部边框 */
+}
+
+:deep(.custom-table .el-table__cell) {
+  border-bottom: 1px solid #f3f4f6;
+}
+
+:deep(.el-progress-bar__inner) {
+  transition: width 0.3s ease;
+}
+</style>

+ 4 - 1
src/views/pms/iotrddailyreport/rd-form.vue

@@ -158,7 +158,10 @@ 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' }]
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+  ]
 })
 
 function noProductionTimeRule(id: number) {

+ 218 - 166
src/views/pms/iotrydailyreport/ry-form.vue

@@ -4,6 +4,8 @@ import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { FormInstance, FormRules } from 'element-plus'
 import { computed, reactive, ref, watch, nextTick } from 'vue'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import dayjs from 'dayjs'
 
 interface Props {
   visible: boolean
@@ -35,14 +37,13 @@ const NON_PROD_FIELDS = [
   { key: 'otherNptTime', label: '其他非生产时间' }
 ] as const
 
-interface ProductionStatus {
-  date: string
-  start_time: string
-  end_time: string
+interface ReportDetail {
+  startTime: string
+  endTime: string
   duration: number
-  endTimeWellDepth: number
-  operatingMode: string
-  detail: string
+  currentDepth: number
+  currentOperation: string
+  constructionDetail: string
 }
 
 interface FormOriginal {
@@ -85,8 +86,9 @@ interface FormOriginal {
 
   // 其他非生产时间原因(仅作为备注字段存在)
   otherNptReason: string
+  constructionBrief: string
 
-  productionStatus: ProductionStatus[]
+  reportDetails: ReportDetail[]
   remark: string
   createTime: string
   opinion: string
@@ -118,11 +120,10 @@ const FORM_KEYS: (keyof FormOriginal)[] = [
   'drillingWorkingTime',
   'otherProductionTime',
   'lastCurrentDepth',
-
-  // 'productionStatus',
+  'constructionBrief',
   'remark',
   'createTime',
-
+  'reportDetails',
   'opinion',
   'repairTime',
   'selfStopTime',
@@ -182,10 +183,14 @@ async function loadDetail(id: number) {
       }
     })
 
-    form.value.productionStatus = []
+    form.value.reportDetails = res.reportDetails ? (res.reportDetails as any[]) : []
+
+    if (!form.value.reportDetails.length) {
+      addProductionStatusRow()
+    }
 
     if (props.type === 'edit') {
-      form.value.currentDepth = form.value.productionStatus.at(-1)?.endTimeWellDepth
+      form.value.currentDepth = form.value.reportDetails.at(-1)?.currentDepth
     }
 
     if (props.type === 'edit' && !props.noValidateStatus && res.status !== 0)
@@ -198,28 +203,31 @@ async function loadDetail(id: number) {
 }
 
 const addProductionStatusRow = () => {
-  if (!form.value.productionStatus) {
-    form.value.productionStatus = []
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = []
   }
-  form.value.productionStatus.push({
-    date: '',
-    start_time: '',
-    end_time: '',
+  form.value.reportDetails.push({
+    startTime: '',
+    endTime: '',
     duration: 0,
-    endTimeWellDepth: 0,
-    operatingMode: '',
-    detail: ''
+    currentDepth: 0,
+    currentOperation: '',
+    constructionDetail: ''
   })
 }
 
 const removeProductionStatusRow = (index: number) => {
-  form.value.productionStatus?.splice(index, 1)
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+  }
+  form.value.reportDetails?.splice(index, 1)
 }
 
-const { ZmTable, ZmTableColumn } = useTableComponents<ProductionStatus>()
+const { ZmTable, ZmTableColumn } = useTableComponents<ReportDetail>()
 
 function handleOpenForm(id: number, type: 'edit' | 'readonly') {
   formType.value = type
+  form.value.reportDetails = []
   emits('update:visible', true)
   loadDetail(id).then(() => {
     nextTick(() => formRef.value?.clearValidate())
@@ -295,7 +303,11 @@ const rules = reactive<FormRules>({
     { required: true, message: '请输入当前深度', trigger: ['change', 'blur'] },
     { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
   ],
-  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
+  // productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+  ],
 
   // 生产时间绑定校验
   drillingWorkingTime: [
@@ -391,6 +403,25 @@ const orange = computed(() => {
   if (Math.abs(total - 24) > 0.01) return true
   return false
 })
+
+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))
+}
 </script>
 
 <template>
@@ -399,6 +430,8 @@ const orange = computed(() => {
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
     size="60%"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
   >
     <template #header>
       <span class="text-xl font-bold text-[var(--el-text-color-primary)]">
@@ -619,7 +652,7 @@ const orange = computed(() => {
         </el-form-item>
 
         <div class="col-span-2">
-          <div class="flex items-center justify-between mb-2">
+          <div class="flex items-center justify-between mb-6">
             <div class="flex items-center gap-2">
               <div class="bg-[var(--el-color-primary)] w-1 h-5 rounded-full"></div>
               <div class="text-lg font-medium text-[var(--el-text-color-primary)]">生产动态</div>
@@ -627,158 +660,177 @@ const orange = computed(() => {
             <el-button
               type="primary"
               link
-              icon="ep:plus"
+              :icon="Plus"
               @click="addProductionStatusRow"
               :disabled="isMainFieldDisabled"
             >
               添加一行
             </el-button>
           </div>
-
-          <ZmTable :data="form.productionStatus!" :loading="false" class="mb-4">
-            <ZmTableColumn label="日期" min-width="140" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item
-                  :prop="`productionStatus.${$index}.date`"
-                  :rules="{ required: true, message: '请选择日期', trigger: 'change' }"
-                  class="mb-0!"
-                >
-                  <el-date-picker
-                    v-model="row.date"
-                    type="date"
-                    value-format="YYYY-MM-DD"
-                    placeholder="选择日期"
-                    :clearable="false"
-                    class="!w-full"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="开始时间" min-width="120" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item
-                  :prop="`productionStatus.${$index}.start_time`"
-                  :rules="{ required: true, message: '必填', trigger: 'change' }"
-                  class="mb-0!"
-                >
-                  <el-time-picker
-                    v-model="row.start_time"
-                    format="HH:mm"
-                    value-format="HH:mm"
-                    placeholder="开始"
-                    class="!w-full"
-                    :clearable="false"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="结束时间" min-width="120" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item
-                  :prop="`productionStatus.${$index}.end_time`"
-                  :rules="{ required: true, message: '必填', trigger: 'change' }"
-                  class="mb-0!"
-                >
-                  <el-time-picker
-                    v-model="row.end_time"
-                    format="HH:mm"
-                    value-format="HH:mm"
-                    placeholder="结束"
-                    class="!w-full"
-                    :clearable="false"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="时长(h)" min-width="100" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item :prop="`productionStatus.${$index}.duration`" class="mb-0!">
-                  <el-input-number
-                    v-model="row.duration"
-                    :min="0"
-                    :controls="false"
-                    class="!w-full"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="工况" min-width="120" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item
-                  :prop="`productionStatus.${$index}.operatingMode`"
-                  :rules="{ required: true, message: '必填', trigger: 'blur' }"
-                  class="mb-0!"
-                >
-                  <el-input
-                    v-model="row.operatingMode"
-                    placeholder="请输入工况"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="结束井深(m)" min-width="120" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item :prop="`productionStatus.${$index}.endTimeWellDepth`" class="mb-0!">
-                  <el-input-number
-                    v-model="row.endTimeWellDepth"
-                    :min="0"
-                    :controls="false"
-                    class="!w-full"
+          <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 :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', 'blur']
+                    }"
+                  >
+                    <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', 'blur']
+                    }"
+                  >
+                    <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="工况" min-width="120">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.currentOperation`"
+                    :rules="{ required: true, message: '请输入工况', trigger: ['change', 'blur'] }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.currentOperation"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      resize="none"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入工况"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="结束井深(m)" min-width="80">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.currentDepth`"
+                    :rules="{
+                      required: true,
+                      message: '请输入结束井深',
+                      trigger: ['blur']
+                    }"
+                    class="mb-0!"
+                  >
+                    <el-input-number
+                      v-model="row.currentDepth"
+                      :min="0"
+                      :controls="false"
+                      class="!w-full"
+                      align="left"
+                      placeholder="请输入结束井深"
+                      :disabled="isMainFieldDisabled"
+                    >
+                      <template #suffix> m </template>
+                    </el-input-number>
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="详细描述" min-width="200" align="center">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.constructionDetail`"
+                    :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.constructionDetail"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      resize="none"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入详细描述"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="操作" width="80" fixed="right" align="center">
+                <template #default="{ $index }">
+                  <el-button
+                    link
+                    type="danger"
+                    :icon="Delete"
+                    @click="removeProductionStatusRow($index)"
                     :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="详细描述" min-width="200" align="center">
-              <template #default="{ row, $index }">
-                <el-form-item
-                  :prop="`productionStatus.${$index}.detail`"
-                  :rules="{ required: true, message: '必填', trigger: 'blur' }"
-                  class="mb-0!"
-                >
-                  <el-input
-                    v-model="row.detail"
-                    placeholder="生产详情"
-                    :disabled="isMainFieldDisabled"
-                  />
-                </el-form-item>
-              </template>
-            </ZmTableColumn>
-
-            <ZmTableColumn label="操作" width="60" fixed="right" align="center">
-              <template #default="{ $index }">
-                <el-button
-                  link
-                  type="danger"
-                  @click="removeProductionStatusRow($index)"
-                  :disabled="isMainFieldDisabled"
-                >
-                  删除
-                </el-button>
-              </template>
-            </ZmTableColumn>
-          </ZmTable>
+                  >
+                    删除
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </el-form-item>
         </div>
 
-        <el-form-item v-if="isApproval" class="col-span-2" label="当日生产简报" prop="remark">
+        <el-form-item
+          v-if="isApproval"
+          class="col-span-2"
+          label="当日施工简报"
+          prop="constructionBrief"
+        >
           <el-input
-            v-model="form.remark"
+            v-model="form.constructionBrief"
             type="textarea"
-            autosize
-            maxlength="1000"
+            :autosize="{ minRows: 2 }"
+            show-word-limit
+            resize="none"
+            :maxlength="1000"
+            placeholder="请输入当日施工简报"
             :disabled="formType === 'readonly'"
-            placeholder="请输入当日生产简报"
           />
         </el-form-item>