Browse Source

Merge remote-tracking branch 'origin/master'

lipenghui 1 ngày trước cách đây
mục cha
commit
e6cc5111b1

+ 64 - 59
src/api/pms/iotdailyreportattrs/index.ts

@@ -1,59 +1,64 @@
-import request from '@/config/axios'
-
-// 日报扩展模板属性 VO
-export interface IotDailyReportAttrsVO {
-  id: number // 主键id
-  templateId: number // 日报扩展模板id
-  deptId: number // 公司id
-  projectId: number // 项目id
-  projectClassification: string // 项目类别(钻井 修井 注氮 酸化压裂 ...)
-  wellType: string // 井型
-  wellCategory: string // 井别
-  technique: string // 施工工艺
-  name: string // 属性名称
-  identifier: string // 属性标识符
-  dataType: string // 数据类型(double text textarea date dropdown)
-  required: number // 是否必填(0非必填 1必填)
-  unit: string // 单位(MPa)
-  accessMode: string // 访问模式(r读  w写)
-  defaultValue: string // 默认值
-  maxValue: string // 最大值
-  minValue: string // 最小值
-  extProperty: string // 不同专业公司的扩展属性
-  sort: number // 排序值
-  remark: string // 备注
-  status: number // 状态(0启用 1禁用)
-}
-
-// 日报扩展模板属性 API
-export const IotDailyReportAttrsApi = {
-  // 查询日报扩展模板属性分页
-  getIotDailyReportAttrsPage: async (params: any) => {
-    return await request.get({ url: `/rq/iot-daily-report-attrs/page`, params })
-  },
-
-  // 查询日报扩展模板属性详情
-  getIotDailyReportAttrs: async (id: number) => {
-    return await request.get({ url: `/rq/iot-daily-report-attrs/get?id=` + id })
-  },
-
-  // 新增日报扩展模板属性
-  createIotDailyReportAttrs: async (data: IotDailyReportAttrsVO) => {
-    return await request.post({ url: `/rq/iot-daily-report-attrs/create`, data })
-  },
-
-  // 修改日报扩展模板属性
-  updateIotDailyReportAttrs: async (data: IotDailyReportAttrsVO) => {
-    return await request.put({ url: `/rq/iot-daily-report-attrs/update`, data })
-  },
-
-  // 删除日报扩展模板属性
-  deleteIotDailyReportAttrs: async (id: number) => {
-    return await request.delete({ url: `/rq/iot-daily-report-attrs/delete?id=` + id })
-  },
-
-  // 导出日报扩展模板属性 Excel
-  exportIotDailyReportAttrs: async (params) => {
-    return await request.download({ url: `/rq/iot-daily-report-attrs/export-excel`, params })
-  },
-}
+import request from '@/config/axios'
+
+// 日报扩展模板属性 VO
+export interface IotDailyReportAttrsVO {
+  id: number // 主键id
+  templateId: number // 日报扩展模板id
+  deptId: number // 公司id
+  projectId: number // 项目id
+  projectClassification: string // 项目类别(钻井 修井 注氮 酸化压裂 ...)
+  wellType: string // 井型
+  wellCategory: string // 井别
+  technique: string // 施工工艺
+  name: string // 属性名称
+  identifier: string // 属性标识符
+  dataType: string // 数据类型(double text textarea date dropdown)
+  required: number // 是否必填(0非必填 1必填)
+  unit: string // 单位(MPa)
+  accessMode: string // 访问模式(r读  w写)
+  defaultValue: string // 默认值
+  maxValue: string // 最大值
+  minValue: string // 最小值
+  extProperty: string // 不同专业公司的扩展属性
+  sort: number // 排序值
+  remark: string // 备注
+  status: number // 状态(0启用 1禁用)
+}
+
+// 日报扩展模板属性 API
+export const IotDailyReportAttrsApi = {
+  // 查询日报扩展模板属性分页
+  getIotDailyReportAttrsPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-daily-report-attrs/page`, params })
+  },
+
+  // 查询日报扩展属性列表
+  dailyReportAttrs: async (params: any) => {
+    return await request.get({ url: `/rq/iot-daily-report-attrs/dailyReportAttrs`, params })
+  },
+
+  // 查询日报扩展模板属性详情
+  getIotDailyReportAttrs: async (id: number) => {
+    return await request.get({ url: `/rq/iot-daily-report-attrs/get?id=` + id })
+  },
+
+  // 新增日报扩展模板属性
+  createIotDailyReportAttrs: async (data: IotDailyReportAttrsVO) => {
+    return await request.post({ url: `/rq/iot-daily-report-attrs/create`, data })
+  },
+
+  // 修改日报扩展模板属性
+  updateIotDailyReportAttrs: async (data: IotDailyReportAttrsVO) => {
+    return await request.put({ url: `/rq/iot-daily-report-attrs/update`, data })
+  },
+
+  // 删除日报扩展模板属性
+  deleteIotDailyReportAttrs: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-daily-report-attrs/delete?id=` + id })
+  },
+
+  // 导出日报扩展模板属性 Excel
+  exportIotDailyReportAttrs: async (params) => {
+    return await request.download({ url: `/rq/iot-daily-report-attrs/export-excel`, params })
+  },
+}

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

@@ -20,6 +20,7 @@ export interface IotMainWorkOrderVO {
   actualStartTime: Date // 实际保养开始时间
   actualEndTime: Date // 实际保养结束时间
   remark: string // 备注
+  delayReason: string // 延时原因
   status: number // 状态 0启用  1停用
   processInstanceId: string // 流程实例id
   auditStatus: number // 审批状态 未提交、审批中、审批通过、审批不通过、已取消

+ 7 - 1
src/api/pms/iotmainworkorderbom/index.ts

@@ -51,7 +51,8 @@ export interface IotMainWorkOrderBomVO {
   totalRunTime: number  // 累计运行时间
   tempTotalMileage: number  // 临时 累计运行公里数
   tempTotalRunTime: number  // 临时 累计运行时间
-
+  mainRuntime: number  // 保养时累计运行公里数
+  mainMileage: number  // 保养时累计运行时间
   // 上次保养时间 不同于自然日保养规则下的 上次保养自然日期
   lastMaintenanceDate: Date
   // 下次保养公里数
@@ -82,6 +83,11 @@ export const IotMainWorkOrderBomApi = {
     return await request.get({ url: `/pms/iot-main-work-order-bom/getWorkOrderBOMs`, params })
   },
 
+  // 获得PMS 保养工单明细BOM列表 保养时累计公里数 时长
+  maintenanceCumulativeValue: async (params: any) => {
+    return await request.get({ url: `/pms/iot-main-work-order-bom/maintenanceCumulativeValue`, params })
+  },
+
   // 查询PMS 保养计划明细BOM详情
   getIotMainWorkOrderBom: async (id: number) => {
     return await request.get({ url: `/pms/iot-main-work-order-bom/get?id=` + id })

+ 87 - 0
src/api/pms/iotrddailyreport/index.ts

@@ -0,0 +1,87 @@
+import request from '@/config/axios'
+
+// 瑞都日报 VO
+export interface IotRdDailyReportVO {
+  id: number // 主键id
+  deptId: number // 施工队伍id
+  projectId: number // 项目id
+  taskId: number // 任务id
+  projectClassification: string // 项目类别(钻井 修井 注氮 酸化压裂... )
+  techniqueIds: string // 施工工艺([123,233])
+  deviceIds: string // 施工设备([123,233])
+  startTime: string // 时间节点-开始
+  endTime: string // 时间节点-结束
+  cumulativeWorkingWell: number // 累计施工井
+  cumulativeWorkingLayers: number // 累计施工层
+  dailyPumpTrips: number // 当日泵车台次
+  dailyToolsSand: number // 当日仪表/混砂
+  runCount: number // 趟数
+  bridgePlug: number // 桥塞
+  waterVolume: number // 水方量
+  hourCount: number // 时间H
+  dailyFuel: number // 当日油耗(吨)
+  dailyPowerUsage: number // 当日用电量(kWh)
+  productionTime: number // 生产时间(H)
+  nonProductionTime: number // 非生产时间(H)
+  rdNptReason: string // 非生产时间原因
+  constructionStartDate: Date // 施工开始日期
+  constructionEndDate: Date // 施工结束日期
+  productionStatus: string // 当日生产情况生产动态
+  externalRental: string // 外租情况(可能有附件)
+  nextPlan: string // 下步工作计划
+  rdStatus: string // 施工状态(动迁上井/动迁下井/施工准备/施工...)
+  malfunction: string // 故障情况
+  faultDowntime: number // 故障误工h
+  personnel: string // 人员情况
+  totalStaffNum: number // 全员数量
+  leaveStaffNum: number // 休假人员数量
+  extProperty: string // 不同专业公司的扩展属性值
+  sort: number // 排序值
+  remark: string // 备注
+  status: number // 状态(0启用 1禁用)
+  processInstanceId: string // 流程实例id
+  auditStatus: number // 审批状态 未提交、审批中、审批通过、审批不通过、已取消
+}
+
+// 瑞都日报 API
+export const IotRdDailyReportApi = {
+  // 查询瑞都日报分页
+  getIotRdDailyReportPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/page`, params })
+  },
+
+  // 查询瑞都日报 汇总统计
+  statistics: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/statistics`, params })
+  },
+
+  // 查询瑞都日报详情
+  getIotRdDailyReport: async (id: number) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/get?id=` + id })
+  },
+
+  // 新增瑞都日报
+  createIotRdDailyReport: async (data: IotRdDailyReportVO) => {
+    return await request.post({ url: `/pms/iot-rd-daily-report/create`, data })
+  },
+
+  // 修改瑞都日报
+  updateIotRdDailyReport: async (data: IotRdDailyReportVO) => {
+    return await request.put({ url: `/pms/iot-rd-daily-report/update`, data })
+  },
+
+  // 审批瑞都日报
+  approveRdDailyReport: async (data: IotRdDailyReportVO) => {
+    return await request.put({ url: `/pms/iot-rd-daily-report/approval`, data })
+  },
+
+  // 删除瑞都日报
+  deleteIotRdDailyReport: async (id: number) => {
+    return await request.delete({ url: `/pms/iot-rd-daily-report/delete?id=` + id })
+  },
+
+  // 导出瑞都日报 Excel
+  exportIotRdDailyReport: async (params) => {
+    return await request.download({ url: `/pms/iot-rd-daily-report/export-excel`, params })
+  },
+}

+ 51 - 0
src/api/pms/iotrddailyreportitem/index.ts

@@ -0,0 +1,51 @@
+import request from '@/config/axios'
+
+// 瑞都日报 (工作量)明细 VO
+export interface IotRdDailyReportItemVO {
+  id: number // 主键id
+  reportId: number // 日报id
+  deptId: number // 施工队伍id
+  projectId: number // 项目id
+  taskId: number // 任务id
+  projectClassification: string // 项目类别(钻井 修井 注氮 酸化压裂... )
+  technique: string // 施工工艺
+  reportAttrIdentifier: string // 工作量属性标识符(bridgePlug waterVolume...)
+  reportAttrName: string // 工作量属性名称(泵车台次 仪表/混砂...)
+  reportAttrValue: number // 工作量属性值
+  sort: number // 排序值
+  remark: string // 备注
+  status: number // 状态(0启用 1禁用)
+}
+
+// 瑞都日报 (工作量)明细 API
+export const IotRdDailyReportItemApi = {
+  // 查询瑞都日报 (工作量)明细分页
+  getIotRdDailyReportItemPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report-item/page`, params })
+  },
+
+  // 查询瑞都日报 (工作量)明细详情
+  getIotRdDailyReportItem: async (id: number) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report-item/get?id=` + id })
+  },
+
+  // 新增瑞都日报 (工作量)明细
+  createIotRdDailyReportItem: async (data: IotRdDailyReportItemVO) => {
+    return await request.post({ url: `/pms/iot-rd-daily-report-item/create`, data })
+  },
+
+  // 修改瑞都日报 (工作量)明细
+  updateIotRdDailyReportItem: async (data: IotRdDailyReportItemVO) => {
+    return await request.put({ url: `/pms/iot-rd-daily-report-item/update`, data })
+  },
+
+  // 删除瑞都日报 (工作量)明细
+  deleteIotRdDailyReportItem: async (id: number) => {
+    return await request.delete({ url: `/pms/iot-rd-daily-report-item/delete?id=` + id })
+  },
+
+  // 导出瑞都日报 (工作量)明细 Excel
+  exportIotRdDailyReportItem: async (params) => {
+    return await request.download({ url: `/pms/iot-rd-daily-report-item/export-excel`, params })
+  },
+}

+ 17 - 1
src/components/UploadFile/src/FileUpload.vue

@@ -13,6 +13,7 @@
         {{ uploading ? '文件上传中...' : '选择文件' }}
       </el-button>
       <el-button
+        v-if="showFolderButton"
         type="success"
         :loading="uploading"
         @click="handleFolderUploadClick"
@@ -31,7 +32,7 @@
         :directory="isFolderMode"
         @change="handleFileChange"
       />
-      <p class="upload-hint">单个文件夹总大小不能超过500M</p>
+      <p class="upload-hint">{{ uploadHintText }}</p>
     </div>
 
     <!-- 上传列表与进度 - 保持不变 -->
@@ -142,6 +143,11 @@ const props = defineProps({
     type: Boolean,
     default: true
   },
+  // 控制文件夹按钮显示
+  showFolderButton: {
+    type: Boolean,
+    default: true
+  },
   maxFolderSize: {
     type: Number,
     default: 300
@@ -166,6 +172,16 @@ const showErrorModal = ref(false);
 const errorMessage = ref('');
 const uploadTasks = ref(new Map());
 
+// 判断父组件是否传递了 showFolderButton 属性(而非判断其值)
+const hasShowFolderButton = Object.prototype.hasOwnProperty.call(props, 'showFolderButton');
+
+// 根据是否传递属性决定显示的提示文本
+const uploadHintText = computed(() => {
+  return hasShowFolderButton
+    ? '文件大小不能超过50M'
+    : '单个文件夹总大小不能超过500M';
+});
+
 // 获取进度条状态 - 保持不变
 const getProgressStatus = (status) => {
   switch(status) {

+ 10 - 2
src/locales/en.ts

@@ -271,6 +271,8 @@ export default {
     currentOperation: 'Current Operation',
     nextPlan: 'Next Plan',
     transitTime: 'Transit Time',
+    dailyReport: 'Daily Report',
+    fillReport: 'Fill Report'
   },
   form: {
     input: 'Input',
@@ -574,6 +576,8 @@ export default {
     enterNumber: "Please fill number",
     enterContent: "Please fill content",
     exceedMax: "The input value cannot exceed{max}",
+    mainSumTime: 'Running Time(H)',
+    mainSumKil:'Running Kilometers(KM)',
   },
   modelTemplate:{
     name:'TemplateName',
@@ -949,7 +953,9 @@ export default {
     source:'Source',
     ConsumptionQuantity:'ConsumptionQuantity',
     materialGroup:'MaterialGroup',
-    groupHolder:'Please select material group'
+    groupHolder:'Please select material group',
+    delayReason: 'Delay Reason',
+    inputDelayReason: 'Please input reason'
   },
   chooseMaintain:{
     selectRepairItem:'SelectRepairItem',
@@ -1102,7 +1108,9 @@ export default {
     notGenerated: "Not Generated",
     generatedNotExecuted: "Not Executed",
     consumeMaterials: "Consumables",
-    mainStatus: "Upkeep Status"
+    mainStatus: "Upkeep Status",
+    mainRuntimeError: 'Main Runtime Error',
+    mainMileageError: 'Main Mileage Error'
   },
   inspect:{
     InspectionItems:'InspectionItems',

+ 10 - 2
src/locales/ru.ts

@@ -236,6 +236,8 @@ export default {
     currentOperation: '目前工序',
     nextPlan: '下步工序',
     transitTime: '运行时效',
+    dailyReport: '日报',
+    fillReport: '日报填报'
   },
   form: {
     input: '输入框',
@@ -516,6 +518,8 @@ export default {
     sumKil:'累计运行公里数(Km)',
     confirm:'确定',
     cancel:'取消',
+    mainSumTime: '保养时累计运行时间(H)',
+    mainSumKil:'保养时累计运行公里数(KM)',
   },
   modelTemplate:{
     name:'模板名称',
@@ -848,7 +852,9 @@ export default {
     source:'来源',
     ConsumptionQuantity:'消耗数量',
     materialGroup:'物料组',
-    groupHolder:'请选择所属物料组'
+    groupHolder:'请选择所属物料组',
+    delayReason: '延保原因',
+    inputDelayReason: '请输入延保原因'
   },
   chooseMaintain:{
     selectRepairItem:'选择维修项',
@@ -1001,7 +1007,9 @@ export default {
     notGenerated: "未生成工单",
     generatedNotExecuted: "已生成工单未执行",
     consumeMaterials: "消耗物料",
-    mainStatus: "保养状态"
+    mainStatus: "保养状态",
+    mainRuntimeError: '保养时累计运行时间错误!',
+    mainMileageError: '保养时累计运行公里数错误!'
   },
   inspect:{
     InspectionItems:'巡检项',

+ 10 - 2
src/locales/zh-CN.ts

@@ -274,6 +274,8 @@ export default {
     currentOperation: '目前工序',
     nextPlan: '下步工序',
     transitTime: '运行时效',
+    dailyReport: '日报',
+    fillReport: '日报填报'
   },
   form: {
     input: '输入框',
@@ -563,7 +565,9 @@ export default {
   operationFillForm:{
     team:'所属队伍',
     sumTime:'累计运行时间(H)',
+    mainSumTime: '保养时累计运行时间(H)',
     sumKil:'累计运行公里数(KM)',
+    mainSumKil:'保养时累计运行公里数(KM)',
     confirm:'确定',
     cancel:'取消',
     alert:'以下数值取自PLC,如有不符请修改',
@@ -946,7 +950,9 @@ export default {
     source:'来源',
     ConsumptionQuantity:'消耗数量',
     materialGroup:'物料组',
-    groupHolder:'请选择所属物料组'
+    groupHolder:'请选择所属物料组',
+    delayReason: '延时原因',
+    inputDelayReason: '请输入延时原因'
   },
   chooseMaintain:{
     selectRepairItem:'选择维修项',
@@ -1099,7 +1105,9 @@ export default {
     notGenerated: "未生成工单",
     generatedNotExecuted: "已生成工单未执行",
     consumeMaterials: "消耗物料",
-    mainStatus: "保养状态"
+    mainStatus: "保养状态",
+    mainRuntimeError: '保养时累计运行时间错误!',
+    mainMileageError: '保养时累计运行公里数错误!'
   },
   inspect:{
     InspectionItems:'巡检项',

+ 39 - 0
src/router/modules/remaining.ts

@@ -936,6 +936,45 @@ const remainingRouter: AppRouteRecordRaw[] = [
     ]
   },
 
+  {
+    path: '/iotpms/rddailyreport',
+    component: Layout,
+    name: 'RDDailyReport',
+    meta: {
+      hidden: true,
+      keepAlive: true
+    },
+    children: [
+      {
+        path: 'dailyReport',
+        component: () => import('@/views/pms/iotrddailyreport/fillDailyReport.vue'),
+        name: 'FillDailyReport',
+        meta: {
+          keepAlive: true,
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:menu',
+          title: t('project.dailyReport'),
+          activeMenu: '/dailyReport/index'
+        }
+      },
+      {
+        path: 'dailyReport/fill/:id(\\d+)/:mode',
+        component: () => import('@/views/pms/iotrddailyreport/FillDailyReportForm.vue'),
+        name: 'FillDailyReportForm',
+        meta: {
+          noCache: false,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:add',
+          title: t('project.fillReport'),
+          activeMenu: '/dailyReport/fill'
+        }
+      }
+    ]
+  },
+
   {
     path: '/iotpms/maincalendar',
     component: Layout,

+ 3 - 1
src/utils/dict.ts

@@ -296,5 +296,7 @@ export enum DICT_TYPE {
   PMS_PROJECT_NPT_REASON = 'nptReason',    // 日报 非生产时间原因
   PMS_PROJECT_RY_NPT_REASON = 'ryNptReason',  // 瑞鹰日报 非生产时间原因
   PMS_PROJECT_WELL_CONTROL_LEVEL = 'rq_iot_well_control_level',  // 井控级别
-  PMS_PROJECT_casing_pipe_size = 'rq_iot_casing_pipe_size'  // 日报 套生段产管尺寸
+  PMS_PROJECT_RD_STATUS = 'rdStatus',                  // 瑞都 施工状态
+  PMS_PROJECT_CASING_PIPE_SIZE = 'rq_iot_casing_pipe_size',    // 日报 套生段产管尺寸
+  PMS_PROJECT_RD_TECHNOLOGY = 'rq_iot_project_technology_rd'   // 瑞都施工工艺
 }

+ 26 - 2
src/views/pms/iotmainworkorder/DeviceAlarmBomList.vue

@@ -63,7 +63,13 @@
             <el-table-column :label="t('mainPlan.RunTimeCycle')" align="center" prop="nextRunningTime"
                              :width="columnWidths.nextRunningTime"/>
             <el-table-column :label="t('mainPlan.nextMaintTime')" align="center" prop="timePeriod"
-                             :width="columnWidths.timePeriod"/>
+                             :width="columnWidths.timePeriod">
+              <template #default="{ row }">
+                <span :class="{ 'negative-value': isNegative(row.timePeriod) }">
+                  {{ row.timePeriod }}
+                </span>
+              </template>
+            </el-table-column>
           </el-table-column>
 
           <!-- 里程分组列 -->
@@ -75,7 +81,13 @@
             <el-table-column :label="t('mainPlan.operatingMileageCycle')" align="center" prop="nextRunningKilometers"
                              :width="columnWidths.nextRunningKilometers"/>
             <el-table-column :label="t('mainPlan.nextMaintKil')" align="center" prop="kilometerCycle"
-                             :width="columnWidths.kilometerCycle"/>
+                             :width="columnWidths.kilometerCycle">
+              <template #default="{ row }">
+                <span :class="{ 'negative-value': isNegative(row.kilometerCycle) }">
+                  {{ row.kilometerCycle }}
+                </span>
+              </template>
+            </el-table-column>
           </el-table-column>
 
           <!-- 日期分组列 -->
@@ -472,6 +484,13 @@ const resetQuery = () => {
   handleQuery()
 }
 
+// 判断是否为负数的辅助函数
+const isNegative = (value: any): boolean => {
+  if (value === null || value === undefined || value === '') return false;
+  const num = Number(value);
+  return !isNaN(num) && num < 0;
+};
+
 // 监听分页数据变化,重新设置列宽
 watch(paginatedList, () => {
   nextTick(() => {
@@ -650,4 +669,9 @@ watch([showTimeColumns, showMileageColumns, showNaturalDateColumns], () => {
   }
 }
 
+/* 负数值样式 */
+.negative-value {
+  color: #f56c6c;
+  font-weight: 600;
+}
 </style>

+ 331 - 117
src/views/pms/iotmainworkorder/IotMainWorkOrderOptimize.vue

@@ -224,6 +224,66 @@
             {{ row.totalMileage ?? row.tempTotalMileage }}
           </template>
         </el-table-column>
+        <el-table-column :label="t('operationFillForm.mainSumTime')" align="center" prop="mainRuntime" v-if="hasTimeRuleInCurrentPage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.mainRuntime">
+          <template #default="{ row }">
+            <el-tooltip
+              :disabled="!row.mainRuntimeError"
+              :content="row.mainRuntimeError"
+              placement="top"
+              effect="light"
+              popper-class="main-runtime-tooltip"
+              :show-after="0"
+            >
+              <div class="main-runtime-input-wrapper">
+                <el-input-number
+                  v-model="row.mainRuntime"
+                  :precision="2"
+                  :min="0"
+                  :controls="false"
+                  style="width: 100%"
+                  :disabled="row.status === 1"
+                  @change="validateMainRuntime(row)"
+                  @blur="validateMainRuntime(row)"
+                  :class="{
+                    'is-required-input': row.mainRuntimeError,
+                    'error-input': row.mainRuntimeError
+                  }"
+                />
+              </div>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('operationFillForm.mainSumKil')" align="center" prop="mainMileage" v-if="hasMileageRuleInCurrentPage"
+                         :formatter="erpPriceTableColumnFormatter" :width="columnWidths.mainMileage">
+          <template #default="{ row }">
+            <el-tooltip
+              :disabled="!row.mainMileageError"
+              :content="row.mainMileageError"
+              placement="top"
+              effect="light"
+              popper-class="main-runtime-tooltip"
+              :show-after="0"
+            >
+              <div class="main-runtime-input-wrapper">
+                <el-input-number
+                  v-model="row.mainMileage"
+                  :precision="2"
+                  :min="0"
+                  :controls="false"
+                  style="width: 100%"
+                  :disabled="row.status === 1"
+                  @change="validateMainMileage(row)"
+                  @blur="validateMainMileage(row)"
+                  :class="{
+                    'is-required-input': row.mainMileageError,
+                    'error-input': row.mainMileageError
+                  }"
+                />
+              </div>
+            </el-tooltip>
+          </template>
+        </el-table-column>
         <el-table-column :label="t('mainPlan.lastMaintenanceDate')" prop="lastMaintenanceDate" :width="columnWidths.lastMaintenanceDate">
           <template #default="{ row }">
             <div class="full-content-cell">
@@ -248,7 +308,9 @@
           <el-table-column :label="t('mainPlan.remainKm')"
                            align="center" prop="remainKm" :width="columnWidths.remainKm">
             <template #default="{ row }">
-              {{ row.remainKm != null ? row.remainKm.toFixed(2) : '-' }}
+              <span :class="{ 'negative-value': row.remainKm != null && row.remainKm < 0 }">
+                {{ row.remainKm != null ? row.remainKm.toFixed(2) : '-' }}
+              </span>
             </template>
           </el-table-column>
         </el-table-column>
@@ -270,7 +332,9 @@
           <el-table-column :label="t('mainPlan.remainH')"
                            align="center" prop="remainH" :width="columnWidths.remainH">
             <template #default="{ row }">
-              {{ row.remainH != null ? row.remainH.toFixed(2) : '-' }}
+              <span :class="{ 'negative-value': row.remainH != null && row.remainH < 0 }">
+                {{ row.remainH != null ? row.remainH.toFixed(2) : '-' }}
+              </span>
             </template>
           </el-table-column>
           <el-table-column
@@ -316,38 +380,16 @@
           <el-table-column :label="t('mainPlan.remainDay')"
                            align="center" prop="remainDay" :width="columnWidths.remainDay">
             <template #default="{ row }">
-              {{ row.remainDay ?? '-' }}
+              <span :class="{ 'negative-value': row.remainDay != null && row.remainDay < 0 }">
+                {{ row.remainDay ?? '-' }}
+              </span>
             </template>
           </el-table-column>
         </el-table-column>
 
-        <!--
-        <el-table-column :label="t('iotMaintain.numberOfMaterials')" align="center" width="90">
-          <template #default="scope">
-            {{ getMaterialCount(scope.row.bomNodeId) }}
-          </template>
-        </el-table-column> -->
-
         <el-table-column :label="t('iotMaintain.operation')" align="center" prop="operation" :width="columnWidths.operation" fixed="right">
           <template #default="scope">
             <div class="horizontal-actions">
-              <!-- 查看当前保养项已经绑定的物料列表
-              <el-tooltip
-                v-if="scope.row.deviceBomMaterials && scope.row.deviceBomMaterials.length > 0"
-                effect="dark"
-                :content="t('mainPlan.deviceBomMaterials')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="View"
-                  @click="handleDropdownCommand({ action: 'deviceBomMaterials', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
-
               <!-- 延迟保养按钮 -->
               <el-tooltip
                 v-if="scope.row.status === 0"
@@ -365,38 +407,6 @@
                 />
               </el-tooltip>
 
-              <!-- 选择物料按钮
-              <el-tooltip
-                v-if="scope.row.status === 0"
-                effect="dark"
-                :content="t('stock.selectMaterial')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="Box"
-                  @click="handleDropdownCommand({ action: 'material', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
-
-              <!-- 物料详情按钮
-              <el-tooltip
-                effect="dark"
-                :content="t('bomList.materialDetail')"
-                placement="top"
-                :show-after="300"
-              >
-                <el-button
-                  type="primary"
-                  link
-                  :icon="Document"
-                  @click="handleDropdownCommand({ action: 'detail', row: scope.row })"
-                  class="action-button"
-                />
-              </el-tooltip> -->
             </div>
           </template>
         </el-table-column>
@@ -539,13 +549,6 @@
         </el-table-column>
         <el-table-column label="消耗数量" align="center" prop="quantity" width="120px">
           <template #default="{ row }">
-            <!-- 新增物料行:显示tooltip、必填红色边框、禁止输入0
-            <el-tooltip
-              effect=""
-              content=""
-              placement=""
-              :disabled="row.quantity > 0"
-            > -->
             <el-input-number
               v-model="row.quantity"
               :precision="4"
@@ -982,11 +985,6 @@ const toggleShowAllMaterials = () => {
   });
 };
 
-// 为表格行添加类名,实现高亮效果
-/* const tableRowClassName = ({ row }) => {
-  return row.isSelected ? 'highlight-row' : '';
-}; */
-
 // 分组合并计算逻辑
 const groupSpans = ref<Record<string, { span: number, index: number }>>({})
 
@@ -1276,6 +1274,20 @@ const handleSizeChange = (newSize: number) => {
 const handleStatusChange = (row: IotMainWorkOrderBomVO) => {
   // 如果是从未完成(0)切换到完成(1)
   if (row.status === 1 && row.initialStatus === 0) {
+    // 校验 mainRuntime
+    if (!validateMainRuntime(row)) {
+      message.error(`${row.deviceCode}-${formatMaintItemName(row.name)} ${t('mainPlan.mainRuntimeError')}`);
+      row.status = 0; // 重置状态
+      return;
+    }
+
+    // 校验 mainMileage
+    if (!validateMainMileage(row)) {
+      message.error(`${row.deviceCode}-${formatMaintItemName(row.name)} ${t('mainPlan.mainMileageError')}`);
+      row.status = 0; // 重置状态
+      return;
+    }
+
     // 检查消耗物料规则:如果设置为不消耗物料(rule=1),则跳过物料校验
     if (row.rule === 1) {
       console.log(`保养项 ${row.name} 设置为不消耗物料,跳过物料校验`);
@@ -1323,6 +1335,86 @@ const validateAllRunningTimes = (): boolean => {
   return isValid;
 };
 
+// 校验 mainRuntime 的方法
+const validateMainRuntime = (row: IotMainWorkOrderBomVO) => {
+  // 清除之前的错误
+  row.mainRuntimeError = '';
+
+  const mainRuntime = Number(row.mainRuntime) || 0;
+  const totalRunTime = Number(row.totalRunTime ?? row.tempTotalRunTime) || 0;
+  const lastRunningTime = Number(row.lastRunningTime) || 0;
+
+  // 如果 mainRuntime 为 0 或空,不显示错误(允许为空)如果设置了累计运行时长保养规则 mainRuntime 必填
+  /* if (!mainRuntime || mainRuntime === 0) {
+    return true;
+  } */
+
+  // 校验规则:lastRunningTime ≤ mainRuntime ≤ totalRunTime
+  if (mainRuntime < lastRunningTime) {
+    row.mainRuntimeError = `保养时累计运行时间不能小于上次保养时长(${lastRunningTime})`;
+    return false;
+  }
+
+  if (mainRuntime > totalRunTime) {
+    row.mainRuntimeError = `保养时累计运行时间不能大于累计运行时间(${totalRunTime})`;
+    return false;
+  }
+  // 校验通过时确保错误信息被清除
+  row.mainRuntimeError = '';
+  return true;
+};
+
+// 校验 mainMileage 的方法
+const validateMainMileage = (row: IotMainWorkOrderBomVO) => {
+  // 清除之前的错误
+  row.mainMileageError = '';
+
+  const mainMileage = Number(row.mainMileage) || 0;
+  const totalMileage = Number(row.totalMileage ?? row.tempTotalMileage) || 0;
+  const lastRunningKilometers = Number(row.lastRunningKilometers) || 0;
+
+  // 如果 mainMileage 为 0 或空,不显示错误(允许为空)如果设置了累计运行时长保养规则 mainMileage 必填
+  /* if (!mainMileage || mainMileage === 0) {
+    return true;
+  } */
+
+  // 校验规则:lastRunningKilometers ≤ mainMileage ≤ totalMileage
+  if (mainMileage < lastRunningKilometers) {
+    row.mainMileageError = `保养时累计运行公里数不能小于上次保养里程数(${lastRunningKilometers})`;
+    return false;
+  }
+
+  if (mainMileage > totalMileage) {
+    row.mainMileageError = `保养时累计运行公里数不能大于累计运行公里数(${totalMileage})`;
+    return false;
+  }
+  // 校验通过时确保错误信息被清除
+  row.mainMileageError = '';
+  return true;
+};
+
+// 全局校验所有 mainRuntime
+const validateAllMainRuntimes = (): boolean => {
+  let isValid = true;
+  list.value.forEach(row => {
+    if (!validateMainRuntime(row)) {
+      isValid = false;
+    }
+  });
+  return isValid;
+};
+
+// 全局校验所有 mainMileage
+const validateAllMainMileages = (): boolean => {
+  let isValid = true;
+  list.value.forEach(row => {
+    if (!validateMainMileage(row)) {
+      isValid = false;
+    }
+  });
+  return isValid;
+};
+
 /** 新增物料 */
 const handleAddMaterial = () => {
   // 检查是否选中保养项
@@ -1467,7 +1559,7 @@ const selectChoose = (selectedMaterial) => {
     }
   });
 
-  // 关键修复:立即更新当前显示的物料列表
+  // 立即更新当前显示的物料列表
   refreshCurrentBomItemMaterials();
 
   // 选择物料后立即计算费用
@@ -1783,6 +1875,18 @@ const submitForm = async () => {
     return; // 校验失败则终止提交
   }
 
+  // 保养时 累计运行时长 mainRuntime 全局校验
+  if (!validateAllMainRuntimes()) {
+    message.error(t('mainPlan.mainRuntimeError'));
+    return;
+  }
+
+  // 保养时 累计运行公里数 mainMileage 全局校验
+  if (!validateAllMainMileages()) {
+    message.error(t('mainPlan.mainMileageError'));
+    return;
+  }
+
   // 校验所有设置为完成状态的保养项的物料数据
   const invalidBomItems = [];
 
@@ -2095,6 +2199,16 @@ const calculateAllColumnsWidth = () => {
       label: t('operationFillForm.sumKil'),
       getValue: (row) => row.totalMileage ?? row.tempTotalMileage
     },
+    {
+      prop: 'mainRuntime',
+      label: t('operationFillForm.mainSumTime'),
+      getValue: (row) => row.mainRuntime
+    },
+    {
+      prop: 'mainMileage',
+      label: t('operationFillForm.mainSumKil'),
+      getValue: (row) => row.mainMileage
+    },
     { prop: 'name', label: t('bomList.bomNode') },
     { prop: 'lastMaintenanceDate', label: t('mainPlan.lastMaintenanceDate') },
     { prop: 'mileageRule', label: t('main.mileage') },
@@ -2198,6 +2312,105 @@ const tableHeaderStyle = ({ row, rowIndex }) => {
   }
 }
 
+// 更新列表数据的函数
+const updateListWithCumulativeData = (cumulativeData: any[]) => {
+  cumulativeData.forEach(item => {
+    // 根据 bomNodeId 找到对应的行
+    const targetRow = list.value.find(row => row.bomNodeId === item.bomNodeId)
+    if (targetRow) {
+      // 按照优先级设置 mainRuntime 字段的值 保养时长
+      // 优先级:mainRuntime > tempMainRunTime
+      if (item.mainRuntime && Number(item.mainRuntime) > 0) {
+        // 如果 mainRuntime 有值且大于0,使用 mainRuntime
+        targetRow.mainRuntime = Number(item.mainRuntime)
+      } else if (item.tempMainRunTime && Number(item.tempMainRunTime) > 0) {
+        // 如果 mainRuntime 没有有效值,但 tempMainRunTime 有值且大于0,使用 tempMainRunTime
+        targetRow.mainRuntime = Number(item.tempMainRunTime)
+      } else {
+        // 两个字段都没有有效值,保持原值或设置为0
+        targetRow.mainRuntime = 0
+      }
+
+      // 按照优先级设置 mainMileage 字段的值 保养里程数
+      // 优先级:mainMileage > tempMainMileage
+      if (item.mainMileage && Number(item.mainMileage) > 0) {
+        // 如果 mainMileage 有值且大于0,使用 mainMileage
+        targetRow.mainMileage = Number(item.mainMileage)
+      } else if (item.tempMainMileage && Number(item.tempMainMileage) > 0) {
+        // 如果 mainMileage 没有有效值,但 tempMainMileage 有值且大于0,使用 tempMainMileage
+        targetRow.mainMileage = Number(item.tempMainMileage)
+      } else {
+        // 两个字段都没有有效值,保持原值或设置为0
+        targetRow.mainMileage = 0
+      }
+
+      // 更新数据后立即重新校验,清除可能的错误状态
+      nextTick(() => {
+        validateMainRuntime(targetRow)
+      })
+    }
+  })
+}
+
+const fetchCumulativeData = async () => {
+  if (!formData.value.actualStartTime) {
+    console.warn('actualStartTime is empty, skip fetching cumulative data');
+    return;
+  }
+
+  try {
+    // 确保时间戳格式正确
+    const startTime = Number(formData.value.actualStartTime);
+    if (isNaN(startTime)) {
+      console.error('Invalid actualStartTime:', formData.value.actualStartTime);
+      return;
+    }
+
+    const queryParams = {
+      workOrderId: id,
+      actualStartTime: dayjs(startTime).format('YYYY-MM-DD')
+    }
+
+    console.log('Fetching cumulative data with params:', queryParams);
+
+    const response = await IotMainWorkOrderBomApi.maintenanceCumulativeValue(queryParams)
+
+    if (response && Array.isArray(response)) {
+      updateListWithCumulativeData(response);
+      // 在所有数据更新完成后,重新校验所有行的 mainRuntime
+      nextTick(() => {
+        list.value.forEach(row => {
+          // 校验 保养时累计运行时长
+          if (row.mainRuntime) {
+            validateMainRuntime(row);
+          }
+          // 校验 保养时累计运行公里数
+          if (row.mainMileage) {
+            validateMainMileage(row);
+          }
+        });
+      });
+      message.success('累计运行数据更新成功');
+    }
+  } catch (error) {
+    console.error('获取累计运行数据失败:', error);
+    message.error('获取累计运行数据失败');
+  }
+}
+
+// 监听保养开始时间变化
+watch(
+  () => formData.value.actualStartTime,
+  async (newVal, oldVal) => {
+    console.log('actualStartTime changed:', { newVal, oldVal });
+
+    if (newVal && newVal !== oldVal) {
+      await fetchCumulativeData();
+    }
+  },
+  { immediate: false, deep: true }
+);
+
 // 原有代码保持不变,新增“选择物料”按钮的点击处理方法
 const handleSelectMaterial = () => {
   // 校验是否已选中保养项(避免无选中项时点击按钮报错)
@@ -2693,49 +2906,15 @@ const handleRowClick = (row) => {
   border-bottom: none; /* 移除默认边框 */
 }
 
-/* 添加选中行高亮样式 - 增强版
-:deep(.el-table .highlight-row) {
-  background-color: #d1eaff !important;
-}
-
-:deep(.el-table .highlight-row:hover>td) {
-  background-color: #b8dfff !important;
-}
-
-:deep(.el-table .highlight-row.current-row>td) {
-  background-color: #99ccff !important;
-  border-bottom: 2px solid #409eff !important;
-} */
-
 /* 增强高亮行样式的特异性,确保覆盖Element UI的默认样式 */
 :deep(.el-table__body tr.current-row>td) {
   background-color: #d1eaff !important;
 }
 
-/*
-:deep(.el-table__body tr.highlight-row:hover td) {
-  background-color: #d9ecff !important;
-}
-
-:deep(.el-table__body tr.highlight-row.current-row td) {
-  background-color: #c6e2ff !important;
-  border-bottom: 2px solid #409eff !important;
-} */
-
-/* 针对斑马纹表格的特殊处理
-:deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped td) {
-  background-color: #d1eaff !important;
-} */
-
 :deep(.el-table__body tr.current-row:hover>td) {
   background-color: #b8dfff !important;
 }
 
-/*
-:deep(.el-table--striped .el-table__body tr.highlight-row.el-table__row--striped.current-row td) {
-  background-color: #99ccff !important;
-} */
-
 /* 物料列表操作区域样式 */
 .material-list-header {
   display: flex;
@@ -2826,8 +3005,10 @@ const handleRowClick = (row) => {
   border-color: #f56c6c !important; /* Element错误色 */
   box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.4) !important; /* 错误阴影 */
 }
-:deep(.is-required-input .el-input-number__input) {
+:deep(.is-required-input .el-input-number__input),
+:deep(.error-input .el-input-number__input) {
   border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
   box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.4) !important;
 }
 
@@ -2835,19 +3016,17 @@ const handleRowClick = (row) => {
 :deep(.is-required-input .el-input__inner:hover) {
   border-color: #f56c6c !important;
 }
-:deep(.is-required-input .el-input-number__input:hover) {
+:deep(.is-required-input .el-input-number__input:hover),
+:deep(.error-input .el-input-number__input:hover) {
   border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
 }
 
-/* Tooltip提示样式优化 */
-:deep(.el-tooltip__popper.is-warning) {
-  background-color: #fff3cd !important;
-  border-color: #ffeeba !important;
-  color: #856404 !important;
-}
-:deep(.el-tooltip__popper.is-warning .el-tooltip__arrow) {
-  border-top-color: #ffeeba !important;
-  border-bottom-color: #ffeeba !important;
+:deep(.is-required-input .el-input-number__input:focus),
+:deep(.error-input .el-input-number__input:focus) {
+  border-color: #f56c6c !important;
+  background-color: #fef0f0 !important;
+  box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.2) !important;
 }
 
 /* 状态列容器样式 - 水平排列 */
@@ -2896,4 +3075,39 @@ const handleRowClick = (row) => {
   border-color: #e6a23c;
 }
 
+/* 自定义淡红色背景的 tooltip */
+:deep(.main-runtime-tooltip) {
+  background: #fef0f0 !important;
+  border: 1px solid #fbc4c4 !important;
+  color: #f56c6c !important;
+  max-width: 300px;
+  font-size: 12px;
+  padding: 8px 12px;
+}
+
+/* 隐藏 Tooltip 菱形箭头(核心需求) */
+:deep(.main-runtime-tooltip .el-tooltip__arrow),
+:deep(.main-runtime-tooltip .el-tooltip__arrow::before) {
+  display: none !important; /* 完全隐藏箭头及伪元素 */
+  width: 0 !important;
+  height: 0 !important;
+}
+
+/* 新增包装层样式,确保tooltip正确触发 */
+.main-runtime-input-wrapper {
+  width: 100%;
+  position: relative;
+}
+
+/* 负数值红色样式 */
+:deep(.negative-value) {
+  color: #f56c6c !important;
+  font-weight: 600;
+}
+
+/* 确保在表格单元格中正确显示 */
+:deep(.el-table .cell .negative-value) {
+  display: inline-block;
+  width: 100%;
+}
 </style>

+ 136 - 2
src/views/pms/iotmainworkorder/index.vue

@@ -136,6 +136,15 @@
           </el-table-column>
           <el-table-column :label="t('iotMaintain.operation')" align="center" :width="columnWidths.operation" fixed="right">
             <template #default="scope">
+              <el-button
+                v-if="isNegativeMainDistance(scope.row.mainDistance)"
+                link
+                :type="getDelayReasonButtonType(scope.row.delayReason)"
+                @click="openDelayReasonDialog(scope.row)"
+                v-hasPermi="['pms:iot-main-work-order:update']"
+              >
+                {{ t('mainPlan.delayed') || '延时' }}
+              </el-button>
               <el-button
                 link
                 type="primary"
@@ -168,6 +177,37 @@
   </el-row>
   <!-- 表单弹窗:添加/修改 -->
   <IotMainWorkOrderForm ref="formRef" @success="getList" />
+
+  <!-- 延保原因弹窗 -->
+  <el-dialog
+    v-model="delayReasonDialogVisible"
+    :title="t('workOrderMaterial.delayReason') || '延时原因'"
+    width="500px"
+    :close-on-click-modal="false"
+  >
+    <el-form :model="delayReasonForm" label-width="0px" :rules="delayReasonRules"
+             ref="delayReasonFormRef">
+      <el-form-item label=" " prop="delayReason" class="required-item" label-width="16px">
+        <el-input
+          v-model="delayReasonForm.delayReason"
+          type="textarea"
+          :rows="4"
+          :placeholder="t('workOrderMaterial.inputDelayReason') || '请输入延时原因'"
+          maxlength="500"
+          show-word-limit
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="delayReasonDialogVisible = false">
+        {{ t('common.cancel') || '取消' }}
+      </el-button>
+      <el-button type="primary" @click="saveDelayReason" :loading="saveDelayReasonLoading">
+        {{ t('common.save') || '保存' }}
+      </el-button>
+    </template>
+  </el-dialog>
+
 </template>
 
 <script setup lang="ts">
@@ -182,6 +222,9 @@ const { push } = useRouter() // 路由跳转
 /** 保养工单 列表 */
 defineOptions({ name: 'IotMainWorkOrder' })
 
+// 表单引用
+const delayReasonFormRef = ref<InstanceType<typeof ElForm>>()
+
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const tableRef = ref() // 表格引用
@@ -219,6 +262,13 @@ const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 const hoverText = ref('');
 
+// 定义表单验证规则
+const delayReasonRules = {
+  delayReason: [
+    { required: true, message: t('workOrderMaterial.inputDelayReason') || '请输入延时原因', trigger: 'blur' }
+  ]
+}
+
 // 列宽度配置
 const columnWidths = ref({
   serial: '80px',
@@ -250,6 +300,75 @@ const getTextWidth = (text: string, fontSize = 14) => {
   return width;
 };
 
+/** 延保原因相关状态 */
+const delayReasonDialogVisible = ref(false)
+const saveDelayReasonLoading = ref(false)
+const delayReasonForm = reactive({
+  id: undefined as number | undefined,
+  delayReason: ''
+})
+
+/** 判断mainDistance是否为负值 */
+const isNegativeMainDistance = (mainDistance: string | null): boolean => {
+  if (!mainDistance) return false
+
+  // 使用正则提取数字部分(包括负号、小数点和科学计数法)
+  const numericPart = mainDistance.match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/)?.[0]
+
+  if (numericPart) {
+    const num = parseFloat(numericPart)
+    return num < 0
+  }
+
+  return false
+}
+
+/** 打开延保原因弹窗 */
+const openDelayReasonDialog = (row: IotMainWorkOrderVO) => {
+  delayReasonForm.id = row.id
+  delayReasonForm.delayReason = row.delayReason || ''
+  delayReasonDialogVisible.value = true
+}
+
+/** 保存延保原因 */
+const saveDelayReason = async () => {
+  // 表单验证
+  if (!delayReasonFormRef.value) return
+  const valid = await delayReasonFormRef.value.validate()
+  if (!valid) return
+
+  if (!delayReasonForm.id) {
+    message.error('ID不能为空')
+    return
+  }
+
+  try {
+    saveDelayReasonLoading.value = true
+    // 调用更新接口,只传递id和delayReason字段
+    await IotMainWorkOrderApi.updateIotMainWorkOrder({
+      id: delayReasonForm.id,
+      delayReason: delayReasonForm.delayReason
+    })
+
+    message.success(t('common.success') || '保存成功')
+    delayReasonDialogVisible.value = false
+
+    // 刷新列表数据
+    await getList()
+  } catch (error) {
+    console.error('保存延保原因失败:', error)
+    message.error(t('sys.api.operationFailed') || '保存失败')
+  } finally {
+    saveDelayReasonLoading.value = false
+  }
+}
+
+/** 根据delayReason是否有值返回按钮类型 */
+const getDelayReasonButtonType = (delayReason: string | null | undefined): string => {
+  // 如果delayReason有值(不为null、undefined且不是空字符串),返回success,否则返回warning
+  return delayReason ? 'success' : 'warning'
+}
+
 /** 处理部门被点击 */
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
@@ -310,8 +429,8 @@ const calculateColumnWidths = () => {
   calculateColumnMinWidth('updateTime', t('dict.fillTime'), (row: any) => row.result == 2 ? formatCellDate(row.updateTime) : '');
 
   // 操作列固定宽度
-  minWidths.operation = 150;
-  totalMinWidth += 150;
+  minWidths.operation = 160;
+  totalMinWidth += 160;
 
   // 2. 计算可伸缩列最终宽度
   const newWidths: Record<string, string> = {};
@@ -382,6 +501,10 @@ const resultOptions = computed(() => [
     label: t('operationFill.all'),
     value: '0' // 空值会触发 clearable 效果
   },
+  {
+    label: t('mainPlan.delayed'),
+    value: '3' // 空值会触发 clearable 效果
+  },
   ...getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_RESULT)
 ])
 
@@ -620,4 +743,15 @@ watch(isLeftContentCollapsed, () => {
   transform: rotate(180deg);
 }
 
+/* 延时原因 必填星号样式 */
+:deep(.required-item .el-form-item__label:before) {
+  content: '*';
+  color: #ff4d4f;
+  margin-right: 2px;
+}
+
+/* 调整标签区域样式 */
+:deep(.required-item .el-form-item__label) {
+  padding-right: 0 !important;
+}
 </style>

+ 176 - 15
src/views/pms/iotprojecttask/IotProjectTaskForm.vue

@@ -289,9 +289,6 @@
             </el-select>
           </el-form-item>
         </el-col>
-      </el-row>
-
-      <el-row>
         <el-col :span="8">
           <el-form-item label="施工队伍" prop="deptIds">
             <el-tree-select
@@ -313,6 +310,9 @@
             />
           </el-form-item>
         </el-col>
+      </el-row>
+
+      <el-row>
         <el-col :span="8">
           <el-form-item label="施工设备" prop="deviceIds">
             <el-button
@@ -338,25 +338,41 @@
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="责任人" prop="responsiblePerson">
+          <el-form-item :label="isSpecialDept ? '带班干部' : '责任人'" prop="responsiblePerson">
             <el-button
               @click="openResponsiblePersonDialogForForm"
               type="primary"
               size="small"
             >
-              选择责任人
+              选择{{ isSpecialDept ? '带班干部' : '责任人' }}
             </el-button>
-            <!--
-            <span v-if="currentTask.responsiblePerson && currentTask.responsiblePerson.length > 0" style="margin-left: 10px;">
-              已选 {{ currentTask.responsiblePerson.length }} 人
-            </span> -->
             <el-tooltip
               v-if="currentTask.responsiblePerson && currentTask.responsiblePerson.length > 0"
               :content="getAllResponsiblePersonNamesForForm(currentTask.responsiblePerson)"
               placement="top"
+            >
+              <span style="margin-left: 10px;">
+                {{ formatResponsiblePersonsForForm(currentTask.responsiblePerson) }}
+              </span>
+            </el-tooltip>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8"  v-if="isSpecialDept">
+          <el-form-item label="填报人" prop="submitter">
+            <el-button
+              @click="openSubmitterDialogForForm"
+              type="primary"
+              size="small"
+            >
+              选择填报人
+            </el-button>
+            <el-tooltip
+              v-if="currentTask.submitter && currentTask.submitter.length > 0"
+              :content="getAllSubmitterNamesForForm(currentTask.submitter)"
+              placement="top"
             >
             <span style="margin-left: 10px;">
-              {{ formatResponsiblePersonsForForm(currentTask.responsiblePerson) }}
+              {{ formatSubmittersForForm(currentTask.submitter) }}
             </span>
             </el-tooltip>
           </el-form-item>
@@ -520,6 +536,44 @@
       </span>
     </template>
   </el-dialog>
+
+  <!-- 填报人选择对话框 -->
+  <el-dialog
+    v-model="submitterDialogVisible"
+    title="选择填报人"
+    width="1000px"
+    :before-close="handleSubmitterDialogClose"
+    class="responsible-person-select-dialog"
+  >
+    <div class="transfer-container">
+      <el-transfer
+        v-model="selectedSubmitterIds"
+        :data="submitterList"
+        :titles="['可选人员', '已选人员']"
+        :props="{ key: 'id', label: 'nickname' }"
+        filterable
+        class="transfer-component"
+      >
+        <template #default="{ option }">
+          <el-tooltip
+            effect="dark"
+            placement="top"
+            :content="`${option.nickname} - ${option.deptName || '未分配部门'}`"
+          >
+            <span class="transfer-option-text">
+              {{ option.nickname }} - {{ option.deptName || '未分配部门' }}
+            </span>
+          </el-tooltip>
+        </template>
+      </el-transfer>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleSubmitterDialogClose">取消</el-button>
+        <el-button type="primary" @click="confirmSubmitterSelection">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
 </template>
 
 <script setup lang="ts">
@@ -581,6 +635,10 @@ const responsiblePersonList = ref([]); // 所有责任人列表
 const selectedResponsiblePersonIds = ref([]); // 选中的责任人ID
 const currentEditingRowForResponsible = ref(null); // 当前正在编辑责任人的行
 
+const submitterDialogVisible = ref(false);
+const submitterList = ref([]); // 所有填报人列表
+const selectedSubmitterIds = ref([]); // 选中的日报填报人ID
+
 // 动态属性相关变量
 const dynamicAttrs = ref([]); // 存储动态属性列表
 
@@ -628,6 +686,11 @@ const close = () => {
   push({ name: 'IotProjectTask', params:{}})
 }
 
+// 添加计算属性来判断是否为特定部门
+const isSpecialDept = computed(() => {
+  return formData.value.deptId === 163;
+});
+
 const getProjectInfo = async (contractId: number) => {
   const project = projectList.value.find(item => item.id === contractId);
   if (project) {
@@ -779,6 +842,7 @@ const currentTask = ref({
   deptIds: [],
   deviceIds: [],
   responsiblePerson: [],
+  submitter: [],
   remark: '',
   projectId: ''
 });
@@ -798,6 +862,10 @@ const taskFormRules = reactive({
   deptIds: [{ required: true, message: '施工队伍不能为空', trigger: 'change' }],
   deviceIds: [{ required: true, message: '施工设备不能为空', trigger: 'change' }],
   responsiblePerson: [{ required: true, message: '责任人不能为空', trigger: 'change' }],
+  // 动态添加填报人验证规则
+  ...(isSpecialDept.value ? {
+    submitter: [{ required: true, message: '填报人不能为空', trigger: 'change' }]
+  } : {})
 });
 
 // 动态属性验证规则
@@ -858,6 +926,28 @@ const openResponsiblePersonDialogForForm = async () => {
   }
 };
 
+// 打开工单人选择对话框(用于表单)
+const openSubmitterDialogForForm = async () => {
+  if (!currentTask.value.deptIds || currentTask.value.deptIds.length === 0) {
+    ElMessage.warning('请先选择施工队伍');
+    return;
+  }
+
+  selectedSubmitterIds.value = [...(currentTask.value.submitter || [])];
+
+  try {
+    const params = {
+      deptIds: currentTask.value.deptIds // 使用当前项目所属部门的deptId
+    };
+    const response = await UserApi.selectedDeptsEmployee(params);
+    submitterList.value = response;
+    submitterDialogVisible.value = true;
+  } catch (error) {
+    ElMessage.error('获取填报人列表失败');
+    console.error('获取填报人列表失败:', error);
+  }
+};
+
 // 获取动态属性
 const fetchDynamicAttrs = async () => {
   // 如果已经通过任务数据获取了动态属性,则不再调用接口
@@ -936,6 +1026,12 @@ const confirmResponsiblePersonSelection = () => {
   responsiblePersonDialogVisible.value = false;
 };
 
+// 确认填报人选择(用于表单)
+const confirmSubmitterSelection = () => {
+  currentTask.value.submitter = [...selectedSubmitterIds.value];
+  submitterDialogVisible.value = false;
+};
+
 // 关闭设备选择对话框
 const handleDeviceDialogClose = () => {
   deviceDialogVisible.value = false;
@@ -946,6 +1042,11 @@ const handleResponsiblePersonDialogClose = () => {
   responsiblePersonDialogVisible.value = false;
 };
 
+// 关闭填报人选择对话框
+const handleSubmitterDialogClose = () => {
+  submitterDialogVisible.value = false;
+};
+
 // 获取井型标签的方法
 const getWellTypeLabel = (value) => {
   const options = getStrDictOptions(DICT_TYPE.PMS_PROJECT_WELL_TYPE)
@@ -1008,6 +1109,8 @@ const syncCurrentTaskToTable = () => {
   return true;
 };
 
+
+
 // 重置任务表单
 const resetTaskForm = () => {
   currentTask.value = {
@@ -1023,7 +1126,9 @@ const resetTaskForm = () => {
     deviceIds: [],
     responsiblePerson: [],
     remark: '',
-    projectId: formData.value.id
+    projectId: formData.value.id,
+    // 如果是特殊部门,初始化填报人字段
+    ...(isSpecialDept.value ? { submitter: [] } : {})
   };
 
   // 重新初始化动态属性
@@ -1164,10 +1269,12 @@ const open = async () => {
       const data = await IotProjectTaskApi.getIotProjectTaskPage(queryParams);
       tableData.value = data.list
 
-      // 1. 收集所有设备ID
+      // 收集所有设备ID
       const allDeviceIds = new Set<number>();
       // 收集所有责任人ID
       const allResponsiblePersonIds = new Set<number>();
+      // 收集所有填报人ID
+      const allSubmitterIds = new Set<number>();
 
       data.list.forEach(item => {
         if (item.deviceIds?.length) {
@@ -1176,16 +1283,19 @@ const open = async () => {
         if (item.responsiblePerson?.length) {
           item.responsiblePerson.forEach(id => allResponsiblePersonIds.add(id));
         }
+        if (item.submitter?.length) {
+          item.submitter.forEach(id => allSubmitterIds.add(id));
+        }
       });
 
-      // 2. 批量获取设备信息
+      // 批量获取设备信息
       if (allDeviceIds.size > 0) {
         const deviceIdsArray = Array.from(allDeviceIds);
         const devices = await IotDeviceApi.getDevicesByDepts({
           deviceIds: deviceIdsArray
         });
 
-        // 3. 更新设备列表和映射表
+        // 更新设备列表和映射表
         deviceList.value = devices;
         deviceMap.value = {};
         devices.forEach(device => {
@@ -1205,6 +1315,18 @@ const open = async () => {
         responsiblePersonList.value = persons;
       }
 
+      // 批量获取填报人信息
+      if (allSubmitterIds.size > 0) {
+        const personIdsArray = Array.from(allSubmitterIds);
+        const params = {
+          userIds: personIdsArray
+        };
+        const persons = await UserApi.companyDeptsEmployee(params);
+
+        // 更新责任人列表
+        submitterList.value = persons;
+      }
+
       if (tableData.value && tableData.value.length > 0) {
         // 使用深拷贝,避免修改表单时直接影响表格数据
         currentTask.value = JSON.parse(JSON.stringify(tableData.value[0]));
@@ -1259,7 +1381,7 @@ const autoSelectContract = async (projectId: string) => {
 
 /** 校验所有行数据 */
 const validateAllRows = (): boolean => {
-  // 新增检查:判断任务列表是否为空
+  // 判断任务列表是否为空
   if (!tableData.value || tableData.value.length === 0) {
     ElMessage.error('没有任务数据,无法保存');
     return false;
@@ -1295,6 +1417,11 @@ const validateAllRows = (): boolean => {
     if (!row.responsiblePerson || row.responsiblePerson.length === 0) {
       allValid = false;
     }
+
+    // 如果是特殊部门,验证填报人
+    if (isSpecialDept.value && (!row.submitter || row.submitter.length === 0)) {
+      allValid = false;
+    }
   });
 
   if (!allValid) {
@@ -1353,6 +1480,26 @@ const formatResponsiblePersonsForForm = (responsiblePersonIds: number[]) => {
   return personNames.join(', ');
 };
 
+// 格式化填报人显示(用于表单)
+const formatSubmittersForForm = (submitterIds: number[]) => {
+  if (!submitterIds || submitterIds.length === 0) return '';
+
+  const personNames = submitterIds
+    .map(id => {
+      const person = submitterList.value.find(p => p.id === id);
+      return person ? person.nickname : '';
+    })
+    .filter(name => name !== undefined && name !== '');
+
+  if (personNames.length === 0) return '';
+
+  if (personNames.length > 2) {
+    return `${personNames[0]}, ${personNames[1]}...`;
+  }
+
+  return personNames.join(', ');
+};
+
 // 获取所有责任人名称(用于表单的tooltip)
 const getAllResponsiblePersonNamesForForm = (responsiblePersonIds: number[]) => {
   if (!responsiblePersonIds || responsiblePersonIds.length === 0) return '无责任人';
@@ -1367,6 +1514,20 @@ const getAllResponsiblePersonNamesForForm = (responsiblePersonIds: number[]) =>
   return personNames.join(', ') || '无有效责任人';
 };
 
+// 获取所有填报人名称(用于表单的tooltip)
+const getAllSubmitterNamesForForm = (submitterIds: number[]) => {
+  if (!submitterIds || submitterIds.length === 0) return '无填报人';
+
+  const personNames = submitterIds
+    .map(id => {
+      const person = submitterList.value.find(p => p.id === id);
+      return person ? person.nickname : '未知人员';
+    })
+    .filter(name => name !== '未知人员');
+
+  return personNames.join(', ') || '无有效填报人';
+};
+
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 

+ 1662 - 0
src/views/pms/iotrddailyreport/FillDailyReportForm.vue

@@ -0,0 +1,1662 @@
+<template>
+  <ContentWrap v-loading="formLoading">
+    <!-- 第一部分:日报标题 -->
+    <div class="daily-report-title">
+      <h2>{{ pageTitle  }}</h2>
+      <!-- 在审批模式下显示审批状态提示 -->
+      <div v-if="isReadonlyMode" class="approval-notice">
+        <el-alert :title="modeNotice" type="info" :closable="false" />
+      </div>
+    </div>
+
+    <!-- 第二部分:项目/任务信息 -->
+    <ContentWrap>
+      <div class="info-table" style="margin-top: 1em">
+        <!-- 表格行 -->
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">甲方:</span>
+              <!-- 甲方字段:添加 single-line-ellipsis 类 + title 绑定完整内容 -->
+              <span
+                class="cell-value single-line-ellipsis"
+                :title="dailyReportData.manufactureName || '-'"
+              >
+              {{ dailyReportData.manufactureName || '-' }}
+            </span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">合同号:</span>
+              <span class="cell-value">{{ dailyReportData.contractName || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">井号:</span>
+              <span class="cell-value">{{ dailyReportData.wellName || dailyReportData.taskName || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工队伍:</span>
+              <span class="cell-value">{{ dailyReportData.deptName || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工地点:</span>
+              <span class="cell-value">{{ dailyReportData.location || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">工艺:</span>
+              <span class="cell-value">{{ dailyReportData.techniqueNames || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">搬迁日期:</span>
+              <span class="cell-value">{{ formatDate(dailyReportData.dpDate) || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">开工日期:</span>
+              <span class="cell-value">{{ formatDate(dailyReportData.sgDate) || '-' }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">完工日期:</span>
+              <span class="cell-value">{{ formatDate(dailyReportData.wgDate) || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="table-row">
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">施工周期D:</span>
+              <span class="cell-value">{{ constructionPeriod || 0 }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">停待时间D:</span>
+              <span class="cell-value">{{ dailyReportData.faultDowntime || 0 }}</span>
+            </div>
+          </div>
+          <div class="table-cell">
+            <div class="cell-content">
+              <span class="cell-label">带班干部:</span>
+              <span class="cell-value">{{ dailyReportData.responsiblePersonNames || '-' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 第五行:设备配置(单独一行) -->
+        <div class="table-row">
+          <div class="table-cell full-width">
+            <div class="cell-content">
+              <span class="cell-label">设备配置:</span>
+              <span class="cell-value indent-multiline">{{ dailyReportData.deviceNames || '-' }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </ContentWrap>
+
+    <!-- 第三部分:日报填报表单 -->
+    <ContentWrap class="section-padding">
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="isReadonlyMode ? {} : formRules"
+        v-loading="formLoading"
+        style="margin-top: 1em"
+        label-width="200px"
+        :disabled="isReadonlyMode"
+      >
+        <!-- 第一行:时间节点、施工状态 -->
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="时间节点" prop="timeRange">
+              <el-time-picker
+                is-range
+                v-model="formData.timeRange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                placeholder="选择时间范围"
+                style="width: 100%"
+                :readonly="isReadonlyMode"
+                :disabled="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="施工状态" prop="rdStatus">
+              <el-select v-model="formData.rdStatus" placeholder="请选择施工状态"
+                         style="width: 100%" :disabled="isReadonlyMode">
+                <el-option
+                  v-for="dict in rdStatusOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 施工设备字段 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="施工设备" prop="deviceIds">
+              <!-- 编辑模式:显示选择按钮 -->
+              <template v-if="isEditMode">
+                <el-button
+                  @click="openDeviceDialog"
+                  type="primary"
+                  size="small"
+                  :disabled="formLoading"
+                >
+                  选择设备
+                </el-button>
+                <el-tooltip
+                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
+                  :content="getAllDeviceNamesForDisplay"
+                  placement="top"
+                >
+                <span class="device-display-container">
+                  {{ formatDevicesForDisplay }}
+                </span>
+                </el-tooltip>
+                <span v-else class="no-device">
+                  未选择设备
+                </span>
+              </template>
+
+              <!-- 只读模式:只显示设备信息 -->
+              <template v-else>
+                <el-tooltip
+                  v-if="formData.deviceIds && formData.deviceIds.length > 0"
+                  :content="getAllDeviceNamesForDisplay"
+                  placement="top"
+                >
+                <span class="device-display-container">
+                  {{ formatDevicesForDisplay }}
+                </span>
+                </el-tooltip>
+                <span v-else class="no-device">-</span>
+              </template>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 未施工设备 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="未施工设备" prop="unSelectedDeviceNames">
+              <el-input
+                v-model="unSelectedDeviceNames"
+                type="textarea"
+                :rows="2"
+                placeholder="未施工的设备将显示在这里"
+                :readonly="true"
+                class="unselected-device"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第二行:施工工艺 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="施工工艺" prop="techniqueIds">
+              <el-select v-model="formData.techniqueIds" placeholder="请选择施工工艺"
+                         style="width: 100%" multiple :disabled="isReadonlyMode">
+                <el-option
+                  v-for="dict in techniqueOptions"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 动态属性区域:施工工艺与当日生产动态之间 -->
+        <el-row v-if="dynamicAttrs.length > 0" :gutter="30">
+          <el-col
+            v-for="attr in dynamicAttrs"
+            :key="attr.id"
+            :span="attr.dataType === 'textarea' ? 24 : 12"
+          >
+            <el-form-item
+              :label="attr.name + (attr.unit ? `(${attr.unit})` : '')"
+              :prop="'dynamicFields.' + attr.identifier"
+              :rules="isReadonlyMode ? [] : getDynamicAttrRules(attr)"
+            >
+              <!-- 文本类型 -->
+              <el-input
+                v-if="attr.dataType === 'text'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 文本域类型 -->
+              <el-input
+                v-else-if="attr.dataType === 'textarea'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                type="textarea"
+                :rows="3"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 数字类型 -->
+              <el-input
+                v-else-if="attr.dataType === 'double'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                type="number"
+                :min="attr.minValue || undefined"
+                :max="attr.maxValue || undefined"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 日期类型 -->
+              <el-date-picker
+                v-else-if="attr.dataType === 'date'"
+                v-model="formData.dynamicFields[attr.identifier]"
+                type="date"
+                value-format="x"
+                :placeholder="`选择${attr.name}`"
+                style="width: 100%"
+                :readonly="isReadonlyMode"
+              />
+
+              <!-- 默认文本输入 -->
+              <el-input
+                v-else
+                v-model="formData.dynamicFields[attr.identifier]"
+                :placeholder="`请输入${attr.name}`"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第三行:当日生产动态 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="当日生产动态" prop="productionStatus">
+              <el-input
+                v-model="formData.productionStatus"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入当日生产动态"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第四行:下步工作计划 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="下步工作计划" prop="nextPlan">
+              <el-input
+                v-model="formData.nextPlan"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入下步工作计划"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第五行:外租设备 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="外租设备" prop="externalRental">
+              <el-input
+                v-model="formData.externalRental"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入外租设备信息"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 故障情况 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="故障情况" prop="malfunction">
+              <el-input
+                v-model="formData.malfunction"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入故障情况"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 故障误工H -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="故障误工H" prop="faultDowntime">
+              <el-input
+                v-model="formData.faultDowntime"
+                type="number"
+                :rows="3"
+                placeholder="请输入故障误工H"
+                :readonly="isReadonlyMode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 第六行:上传附件 -->
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="附件">
+              <!-- 文件上传组件 -->
+              <FileUpload
+                v-if="!isReadonlyMode"
+                ref="fileUploadRef"
+                :device-id="deviceId"
+                :show-folder-button="false"
+                @upload-success="handleUploadSuccess"
+              />
+
+              <!-- 已上传附件列表显示 -->
+              <div v-if="formData.attachments && formData.attachments.length > 0" class="attachment-container">
+                <div class="attachment-list">
+                  <div
+                    v-for="(attachment, index) in formData.attachments"
+                    :key="attachment.id || index"
+                    class="attachment-item"
+                  >
+                    <!-- 为附件名称添加点击事件,传递整个附件对象 -->
+                    <a class="attachment-name" @click="inContent(attachment)">
+                      {{ attachment.filename }}
+                    </a>
+                    <el-button
+                      v-if="!isReadonlyMode"
+                      type="danger"
+                      link
+                      size="small"
+                      @click="removeAttachment(index)"
+                    >
+                      删除
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 审批模式下只显示附件列表 -->
+              <div v-else-if="isApprovalMode && (!formData.attachments || formData.attachments.length === 0)" class="no-attachment">
+                无附件
+              </div>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+      </el-form>
+    </ContentWrap>
+
+    <!-- 第四部分:审批意见 - 只在审批模式下显示 -->
+    <ContentWrap class="section-padding" v-if="isApprovalMode">
+      <el-form
+        ref="approvalFormRef"
+        :model="approvalForm"
+        style="margin-top: 1em"
+        label-width="200px"
+      >
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="审批意见" prop="opinion">
+              <el-input
+                v-model="approvalForm.opinion"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入审批意见"
+                maxlength="500"
+                show-word-limit
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 操作按钮 -->
+    <ContentWrap class="section-padding" v-if="isEditMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="submitForm" type="primary" :disabled="formLoading">
+            {{ t('common.save') }}
+          </el-button>
+          <el-button @click="close">{{ t('common.cancel') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 审批模式下的操作按钮 -->
+    <ContentWrap class="section-padding" v-if="isApprovalMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="handleApprove('pass')" type="success">
+            审批通过
+          </el-button>
+          <el-button @click="handleApprove('reject')" type="danger">
+            审批驳回
+          </el-button>
+          <el-button @click="close">{{ t('common.close') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 详情模式下的操作按钮 - 只有关闭按钮 -->
+    <ContentWrap class="section-padding" v-if="isDetailMode">
+      <el-form>
+        <el-form-item style="float: right">
+          <el-button @click="close">{{ t('common.close') }}</el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+  </ContentWrap>
+
+  <!-- 设备选择对话框 -->
+  <el-dialog
+    v-model="deviceDialogVisible"
+    title="选择施工设备"
+    width="1000px"
+    :before-close="handleDeviceDialogClose"
+    class="device-select-dialog"
+  >
+    <div class="transfer-container">
+      <el-transfer
+        v-model="selectedDeviceIds"
+        :data="filteredDeviceList"
+        :titles="['可选设备', '已选设备']"
+        :props="{ key: 'id', label: 'deviceCode' }"
+        filterable
+        class="transfer-component"
+        @change="handleTransferChange"
+      >
+        <template #default="{ option }">
+          <el-tooltip
+            effect="dark"
+            placement="top"
+            :content="`${option.deviceCode || ''} - ${option.deviceName || ''}`"
+            :disabled="!option.deviceCode && !option.deviceName"
+            transition="fade-in-linear"
+          >
+          <span class="transfer-option-text">
+            {{ option.deviceCode }} - {{ option.deviceName }}
+          </span>
+          </el-tooltip>
+        </template>
+      </el-transfer>
+    </div>
+    <template #footer>
+    <span class="dialog-footer">
+      <el-button @click="handleDeviceDialogClose">取消</el-button>
+      <el-button type="primary" @click="confirmDeviceSelection">确定</el-button>
+    </span>
+    </template>
+  </el-dialog>
+
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useMessage } from '@/hooks/web/useMessage'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import { useRouter, useRoute } from 'vue-router'
+import {DICT_TYPE, getDictLabel, getStrDictOptions} from '@/utils/dict'
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
+import * as DeptApi from '@/api/system/dept'
+import { useUserStore } from '@/store/modules/user'
+import dayjs from 'dayjs'
+import FileUpload from "@/components/UploadFile/src/FileUpload.vue";
+
+const { t } = useI18n()
+const message = useMessage()
+const { delView } = useTagsViewStore()
+const { push, currentRoute } = useRouter()
+const { params } = useRoute()
+const userStore = useUserStore()
+
+/** 填报日报 表单 */
+defineOptions({ name: 'FillDailyReportForm' })
+
+const formLoading = ref(false)
+const formRef = ref()
+const id = params.id // 瑞都日报id
+
+// 日报数据
+const dailyReportData = ref<any>({})
+
+// 添加模式判断计算属性
+const isApprovalMode = computed(() => params.mode === 'approval')
+const isDetailMode = computed(() => params.mode === 'detail')
+const isEditMode = computed(() => params.mode === 'fill' || !params.mode) // 默认为编辑模式
+
+// 只读模式判断:审批模式或详情模式都为只读
+const isReadonlyMode = computed(() => isApprovalMode.value || isDetailMode.value)
+
+// 页面标题计算
+const pageTitle = computed(() => {
+  if (isApprovalMode.value) {
+    return dailyReportData.value.wellName && dailyReportData.value.constructionStartDate
+      ? `${dailyReportData.value.wellName} - ${formatDate(dailyReportData.value.constructionStartDate)} 日报审批`
+      : '日报审批'
+  } else if (isDetailMode.value) {
+    return dailyReportData.value.wellName && dailyReportData.value.constructionStartDate
+      ? `${dailyReportData.value.wellName} - ${formatDate(dailyReportData.value.constructionStartDate)} 日报详情`
+      : '日报详情'
+  } else {
+    return dailyReportData.value.wellName && dailyReportData.value.constructionStartDate
+      ? `${dailyReportData.value.wellName} - ${formatDate(dailyReportData.value.constructionStartDate)} 生产日报`
+      : '日报填报'
+  }
+})
+
+// 模式提示信息
+const modeNotice = computed(() => {
+  if (isApprovalMode.value) {
+    return '审批模式:所有字段均为只读'
+  } else if (isDetailMode.value) {
+    return '详情模式:所有字段均为只读'
+  }
+  return ''
+})
+
+// 动态属性相关变量
+const dynamicAttrs = ref<any[]>([]) // 存储动态属性列表
+
+// 添加设备选择相关变量
+const deviceDialogVisible = ref(false)
+const filteredDeviceList = ref<any[]>([])
+const selectedDeviceIds = ref<number[]>([])
+const deviceMap = ref<Record<number, any>>({})
+
+// 添加审批表单相关变量
+const approvalFormRef = ref()
+const approvalForm = reactive({
+  opinion: '' // 审批意见
+})
+
+// 审批表单验证规则(可选,根据需求添加)
+const approvalFormRules = reactive({
+  opinion: [
+    { required: false, message: '请输入审批意见', trigger: 'blur' },
+    { min: 0, max: 500, message: '审批意见长度不能超过500个字符', trigger: 'blur' }
+  ]
+})
+
+// 添加文件上传组件的引用
+const fileUploadRef = ref()
+
+// 表单数据
+const formData = ref({
+  id: undefined,
+  deptId: undefined,
+  companyId: undefined,
+  deptName: undefined,
+  constructionStartDate: undefined,
+  contractName: undefined,
+  projectDepartment: '',
+  costCenterId: undefined,
+  costCenter: '',
+  // 新增日报填报字段
+  timeRange: [ // 设置默认时间范围 8:00 - 8:00
+    dayjs().hour(8).minute(0).second(0).toDate(),
+    dayjs().hour(8).minute(0).second(0).toDate()
+  ],
+  startTime: undefined, // 开始时间
+  endTime: undefined, // 结束时间
+  rdStatus: '', // 施工状态
+  deviceIds: [] as number[], // 设备ID数组
+  techniqueIds: [], // 施工工艺
+  productionStatus: '', // 当日生产动态
+  nextPlan: '', // 下步工作计划
+  externalRental: '', // 外租设备
+  malfunction: '',    // 故障情况
+  faultDowntime: '',  // 故障误工
+  // 添加动态字段对象
+  dynamicFields: {} as Record<string, any>,
+  // 附件列表
+  attachments: [] as any[]
+})
+
+// 添加上传成功处理函数
+const handleUploadSuccess = (result: any) => {
+  console.log('上传成功:', result)
+
+  try {
+    // 检查响应是否成功
+    if (!result.response) {
+      message.error('上传响应数据异常')
+      return
+    }
+
+    if (result.response.code !== 0) {
+      message.error(result.response.msg || '文件上传失败')
+      return
+    }
+
+    const responseData = result.response.data
+
+    if (!responseData) {
+      message.error('上传数据为空')
+      return
+    }
+
+    // 处理返回的文件列表
+    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
+      responseData.files.forEach((file: any) => {
+        if (!file.filePath) {
+          console.warn('文件缺少 filePath:', file)
+          return
+        }
+
+        // 根据后端返回的数据结构构建附件对象
+        const attachment = {
+          id: undefined,
+          category: 'daily_report',
+          bizId: formData.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath, //使用正确的 filePath
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
+        }
+
+        // 添加到附件列表
+        if (!formData.value.attachments) {
+          formData.value.attachments = []
+        }
+        formData.value.attachments.push(attachment)
+      })
+
+      message.success(`成功上传 ${responseData.files.length} 个文件`)
+    } else {
+      console.warn('上传成功但没有返回文件信息')
+      message.warning('上传成功但未获取到文件信息')
+    }
+  } catch (error) {
+    console.error('处理上传结果时发生错误:', error)
+    message.error('处理上传结果失败')
+  }
+}
+
+// 删除附件
+const removeAttachment = (index: number) => {
+  if (formData.value.attachments && formData.value.attachments.length > index) {
+    formData.value.attachments.splice(index, 1)
+  }
+}
+
+// 计算属性:未施工设备名称
+const unSelectedDeviceNames = computed(() => {
+  const selectedDevices = dailyReportData.value.selectedDevices || []
+  const selectedDeviceIds = formData.value.deviceIds || []
+
+  if (selectedDevices.length === 0) {
+    return '无可用设备'
+  }
+
+  // 筛选出未选择的设备
+  const unselectedDevices = selectedDevices.filter((device: any) =>
+    !selectedDeviceIds.includes(device.id)
+  )
+
+  if (unselectedDevices.length === 0) {
+    return '所有设备都已施工'
+  }
+
+  // 提取设备名称并用逗号分隔
+  const deviceNames = unselectedDevices
+    .map((device: any) => device.deviceName || device.deviceCode || '未知设备')
+    .filter((name: string) => name !== '未知设备')
+
+  return deviceNames.join(', ') || '无未选择设备'
+})
+
+// 附件名称点击事件
+const inContent = async (attachment) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
+    return
+  }
+
+  try {
+    // 直接使用 attachment.filePath
+    const filePath = attachment.filePath
+    // 确保 filePath 是编码后的格式
+    const encodedPath = encodeURIComponent(Base64.encode(filePath))
+
+    // 打开预览窗口
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+  } catch (error) {
+    console.error('预览附件失败:', error)
+    message.error('预览附件失败')
+  }
+}
+
+// 获取文件类型辅助函数
+const getFileType = (filename: string) => {
+  const ext = filename.split('.').pop()?.toLowerCase()
+  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
+    return 'image'
+  } else if (['pdf'].includes(ext || '')) {
+    return 'pdf'
+  } else if (['doc', 'docx'].includes(ext || '')) {
+    return 'word'
+  } else if (['xls', 'xlsx'].includes(ext || '')) {
+    return 'excel'
+  } else {
+    return 'other'
+  }
+}
+
+// 格式化文件大小辅助函数
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+// 计算属性:格式化设备显示
+const formatDevicesForDisplay = computed(() => {
+  const deviceIds = formData.value.deviceIds
+  if (!deviceIds || deviceIds.length === 0) {
+    return '无设备'
+  }
+
+  const deviceNames = deviceIds
+    .map(id => deviceMap.value[id]?.deviceName)
+    .filter(name => name !== undefined && name !== '')
+
+  if (deviceNames.length === 0) return '无设备'
+
+  // 如果设备数量超过2个,显示前两个加省略号
+  /* if (deviceNames.length > 2) {
+    return `${deviceNames[0]}, ${deviceNames[1]}...`
+  } */
+
+  return deviceNames.join(', ')
+})
+
+// 计算属性:获取所有设备名称(用于tooltip)
+const getAllDeviceNamesForDisplay = computed(() => {
+  const deviceIds = formData.value.deviceIds
+  if (!deviceIds || deviceIds.length === 0) {
+    return '无设备'
+  }
+
+  const deviceNames = deviceIds
+    .map(id => deviceMap.value[id]?.deviceCode || '未知设备')
+    .filter(name => name !== '未知设备')
+
+  return deviceNames.join(', ') || '无有效设备'
+})
+
+// 打开设备选择对话框
+const openDeviceDialog = async () => {
+  if (!dailyReportData.value.deptId) {
+    message.error('请先加载项目信息')
+    return
+  }
+
+  try {
+    formLoading.value = true
+    selectedDeviceIds.value = [...(formData.value.deviceIds || [])]
+
+    // 直接从日报数据的 selectedDevices 中获取设备列表
+    const selectedDevices = dailyReportData.value.selectedDevices || []
+
+    // 更新设备映射表
+    const newDeviceMap = { ...deviceMap.value }
+    selectedDevices.forEach((device: any) => {
+      if (device.id) {
+        newDeviceMap[device.id] = device
+      }
+    })
+    deviceMap.value = newDeviceMap
+
+    filteredDeviceList.value = selectedDevices
+    deviceDialogVisible.value = true
+
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    message.error('获取设备列表失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+// 处理穿梭框变化
+const handleTransferChange = (value: number[], direction: string, movedKeys: number[]) => {
+  // 可以添加额外的处理逻辑
+}
+
+// 确认设备选择
+const confirmDeviceSelection = () => {
+  formData.value.deviceIds = [...selectedDeviceIds.value]
+  deviceDialogVisible.value = false
+  message.success(`已选择 ${selectedDeviceIds.value.length} 台设备`)
+}
+
+// 关闭设备选择对话框
+const handleDeviceDialogClose = () => {
+  deviceDialogVisible.value = false
+}
+
+// 初始化设备数据
+const initDeviceData = (reportData: any) => {
+  // 初始化设备ID
+  if (reportData.deviceIds && Array.isArray(reportData.deviceIds)) {
+    formData.value.deviceIds = [...reportData.deviceIds]
+  } else {
+    formData.value.deviceIds = []
+  }
+
+  // 初始化设备映射表(用于显示设备名称)
+  if (reportData.selectedDevices && Array.isArray(reportData.selectedDevices)) {
+    const newDeviceMap = { ...deviceMap.value }
+    reportData.selectedDevices.forEach((device: any) => {
+      if (device.id) {
+        newDeviceMap[device.id] = device
+      }
+    })
+    deviceMap.value = newDeviceMap
+  }
+}
+
+// 表单验证规则
+const formRules = reactive({
+  timeRange: [{ required: true, message: '时间节点不能为空', trigger: 'change' }],
+  rdStatus: [{ required: true, message: '施工状态不能为空', trigger: 'change' }],
+  techniqueIds: [{ required: true, message: '施工工艺不能为空', trigger: 'change' }],
+  productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }]
+})
+
+const queryParams = reactive({
+  deptId: undefined,
+  techniqueIds: [],
+})
+
+// 下拉选项
+const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS)   // 施工状态
+const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY) // 瑞都施工工艺
+
+// 计算属性:日报标题
+const dailyReportTitle = computed(() => {
+  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
+    return '日报填报'
+  }
+  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
+  return `${dailyReportData.value.wellName} - ${dateStr} 生产日报`
+})
+
+// 日报审批:日报标题
+const dailyReportApprovalTitle = computed(() => {
+  if (!dailyReportData.value.wellName || !dailyReportData.value.constructionStartDate) {
+    return '日报审批'
+  }
+  const dateStr = formatDate(dailyReportData.value.constructionStartDate)
+  return `${dailyReportData.value.wellName} - ${dateStr} 日报审批`
+})
+
+// 计算属性:施工周期
+const constructionPeriod = computed(() => {
+  const start = dailyReportData.value.constructionStartDate
+  const end = dailyReportData.value.constructionEndDate
+  if (!start || !end) return 0
+
+  const startDate = dayjs(start)
+  const endDate = dayjs(end)
+  return endDate.diff(startDate, 'day')
+})
+
+// 日期格式化函数
+const formatDate = (timestamp: number) => {
+  if (!timestamp) return ''
+  return dayjs(timestamp).format('YYYY-MM-DD')
+}
+
+const close = () => {
+  delView(unref(currentRoute))
+  push({ name: 'FillDailyReport', params: {} })
+}
+
+/** 提交表单 */
+const emit = defineEmits(['success'])
+const submitForm = async () => {
+  // 验证表单
+  try {
+    await formRef.value.validate()
+  } catch (error) {
+    return
+  }
+
+  // 处理时间范围数据
+  if (formData.value.timeRange && formData.value.timeRange.length === 2) {
+    // 将时间范围转换为 LocalTime 格式的字符串
+    const startDate = dayjs(formData.value.timeRange[0])
+    const endDate = dayjs(formData.value.timeRange[1])
+
+    // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
+    formData.value.startTime = startDate.format('HH:mm:ss')
+    formData.value.endTime = endDate.format('HH:mm:ss')
+  }
+
+  // 构建动态属性 extProperty 数组
+  const extProperties = dynamicAttrs.value.map(attr => {
+    return {
+      name: attr.name,
+      sort: attr.sort,
+      unit: attr.unit,
+      actualValue: formData.value.dynamicFields[attr.identifier] || '', // 从 dynamicFields 中获取用户填写的值
+      dataType: attr.dataType,
+      maxValue: attr.maxValue,
+      minValue: attr.minValue,
+      required: attr.required,
+      accessMode: attr.accessMode,
+      identifier: attr.identifier,
+      defaultValue: attr.defaultValue
+    }
+  })
+
+  // 准备提交数据,包含动态字段
+  const submitData = {
+    ...formData.value,
+    // 将动态字段组装成 extProperty 数组
+    extProperty: extProperties,
+    deviceIds: formData.value.deviceIds, // 设备ID集合
+  }
+
+  // 提交请求
+  formLoading.value = true
+  try {
+    // 调用更新接口
+    await IotRdDailyReportApi.updateIotRdDailyReport(submitData)
+    message.success(t('common.updateSuccess'))
+    close()
+    // 发送操作成功的事件
+    emit('success')
+  } catch (error) {
+    console.error('提交失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+
+// 初始化动态属性
+const initDynamicAttrs = (reportData: any) => {
+  if (reportData.dailyReportAttrs && reportData.dailyReportAttrs.length > 0) {
+    dynamicAttrs.value = reportData.dailyReportAttrs
+
+    // 初始化动态字段的值
+    const initialDynamicFields: Record<string, any> = {}
+
+    // 优先从 extProperty 中获取实际值(编辑时)
+    if (reportData.extProperty && reportData.extProperty.length > 0) {
+      reportData.extProperty.forEach((extProp: any) => {
+        if (extProp.identifier) {
+          initialDynamicFields[extProp.identifier] = extProp.actualValue || ''
+        }
+      })
+    }
+
+    reportData.dailyReportAttrs.forEach((attr: any) => {
+      if (!initialDynamicFields.hasOwnProperty(attr.identifier)) {
+        // 优先使用实际值,如果没有则使用默认值
+        const value = (attr.extProperty && attr.extProperty.actualValue !== undefined &&
+          attr.extProperty.actualValue !== null && attr.extProperty.actualValue !== '')
+          ? attr.extProperty.actualValue
+          : (attr.defaultValue || (attr.extProperty?.defaultValue || ''))
+
+        initialDynamicFields[attr.identifier] = value
+      }
+    })
+
+    formData.value.dynamicFields = initialDynamicFields
+  }
+}
+
+// 获取动态字段的验证规则
+const getDynamicAttrRules = (attr: any) => {
+  const rules = []
+  if (attr.required === 1) {
+    rules.push({
+      required: true,
+      message: `${attr.name}不能为空`,
+      trigger: 'blur'
+    })
+  }
+
+  // 数字类型验证
+  if (attr.dataType === 'double') {
+    rules.push({
+      validator: (rule: any, value: any, callback: any) => {
+        if (value === '' || value === null || value === undefined) {
+          callback()
+          return
+        }
+
+        const numValue = Number(value)
+        if (isNaN(numValue)) {
+          callback(new Error(`${attr.name}必须是数字`))
+        } else if (attr.minValue && numValue < Number(attr.minValue)) {
+          callback(new Error(`${attr.name}不能小于${attr.minValue}`))
+        } else if (attr.maxValue && numValue > Number(attr.maxValue)) {
+          callback(new Error(`${attr.name}不能大于${attr.maxValue}`))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    })
+  }
+
+  return rules
+}
+
+// 更新动态属性(处理交集、新增和删除)
+const updateDynamicAttrs = async (newAttrs: any[], newTechniqueIds: string[], oldTechniqueIds?: string[]) => {
+  const oldAttrs = [...dynamicAttrs.value]
+  const oldDynamicFields = { ...formData.value.dynamicFields }
+
+  // 计算需要保留的字段(交集)
+  const commonAttrs = oldAttrs.filter(oldAttr =>
+    newAttrs.some(newAttr => newAttr.identifier === oldAttr.identifier)
+  )
+
+  // 计算需要新增的字段
+  const addedAttrs = newAttrs.filter(newAttr =>
+    !oldAttrs.some(oldAttr => oldAttr.identifier === newAttr.identifier)
+  )
+
+  // 计算需要删除的字段
+  const removedAttrs = oldAttrs.filter(oldAttr =>
+    !newAttrs.some(newAttr => newAttr.identifier === oldAttr.identifier)
+  )
+
+  // 构建新的动态属性数组
+  const updatedAttrs = [...commonAttrs, ...addedAttrs]
+
+  // 构建新的动态字段对象
+  const updatedDynamicFields = { ...oldDynamicFields }
+
+  // 移除已删除的字段
+  removedAttrs.forEach(attr => {
+    delete updatedDynamicFields[attr.identifier]
+  })
+
+  // 初始化新增字段的值
+  addedAttrs.forEach(attr => {
+    if (!updatedDynamicFields[attr.identifier]) {
+      // 如果有默认值使用默认值,否则为空
+      updatedDynamicFields[attr.identifier] = attr.defaultValue ||
+        (attr.extProperty?.defaultValue || '')
+    }
+  })
+
+  // 更新响应式数据
+  dynamicAttrs.value = updatedAttrs
+  formData.value.dynamicFields = updatedDynamicFields
+}
+
+// 加载动态属性
+const loadDynamicAttrs = async (newTechniqueIds: string[], oldTechniqueIds?: string[]) => {
+  try {
+    formLoading.value = true
+
+    const queryParams = {
+      techniqueIds: newTechniqueIds.join(',')
+    }
+
+    const response = await IotDailyReportAttrsApi.dailyReportAttrs(queryParams)
+    const newAttrs = response || []
+
+    // 处理动态属性更新
+    await updateDynamicAttrs(newAttrs, newTechniqueIds, oldTechniqueIds)
+
+  } catch (error) {
+    console.error('加载动态属性失败:', error)
+    message.error('加载动态属性失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+// 监听施工工艺变化
+watch(() => formData.value.techniqueIds, async (newTechniqueIds, oldTechniqueIds) => {
+  if (newTechniqueIds && newTechniqueIds.length > 0) {
+    await loadDynamicAttrs(newTechniqueIds, oldTechniqueIds)
+  } else {
+    dynamicAttrs.value = []
+    formData.value.dynamicFields = {}
+  }
+}, { deep: true })
+
+// 初始化表单数据
+const initFormData = (reportData: any) => {
+  // 处理附件数据格式转换
+  const formattedAttachments = (reportData.attachments || []).map((attachment: any) => ({
+    id: attachment.id,
+    category: attachment.category?.toLowerCase() || 'daily_report',
+    bizId: attachment.bizId,
+    type: attachment.type?.toLowerCase() || 'attachment',
+    filename: attachment.filename,
+    fileType: attachment.fileType, // 使用辅助函数获取文件类型
+    filePath: attachment.filePath,
+    fileSize: attachment.fileSize,
+    remark: attachment.remark || ''
+  }))
+
+  formData.value = {
+    ...formData.value,
+    id: reportData.id,
+    deptId: reportData.deptId,
+    rdStatus: reportData.rdStatus || '',
+    techniqueIds: reportData.techniqueIds ? reportData.techniqueIds.map((id: number) => id.toString()) : [],
+    productionStatus: reportData.productionStatus || '',
+    nextPlan: reportData.nextPlan || '',
+    externalRental: reportData.externalRental || '',
+    malfunction: reportData.malfunction || '',
+    faultDowntime: reportData.faultDowntime || '',
+    startTime: reportData.startTime || undefined,
+    endTime: reportData.endTime || undefined,
+    companyId: reportData.companyId || '',
+    dynamicFields: {}, // 确保有初始值
+    // 初始化附件数据
+    attachments: formattedAttachments
+  }
+  queryParams.deptId = reportData.companyId
+  // 设置时间范围选择器
+  if (reportData.startTime && reportData.startTime[0] && reportData.endTime && reportData.endTime[0]) {
+    formData.value.timeRange = [
+      new Date(reportData.startTime[0]),
+      new Date(reportData.endTime[0])
+    ]
+  }
+  // 初始化动态属性
+  initDynamicAttrs(reportData)
+
+  // 初始化设备数据
+  initDeviceData(reportData)
+}
+
+onMounted(async () => {
+  formLoading.value = true
+  try {
+    // 加载当前登录人所属部门
+    const deptId = userStore.getUser.deptId
+    const dept = await DeptApi.getDept(deptId)
+
+    // 查询瑞都日报详情
+    if (id) {
+      const response = await IotRdDailyReportApi.getIotRdDailyReport(id)
+      dailyReportData.value = response || {}
+      initFormData(dailyReportData.value)
+    }
+  } catch (error) {
+    console.error('初始化数据失败:', error)
+    message.error('数据加载失败')
+  } finally {
+    formLoading.value = false
+  }
+})
+
+/** 审批操作 */
+const handleApprove = async (action: 'pass' | 'reject') => {
+  // 只有在审批模式下才执行审批操作
+  if (!isApprovalMode.value) {
+    message.warning('当前不是审批模式')
+    return
+  }
+  try {
+    // 验证审批表单(如果需要)
+    // await approvalFormRef.value.validate()
+
+    formLoading.value = true
+
+    // 处理时间范围数据
+    if (formData.value.timeRange && formData.value.timeRange.length === 2) {
+      // 将时间范围转换为 LocalTime 格式的字符串
+      const startDate = dayjs(formData.value.timeRange[0])
+      const endDate = dayjs(formData.value.timeRange[1])
+
+      // 格式化为 HH:mm:ss 字符串,后端会自动转换为 LocalTime
+      formData.value.startTime = startDate.format('HH:mm:ss')
+      formData.value.endTime = endDate.format('HH:mm:ss')
+    }
+
+    // 构建审批数据,包含审批意见
+    const approveData = {
+      ...formData.value,
+      id: Number(id),
+      opinion: approvalForm.opinion,
+      auditStatus: action === 'pass' ? 20 : 30
+    }
+
+    // 这里可以调用审批API
+    if (action === 'pass') {
+      // 审批通过逻辑
+      await IotRdDailyReportApi.approveRdDailyReport(approveData)
+      message.success('审批通过')
+    } else {
+      // 审批驳回逻辑
+      await IotRdDailyReportApi.approveRdDailyReport(approveData)
+      message.success('审批驳回')
+    }
+    close()
+  } catch (error) {
+    console.error('审批操作失败:', error)
+    message.error('审批操作失败')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+</script>
+
+<style scoped>
+
+.info-table {
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.table-row {
+  display: flex;
+  border-bottom: 1px solid #e0e0e0;
+}
+
+.table-row:last-child {
+  border-bottom: none;
+}
+
+.table-cell {
+  flex: 1;
+  border-right: 1px solid #e0e0e0;
+  padding: 12px 8px;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+}
+
+.table-cell:last-child {
+  border-right: none;
+}
+
+.table-cell.full-width {
+  flex: 1;
+  border-right: none;
+}
+
+.cell-content {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+.cell-label {
+  font-weight: 500;
+  /* 统一字体大小为 14px(Element 表单默认) */
+  font-size: 14px;
+  color: #606266;
+  min-width: 80px;
+  margin-right: 8px;
+  flex-shrink: 0;
+}
+
+.cell-value {
+  /* 统一字体大小为 14px(Element 输入框默认) */
+  font-size: 14px;
+  color: #303133;
+  /* 统一行高为 1.5(Element 组件默认行高) */
+  line-height: 1.5;
+  flex: 1;
+  word-break: break-all;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .table-row {
+    flex-direction: column;
+  }
+
+  .table-cell {
+    border-right: none;
+    border-bottom: 1px solid #e0e0e0;
+  }
+
+  .table-cell:last-child {
+    border-bottom: none;
+  }
+}
+
+.daily-report-title {
+  text-align: center;
+  margin: 20px 0;
+  padding: 10px;
+  border-bottom: 2px solid #409eff;
+}
+
+.daily-report-title h2 {
+  margin: 0;
+  color: #303133;
+  font-size: 16px;
+  font-weight: bold;
+}
+
+/* 为第二、三部分增加左右留白 */
+.section-padding {
+  padding-left: 0px;
+  padding-right: 40px;
+}
+
+.project-info-section {
+  margin: 20px 0;
+  padding: 20px;
+  background-color: #f8f9fa;
+  border-radius: 4px;
+  border: 1px solid #e9ecef;
+}
+
+.info-row {
+  padding: 12px 0;
+  border-bottom: 1px solid #e9ecef;
+}
+
+.info-row:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  font-weight: bold;
+  color: #495057;
+  margin-right: 8px;
+}
+
+.info-value {
+  color: #212529;
+}
+
+:deep(.el-textarea .el-textarea__inner) {
+  min-height: 80px;
+}
+
+/* 确保表单label不换行 */
+:deep(.el-form-item__label) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 甲方字段:单行显示+超出省略 */
+.single-line-ellipsis {
+  /* 强制文本单行显示 */
+  white-space: nowrap;
+  /* 超出容器部分隐藏 */
+  overflow: hidden;
+  /* 超出部分显示省略号 */
+  text-overflow: ellipsis;
+  /* 避免文本被截断(可选,根据需求调整) */
+  word-break: normal;
+}
+
+/* 设备配置字段:换行缩进(与首行对齐) */
+.indent-multiline {
+  /* 首行及换行后缩进 2em(与 label 宽度匹配,可根据需求调整) */
+  text-indent: 0em;
+  /* 允许长文本换行(覆盖原有 cell-value 的 break-all,确保中文换行正常) */
+  word-break: break-word;
+  /* 保证换行后文本正常显示(可选,清除可能的 nowrap 影响) */
+  white-space: normal;
+}
+
+/* 添加审批模式下的样式 */
+.approval-notice {
+  margin-top: 10px;
+}
+
+/* 审批模式下表单字段的只读样式 */
+:deep(.el-form-item.is-disabled .el-input__inner),
+:deep(.el-form-item.is-disabled .el-textarea__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+:deep(.el-form-item.is-disabled .el-select .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+/* 只读模式下表单字段的样式 */
+:deep(.el-form-item.is-disabled .el-input__inner),
+:deep(.el-form-item.is-disabled .el-textarea__inner),
+:deep(.el-form-item.is-disabled .el-select .el-input__inner),
+:deep(.el-form-item.is-disabled .el-date-editor .el-input__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266; /* 保持文字可读性 */
+  cursor: not-allowed;
+}
+
+/* 详情模式下的特殊样式 */
+.detail-mode .cell-value {
+  color: #303133;
+  font-weight: normal;
+}
+
+/* 添加审批意见区域的样式 */
+.approval-opinion-section {
+  margin-top: 20px;
+  border-top: 2px solid #f0f0f0;
+  padding-top: 20px;
+}
+
+/* 审批意见文本域样式 */
+:deep(.approval-opinion .el-textarea__inner) {
+  min-height: 100px;
+  resize: vertical;
+}
+
+/* 审批意见标签样式 */
+:deep(.approval-opinion .el-form-item__label) {
+  font-weight: bold;
+  color: #606266;
+}
+
+/* 附件列表样式 */
+.attachment-list {
+  width: 100%;
+  margin-top: 5px;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+  padding: 10px;
+  background-color: #fafafa;
+  box-sizing: border-box;
+}
+
+.attachment-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.attachment-item:last-child {
+  border-bottom: none;
+}
+
+.attachment-name {
+  flex: 1;
+  color: #606266;
+  font-size: 11px;
+}
+
+.no-attachment {
+  color: #909399;
+  font-style: italic;
+  margin-top: 5px;
+  padding: 10px;
+}
+
+/* 附件名称链接样式 */
+.attachment-name {
+  color: #409eff;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.attachment-name:hover {
+  color: #66b1ff;
+}
+
+/* 只读模式下的设备显示样式 */
+.device-display-readonly {
+  color: #606266;
+  font-size: 11px;
+  line-height: 1.5;
+  background-color: #f5f7fa;
+  padding: 8px 12px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  display: inline-block;
+  min-width: 200px;
+}
+
+.no-device {
+  margin-left: 10px;
+  color: #909399;
+  font-style: italic;
+}
+
+/* 设备选择对话框样式 */
+.transfer-container {
+  text-align: center;
+  padding: 0px;
+}
+
+.transfer-component {
+  width: 100%;
+  min-width: 600px;
+}
+
+:deep(.el-transfer-panel) {
+  width: 40% !important;
+}
+
+:deep(.el-transfer-panel__item) {
+  display: flex !important;
+  align-items: center !important;
+  height: 32px !important;
+  line-height: 32px !important;
+  padding: 0 8px !important;
+  margin: 0 !important;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.transfer-option-text {
+  display: inline-block;
+  max-width: 100%;
+}
+
+:deep(.el-transfer-panel__list) {
+  width: 100% !important;
+}
+
+.device-display-container {
+  /* 与其他文本域保持相同的宽度和样式 */
+  display: inline-block;
+  width: 100%; /* 减去按钮宽度 */
+  min-height: 32px;
+  line-height: 32px;
+  padding: 0 12px;
+  margin-left: 0px;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  background-color: #fff;
+  font-size: 11px;
+  /* 文本溢出处理 */
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 只读模式下的设备显示样式 */
+:deep(.is-disabled) .device-display-container {
+  background-color: #f5f7fa;
+  color: #606266;
+  cursor: not-allowed;
+}
+
+/* 附件容器样式调整 */
+.attachment-container {
+  /* 与其他文本域保持相同的宽度和边距 */
+  width: 100%;
+  margin-top: 10px;
+}
+
+/* 未选择设备字段的只读样式 */
+:deep(.unselected-device .el-textarea__inner) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #909399;
+  cursor: not-allowed;
+  resize: none;
+}
+</style>

+ 319 - 0
src/views/pms/iotrddailyreport/IotRdDailyReportForm.vue

@@ -0,0 +1,319 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="施工队伍id" prop="deptId">
+        <el-input v-model="formData.deptId" placeholder="请输入施工队伍id" />
+      </el-form-item>
+      <el-form-item label="项目id" prop="projectId">
+        <el-input v-model="formData.projectId" placeholder="请输入项目id" />
+      </el-form-item>
+      <el-form-item label="任务id" prop="taskId">
+        <el-input v-model="formData.taskId" placeholder="请输入任务id" />
+      </el-form-item>
+      <el-form-item label="项目类别(钻井 修井 注氮 酸化压裂... )" prop="projectClassification">
+        <el-input v-model="formData.projectClassification" placeholder="请输入项目类别(钻井 修井 注氮 酸化压裂... )" />
+      </el-form-item>
+      <el-form-item label="施工工艺([123,233])" prop="techniqueIds">
+        <el-input v-model="formData.techniqueIds" placeholder="请输入施工工艺([123,233])" />
+      </el-form-item>
+      <el-form-item label="施工设备([123,233])" prop="deviceIds">
+        <el-input v-model="formData.deviceIds" placeholder="请输入施工设备([123,233])" />
+      </el-form-item>
+      <el-form-item label="时间节点-开始" prop="startTime">
+        <el-date-picker
+          v-model="formData.startTime"
+          type="date"
+          value-format="x"
+          placeholder="选择时间节点-开始"
+        />
+      </el-form-item>
+      <el-form-item label="时间节点-结束" prop="endTime">
+        <el-date-picker
+          v-model="formData.endTime"
+          type="date"
+          value-format="x"
+          placeholder="选择时间节点-结束"
+        />
+      </el-form-item>
+      <el-form-item label="累计施工井" prop="cumulativeWorkingWell">
+        <el-input v-model="formData.cumulativeWorkingWell" placeholder="请输入累计施工井" />
+      </el-form-item>
+      <el-form-item label="累计施工层" prop="cumulativeWorkingLayers">
+        <el-input v-model="formData.cumulativeWorkingLayers" placeholder="请输入累计施工层" />
+      </el-form-item>
+      <el-form-item label="当日泵车台次" prop="dailyPumpTrips">
+        <el-input v-model="formData.dailyPumpTrips" placeholder="请输入当日泵车台次" />
+      </el-form-item>
+      <el-form-item label="当日仪表/混砂" prop="dailyToolsSand">
+        <el-input v-model="formData.dailyToolsSand" placeholder="请输入当日仪表/混砂" />
+      </el-form-item>
+      <el-form-item label="趟数" prop="runCount">
+        <el-input v-model="formData.runCount" placeholder="请输入趟数" />
+      </el-form-item>
+      <el-form-item label="桥塞" prop="bridgePlug">
+        <el-input v-model="formData.bridgePlug" placeholder="请输入桥塞" />
+      </el-form-item>
+      <el-form-item label="水方量" prop="waterVolume">
+        <el-input v-model="formData.waterVolume" placeholder="请输入水方量" />
+      </el-form-item>
+      <el-form-item label="时间H" prop="hourCount">
+        <el-input v-model="formData.hourCount" placeholder="请输入时间H" />
+      </el-form-item>
+      <el-form-item label="当日油耗(吨)" prop="dailyFuel">
+        <el-input v-model="formData.dailyFuel" placeholder="请输入当日油耗(吨)" />
+      </el-form-item>
+      <el-form-item label="当日用电量(kWh)" prop="dailyPowerUsage">
+        <el-input v-model="formData.dailyPowerUsage" placeholder="请输入当日用电量(kWh)" />
+      </el-form-item>
+      <el-form-item label="生产时间(H)" prop="productionTime">
+        <el-date-picker
+          v-model="formData.productionTime"
+          type="date"
+          value-format="x"
+          placeholder="选择生产时间(H)"
+        />
+      </el-form-item>
+      <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+        <el-date-picker
+          v-model="formData.nonProductionTime"
+          type="date"
+          value-format="x"
+          placeholder="选择非生产时间(H)"
+        />
+      </el-form-item>
+      <el-form-item label="非生产时间原因" prop="rdNptReason">
+        <el-input v-model="formData.rdNptReason" placeholder="请输入非生产时间原因" />
+      </el-form-item>
+      <el-form-item label="施工开始日期" prop="constructionStartDate">
+        <el-date-picker
+          v-model="formData.constructionStartDate"
+          type="date"
+          value-format="x"
+          placeholder="选择施工开始日期"
+        />
+      </el-form-item>
+      <el-form-item label="施工结束日期" prop="constructionEndDate">
+        <el-date-picker
+          v-model="formData.constructionEndDate"
+          type="date"
+          value-format="x"
+          placeholder="选择施工结束日期"
+        />
+      </el-form-item>
+      <el-form-item label="当日生产情况生产动态" prop="productionStatus">
+        <el-radio-group v-model="formData.productionStatus">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="外租情况(可能有附件)" prop="externalRental">
+        <el-input v-model="formData.externalRental" placeholder="请输入外租情况(可能有附件)" />
+      </el-form-item>
+      <el-form-item label="下步工作计划" prop="nextPlan">
+        <el-input v-model="formData.nextPlan" placeholder="请输入下步工作计划" />
+      </el-form-item>
+      <el-form-item label="施工状态(动迁上井/动迁下井/施工准备/施工...)" prop="rdStatus">
+        <el-radio-group v-model="formData.rdStatus">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="故障情况" prop="malfunction">
+        <el-input v-model="formData.malfunction" placeholder="请输入故障情况" />
+      </el-form-item>
+      <el-form-item label="故障误工h" prop="faultDowntime">
+        <el-date-picker
+          v-model="formData.faultDowntime"
+          type="date"
+          value-format="x"
+          placeholder="选择故障误工h"
+        />
+      </el-form-item>
+      <el-form-item label="人员情况" prop="personnel">
+        <el-input v-model="formData.personnel" placeholder="请输入人员情况" />
+      </el-form-item>
+      <el-form-item label="全员数量" prop="totalStaffNum">
+        <el-input v-model="formData.totalStaffNum" placeholder="请输入全员数量" />
+      </el-form-item>
+      <el-form-item label="休假人员数量" prop="leaveStaffNum">
+        <el-input v-model="formData.leaveStaffNum" placeholder="请输入休假人员数量" />
+      </el-form-item>
+      <el-form-item label="不同专业公司的扩展属性值" prop="extProperty">
+        <el-input v-model="formData.extProperty" placeholder="请输入不同专业公司的扩展属性值" />
+      </el-form-item>
+      <el-form-item label="排序值" prop="sort">
+        <el-input v-model="formData.sort" placeholder="请输入排序值" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注" />
+      </el-form-item>
+      <el-form-item label="状态(0启用 1禁用)" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="流程实例id" prop="processInstanceId">
+        <el-input v-model="formData.processInstanceId" placeholder="请输入流程实例id" />
+      </el-form-item>
+      <el-form-item label="审批状态 未提交、审批中、审批通过、审批不通过、已取消" prop="auditStatus">
+        <el-radio-group v-model="formData.auditStatus">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
+
+/** 瑞都日报 表单 */
+defineOptions({ name: 'IotRdDailyReportForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  techniqueIds: undefined,
+  deviceIds: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  cumulativeWorkingWell: undefined,
+  cumulativeWorkingLayers: undefined,
+  dailyPumpTrips: undefined,
+  dailyToolsSand: undefined,
+  runCount: undefined,
+  bridgePlug: undefined,
+  waterVolume: undefined,
+  hourCount: undefined,
+  dailyFuel: undefined,
+  dailyPowerUsage: undefined,
+  productionTime: undefined,
+  nonProductionTime: undefined,
+  rdNptReason: undefined,
+  constructionStartDate: undefined,
+  constructionEndDate: undefined,
+  productionStatus: undefined,
+  externalRental: undefined,
+  nextPlan: undefined,
+  rdStatus: undefined,
+  malfunction: undefined,
+  faultDowntime: undefined,
+  personnel: undefined,
+  totalStaffNum: undefined,
+  leaveStaffNum: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+})
+const formRules = reactive({
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await IotRdDailyReportApi.getIotRdDailyReport(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotRdDailyReportVO
+    if (formType.value === 'create') {
+      await IotRdDailyReportApi.createIotRdDailyReport(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotRdDailyReportApi.updateIotRdDailyReport(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    deptId: undefined,
+    projectId: undefined,
+    taskId: undefined,
+    projectClassification: undefined,
+    techniqueIds: undefined,
+    deviceIds: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    cumulativeWorkingWell: undefined,
+    cumulativeWorkingLayers: undefined,
+    dailyPumpTrips: undefined,
+    dailyToolsSand: undefined,
+    runCount: undefined,
+    bridgePlug: undefined,
+    waterVolume: undefined,
+    hourCount: undefined,
+    dailyFuel: undefined,
+    dailyPowerUsage: undefined,
+    productionTime: undefined,
+    nonProductionTime: undefined,
+    rdNptReason: undefined,
+    constructionStartDate: undefined,
+    constructionEndDate: undefined,
+    productionStatus: undefined,
+    externalRental: undefined,
+    nextPlan: undefined,
+    rdStatus: undefined,
+    malfunction: undefined,
+    faultDowntime: undefined,
+    personnel: undefined,
+    totalStaffNum: undefined,
+    leaveStaffNum: undefined,
+    extProperty: undefined,
+    sort: undefined,
+    remark: undefined,
+    status: undefined,
+    processInstanceId: undefined,
+    auditStatus: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 277 - 0
src/views/pms/iotrddailyreport/fillDailyReport.vue

@@ -0,0 +1,277 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="项目" prop="projectId">
+        <el-input
+          v-model="queryParams.projectId"
+          placeholder="请输入项目"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="任务" prop="taskId">
+        <el-input
+          v-model="queryParams.taskId"
+          placeholder="请输入任务"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</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
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-rd-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="主键id" align="center" prop="id" v-if="false"/>
+      <el-table-column label="施工队伍" align="center" prop="deptName" />
+      <el-table-column label="项目" align="center" prop="contractName" />
+      <el-table-column label="任务" align="center" prop="taskName" />
+      <el-table-column label="带班干部" align="center" prop="responsiblePersonNames" />
+      <el-table-column label="填报人" align="center" prop="submitterNames" />
+      <el-table-column label="日报状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.OPERATION_FILL_ORDER_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <!--
+      <el-table-column label="项目类别(钻井 修井 注氮 酸化压裂... )" align="center" prop="projectClassification" /> -->
+      <!--
+      <el-table-column label="时间节点-结束" align="center" prop="endTime" /> -->
+      <el-table-column
+        label="施工开始日期"
+        align="center"
+        prop="constructionStartDate"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column
+        label="施工结束日期"
+        align="center"
+        prop="constructionEndDate"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('fill', scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:update']"
+          >
+            填报
+          </el-button>
+          <el-button
+            link
+            type="success"
+            @click="handleDetail(scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:query']"
+          >
+            查看
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改
+  <IotRdDailyReportForm ref="formRef" @success="getList" /> -->
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
+import FillDailyReportForm from './FillDailyReportForm.vue'
+import {DICT_TYPE} from "@/utils/dict";
+
+/** 瑞都日报 填报 列表 */
+defineOptions({ name: 'FillDailyReport' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRdDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  techniqueIds: undefined,
+  deviceIds: undefined,
+  startTime: [],
+  endTime: [],
+  cumulativeWorkingWell: undefined,
+  cumulativeWorkingLayers: undefined,
+  dailyPumpTrips: undefined,
+  dailyToolsSand: undefined,
+  runCount: undefined,
+  bridgePlug: undefined,
+  waterVolume: undefined,
+  hourCount: undefined,
+  dailyFuel: undefined,
+  dailyPowerUsage: undefined,
+  productionTime: [],
+  nonProductionTime: [],
+  rdNptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  externalRental: undefined,
+  nextPlan: undefined,
+  rdStatus: undefined,
+  malfunction: undefined,
+  faultDowntime: [],
+  personnel: undefined,
+  totalStaffNum: undefined,
+  leaveStaffNum: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRdDailyReportApi.getIotRdDailyReportPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  push({ name: 'FillDailyReportForm', params:{ id: id, mode: 'fill' } })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRdDailyReportApi.deleteIotRdDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 查看日报详情 */
+const handleDetail = async (id: number) => {
+  try {
+    // 跳转到 FillDailyReportForm 页面,传递审批模式和ID
+    push({
+      name: 'FillDailyReportForm',
+      params: {
+        id: id.toString(),
+        mode: 'detail'  // 添加详情模式标识
+      }
+    })
+  } catch (error) {
+    console.error('跳转详情页面失败:', error)
+  }
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotRdDailyReportApi.exportIotRdDailyReport(queryParams)
+    download.excel(data, '瑞都日报.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 470 - 0
src/views/pms/iotrddailyreport/index.vue

@@ -0,0 +1,470 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="项目" prop="projectId">
+        <el-input
+          v-model="queryParams.projectId"
+          placeholder="请输入项目"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="任务" prop="taskId">
+        <el-input
+          v-model="queryParams.taskId"
+          placeholder="请输入任务"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</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
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-rd-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap ref="tableContainerRef">
+    <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="主键id" align="center" prop="id" v-if="false"/>
+      <el-table-column label="施工队伍" align="center" prop="deptName" :width="columnWidths.deptName"/>
+      <el-table-column label="项目" align="center" prop="contractName" :width="columnWidths.contractName"/>
+      <el-table-column label="任务" align="center" prop="taskName" :width="columnWidths.taskName"/>
+      <el-table-column label="时间节点" align="center" prop="timeRange" :width="columnWidths.timeRange"/>
+      <el-table-column :label="t('project.status')" align="center" prop="rdStatus" :width="columnWidths.rdStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PMS_PROJECT_RD_STATUS" :value="scope.row.rdStatus" />
+        </template>
+      </el-table-column>
+      <!--
+      <el-table-column label="项目类别(钻井 修井 注氮 酸化压裂... )" align="center" prop="projectClassification" />
+      <el-table-column label="施工工艺" align="center" prop="techniqueIds" /> -->
+      <!--
+      <el-table-column label="施工设备" align="center" prop="deviceIds" /> -->
+      <!--
+      <el-table-column label="时间节点-结束" align="center" prop="endTime" /> -->
+      <el-table-column label="累计施工井" align="center" prop="cumulativeWorkingWell" :width="columnWidths.cumulativeWorkingWell"/>
+      <el-table-column label="累计施工层" align="center" prop="cumulativeWorkingLayers" :width="columnWidths.cumulativeWorkingLayers"/>
+      <el-table-column label="当日泵车台次" align="center" prop="dailyPumpTrips" :width="columnWidths.dailyPumpTrips"/>
+      <el-table-column label="当日仪表/混砂" align="center" prop="dailyToolsSand" :width="columnWidths.dailyToolsSand"/>
+      <el-table-column label="趟数" align="center" prop="runCount" :width="columnWidths.runCount"/>
+      <el-table-column label="桥塞" align="center" prop="bridgePlug" :width="columnWidths.bridgePlug"/>
+      <el-table-column label="水方量" align="center" prop="waterVolume" :width="columnWidths.waterVolume"/>
+      <el-table-column label="时间H" align="center" prop="hourCount" :width="columnWidths.hourCount"/>
+      <el-table-column
+        label="施工开始日期"
+        align="center"
+        prop="constructionStartDate"
+        :formatter="dateFormatter"
+        :width="columnWidths.constructionStartDate"
+      />
+      <el-table-column
+        label="施工结束日期"
+        align="center"
+        prop="constructionEndDate"
+        :formatter="dateFormatter"
+        :width="columnWidths.constructionEndDate"
+      />
+      <el-table-column label="当日生产动态" align="center" prop="productionStatus" :width="columnWidths.productionStatus"/>
+      <el-table-column label="下步工作计划" align="center" prop="nextPlan" :width="columnWidths.nextPlan"/>
+      <el-table-column label="外租设备" align="center" prop="externalRental" :width="columnWidths.externalRental"/>
+      <el-table-column label="故障情况" align="center" prop="malfunction" :width="columnWidths.malfunction"/>
+      <el-table-column label="故障误工H" align="center" prop="faultDowntime" :width="columnWidths.faultDowntime"/>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        :width="columnWidths.createTime"
+      />
+      <el-table-column label="操作" align="center" min-width="120px" fixed="right">
+        <template #default="scope">
+          <!--
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:update']"
+          >
+            编辑
+          </el-button> -->
+          <el-button
+            link
+            type="warning"
+            @click="handleApprove(scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:update']"
+          >
+            审批
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotRdDailyReportForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
+import IotRdDailyReportForm from './IotRdDailyReportForm.vue'
+import {DICT_TYPE} from "@/utils/dict";
+
+
+/** 瑞都日报 列表 */
+defineOptions({ name: 'IotRdDailyReport' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRdDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  techniqueIds: undefined,
+  deviceIds: undefined,
+  startTime: [],
+  endTime: [],
+  cumulativeWorkingWell: undefined,
+  cumulativeWorkingLayers: undefined,
+  dailyPumpTrips: undefined,
+  dailyToolsSand: undefined,
+  runCount: undefined,
+  bridgePlug: undefined,
+  waterVolume: undefined,
+  hourCount: undefined,
+  dailyFuel: undefined,
+  dailyPowerUsage: undefined,
+  productionTime: [],
+  nonProductionTime: [],
+  rdNptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  externalRental: undefined,
+  nextPlan: undefined,
+  rdStatus: undefined,
+  malfunction: undefined,
+  faultDowntime: [],
+  personnel: undefined,
+  totalStaffNum: undefined,
+  leaveStaffNum: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+// 表格引用
+const tableRef = ref()
+// 表格容器引用
+const tableContainerRef = ref()
+
+// 列宽度配置
+const columnWidths = ref({
+  id: '80px',
+  deptName: '120px',
+  contractName: '120px',
+  taskName: '120px',
+  timeRange: '120px',
+  rdStatus: '120px',
+  cumulativeWorkingWell: '120px',
+  cumulativeWorkingLayers: '120px',
+  dailyPumpTrips: '120px',
+  dailyToolsSand: '120px',
+  runCount: '80px',
+  bridgePlug: '80px',
+  waterVolume: '100px',
+  hourCount: '80px',
+  constructionStartDate: '180px',
+  constructionEndDate: '180px',
+  productionStatus: '200px',
+  nextPlan: '200px',
+  externalRental: '120px',
+  malfunction: '150px',
+  faultDowntime: '120px',
+  createTime: '180px',
+  operation: '120px'
+})
+
+// 计算文本宽度
+const getTextWidth = (text: string, fontSize = 14) => {
+  const span = document.createElement('span');
+  span.style.visibility = 'hidden';
+  span.style.position = 'absolute';
+  span.style.whiteSpace = 'nowrap';
+  span.style.fontSize = `${fontSize}px`;
+  span.style.fontFamily = 'inherit';
+  span.innerText = text;
+
+  document.body.appendChild(span);
+  const width = span.offsetWidth;
+  document.body.removeChild(span);
+
+  return width;
+};
+
+// 计算列宽度
+const calculateColumnWidths = () => {
+  const MIN_WIDTH = 80; // 最小列宽
+  const PADDING = 25; // 列内边距
+
+  // 确保表格容器存在
+  if (!tableContainerRef.value?.$el) return;
+
+  const newWidths: Record<string, string> = {};
+
+  // 计算各列宽度的函数
+  const calculateColumnWidth = (key: string, label: string, getValue: Function) => {
+    const headerWidth = getTextWidth(label) + PADDING;
+    let contentMaxWidth = MIN_WIDTH;
+
+    // 计算内容最大宽度
+    list.value.forEach((row, index) => {
+      let text = '';
+      if (key === 'rdStatus') {
+        // 特殊处理字典列,这里简化处理,实际应该获取字典标签
+        text = String(row[key] || '');
+      } else if (key.includes('Date') || key === 'createTime') {
+        // 日期列使用格式化后的值
+        text = dateFormatter(null, null, row[key]) || '';
+      } else {
+        text = String(getValue ? getValue(row, index) : (row[key] || ''));
+      }
+
+      const textWidth = getTextWidth(text) + PADDING;
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    // 取标题宽度和内容最大宽度的较大值
+    const finalWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH);
+    newWidths[key] = `${finalWidth}px`;
+  };
+
+  // 计算各列宽度
+  calculateColumnWidth('deptName', '施工队伍', (row: any) => row.deptName);
+  calculateColumnWidth('contractName', '项目', (row: any) => row.contractName);
+  calculateColumnWidth('taskName', '任务', (row: any) => row.taskName);
+  calculateColumnWidth('timeRange', '时间节点', (row: any) => row.timeRange);
+  calculateColumnWidth('rdStatus', t('project.status'), (row: any) => row.rdStatus);
+  calculateColumnWidth('cumulativeWorkingWell', '累计施工井', (row: any) => row.cumulativeWorkingWell);
+  calculateColumnWidth('cumulativeWorkingLayers', '累计施工层', (row: any) => row.cumulativeWorkingLayers);
+  calculateColumnWidth('dailyPumpTrips', '当日泵车台次', (row: any) => row.dailyPumpTrips);
+  calculateColumnWidth('dailyToolsSand', '当日仪表/混砂', (row: any) => row.dailyToolsSand);
+  calculateColumnWidth('runCount', '趟数', (row: any) => row.runCount);
+  calculateColumnWidth('bridgePlug', '桥塞', (row: any) => row.bridgePlug);
+  calculateColumnWidth('waterVolume', '水方量', (row: any) => row.waterVolume);
+  calculateColumnWidth('hourCount', '时间H', (row: any) => row.hourCount);
+  calculateColumnWidth('constructionStartDate', '施工开始日期', (row: any) => dateFormatter(null, null, row.constructionStartDate));
+  calculateColumnWidth('constructionEndDate', '施工结束日期', (row: any) => dateFormatter(null, null, row.constructionEndDate));
+  calculateColumnWidth('productionStatus', '当日生产动态', (row: any) => row.productionStatus);
+  calculateColumnWidth('nextPlan', '下步工作计划', (row: any) => row.nextPlan);
+  calculateColumnWidth('externalRental', '外租设备', (row: any) => row.externalRental);
+  calculateColumnWidth('malfunction', '故障情况', (row: any) => row.malfunction);
+  calculateColumnWidth('faultDowntime', '故障误工H', (row: any) => row.faultDowntime);
+  calculateColumnWidth('createTime', '创建时间', (row: any) => dateFormatter(null, null, row.createTime));
+
+  // 操作列固定宽度
+  newWidths.operation = '120px';
+  // id列固定宽度(虽然隐藏)
+  newWidths.id = '80px';
+
+  // 更新列宽配置
+  columnWidths.value = newWidths;
+
+  // 触发表格重新布局
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRdDailyReportApi.getIotRdDailyReportPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+    // 获取数据后计算列宽
+    nextTick(() => {
+      calculateColumnWidths();
+    });
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 审批按钮操作 */
+const handleApprove = async (id: number) => {
+  try {
+    // 跳转到 FillDailyReportForm 页面,传递审批模式和ID
+    push({
+      name: 'FillDailyReportForm',
+      params: {
+        id: id.toString(),
+        mode: 'approval'  // 添加审批模式标识
+      }
+    })
+  } catch (error) {
+    console.error('跳转审批页面失败:', error)
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRdDailyReportApi.deleteIotRdDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotRdDailyReportApi.exportIotRdDailyReport(queryParams)
+    download.excel(data, '瑞都日报.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 声明 ResizeObserver 实例
+let resizeObserver: ResizeObserver | null = null;
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+  // 创建 ResizeObserver 监听表格容器尺寸变化
+  if (tableContainerRef.value?.$el) {
+    resizeObserver = new ResizeObserver(() => {
+      // 使用防抖避免频繁触发
+      clearTimeout((window as any).resizeTimer);
+      (window as any).resizeTimer = setTimeout(() => {
+        calculateColumnWidths();
+      }, 100);
+    });
+    resizeObserver.observe(tableContainerRef.value.$el);
+  }
+})
+
+onUnmounted(() => {
+  // 清除 ResizeObserver
+  if (resizeObserver && tableContainerRef.value?.$el) {
+    resizeObserver.unobserve(tableContainerRef.value.$el);
+    resizeObserver = null;
+  }
+
+  // 清除定时器
+  if ((window as any).resizeTimer) {
+    clearTimeout((window as any).resizeTimer);
+  }
+})
+
+// 监听列表数据变化重新计算列宽
+watch(list, () => {
+  nextTick(calculateColumnWidths)
+}, { deep: true })
+</script>
+
+<style scoped>
+/* 确保表格单元格内容不换行 */
+:deep(.el-table .cell) {
+white-space: nowrap;
+}
+
+/* 确保表格列标题不换行 */
+:deep(.el-table th > .cell) {
+white-space: nowrap;
+}
+
+/* 调整表格最小宽度,确保内容完全显示 */
+:deep(.el-table) {
+min-width: 100%;
+}
+</style>

+ 463 - 0
src/views/pms/iotrddailyreport/statistics.vue

@@ -0,0 +1,463 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="项目" prop="projectId">
+        <el-input
+          v-model="queryParams.projectId"
+          placeholder="请输入项目"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="任务" prop="taskId">
+        <el-input
+          v-model="queryParams.taskId"
+          placeholder="请输入任务"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</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
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-rd-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap ref="tableContainerRef">
+    <el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="项目部" align="center" prop="projectDeptName" :width="columnWidths.deptName"/>
+      <el-table-column label="队伍" align="center" prop="deptName" :width="columnWidths.contractName"/>
+      <el-table-column label="甲方" align="center" prop="manufactureName" :width="columnWidths.taskName"/>
+      <el-table-column label="井号" align="center" prop="wellName" :width="columnWidths.timeRange"/>
+      <el-table-column label="工艺" align="center" prop="techniques" :width="columnWidths.timeRange"/>
+      <el-table-column label="总工作量" align="center" prop="workloadDesign" :width="columnWidths.timeRange"/>
+      <!-- 已完成工作量分组列 -->
+      <el-table-column label="已完成工作量" align="center">
+        <!-- 动态生成列 -->
+        <el-table-column
+          v-for="column in dynamicColumns"
+          :key="column"
+          :label="column"
+          :prop="column"
+          align="center"
+          min-width="120"
+        >
+          <template #default="scope">
+            {{ getWorkloadByUnit(scope.row.items, column) }}
+          </template>
+        </el-table-column>
+      </el-table-column>
+
+      <!--
+      <el-table-column label="操作" align="center" min-width="120px" fixed="right">
+        <template #default="scope">
+
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="warning"
+            @click="handleApprove(scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report:update']"
+          >
+            审批
+          </el-button>
+        </template>
+      </el-table-column> -->
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotRdDailyReportForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
+import IotRdDailyReportForm from './IotRdDailyReportForm.vue'
+import {DICT_TYPE} from "@/utils/dict";
+import { ref, reactive, onMounted, computed } from 'vue'
+
+/** 瑞都日报 汇总统计 */
+defineOptions({ name: 'IotRdDailyReportStatistics' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由跳转
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRdDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  techniqueIds: undefined,
+  deviceIds: undefined,
+  startTime: [],
+  endTime: [],
+  cumulativeWorkingWell: undefined,
+  cumulativeWorkingLayers: undefined,
+  dailyPumpTrips: undefined,
+  dailyToolsSand: undefined,
+  runCount: undefined,
+  bridgePlug: undefined,
+  waterVolume: undefined,
+  hourCount: undefined,
+  dailyFuel: undefined,
+  dailyPowerUsage: undefined,
+  productionTime: [],
+  nonProductionTime: [],
+  rdNptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  externalRental: undefined,
+  nextPlan: undefined,
+  rdStatus: undefined,
+  malfunction: undefined,
+  faultDowntime: [],
+  personnel: undefined,
+  totalStaffNum: undefined,
+  leaveStaffNum: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+// 表格引用
+const tableRef = ref()
+// 表格容器引用
+const tableContainerRef = ref()
+
+// 计算属性:获取所有动态列(去重的unit)
+const dynamicColumns = computed(() => {
+  const units = new Set()
+  list.value.forEach(item => {
+    item.items.forEach(subItem => {
+      if (subItem.unit) {
+        units.add(subItem.unit)
+      }
+    })
+  })
+  return Array.from(units)
+})
+
+// 根据unit获取对应workload
+const getWorkloadByUnit = (items, unit) => {
+  if (!items || !Array.isArray(items)) return ''
+  const targetItem = items.find(item => item.unit === unit)
+  return targetItem ? targetItem.workload : ''
+}
+
+// 列宽度配置
+const columnWidths = ref({
+  id: '80px',
+  deptName: '120px',
+  contractName: '120px',
+  taskName: '120px',
+  timeRange: '120px',
+  rdStatus: '120px',
+  cumulativeWorkingWell: '120px',
+  cumulativeWorkingLayers: '120px',
+  dailyPumpTrips: '120px',
+  dailyToolsSand: '120px',
+  runCount: '80px',
+  bridgePlug: '80px',
+  waterVolume: '100px',
+  hourCount: '80px',
+  constructionStartDate: '180px',
+  constructionEndDate: '180px',
+  productionStatus: '200px',
+  nextPlan: '200px',
+  externalRental: '120px',
+  malfunction: '150px',
+  faultDowntime: '120px',
+  createTime: '180px',
+  operation: '120px'
+})
+
+// 计算文本宽度
+const getTextWidth = (text: string, fontSize = 14) => {
+  const span = document.createElement('span');
+  span.style.visibility = 'hidden';
+  span.style.position = 'absolute';
+  span.style.whiteSpace = 'nowrap';
+  span.style.fontSize = `${fontSize}px`;
+  span.style.fontFamily = 'inherit';
+  span.innerText = text;
+
+  document.body.appendChild(span);
+  const width = span.offsetWidth;
+  document.body.removeChild(span);
+
+  return width;
+};
+
+// 计算列宽度
+const calculateColumnWidths = () => {
+  const MIN_WIDTH = 80; // 最小列宽
+  const PADDING = 25; // 列内边距
+
+  // 确保表格容器存在
+  if (!tableContainerRef.value?.$el) return;
+
+  const newWidths: Record<string, string> = {};
+
+  // 计算各列宽度的函数
+  const calculateColumnWidth = (key: string, label: string, getValue: Function) => {
+    const headerWidth = getTextWidth(label) + PADDING;
+    let contentMaxWidth = MIN_WIDTH;
+
+    // 计算内容最大宽度
+    list.value.forEach((row, index) => {
+      let text = '';
+      if (key === 'rdStatus') {
+        // 特殊处理字典列,这里简化处理,实际应该获取字典标签
+        text = String(row[key] || '');
+      } else if (key.includes('Date') || key === 'createTime') {
+        // 日期列使用格式化后的值
+        text = dateFormatter(null, null, row[key]) || '';
+      } else {
+        text = String(getValue ? getValue(row, index) : (row[key] || ''));
+      }
+
+      const textWidth = getTextWidth(text) + PADDING;
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
+    });
+
+    // 取标题宽度和内容最大宽度的较大值
+    const finalWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH);
+    newWidths[key] = `${finalWidth}px`;
+  };
+
+  // 计算各列宽度
+  calculateColumnWidth('deptName', '施工队伍', (row: any) => row.deptName);
+  calculateColumnWidth('contractName', '项目', (row: any) => row.contractName);
+  calculateColumnWidth('taskName', '任务', (row: any) => row.taskName);
+  calculateColumnWidth('timeRange', '时间节点', (row: any) => row.timeRange);
+  calculateColumnWidth('rdStatus', t('project.status'), (row: any) => row.rdStatus);
+  calculateColumnWidth('cumulativeWorkingWell', '累计施工井', (row: any) => row.cumulativeWorkingWell);
+  calculateColumnWidth('cumulativeWorkingLayers', '累计施工层', (row: any) => row.cumulativeWorkingLayers);
+  calculateColumnWidth('dailyPumpTrips', '当日泵车台次', (row: any) => row.dailyPumpTrips);
+  calculateColumnWidth('dailyToolsSand', '当日仪表/混砂', (row: any) => row.dailyToolsSand);
+  calculateColumnWidth('runCount', '趟数', (row: any) => row.runCount);
+  calculateColumnWidth('bridgePlug', '桥塞', (row: any) => row.bridgePlug);
+  calculateColumnWidth('waterVolume', '水方量', (row: any) => row.waterVolume);
+  calculateColumnWidth('hourCount', '时间H', (row: any) => row.hourCount);
+  calculateColumnWidth('constructionStartDate', '施工开始日期', (row: any) => dateFormatter(null, null, row.constructionStartDate));
+  calculateColumnWidth('constructionEndDate', '施工结束日期', (row: any) => dateFormatter(null, null, row.constructionEndDate));
+  calculateColumnWidth('productionStatus', '当日生产动态', (row: any) => row.productionStatus);
+  calculateColumnWidth('nextPlan', '下步工作计划', (row: any) => row.nextPlan);
+  calculateColumnWidth('externalRental', '外租设备', (row: any) => row.externalRental);
+  calculateColumnWidth('malfunction', '故障情况', (row: any) => row.malfunction);
+  calculateColumnWidth('faultDowntime', '故障误工H', (row: any) => row.faultDowntime);
+  calculateColumnWidth('createTime', '创建时间', (row: any) => dateFormatter(null, null, row.createTime));
+
+  // 操作列固定宽度
+  newWidths.operation = '120px';
+  // id列固定宽度(虽然隐藏)
+  newWidths.id = '80px';
+
+  // 更新列宽配置
+  columnWidths.value = newWidths;
+
+  // 触发表格重新布局
+  nextTick(() => {
+    tableRef.value?.doLayout();
+  });
+};
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRdDailyReportApi.statistics(queryParams)
+    list.value = data
+    // total.value = data.total
+    // 获取数据后计算列宽
+    nextTick(() => {
+      calculateColumnWidths();
+    });
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 审批按钮操作 */
+const handleApprove = async (id: number) => {
+  try {
+    // 跳转到 FillDailyReportForm 页面,传递审批模式和ID
+    push({
+      name: 'FillDailyReportForm',
+      params: {
+        id: id.toString(),
+        mode: 'approval'  // 添加审批模式标识
+      }
+    })
+  } catch (error) {
+    console.error('跳转审批页面失败:', error)
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRdDailyReportApi.deleteIotRdDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotRdDailyReportApi.exportIotRdDailyReport(queryParams)
+    download.excel(data, '瑞都日报.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 声明 ResizeObserver 实例
+let resizeObserver: ResizeObserver | null = null;
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+  // 创建 ResizeObserver 监听表格容器尺寸变化
+  if (tableContainerRef.value?.$el) {
+    resizeObserver = new ResizeObserver(() => {
+      // 使用防抖避免频繁触发
+      clearTimeout((window as any).resizeTimer);
+      (window as any).resizeTimer = setTimeout(() => {
+        calculateColumnWidths();
+      }, 100);
+    });
+    resizeObserver.observe(tableContainerRef.value.$el);
+  }
+})
+
+onUnmounted(() => {
+  // 清除 ResizeObserver
+  if (resizeObserver && tableContainerRef.value?.$el) {
+    resizeObserver.unobserve(tableContainerRef.value.$el);
+    resizeObserver = null;
+  }
+
+  // 清除定时器
+  if ((window as any).resizeTimer) {
+    clearTimeout((window as any).resizeTimer);
+  }
+})
+
+// 监听列表数据变化重新计算列宽
+watch(list, () => {
+  nextTick(calculateColumnWidths)
+}, { deep: true })
+</script>
+
+<style scoped>
+/* 确保表格单元格内容不换行 */
+:deep(.el-table .cell) {
+white-space: nowrap;
+}
+
+/* 确保表格列标题不换行 */
+:deep(.el-table th > .cell) {
+white-space: nowrap;
+}
+
+/* 调整表格最小宽度,确保内容完全显示 */
+:deep(.el-table) {
+min-width: 100%;
+}
+</style>

+ 148 - 0
src/views/pms/iotrddailyreportitem/IotRdDailyReportItemForm.vue

@@ -0,0 +1,148 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="日报id" prop="reportId">
+        <el-input v-model="formData.reportId" placeholder="请输入日报id" />
+      </el-form-item>
+      <el-form-item label="施工队伍id" prop="deptId">
+        <el-input v-model="formData.deptId" placeholder="请输入施工队伍id" />
+      </el-form-item>
+      <el-form-item label="项目id" prop="projectId">
+        <el-input v-model="formData.projectId" placeholder="请输入项目id" />
+      </el-form-item>
+      <el-form-item label="任务id" prop="taskId">
+        <el-input v-model="formData.taskId" placeholder="请输入任务id" />
+      </el-form-item>
+      <el-form-item label="项目类别(钻井 修井 注氮 酸化压裂... )" prop="projectClassification">
+        <el-input v-model="formData.projectClassification" placeholder="请输入项目类别(钻井 修井 注氮 酸化压裂... )" />
+      </el-form-item>
+      <el-form-item label="施工工艺" prop="technique">
+        <el-input v-model="formData.technique" placeholder="请输入施工工艺" />
+      </el-form-item>
+      <el-form-item label="工作量属性标识符(bridgePlug waterVolume...)" prop="reportAttrIdentifier">
+        <el-input v-model="formData.reportAttrIdentifier" placeholder="请输入工作量属性标识符(bridgePlug waterVolume...)" />
+      </el-form-item>
+      <el-form-item label="工作量属性名称(泵车台次 仪表/混砂...)" prop="reportAttrName">
+        <el-input v-model="formData.reportAttrName" placeholder="请输入工作量属性名称(泵车台次 仪表/混砂...)" />
+      </el-form-item>
+      <el-form-item label="工作量属性值" prop="reportAttrValue">
+        <el-input v-model="formData.reportAttrValue" placeholder="请输入工作量属性值" />
+      </el-form-item>
+      <el-form-item label="排序值" prop="sort">
+        <el-input v-model="formData.sort" placeholder="请输入排序值" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注" />
+      </el-form-item>
+      <el-form-item label="状态(0启用 1禁用)" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio value="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { IotRdDailyReportItemApi, IotRdDailyReportItemVO } from '@/api/pms/iotrddailyreportitem'
+
+/** 瑞都日报 (工作量)明细 表单 */
+defineOptions({ name: 'IotRdDailyReportItemForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  reportId: undefined,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  technique: undefined,
+  reportAttrIdentifier: undefined,
+  reportAttrName: undefined,
+  reportAttrValue: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+})
+const formRules = reactive({
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await IotRdDailyReportItemApi.getIotRdDailyReportItem(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as IotRdDailyReportItemVO
+    if (formType.value === 'create') {
+      await IotRdDailyReportItemApi.createIotRdDailyReportItem(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotRdDailyReportItemApi.updateIotRdDailyReportItem(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    reportId: undefined,
+    deptId: undefined,
+    projectId: undefined,
+    taskId: undefined,
+    projectClassification: undefined,
+    technique: undefined,
+    reportAttrIdentifier: undefined,
+    reportAttrName: undefined,
+    reportAttrValue: undefined,
+    sort: undefined,
+    remark: undefined,
+    status: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 309 - 0
src/views/pms/iotrddailyreportitem/index.vue

@@ -0,0 +1,309 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="日报id" prop="reportId">
+        <el-input
+          v-model="queryParams.reportId"
+          placeholder="请输入日报id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="施工队伍id" prop="deptId">
+        <el-input
+          v-model="queryParams.deptId"
+          placeholder="请输入施工队伍id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="项目id" prop="projectId">
+        <el-input
+          v-model="queryParams.projectId"
+          placeholder="请输入项目id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="任务id" prop="taskId">
+        <el-input
+          v-model="queryParams.taskId"
+          placeholder="请输入任务id"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="项目类别(钻井 修井 注氮 酸化压裂... )" prop="projectClassification">
+        <el-input
+          v-model="queryParams.projectClassification"
+          placeholder="请输入项目类别(钻井 修井 注氮 酸化压裂... )"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="施工工艺" prop="technique">
+        <el-input
+          v-model="queryParams.technique"
+          placeholder="请输入施工工艺"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="工作量属性标识符(bridgePlug waterVolume...)" prop="reportAttrIdentifier">
+        <el-input
+          v-model="queryParams.reportAttrIdentifier"
+          placeholder="请输入工作量属性标识符(bridgePlug waterVolume...)"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="工作量属性名称(泵车台次 仪表/混砂...)" prop="reportAttrName">
+        <el-input
+          v-model="queryParams.reportAttrName"
+          placeholder="请输入工作量属性名称(泵车台次 仪表/混砂...)"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="工作量属性值" prop="reportAttrValue">
+        <el-input
+          v-model="queryParams.reportAttrValue"
+          placeholder="请输入工作量属性值"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="排序值" prop="sort">
+        <el-input
+          v-model="queryParams.sort"
+          placeholder="请输入排序值"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          v-model="queryParams.remark"
+          placeholder="请输入备注"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="状态(0启用 1禁用)" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择状态(0启用 1禁用)"
+          clearable
+          class="!w-240px"
+        >
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['pms:iot-rd-daily-report-item:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['pms:iot-rd-daily-report-item:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="主键id" align="center" prop="id" />
+      <el-table-column label="日报id" align="center" prop="reportId" />
+      <el-table-column label="施工队伍id" align="center" prop="deptId" />
+      <el-table-column label="项目id" align="center" prop="projectId" />
+      <el-table-column label="任务id" align="center" prop="taskId" />
+      <el-table-column label="项目类别(钻井 修井 注氮 酸化压裂... )" align="center" prop="projectClassification" />
+      <el-table-column label="施工工艺" align="center" prop="technique" />
+      <el-table-column label="工作量属性标识符(bridgePlug waterVolume...)" align="center" prop="reportAttrIdentifier" />
+      <el-table-column label="工作量属性名称(泵车台次 仪表/混砂...)" align="center" prop="reportAttrName" />
+      <el-table-column label="工作量属性值" align="center" prop="reportAttrValue" />
+      <el-table-column label="排序值" align="center" prop="sort" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="状态(0启用 1禁用)" align="center" prop="status" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report-item:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['pms:iot-rd-daily-report-item:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotRdDailyReportItemForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRdDailyReportItemApi, IotRdDailyReportItemVO } from '@/api/pms/iotrddailyreportitem'
+import IotRdDailyReportItemForm from './IotRdDailyReportItemForm.vue'
+
+/** 瑞都日报 (工作量)明细 列表 */
+defineOptions({ name: 'IotRdDailyReportItem' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRdDailyReportItemVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  reportId: undefined,
+  deptId: undefined,
+  projectId: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  technique: undefined,
+  reportAttrIdentifier: undefined,
+  reportAttrName: undefined,
+  reportAttrValue: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  createTime: [],
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRdDailyReportItemApi.getIotRdDailyReportItemPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRdDailyReportItemApi.deleteIotRdDailyReportItem(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotRdDailyReportItemApi.exportIotRdDailyReportItem(queryParams)
+    download.excel(data, '瑞都日报 (工作量)明细.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 2 - 2
src/views/pms/iotrydailyreport/xjindex.vue

@@ -201,7 +201,7 @@
                   link
                   type="primary"
                   @click="openForm('update', scope.row.id, scope.row)"
-                  v-hasPermi="['pms:iot-rh-daily-report:update']"
+                  v-hasPermi="['pms:iot-ry-daily-report:update']"
                 >
                   编辑
                 </el-button>
@@ -209,7 +209,7 @@
                   link
                   type="danger"
                   @click="handleDelete(scope.row.id)"
-                  v-hasPermi="['pms:iot-rh-daily-report:delete']"
+                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
                 >
                   删除
                 </el-button>