yanghao 9 часов назад
Родитель
Сommit
a67247967b
73 измененных файлов с 19349 добавлено и 8683 удалено
  1. 1 1
      .env.dev
  2. 3 3
      .vscode/settings.json
  3. 2 0
      package.json
  4. 353 936
      pnpm-lock.yaml
  5. 29 0
      src/App.vue
  6. 5 1
      src/api/pms/iotopeationfill/index.ts
  7. 2 1
      src/api/pms/iotrddailyreport/index.ts
  8. 6 1
      src/api/pms/iotrydailyreport/index.ts
  9. 238 0
      src/api/pms/qhse/index.ts
  10. 6 0
      src/api/pms/stat/index.ts
  11. 1 1
      src/components/ZmTable/ZmTableColumn.vue
  12. 10 6
      src/components/ZmTable/index.vue
  13. 487 0
      src/components/ZmUpload/index.vue
  14. 81 0
      src/composable/useAutoLogout.ts
  15. 4 0
      src/config/axios/index.ts
  16. 31 1
      src/layout/components/Menu/src/components/useRenderMenuItem.tsx
  17. 14 8
      src/layout/components/Message/src/Message.vue
  18. 1 1
      src/layout/components/TagsView/src/TagsView.vue
  19. 6 0
      src/layout/components/TagsView/src/helper.ts
  20. 44 7
      src/permission.ts
  21. 12 1
      src/utils/dict.ts
  22. 27 0
      src/utils/formatTime.ts
  23. 1 1
      src/views/Error/404.vue
  24. 1 1
      src/views/pms/device/allotlog/ConfigDeviceAllot.vue
  25. 9 0
      src/views/pms/device/completeSet/DeviceCompleteSet.vue
  26. 17 4
      src/views/pms/dingding.vue
  27. 371 240
      src/views/pms/iotopeationfill/index.vue
  28. 269 8
      src/views/pms/iotopeationfill/index1.vue
  29. 47 47
      src/views/pms/iotrddailyreport/fillDailyReport.vue
  30. 154 36
      src/views/pms/iotrddailyreport/index.vue
  31. 1531 0
      src/views/pms/iotrddailyreport/rd-form.vue
  32. 3 3
      src/views/pms/iotrddailyreport/summary.vue
  33. 100 575
      src/views/pms/iotrhdailyreport/approval.vue
  34. 669 0
      src/views/pms/iotrhdailyreport/approval1.vue
  35. 102 581
      src/views/pms/iotrhdailyreport/fill.vue
  36. 674 0
      src/views/pms/iotrhdailyreport/fill1.vue
  37. 306 1272
      src/views/pms/iotrhdailyreport/index.vue
  38. 1339 0
      src/views/pms/iotrhdailyreport/index1.vue
  39. 7 3
      src/views/pms/iotrhdailyreport/rh-form.vue
  40. 318 0
      src/views/pms/iotrhdailyreport/rh-table.vue
  41. 28 8
      src/views/pms/iotrhdailyreport/summary.vue
  42. 103 630
      src/views/pms/iotrydailyreport/approval.vue
  43. 724 0
      src/views/pms/iotrydailyreport/approval1.vue
  44. 103 630
      src/views/pms/iotrydailyreport/fill.vue
  45. 724 0
      src/views/pms/iotrydailyreport/fill1.vue
  46. 212 1099
      src/views/pms/iotrydailyreport/index.vue
  47. 1169 0
      src/views/pms/iotrydailyreport/index1.vue
  48. 290 23
      src/views/pms/iotrydailyreport/ry-form.vue
  49. 432 0
      src/views/pms/iotrydailyreport/ry-table.vue
  50. 238 16
      src/views/pms/iotrydailyreport/ry-xj-form.vue
  51. 475 0
      src/views/pms/iotrydailyreport/ry-xj-table.vue
  52. 27 7
      src/views/pms/iotrydailyreport/summary.vue
  53. 102 629
      src/views/pms/iotrydailyreport/xapproval.vue
  54. 723 0
      src/views/pms/iotrydailyreport/xapproval1.vue
  55. 102 636
      src/views/pms/iotrydailyreport/xfill.vue
  56. 730 0
      src/views/pms/iotrydailyreport/xfill1.vue
  57. 218 1217
      src/views/pms/iotrydailyreport/xjindex.vue
  58. 1265 0
      src/views/pms/iotrydailyreport/xjindex1.vue
  59. 27 7
      src/views/pms/iotrydailyreport/xsummary.vue
  60. 13 0
      src/views/pms/maintain/index.vue
  61. 28 15
      src/views/pms/maintenance/IotMaintenancePlanEdit.vue
  62. 74 22
      src/views/pms/monitor/index.vue
  63. 614 0
      src/views/pms/qhse/certificate.vue
  64. 680 0
      src/views/pms/qhse/factor/index.vue
  65. 620 0
      src/views/pms/qhse/faultReport/index.vue
  66. 568 0
      src/views/pms/qhse/hazard/index.vue
  67. 545 0
      src/views/pms/qhse/index.vue
  68. 392 0
      src/views/pms/qhse/iotmeasuredetect/IotMeasureDetectForm.vue
  69. 222 0
      src/views/pms/qhse/iotmeasuredetect/index.vue
  70. 349 0
      src/views/pms/qhse/iotmeasurerecord/IotMeasureRecordForm.vue
  71. 219 0
      src/views/pms/qhse/iotmeasurerecord/index.vue
  72. 47 0
      src/views/pms/stat/rdkbfr.vue
  73. 5 5
      src/views/report-statistics/rd-daily-report.vue

+ 1 - 1
.env.dev

@@ -4,7 +4,7 @@ NODE_ENV=production
 VITE_DEV=true
 
 # 请求路径
-VITE_BASE_URL='http://1.94.244.160:66'
+VITE_BASE_URL='https://iot.deepoil.cc'
 
 # MQTT服务地址
 VITE_MQTT_SERVER_URL = ''

+ 3 - 3
.vscode/settings.json

@@ -65,7 +65,7 @@
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "[typescriptreact]": {
-    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "[html]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -83,8 +83,8 @@
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "editor.codeActionsOnSave": {
-    "source.fixAll.eslint": "explicit"
-    // "source.fixAll.stylelint": "explicit"
+    "source.fixAll.eslint": "explicit",
+    "source.fixAll.stylelint": "explicit"
   },
   "[vue]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode"

+ 2 - 0
package.json

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

Разница между файлами не показана из-за своего большого размера
+ 353 - 936
pnpm-lock.yaml


+ 29 - 0
src/App.vue

@@ -4,6 +4,35 @@ import { useAppStore } from '@/store/modules/app'
 import { useDesign } from '@/hooks/web/useDesign'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import routerSearch from '@/components/RouterSearch/index.vue'
+import { useAutoLogout } from './composable/useAutoLogout'
+
+const route = useRoute()
+const { addListeners, removeListeners } = useAutoLogout()
+
+const whiteList = [
+  '/login',
+  '/social-login',
+  '/auth-redirect',
+  '/bind',
+  '/register',
+  '/oauthLogin/gitee',
+  '/dingding',
+  '/deepoil'
+]
+
+watch(
+  () => route.path,
+  (newPath) => {
+    if (whiteList.includes(newPath)) {
+      console.log('进入白名单页面,移除超时检测')
+      removeListeners()
+    } else {
+      console.log('进入受控页面,开启超时检测')
+      addListeners()
+    }
+  },
+  { immediate: true } // 初始化时立即执行一次
+)
 
 defineOptions({ name: 'APP' })
 

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

@@ -85,10 +85,14 @@ export const IotOpeationFillApi = {
   },
 
   // 新增运行记录填报
-  insertLog: async (data: IotOpeationFillVO) => {
+  insertLog: async (data: any) => {
     return await request.post({ url: `/rq/iot-opeation-fill/insertLog`, data })
   },
 
+  getReportDetails: async (id: any) => {
+    return await request.get({ url: `/rq/iot-opeation-fill/prod/detail/${id}` })
+  },
+
   // 修改运行记录填报
   updateIotOpeationFill: async (data: IotOpeationFillVO) => {
     return await request.put({ url: `/rq/iot-opeation-fill/update`, data })

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

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

+ 6 - 1
src/api/pms/iotrydailyreport/index.ts

@@ -121,7 +121,12 @@ export const IotRyDailyReportApi = {
     return await request.download({ url: `/pms/iot-ry-daily-report/export-excel`, params })
   },
 
-  approvalIotRyDailyReport: async (data: { id: number; auditStatus: 20 | 30; opinion: string }) => {
+  approvalIotRyDailyReport: async (data: {
+    id: number
+    auditStatus: 20 | 30
+    opinion: string
+    constructionBrief?: string
+  }) => {
     return await request.put({ url: `/pms/iot-ry-daily-report/approval`, data })
   },
   exportIotRyDailyReportWell: async (params) => {

+ 238 - 0
src/api/pms/qhse/index.ts

@@ -0,0 +1,238 @@
+import request from '@/config/axios'
+
+// 计量器具-证书管理 VO
+export interface IotMeasureCertVO {
+  id: number // 主键id
+  type: string // 证书类型
+  classify: string // 证书类别
+  certBelong: string // 证书所属公司/个人
+  certOrg: string // 证书颁发机构
+  certStandard: string // 证书标准
+  certIssue: Date // 证书颁发时间
+  certExpire: Date // 证书有效期
+  noticeBefore: number // 到期前提醒
+  certPic: string // 证书图片上传
+  remark: string // 备注
+  deptId: number // 部门id
+}
+
+// 计量器具-证书管理 API
+export const IotMeasureCertApi = {
+  // 查询计量器具-证书管理分页
+  getIotMeasureCertPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-cert/page`, params })
+  },
+
+  // 查询计量器具-证书管理详情
+  getIotMeasureCert: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-cert/get?id=` + id })
+  },
+
+  // 新增计量器具-证书管理
+  createIotMeasureCert: async (data: IotMeasureCertVO) => {
+    return await request.post({ url: `/rq/iot-measure-cert/create`, data })
+  },
+
+  // 修改计量器具-证书管理
+  updateIotMeasureCert: async (data: IotMeasureCertVO) => {
+    return await request.put({ url: `/rq/iot-measure-cert/update`, data })
+  },
+
+  // 删除计量器具-证书管理
+  deleteIotMeasureCert: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-cert/delete?id=` + id })
+  },
+
+  // 导出计量器具-证书管理 Excel
+  exportIotMeasureCert: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-cert/export-excel`, params })
+  }
+}
+
+// 计量器具台账 VO
+export const IotInstrumentApi = {
+  // 获得计量器具台账分页
+  getInstrumentList: async (params) => {
+    return await request.get({ url: `rq/iot-measure-book/page`, params })
+  },
+  // 删除计量器具台账
+  deleteInstrument: async (id) => {
+    return await request.delete({ url: `/rq/iot-measure-book/delete?id=` + id })
+  },
+  // 更新计量器具台账
+  updateInstrument: async (data) => {
+    return await request.put({ url: `/rq/iot-measure-book/update`, data })
+  },
+  // 新增计量器具台账
+  createInstrument: async (data) => {
+    return await request.post({ url: `/rq/iot-measure-book/create`, data })
+  },
+  // 导出计量器具台账 Excel
+  exportInstrument: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-book/export-excel`, params })
+  }
+}
+
+// 计量器具-检测校准明细 VO
+export interface IotMeasureDetectVO {
+  id: number // 主键id
+  measureId: number // 计量器具id
+  detectDate: string // 检测/校准日期
+  detectOrg: string // 检测/校准机构
+  detectContent: string // 检测/校准内容
+  validityPeriod: Date // 检测/校准有效期
+  detectAmount: number // 校准金额
+  deptId: number // 部门id
+}
+
+// 计量器具-检测校准明细 API
+export const IotMeasureDetectApi = {
+  // 查询计量器具-检测校准明细分页
+  getIotMeasureDetectPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-detect/page`, params })
+  },
+
+  // 查询计量器具-检测校准明细详情
+  getIotMeasureDetect: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-detect/get?id=` + id })
+  },
+
+  // 新增计量器具-检测校准明细
+  createIotMeasureDetect: async (data: IotMeasureDetectVO) => {
+    return await request.post({ url: `/rq/iot-measure-detect/create`, data })
+  },
+
+  // 修改计量器具-检测校准明细
+  updateIotMeasureDetect: async (data: IotMeasureDetectVO) => {
+    return await request.put({ url: `/rq/iot-measure-detect/update`, data })
+  },
+
+  // 删除计量器具-检测校准明细
+  deleteIotMeasureDetect: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-detect/delete?id=` + id })
+  },
+
+  // 导出计量器具-检测校准明细 Excel
+  exportIotMeasureDetect: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-detect/export-excel`, params })
+  }
+}
+
+// 计量器具-使用记录 VO
+export interface IotMeasureRecordVO {
+  id: number // 主键id
+  measureId: number // 计量器具id
+  useDate: string // 使用日期
+  useReason: string // 使用原因
+  measureProject: string // 计量项目
+  usePerson: string // 使用人
+  userId: number // 使用人id
+  deptId: number // 部门id
+}
+
+// 计量器具-使用记录 API
+export const IotMeasureRecordApi = {
+  // 查询计量器具-使用记录分页
+  getIotMeasureRecordPage: async (params: any) => {
+    return await request.get({ url: `/rq/iot-measure-record/page`, params })
+  },
+
+  // 查询计量器具-使用记录详情
+  getIotMeasureRecord: async (id: number) => {
+    return await request.get({ url: `/rq/iot-measure-record/get?id=` + id })
+  },
+
+  // 新增计量器具-使用记录
+  createIotMeasureRecord: async (data: IotMeasureRecordVO) => {
+    return await request.post({ url: `/rq/iot-measure-record/create`, data })
+  },
+
+  // 修改计量器具-使用记录
+  updateIotMeasureRecord: async (data: IotMeasureRecordVO) => {
+    return await request.put({ url: `/rq/iot-measure-record/update`, data })
+  },
+
+  // 删除计量器具-使用记录
+  deleteIotMeasureRecord: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-measure-record/delete?id=` + id })
+  },
+
+  // 导出计量器具-使用记录 Excel
+  exportIotMeasureRecord: async (params) => {
+    return await request.download({ url: `/rq/iot-measure-record/export-excel`, params })
+  }
+}
+
+// 危险源管理
+export const IotDangerApi = {
+  // 获得危险源分页
+  getDangerList: async (params) => {
+    return await request.get({ url: `/rq/iot-danger-source/page`, params })
+  },
+  // 删除危险源
+  deleteDanger: async (id) => {
+    return await request.delete({ url: `/rq/iot-danger-source/delete?id=` + id })
+  },
+  // 添加危险源
+  createDanger: async (data) => {
+    return await request.post({ url: `/rq/iot-danger-source/create`, data })
+  },
+  // 修改危险源
+  updateDanger: async (data) => {
+    return await request.put({ url: `/rq/iot-danger-source/update`, data })
+  },
+  // 导出危险源 Excel
+  exportDanger: async (params) => {
+    return await request.download({ url: `/rq/iot-danger-source/export-excel`, params })
+  }
+}
+
+// 环境因素识别
+export const IotEnvironmentApi = {
+  // 获得环境因素分页
+  getEnvironmentList: async (params) => {
+    return await request.get({ url: `/rq/iot-environment-recognize/page`, params })
+  },
+  // 删除环境因素
+  deleteEnvironment: async (id) => {
+    return await request.delete({ url: `/rq/iot-environment-recognize/delete?id=` + id })
+  },
+
+  // 添加环境因素
+  createEnvironment: async (data) => {
+    return await request.post({ url: `/rq/iot-environment-recognize/create`, data })
+  },
+  // 修改环境因素
+  updateEnvironment: async (data) => {
+    return await request.put({ url: `/rq/iot-environment-recognize/update`, data })
+  },
+  // 导出环境因素 Excel
+  exportEnvironment: async (params) => {
+    return await request.download({ url: `/rq/iot-environment-recognize/export-excel`, params })
+  }
+}
+
+// 故障上报
+export const IotFailureApi = {
+  // 获得故障上报分页
+  getFailureList: async (params) => {
+    return await request.get({ url: `/rq/iot-accident-report/page`, params })
+  },
+  // 删除故障上报
+  deleteFailure: async (id) => {
+    return await request.delete({ url: `/rq/iot-accident-report/delete?id=` + id })
+  },
+  // 添加故障上报
+  createFailure: async (data) => {
+    return await request.post({ url: `/rq/iot-accident-report/create`, data })
+  },
+  // 导出
+  exportFailure: async (params) => {
+    return await request.download({ url: `/rq/iot-accident-report/export-excel`, params })
+  },
+
+  // 修改故障上报
+  updateFailure: async (data) => {
+    return await request.put({ url: `/rq/iot-accident-report/update`, data })
+  }
+}

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

@@ -204,5 +204,11 @@ export const IotStatApi = {
   },
   getConstructionBriefing: async () => {
     return await request.get({ url: `/pms/iot-rd-daily-report/constructionBriefing` })
+  },
+
+  // 瑞都看板(新)
+  // 获取ssoToken
+  getSsoToken: async () => {
+    return await request.get({ url: `/rq/iot-fine-report/createSsoToken` })
   }
 }

+ 1 - 1
src/components/ZmTable/ZmTableColumn.vue

@@ -9,7 +9,7 @@ interface Props extends /* @vue-ignore */ Partial<Omit<TableColumnCtx<T>, 'prop'
   zmSortable?: boolean
   zmFilterable?: boolean
   filterModelValue?: any
-  realValue?: (value: any) => any
+  realValue?: (...args: any[]) => any
   coverFormatter?: boolean
 }
 

+ 10 - 6
src/components/ZmTable/index.vue

@@ -31,7 +31,7 @@ const defaultOptions: Partial<Props> = {
   showBorder: false,
   customClass: false,
   tooltipOptions: {
-    popperClass: 'max-w-180'
+    popperClass: 'max-w-120'
   }
 }
 
@@ -145,13 +145,17 @@ defineExpose({
           border-right: none;
         }
       }
+    }
 
-      &:first-child {
-        border-bottom-left-radius: 8px;
-      }
+    tr:first-child {
+      .el-table__cell {
+        &:first-child {
+          border-bottom-left-radius: 8px;
+        }
 
-      &:last-child {
-        border-bottom-right-radius: 8px;
+        &:last-child {
+          border-bottom-right-radius: 8px;
+        }
       }
     }
   }

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

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

+ 81 - 0
src/composable/useAutoLogout.ts

@@ -0,0 +1,81 @@
+import { onMounted, onUnmounted } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import { useRouter } from 'vue-router'
+import { useUserStore } from '@/store/modules/user'
+import { getAccessToken } from '@/utils/auth'
+import { throttle } from 'lodash-es'
+
+// --- 关键修改:将变量移到函数外部,变成全局共享变量 ---
+let timer: ReturnType<typeof setTimeout> | null = null
+const TIMEOUT = 30 * 60 * 1000 // 30分钟
+const events = ['click', 'mousedown', 'keydown', 'scroll', 'mousemove']
+
+export function useAutoLogout() {
+  const userStore = useUserStore()
+  const router = useRouter()
+
+  // 处理登出逻辑
+  const handleLogout = () => {
+    // 防止重复触发,先清理
+    removeListeners()
+    userStore.loginOut()
+
+    ElMessageBox.alert('长时间未操作,登录已超时,请重新登录!', '提示', {
+      confirmButtonText: '确定',
+      type: 'warning',
+      showClose: false,
+      callback: () => {
+        router.push('/login')
+      }
+    })
+  }
+
+  // 节流的重置计时器
+  const resetTimer = throttle(() => {
+    // 这里引用的 timer 是文件顶部的全局变量
+    if (timer) clearTimeout(timer)
+
+    // 只有有 Token 时才开启倒计时
+    if (getAccessToken()) {
+      timer = setTimeout(handleLogout, TIMEOUT)
+    }
+  }, 1000)
+
+  const addListeners = () => {
+    // 每次添加前先移除,防止重复绑定导致事件堆积
+    removeListeners()
+
+    events.forEach((event) => {
+      window.addEventListener(event, resetTimer)
+    })
+    resetTimer()
+  }
+
+  const removeListeners = () => {
+    if (timer) {
+      clearTimeout(timer)
+      timer = null
+    }
+    events.forEach((event) => {
+      window.removeEventListener(event, resetTimer)
+    })
+  }
+
+  // 注意:如果你要在路由守卫中手动控制,
+  // 建议去掉这里的生命周期钩子,或者只在 App.vue 中保留
+  // 如果这里保留,会导致只要 import 并在组件 setup 中调用,就会自动开启监听
+  onMounted(() => {
+    // 如果你想完全由路由守卫控制,注释掉下面这行
+    // addListeners()
+  })
+
+  onUnmounted(() => {
+    // 组件卸载时销毁监听
+    removeListeners()
+  })
+
+  return {
+    addListeners,
+    removeListeners
+  }
+}

+ 4 - 0
src/config/axios/index.ts

@@ -6,10 +6,14 @@ const { default_headers } = config
 
 const request = (option: any) => {
   const { headersType, headers, ...otherOption } = option
+
+  const langStore = JSON.parse(localStorage.getItem('lang') || '{}')
+
   return service({
     ...otherOption,
     headers: {
       'Content-Type': headersType || default_headers,
+      Lang: langStore.v.replace(/"/g, ''),
       ...headers
     }
   })

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

@@ -17,7 +17,37 @@ export const useRenderMenuItem = () =>
           if (currentSource === 'zhly') {
             return (
               !v.meta?.hidden &&
-              ['智慧连油', '连油11监控', '监控查询', '视频告警', '分屏管理','告警配置'].includes(v.meta?.title)
+              ['智慧连油', '连油监控', '监控查询', '视频告警', '分屏管理', '告警配置'].includes(
+                v.meta?.title
+              )
+            )
+          } else if (currentSource === 'znzq') {
+            return !v.meta?.hidden && ['/device_monitor'].includes(v.path)
+          } else if (currentSource === 'spzx') {
+            return (
+              !v.meta?.hidden &&
+              [
+                '视频中心',
+                '协议管理',
+                '产品分类',
+                '产品管理',
+                '视频设备',
+                '视频配置',
+                '通道管理'
+              ].includes(v.meta?.title)
+            )
+          } else if (currentSource === 'qhse') {
+            return (
+              !v.meta?.hidden &&
+              [
+                'QHSE',
+                '计量器具',
+                '仪器检测',
+                '台账管理',
+                '证书管理',
+                '危险源管理',
+                '环境因素识别'
+              ].includes(v.meta?.title)
             )
           } else {
             return !v.meta?.hidden

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

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

+ 1 - 1
src/layout/components/TagsView/src/TagsView.vue

@@ -33,7 +33,7 @@ const route = useRoute()
 
 const routers = computed(() => {
   const currentSource = sessionStorage.getItem('LOGIN_SOURCE') || route.query.source
-  return currentSource === 'zhly'
+  return currentSource
     ? permissionStore.getRouters.filter((item) => item.path !== '/')
     : permissionStore.getRouters
 })

+ 6 - 0
src/layout/components/TagsView/src/helper.ts

@@ -9,6 +9,12 @@ export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') =>
     const currentSource = sessionStorage.getItem('LOGIN_SOURCE')
     if (currentSource === 'zhly' && tagPath === '/oli-connection/monitoring') {
       route.meta.affix = true
+    } else if (currentSource === 'znzq' && route.meta.title === '设备成套监控') {
+      route.meta.affix = true
+    } else if (currentSource === 'spzx' && tagPath === '/video_center/protocol') {
+      route.meta.affix = true
+    } else if (currentSource === 'qhse' && tagPath === '/qhse/measure/ledger') {
+      route.meta.affix = true
     }
     if (meta?.affix) {
       tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded)

+ 44 - 7
src/permission.ts

@@ -98,11 +98,26 @@ router.beforeEach(async (to, from, next) => {
 
       authUtil.setToken(res)
       const source = to.query.source
-      if (source) {
+      if (source && source === 'zhly') {
         sessionStorage.setItem('LOGIN_SOURCE', source as string)
         next({ path: '/oli-connection/monitoring' })
       }
 
+      if (source && source === 'znzq') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/device_monitor' })
+      }
+
+      if (source && source === 'spzx') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/video_center/protocol' })
+      }
+
+      if (source && source === 'qhse') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/qhse/measure/ledger' })
+      }
+
       next({ path: '/' })
     } else {
       // 获取所有字典
@@ -114,6 +129,7 @@ router.beforeEach(async (to, from, next) => {
       }
 
       if (!userStore.getIsSetUser) {
+        console.log(from, to)
         isRelogin.show = true
         await userStore.setUserInfoAction()
         isRelogin.show = false
@@ -141,11 +157,26 @@ router.beforeEach(async (to, from, next) => {
 
       authUtil.setToken(res)
 
-      // const source = to.query.source
-      // if (source) {
-      //   sessionStorage.setItem('LOGIN_SOURCE', source as string)
-      //   next({ path: '/oli-connection/monitoring' })
-      // }
+      const source = to.query.source
+      if (source && source === 'zhly') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/oli-connection/monitoring' })
+      }
+
+      if (source && source === 'znzq') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/device_monitor' })
+      }
+
+      if (source && source === 'spzx') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/video_center/protocol' })
+      }
+
+      if (source && source === 'qhse') {
+        sessionStorage.setItem('LOGIN_SOURCE', source as string)
+        next({ path: '/qhse/measure/ledger' })
+      }
 
       next({ path: '/index' })
     } else if (whiteList.indexOf(to.path) !== -1) {
@@ -165,8 +196,14 @@ router.beforeEach(async (to, from, next) => {
       if (source) {
         sessionStorage.setItem('LOGIN_SOURCE', source as string)
       }
-      if (source) {
+      if (source && source === 'zhly') {
         next(`/login?redirect=/oli-connection/monitoring`)
+      } else if (source && source === 'znzq') {
+        next(`/login?redirect=/device_monitor`)
+      } else if (source && source === 'spzx') {
+        next(`/login?redirect=/video_center/protocol`)
+      } else if (source && source === 'qhse') {
+        next(`/login?redirect=/qhse/measure/ledger`)
       } else next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
     }
   }

+ 12 - 1
src/utils/dict.ts

@@ -314,5 +314,16 @@ export enum DICT_TYPE {
 
   DEVICE_GROUP_TYPE = 'device_group_type',
   EVENT_TYPE = 'event_type',
-  EVENT_STATE = 'event_state'
+  EVENT_STATE = 'event_state',
+
+  // QHSE
+  MEASURE_TYPE = 'measure_type',
+  PERSON_CERT = 'person_cert',
+  ORG_CERT = 'org_cert',
+  DANGER_GRADE = 'danger_grade'
+}
+
+export function realValue(type: any, value: string) {
+  const option = getDictOptions(type).find((item) => item.value === value)
+  return option?.label || value
 }

+ 27 - 0
src/utils/formatTime.ts

@@ -438,3 +438,30 @@ export function getDateRange(
     dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
   ]
 }
+
+export function formatDateNoTime(timestamp?: number) {
+  if (!timestamp) return ''
+  return dayjs(timestamp).format('YYYY-MM-DD')
+}
+
+export const formatT = (arr: number[]) =>
+  `${arr[0].toString().padStart(2, '0')}:${arr[1].toString().padStart(2, '0')}`
+
+export const calculateDuration = (row: any) => {
+  if (!row.startTime || !row.endTime) {
+    row.duration = 0
+    return
+  }
+
+  const todayStr = dayjs().format('YYYY-MM-DD')
+  const start = dayjs(`${todayStr} ${row.startTime}`)
+  const end = dayjs(`${todayStr} ${row.endTime}`)
+
+  let diffMinutes = end.diff(start, 'minute')
+
+  if (diffMinutes < 0) {
+    diffMinutes += 1440
+  }
+
+  row.duration = Number((diffMinutes / 60).toFixed(2))
+}

+ 1 - 1
src/views/Error/404.vue

@@ -1,5 +1,5 @@
 <template>
-  <Error :show-button="source !== 'zhly'" @error-click="push('/')" />
+  <Error :show-button="!source" @error-click="push('/')" />
 </template>
 <script lang="ts" setup>
 defineOptions({ name: 'Error404' })

+ 1 - 1
src/views/pms/device/allotlog/ConfigDeviceAllot.vue

@@ -65,7 +65,7 @@
         <div class="control-row">
           <!-- 左侧人员选择 -->
           <div class="control-group">
-            <label class="control-title">{{ t('devicePerson.rp') }}</label>
+            <label class="control-title">{{ t('devicePerson.rp') }} <span class="required-star">*</span></label>
             <div class="person-selector">
               <el-select
                 v-model="selectedPersons"

+ 9 - 0
src/views/pms/device/completeSet/DeviceCompleteSet.vue

@@ -64,6 +64,15 @@
             </template>
           </el-table-column>
 
+          <el-table-column label="在线状态" align="center">
+            <template #default="scope">
+              <el-tag type="success" :underline="false" v-if="scope.row.ifOnline === true"
+                >在线</el-tag
+              >
+              <el-tag type="info" :underline="false" v-else>离线</el-tag>
+            </template>
+          </el-table-column>
+
           <el-table-column label="描述" align="center" prop="remark" />
           <el-table-column label="设备数量" align="center" prop="deviceCount">
             <template #default="scope">

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

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

+ 371 - 240
src/views/pms/iotopeationfill/index.vue

@@ -6,62 +6,70 @@
       </ContentWrap>
     </el-col>
     <el-col :span="20" :xs="24">
-    <ContentWrap>
-      <!-- 搜索工作栏 -->
-      <el-form
-        class="-mb-15px"
-        :model="queryParams"
-        ref="queryFormRef"
-        :inline="true"
-        label-width="68px"
-      >
-        <el-form-item :label="t('operationFill.name')" prop="orderName" style="margin-left: 15px">
-          <el-input
-            v-model="queryParams.orderName"
-            :placeholder="t('operationFill.nameHolder')"
-            clearable
-            @keyup.enter="handleQuery"
-            class="!w-240px"
-          />
-        </el-form-item>
-        <el-form-item :label="t('operationFill.status')" prop="orderStatus">
-          <el-select
-            v-model="queryParams.orderStatus"
-            :placeholder="t('operationFill.status')"
-            clearable
-            class="!w-240px"
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item :label="t('operationFill.name')" prop="orderName" style="margin-left: 15px">
+            <el-input
+              v-model="queryParams.orderName"
+              :placeholder="t('operationFill.nameHolder')"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item :label="t('operationFill.status')" prop="orderStatus">
+            <el-select
+              v-model="queryParams.orderStatus"
+              :placeholder="t('operationFill.status')"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.OPERATION_FILL_ORDER_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            :label="t('operationFill.createTime')"
+            prop="createTime"
+            label-width="100px"
           >
-            <el-option
-              v-for="dict in getStrDictOptions(DICT_TYPE.OPERATION_FILL_ORDER_STATUS)"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              :start-placeholder="t('operationFill.start')"
+              :end-placeholder="t('operationFill.end')"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              class="!w-220px"
             />
-          </el-select>
-        </el-form-item>
-        <el-form-item :label="t('operationFill.createTime')" prop="createTime" label-width="100px">
-          <el-date-picker
-            v-model="queryParams.createTime"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            type="daterange"
-            :start-placeholder="t('operationFill.start')"
-            :end-placeholder="t('operationFill.end')"
-            :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" />{{t('operationFill.search')}}</el-button>
-          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />{{t('operationFill.reset')}}</el-button>
-<!--          <el-button-->
-<!--            type="primary"-->
-<!--            plain-->
-<!--            @click="openForm('create')"-->
-<!--            v-hasPermi="['rq:iot-inspect-order:create']"-->
-<!--          >-->
-<!--            <Icon icon="ep:plus" class="mr-5px" /> 新增-->
-<!--          </el-button>-->
-<!--          <el-button
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" />{{ t('operationFill.search') }}</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" />{{ t('operationFill.reset') }}</el-button
+            >
+            <!--          <el-button-->
+            <!--            type="primary"-->
+            <!--            plain-->
+            <!--            @click="openForm('create')"-->
+            <!--            v-hasPermi="['rq:iot-inspect-order:create']"-->
+            <!--          >-->
+            <!--            <Icon icon="ep:plus" class="mr-5px" /> 新增-->
+            <!--          </el-button>-->
+            <!--          <el-button
             type="success"
             plain
             @click="handleExport"
@@ -70,79 +78,151 @@
           >
             <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"  >
-        <el-table-column :label="t('common.index')" min-width="60" align="center">
-          <template #default="scope">
-            {{ scope.$index + 1 }}
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('bomList.name')" align="center" prop="orderName" min-width="320"/>
-        <el-table-column :label="t('operationFill.duty')" align="center" prop="userName" min-width="100"/>
-        <el-table-column :label="t('operationFill.orderDevice')" align="center" prop="fillList" min-width="150" :show-overflow-tooltip="true"/>
-        <el-table-column :label="t('operationFill.status')" align="center" prop="orderStatus" min-width="120">
-          <template #default="scope">
-            <el-tooltip
-              v-if="scope.row.orderStatus === 3"
-              effect="dark"
-              :content="scope.row.reason"
-              placement="top"
-            >
-              <dict-tag :type="DICT_TYPE.OPERATION_FILL_ORDER_STATUS" :value="scope.row.orderStatus" />
-            </el-tooltip>
-            <dict-tag
-              v-else
-              :type="DICT_TYPE.OPERATION_FILL_ORDER_STATUS"
-              :value="scope.row.orderStatus"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('operationFill.deviceCount')" align="center" prop="allDev" min-width="120">
-          <template #default="scope">
-            <el-tag  type="info"> {{scope.row.allDev}}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('operationFill.fillCount')" align="center" prop="fillDev" min-width="120">
-          <template #default="scope">
-            <el-tag  type="success"> {{scope.row.fillDev}}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column :label="t('operationFill.unFillCount')" align="center" prop="unFillDev" min-width="120">
-          <template #default="scope">
-            <el-tag  type="danger"> {{scope.row.unFillDev}}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column
-          :label="t('dict.createTime')"
-          align="center"
-          prop="createTime"
-          :formatter="dateFormatter"
-          min-width="170"
-        />
-        <el-table-column
-          :label="t('dict.fillTime')"
-          align="center"
-          prop="updateTime"
-          :formatter="dateFormatter"
-          min-width="170"
-        />
-        <el-table-column :label="t('operationFill.operation')" align="center" min-width="120px" fixed="right">
-          <template #default="scope">
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
 
-              <div v-if="scope.row.orderStatus == 0||scope.row.orderStatus == 2">
+      <!-- 列表 -->
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list" :stripe="true">
+          <el-table-column :label="t('common.index')" min-width="60" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('bomList.name')"
+            align="center"
+            prop="orderName"
+            min-width="320"
+          />
+          <el-table-column
+            :label="t('operationFill.duty')"
+            align="center"
+            prop="userName"
+            min-width="100"
+          />
+          <el-table-column
+            :label="t('operationFill.orderDevice')"
+            align="center"
+            prop="fillList"
+            min-width="150"
+          >
+            <template #default="scope">
+              <el-popover
+                style="padding: 0"
+                placement="left"
+                :width="300"
+                :hide-after="0"
+                trigger="hover"
+                popper-class="project-popover"
+              >
+                <template #reference>
+                  <span class="cursor-pointer">{{ truncateText(scope.row.fillList, 20) }}</span>
+                </template>
+                <div class="scrollable-tooltip">
+                  <p>{{ scope.row.fillList }}</p>
+                </div>
+              </el-popover>
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('operationFill.status')"
+            align="center"
+            prop="orderStatus"
+            min-width="120"
+          >
+            <template #default="scope">
+              <el-tooltip
+                v-if="scope.row.orderStatus === 3"
+                effect="dark"
+                :content="scope.row.reason"
+                placement="top"
+              >
+                <dict-tag
+                  :type="DICT_TYPE.OPERATION_FILL_ORDER_STATUS"
+                  :value="scope.row.orderStatus"
+                />
+              </el-tooltip>
+              <dict-tag
+                v-else
+                :type="DICT_TYPE.OPERATION_FILL_ORDER_STATUS"
+                :value="scope.row.orderStatus"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('operationFill.deviceCount')"
+            align="center"
+            prop="allDev"
+            min-width="120"
+          >
+            <template #default="scope">
+              <el-tag type="info"> {{ scope.row.allDev }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('operationFill.fillCount')"
+            align="center"
+            prop="fillDev"
+            min-width="120"
+          >
+            <template #default="scope">
+              <el-tag type="success"> {{ scope.row.fillDev }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('operationFill.unFillCount')"
+            align="center"
+            prop="unFillDev"
+            min-width="120"
+          >
+            <template #default="scope">
+              <el-tag type="danger"> {{ scope.row.unFillDev }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column
+            :label="t('dict.createTime')"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('dict.fillTime')"
+            align="center"
+            prop="updateTime"
+            :formatter="dateFormatter"
+            min-width="170"
+          />
+          <el-table-column
+            :label="t('operationFill.operation')"
+            align="center"
+            min-width="120px"
+            fixed="right"
+          >
+            <template #default="scope">
+              <div v-if="scope.row.orderStatus == 0 || scope.row.orderStatus == 2">
                 <el-button
                   link
                   type="primary"
-                  @click="openWrite(scope.row.deptId+','+scope.row.userId+','+scope.row.createTime+','+scope.row.id+','+scope.row.orderStatus)"
+                  @click="
+                    openWrite(
+                      scope.row.deptId +
+                        ',' +
+                        scope.row.userId +
+                        ',' +
+                        scope.row.createTime +
+                        ',' +
+                        scope.row.id +
+                        ',' +
+                        scope.row.orderStatus
+                    )
+                  "
                   v-hasPermi="['rq:iot-opeation-fill:update']"
                   v-if="scope.row.orderStatus !== 1"
                 >
-                  {{t('operationFill.fill')}}
+                  {{ t('operationFill.fill') }}
                 </el-button>
                 <el-button
                   link
@@ -151,82 +231,110 @@
                   v-if="scope.row.orderStatus !== 1"
                   @click="openDialog(scope.row.id)"
                 >
-                  {{t('operationFill.ignore')}}
+                  {{ t('operationFill.ignore') }}
                 </el-button>
               </div>
               <div v-else-if="scope.row.orderStatus === 3">
                 <el-button
                   link
                   type="success"
-                  @click="openWrite(scope.row.deptId+','+scope.row.userId+','+scope.row.createTime+','+scope.row.id+','+scope.row.orderStatus)"
+                  @click="
+                    openWrite(
+                      scope.row.deptId +
+                        ',' +
+                        scope.row.userId +
+                        ',' +
+                        scope.row.createTime +
+                        ',' +
+                        scope.row.id +
+                        ',' +
+                        scope.row.orderStatus
+                    )
+                  "
                 >
-                  {{t('operationFill.view')}}
+                  {{ t('operationFill.view') }}
                 </el-button>
               </div>
               <div v-else>
                 <el-button
                   link
                   type="primary"
-                  @click="openWrite(scope.row.deptId+','+scope.row.userId+','+scope.row.createTime+','+scope.row.id+','+0)"
+                  @click="
+                    openWrite(
+                      scope.row.deptId +
+                        ',' +
+                        scope.row.userId +
+                        ',' +
+                        scope.row.createTime +
+                        ',' +
+                        scope.row.id +
+                        ',' +
+                        0
+                    )
+                  "
                   v-hasPermi="['rq:iot-opeation-fill:update']"
                   v-if="isSameDay(scope.row.createTime)"
                 >
-                  {{t('fault.edit')}}
+                  {{ t('fault.edit') }}
                 </el-button>
                 <el-button
                   link
                   type="success"
-                  @click="openWrite(scope.row.deptId+','+scope.row.userId+','+scope.row.createTime+','+scope.row.id+','+scope.row.orderStatus)"
+                  @click="
+                    openWrite(
+                      scope.row.deptId +
+                        ',' +
+                        scope.row.userId +
+                        ',' +
+                        scope.row.createTime +
+                        ',' +
+                        scope.row.id +
+                        ',' +
+                        scope.row.orderStatus
+                    )
+                  "
                 >
-                  {{t('operationFill.view')}}
+                  {{ t('operationFill.view') }}
                 </el-button>
               </div>
 
-
-            <!-- 编辑按钮 -->
-
-
-          </template>
-        </el-table-column>
-      </el-table>
-      <el-dialog
-        v-model="dialogVisible"
-        title="忽略理由"
-        :width="600"
-        :before-close="handleClose"
-        append-to-body
-        :close-on-click-modal="false"
-      >
-        <el-form
-          ref="reasonFormRef"
-          :model="form"
-          :rules="rules"
-          label-width="60px"
+              <!-- 编辑按钮 -->
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-dialog
+          v-model="dialogVisible"
+          title="忽略理由"
+          :width="600"
+          :before-close="handleClose"
+          append-to-body
+          :close-on-click-modal="false"
         >
-          <el-form-item label="理由" prop="reason">
-            <el-input
-              type="textarea"
-              v-model="form.reason"
-              placeholder="请输入忽略理由"
-              :rows="4"
-              resize="none"
-            />
-          </el-form-item>
-        </el-form>
-
-        <template #footer>
-          <el-button @click="handleCancel">取消</el-button>
-          <el-button type="primary" @click="handleConfirm">确定</el-button>
-        </template>
-      </el-dialog>
-      <!-- 分页 -->
-      <Pagination
-        :total="total"
-        v-model:page="queryParams.pageNo"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
-    </ContentWrap>
+          <el-form ref="reasonFormRef" :model="form" :rules="rules" label-width="60px">
+            <el-form-item label="理由" prop="reason">
+              <el-input
+                type="textarea"
+                v-model="form.reason"
+                placeholder="请输入忽略理由"
+                :rows="4"
+                resize="none"
+              />
+            </el-form-item>
+          </el-form>
+
+          <template #footer>
+            <el-button @click="handleCancel">取消</el-button>
+            <el-button type="primary" @click="handleConfirm">确定</el-button>
+          </template>
+        </el-dialog>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
     </el-col>
   </el-row>
   <!-- 表单弹窗:添加/修改 -->
@@ -234,25 +342,23 @@
 </template>
 
 <script setup lang="ts">
-
-
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotInspectOrderApi, IotInspectOrderVO } from '@/api/pms/inspect/order'
 //import IotInspectOrderForm from './IotInspectOrderForm.vue'
-import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
-import DeptTree from "@/views/system/user/DeptTree.vue";
-import {onMounted, ref} from "vue";
-import {IotOpeationFillApi, IotOpeationFillVO} from "@/api/pms/iotopeationfill";
-import {useUserStore} from "@/store/modules/user";
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { onMounted, ref } from 'vue'
+import { IotOpeationFillApi, IotOpeationFillVO } from '@/api/pms/iotopeationfill'
+import { useUserStore } from '@/store/modules/user'
 const { push } = useRouter()
-const { query} = useRoute() // 查询参数
-const deptId= query.deptId;
-const orderStatus = query.orderStatus;
-const createTime = query.createTime;
+const { query } = useRoute() // 查询参数
+const deptId = query.deptId
+const orderStatus = query.orderStatus
+const createTime = query.createTime
 /** 巡检工单 列表 */
 defineOptions({ name: 'IotOpeationFill1' })
-const dialogVisible = ref(false);
+const dialogVisible = ref(false)
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
@@ -269,15 +375,14 @@ const queryParams = reactive({
   createTime: [],
   deptId: useUserStore().getUser.deptId,
   deviceIds: undefined,
-  orderStatus:undefined
+  orderStatus: undefined
   //userId:useUserStore().getUser.id
 })
 
-
 const form = reactive({
   id: undefined,
-  reason: '',
-});
+  reason: ''
+})
 
 // 表单验证规则
 const rules = {
@@ -285,45 +390,47 @@ const rules = {
     { required: true, message: '请输入忽略理由', trigger: 'blur' },
     { min: 2, message: '理由长度不能少于2个字符', trigger: 'blur' }
   ]
-};
+}
 // 打开对话框
-const openDialog = (id:number) => {
-  dialogVisible.value = true;
-  form.id = id;
-  form.reason = '';
-};
+const openDialog = (id: number) => {
+  dialogVisible.value = true
+  form.id = id
+  form.reason = ''
+}
+
+const truncateText = (text: string, maxLength: number) => {
+  if (!text) return ''
+  return text.length > maxLength ? text.substring(0, maxLength) + '...' : text
+}
 // 取消按钮处理
 const handleCancel = () => {
-  dialogVisible.value = false;
-  resetForm();
-};
+  dialogVisible.value = false
+  resetForm()
+}
 
 // 确定按钮处理
 const handleConfirm = async () => {
   // 表单验证
   try {
-    await reasonFormRef.value.validate();
+    await reasonFormRef.value.validate()
     // 验证通过,调用接口
     await IotOpeationFillApi.updateIotOpeationFill1(form)
-    ElMessage.success('操作成功');
-    dialogVisible.value = false;
-    resetForm();
+    ElMessage.success('操作成功')
+    dialogVisible.value = false
+    resetForm()
   } catch (error) {
-    return;
+    return
   }
-};
+}
 // 重置表单
 const resetForm = () => {
-  reasonFormRef.value?.resetFields();
-};
-const reasonFormRef = ref(null);
-
-
+  reasonFormRef.value?.resetFields()
+}
+const reasonFormRef = ref(null)
 
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
-
 // 新增变量用于控制悬浮提示
 const hoverRowId = ref<number | null>(null)
 
@@ -347,8 +454,7 @@ const getTooltipContent = (row: IotOpeationFillVO) => {
   `
 }
 
-
- // 判断两个日期是否为同一天
+// 判断两个日期是否为同一天
 const isSameDay = (dateString) => {
   if (!dateString) return false
 
@@ -357,12 +463,13 @@ const isSameDay = (dateString) => {
   const today = new Date()
 
   // 比较年、月、日
-  return targetDate.getFullYear() === today.getFullYear() &&
+  return (
+    targetDate.getFullYear() === today.getFullYear() &&
     targetDate.getMonth() === today.getMonth() &&
     targetDate.getDate() === today.getDate()
+  )
 }
 
-
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
   await getList()
@@ -394,11 +501,11 @@ const resetQuery = () => {
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (id?: number) => {
-  push({ name: 'InspectOrderDetail', params:{id} })
+  push({ name: 'InspectOrderDetail', params: { id } })
 }
 
 const openWrite = (id?: string) => {
-  push({ name: 'FillOrderInfo',params:{id}})
+  push({ name: 'FillOrderInfo', params: { id } })
 }
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
@@ -430,49 +537,73 @@ const handleExport = async () => {
 
 /** 初始化 **/
 onMounted(async () => {
-
   // 计算近一周时间
-  const end = new Date();
-  const start = new Date();
-  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000);
+  const end = new Date()
+  const start = new Date()
+  start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000)
 
   // 格式化日期为后端需要的格式
   const formatDate = (date) => {
-    const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, '0');
-    const day = String(date.getDate()).padStart(2, '0');
-    const hours = String(date.getHours()).padStart(2, '0');
-    const minutes = String(date.getMinutes()).padStart(2, '0');
-    const seconds = String(date.getSeconds()).padStart(2, '0');
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
-  };
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+  }
 
-  queryParams.createTime = [formatDate(start), formatDate(end)];
+  queryParams.createTime = [formatDate(start), formatDate(end)]
 
-  if(deptId != null){
-    queryParams.deptId = deptId;
+  if (deptId != null) {
+    queryParams.deptId = deptId
   }
-  if(orderStatus === 4){
+  if (orderStatus === 4) {
     queryParams.orderStatus = null
   }
-  if(orderStatus != null && orderStatus != 4){
-    queryParams.orderStatus = orderStatus;
+  if (orderStatus != null && orderStatus != 4) {
+    queryParams.orderStatus = orderStatus
   }
-  if(createTime){
-    const timeArr = createTime.split(',');
+  if (createTime) {
+    const timeArr = createTime.split(',')
     if (timeArr.length === 2) {
-      queryParams.createTime = timeArr;
+      queryParams.createTime = timeArr
     } else {
       // 处理格式不正确的情况,可以给个默认值或提示
-      console.warn('createTime参数格式不正确');
+      console.warn('createTime参数格式不正确')
     }
     //queryParams.createTime = createTime;
   }
 
-
-
-
-
   getList()
 })
 </script>
+
+<style lang="scss">
+.scrollable-tooltip {
+  min-height: 100px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  /* white-space: pre-wrap;
+  word-break: break-word; */
+  background-color: rgba(0, 0, 0, 0.8);
+  color: #fff;
+  font-size: 14px;
+}
+.project-popover.el-popover.el-popper {
+  opacity: 0.8;
+  background: #000000;
+  border: none !important;
+  font-size: 14px;
+  color: #ffffff;
+  font-weight: 400;
+  min-width: 54px;
+
+  .el-popper__arrow::before {
+    background: #000000;
+    border: none;
+  }
+}
+</style>
+
+<style scoped lang="scss"></style>

+ 269 - 8
src/views/pms/iotopeationfill/index1.vue

@@ -44,7 +44,7 @@
             size="default"
             label-width="120px"
             class="scrollable-form"
-            :model="{ attrList: attrList }"
+            :model="{ attrList: attrList, reportDetails }"
           >
             <div style="margin-left: 24px">
               <el-form class="demo-form-inline" :inline="true">
@@ -106,7 +106,13 @@
                   style="width: 320px"
                 />
               </div>
-              <el-form-item :label="attrItem.name" prop="deviceId" label-position="top">
+
+              <el-form-item
+                v-if="companyName === 'ry' ? attrItem.description !== 'productionStatus' : true"
+                :label="attrItem.name"
+                prop="deviceId"
+                label-position="top"
+              >
                 <div v-if="fillStatus === '1'">
                   <el-select
                     disabled
@@ -200,6 +206,7 @@
                   :controls="false"
                   align="left"
                   placeholder="请输入数字"
+                  :disabled="fillStatus === '1'"
                 />
               </el-form-item>
 
@@ -214,13 +221,192 @@
                   class="w-80!"
                   v-model="attrItem.fillContent"
                   placeholder="请输入其他非生产原因"
+                  :disabled="fillStatus === '1'"
                 />
               </el-form-item>
             </div>
+
+            <div v-if="companyName === 'ry'">
+              <div class="flex items-center justify-between mb-6">
+                <div class="flex items-center gap-2">
+                  <div class="bg-[var(--el-color-primary)] w-1 h-5 rounded-full"></div>
+                  <div class="text-lg font-medium text-[var(--el-text-color-primary)]"
+                    >生产动态</div
+                  >
+                </div>
+                <el-button
+                  :disabled="fillStatus === '1'"
+                  type="primary"
+                  link
+                  :icon="Plus"
+                  @click="addProductionStatusRow"
+                >
+                  添加一行
+                </el-button>
+              </div>
+              <el-form-item prop="reportDetails" class="table-form-item">
+                <ZmTable :data="reportDetails" :loading="false" class="mb-4">
+                  <ZmTableColumn
+                    label="日期"
+                    :width="105"
+                    cover-formatter
+                    :real-value="() => createTime"
+                  />
+
+                  <ZmTableColumn :width="130" label="开始时间" prop="startTime">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        class="mb-0!"
+                        :prop="`reportDetails.${$index}.startTime`"
+                        :rules="{
+                          required: true,
+                          message: '请选择开始时间',
+                          trigger: ['change', 'blur']
+                        }"
+                      >
+                        <el-time-picker
+                          v-model="row.startTime"
+                          placeholder="选择开始时间"
+                          clearable
+                          format="HH:mm"
+                          value-format="HH:mm"
+                          class="w-full!"
+                          @change="calculateDuration(row)"
+                          :disabled="fillStatus === '1'"
+                        />
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
+                  <ZmTableColumn :width="130" label="结束时间" prop="endTime">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        class="mb-0!"
+                        :prop="`reportDetails.${$index}.endTime`"
+                        :rules="{
+                          required: true,
+                          message: '请选择结束时间',
+                          trigger: ['change', 'blur']
+                        }"
+                      >
+                        <el-time-picker
+                          v-model="row.endTime"
+                          placeholder="选择结束时间"
+                          clearable
+                          format="HH:mm"
+                          value-format="HH:mm"
+                          class="w-full!"
+                          @change="calculateDuration(row)"
+                          :disabled="fillStatus === '1'"
+                        />
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
+                  <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+
+                  <ZmTableColumn label="工况" min-width="140">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        :prop="`reportDetails.${$index}.currentOperation`"
+                        :rules="{
+                          required: true,
+                          message: '请输入工况',
+                          trigger: ['change', 'blur']
+                        }"
+                        class="mb-0!"
+                      >
+                        <el-input
+                          v-model="row.currentOperation"
+                          type="textarea"
+                          :autosize="{ minRows: 1 }"
+                          show-word-limit
+                          :maxlength="1000"
+                          placeholder="请输入工况"
+                          :disabled="fillStatus === '1'"
+                        />
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
+
+                  <ZmTableColumn v-if="showDepth" label="结束井深(m)" min-width="80">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        :prop="`reportDetails.${$index}.currentDepth`"
+                        :rules="[
+                          {
+                            required: true,
+                            message: '请输入结束井深',
+                            trigger: ['blur']
+                          }
+                          // { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
+                        ]"
+                        class="mb-0!"
+                      >
+                        <el-input-number
+                          v-model="row.currentDepth"
+                          :min="0"
+                          :controls="false"
+                          class="!w-full"
+                          align="left"
+                          placeholder="请输入结束井深"
+                          @input="(val) => inputCurrentDepth(val, $index)"
+                          :disabled="fillStatus === '1'"
+                        >
+                          <template #suffix> m </template>
+                        </el-input-number>
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
+
+                  <ZmTableColumn label="详细描述" min-width="200" align="center">
+                    <template #default="{ row, $index }">
+                      <el-form-item
+                        v-if="$index >= 0"
+                        :prop="`reportDetails.${$index}.constructionDetail`"
+                        :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
+                        class="mb-0!"
+                      >
+                        <el-input
+                          v-model="row.constructionDetail"
+                          type="textarea"
+                          :autosize="{ minRows: 1 }"
+                          show-word-limit
+                          :maxlength="1000"
+                          placeholder="请输入详细描述"
+                          :disabled="fillStatus === '1'"
+                        />
+                      </el-form-item>
+                    </template>
+                  </ZmTableColumn>
+
+                  <ZmTableColumn label="操作" width="80" fixed="right" align="center">
+                    <template #default="{ $index }">
+                      <el-button
+                        link
+                        type="danger"
+                        :icon="Delete"
+                        @click="removeProductionStatusRow($index)"
+                        :disabled="fillStatus === '1'"
+                      >
+                        删除
+                      </el-button>
+                    </template>
+                  </ZmTableColumn>
+                </ZmTable>
+              </el-form-item>
+            </div>
+
             <el-form-item>
-              <el-button type="primary" @click="getFillInfo" v-show="showStatus">{{
-                t('operationFillForm.confirm')
-              }}</el-button>
+              <el-button
+                :loading="submitLoading"
+                type="primary"
+                @click="getFillInfo"
+                v-show="showStatus"
+                >{{ t('operationFillForm.confirm') }}</el-button
+              >
               <el-button type="info" @click="deleteFillInfo" v-show="showStatus">{{
                 t('operationFill.clear')
               }}</el-button>
@@ -238,6 +424,9 @@ import { ElMessage, FormInstance, FormRules } from 'element-plus'
 import moment from 'moment'
 import { getIntDictOptions, getStrDictOptions } from '@/utils/dict'
 import { useRoute } from 'vue-router'
+import { calculateDuration, formatT } from '@/utils/formatTime'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import { useDebounceFn } from '@vueuse/core'
 
 /** 运行记录填报 列表 */
 defineOptions({ name: 'FillOrderInfo' })
@@ -284,6 +473,50 @@ const queryParams = reactive<any>({
   isSum: undefined
 })
 
+interface ReportDetail {
+  startTime: string
+  endTime: string
+  duration: number
+  currentDepth: number
+  currentOperation: string
+  constructionDetail: string
+}
+
+const reportDetails = ref<ReportDetail[]>([])
+
+const addProductionStatusRow = () => {
+  if (!reportDetails.value) {
+    reportDetails.value = []
+  }
+  reportDetails.value.push({
+    startTime: '',
+    endTime: '',
+    duration: 0,
+    currentDepth: 0,
+    currentOperation: '',
+    constructionDetail: ''
+  })
+}
+
+const removeProductionStatusRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+    return
+  }
+  reportDetails.value.splice(index, 1)
+}
+
+const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, index: number) {
+  if (reportDetails.value && index === reportDetails.value.length - 1) {
+    const currentDepth = attrList.value.find((item) => item.description === 'currentDepth')
+    if (currentDepth) currentDepth.fillContent = val
+  }
+}, 300)
+
+const showDepth = computed(() => {
+  return attrList.value.some((item) => item.description === 'currentDepth')
+})
+
 const NON_KEYS = [
   'repairTime',
   'selfStopTime',
@@ -496,7 +729,21 @@ const getList = async () => {
       queryParams.deviceName = list.value[0].deviceName
       queryParams.deviceId = list.value[0].deviceId
     }
-    getAttrList()
+    await getAttrList()
+    IotOpeationFillApi.getReportDetails(deptId.split(',')[3]).then((res) => {
+      reportDetails.value = (res ? (res as any[]) : []).map((item) => ({
+        startTime: formatT(item.startTime),
+        endTime: formatT(item.endTime),
+        duration: item.duration,
+        currentDepth: item.currentDepth,
+        currentOperation: item.currentOperation,
+        constructionDetail: item.constructionDetail
+      }))
+
+      if (!reportDetails.value.length) {
+        addProductionStatusRow()
+      }
+    })
   } finally {
     loading.value = false
   }
@@ -680,10 +927,14 @@ const getAttrList = async () => {
 }
 const formRef = ref<FormInstance[] | null>(null)
 
+const submitLoading = ref(false)
+
 /** 获取填写信息保存到后台*/
 const getFillInfo = async () => {
   if (!formRef.value) return
 
+  submitLoading.value = true
+
   try {
     const validations = formRef.value.map((form) => form.validate())
     await Promise.all(validations)
@@ -699,6 +950,7 @@ const getFillInfo = async () => {
           (item.fillContent === undefined || item.fillContent === '')
         )
       })
+
       if (emptyFields.length > 0) {
         ElMessage.error(t('operationFillForm.fill'))
         return
@@ -757,8 +1009,9 @@ const getFillInfo = async () => {
     })
     const data = attrList2.value as unknown as IotOpeationFillVO
 
-    console.log('data :>> ', data)
-    await IotOpeationFillApi.insertLog(data)
+    const reqData = { createReqVO: data, reportDetails: reportDetails.value }
+
+    await IotOpeationFillApi.insertLog(reqData)
     message.success(t('common.createSuccess'))
     // 发送操作成功的事件
     emit('success')
@@ -766,6 +1019,8 @@ const getFillInfo = async () => {
     getList()
   } catch (error) {
     console.error('保存失败:', error)
+  } finally {
+    submitLoading.value = false
   }
 }
 
@@ -935,4 +1190,10 @@ onMounted(async () => {
   border: 2px solid #42b983; /* 使用Vue标志性的绿色边框 */
   border-radius: 4px;
 }
+
+:deep(.table-form-item) {
+  .el-form-item__content {
+    margin-left: 0 !important;
+  }
+}
 </style>

+ 47 - 47
src/views/pms/iotrddailyreport/fillDailyReport.vue

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

+ 154 - 36
src/views/pms/iotrddailyreport/index.vue

@@ -3,17 +3,17 @@ import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { useUserStore } from '@/store/modules/user'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
-import { rangeShortcuts } from '@/utils/formatTime'
+import { formatT, rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
-import IotRdDailyReportForm from './IotRdDailyReportForm.vue'
 import dayjs from 'dayjs'
 import download from '@/utils/download'
+import rdForm from './rd-form.vue'
 
 defineOptions({ name: 'IotRdDailyReport' })
 
 const { t } = useI18n()
 
-const router = useRouter()
+// const router = useRouter()
 const route = useRoute()
 
 const message = useMessage()
@@ -135,10 +135,10 @@ watch(
   { immediate: true }
 )
 
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
+// const formRef = ref()
+// const openForm = (type: string, id?: number) => {
+//   formRef.value.open(type, id)
+// }
 
 const exportLoading = ref(false)
 
@@ -166,33 +166,52 @@ function realValue(type: any, value: string) {
   return option?.label || value
 }
 
-function handleDetail(id: number) {
-  try {
-    router.push({
-      name: 'FillDailyReportForm',
-      params: {
-        id: id.toString(),
-        mode: 'detail'
-      }
-    })
-  } catch (error) {
-    console.error('跳转详情页面失败:', error)
+// function handleDetail(id: number) {
+//   try {
+//     router.push({
+//       name: 'FillDailyReportForm',
+//       params: {
+//         id: id.toString(),
+//         mode: 'detail'
+//       }
+//     })
+//   } catch (error) {
+//     console.error('跳转详情页面失败:', error)
+//   }
+// }
+
+// function handleApprove(id: number) {
+//   try {
+//     router.push({
+//       name: 'FillDailyReportForm',
+//       params: {
+//         id: id.toString(),
+//         mode: 'approval'
+//       }
+//     })
+//   } catch (error) {
+//     console.error('跳转审批页面失败:', error)
+//   }
+// }
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'detail' | 'approval' | 'time') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
   }
 }
 
-function handleApprove(id: number) {
-  try {
-    router.push({
-      name: 'FillDailyReportForm',
-      params: {
-        id: id.toString(),
-        mode: 'approval'
-      }
-    })
-  } catch (error) {
-    console.error('跳转审批页面失败:', error)
+onMounted(() => {
+  if (route.query.mode) {
+    handleOpenForm(
+      Number(route.query.id),
+      route.query.mode as 'edit' | 'detail' | 'approval' | 'time'
+    )
   }
-}
+})
 </script>
 
 <template>
@@ -243,14 +262,14 @@ function handleApprove(id: number) {
           <Icon icon="ep:search" class="mr-5px" /> 搜索
         </el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
-        <el-button
+        <!-- <el-button
           type="primary"
           plain
           @click="openForm('create')"
           v-hasPermi="['pms:iot-rd-daily-report:create']"
         >
           <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
+        </el-button> -->
         <el-button type="success" plain @click="handleExport" :loading="exportLoading">
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
@@ -290,7 +309,105 @@ function handleApprove(id: number) {
                   />
                 </template>
               </zm-table-column>
-              <zm-table-column prop="productionStatus" label="当日生产动态" />
+              <zm-table-column
+                prop="reportDetails"
+                label="当日生产动态"
+                width="320"
+                :show-overflow-tooltip="false"
+              >
+                <template #default="{ row }">
+                  <div v-if="row.reportDetails && row.reportDetails.length > 0" class="py-2">
+                    <el-popover
+                      placement="right"
+                      trigger="hover"
+                      width="320"
+                      popper-class="!p-0"
+                      :disabled="row.reportDetails.length <= 1"
+                    >
+                      <template #reference>
+                        <el-badge
+                          :value="row.reportDetails.length"
+                          type="primary"
+                          class="max-w-full"
+                        >
+                          <div
+                            class="w-72 flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                          >
+                            <div class="flex items-center gap-x-2">
+                              <div class="flex items-center">
+                                <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                                <div class="font-medium mr-2">{{
+                                  dayjs(row.createTime).format('YYYY-MM-DD')
+                                }}</div>
+                                <div class="flex items-center">
+                                  <span>{{ formatT(row.reportDetails[0].startTime) }}</span>
+                                  <span class="mx-1">-</span>
+                                  <span>{{ formatT(row.reportDetails[0].endTime) }}</span>
+                                </div>
+                              </div>
+
+                              <div class="ml-auto group-hover:text-blue-600 font-medium">
+                                {{ row.reportDetails[0].duration }} H
+                              </div>
+                            </div>
+
+                            <div class="flex items-center">
+                              <div class="font-medium flex-shrink-0">施工详情:</div>
+                              <el-tooltip
+                                effect="dark"
+                                :content="row.reportDetails[0].constructionDetail"
+                                placement="top"
+                                popper-class="max-w-100"
+                              >
+                                <span
+                                  class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                                >
+                                  {{ row.reportDetails[0].constructionDetail || '-' }}
+                                </span>
+                              </el-tooltip>
+                            </div>
+                          </div>
+                        </el-badge>
+                      </template>
+                      <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                        <div
+                          v-for="(item, index) in row.reportDetails"
+                          :key="index"
+                          class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2 max-w-80"
+                        >
+                          <div class="flex items-center gap-x-2">
+                            <div class="flex items-center">
+                              <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                              <div class="font-medium mr-2">{{
+                                dayjs(row.createTime).format('YYYY-MM-DD')
+                              }}</div>
+                              <div class="flex items-center">
+                                <span>{{ formatT(item.startTime) }}</span>
+                                <span class="mx-1">-</span>
+                                <span>{{ formatT(item.endTime) }}</span>
+                              </div>
+                            </div>
+
+                            <div class="ml-auto group-hover:text-blue-600 font-medium">
+                              {{ item.duration }} H
+                            </div>
+                          </div>
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">施工详情:</div>
+                            <span
+                              class="font-medium group-hover:text-blue-600 transition-colors break-all whitespace-pre-wrap"
+                            >
+                              {{ item.constructionDetail || '-' }}
+                            </span>
+                          </div>
+                        </div>
+                      </el-scrollbar>
+                    </el-popover>
+                  </div>
+                  <span v-else class="text-gray-300">-</span>
+                </template>
+              </zm-table-column>
               <zm-table-column prop="nextPlan" label="下步工作计划" />
               <zm-table-column label="当日">
                 <zm-table-column prop="cumulativeWorkingWell" label="施工井" />
@@ -367,7 +484,7 @@ function handleApprove(id: number) {
                   <el-button
                     link
                     type="success"
-                    @click="handleDetail(scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'detail')"
                     v-hasPermi="['pms:iot-rd-daily-report:query']"
                   >
                     查看
@@ -375,7 +492,7 @@ function handleApprove(id: number) {
                   <el-button
                     link
                     type="warning"
-                    @click="handleApprove(scope.row.id)"
+                    @click="handleOpenForm(scope.row.id, 'approval')"
                     v-hasPermi="['pms:iot-rd-daily-report:update']"
                     v-if="scope.row.auditStatus === 10"
                   >
@@ -404,7 +521,8 @@ function handleApprove(id: number) {
     </div>
   </div>
 
-  <IotRdDailyReportForm ref="formRef" @success="loadList" />
+  <rd-form ref="formRef" :load-list="loadList" v-model:visible="visible" />
+  <!-- <IotRdDailyReportForm ref="formRef" @success="loadList" /> -->
 </template>
 
 <style scoped>

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

@@ -0,0 +1,1531 @@
+<script lang="ts" setup>
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { FormInstance, FormRules } from 'element-plus'
+import { Close, CircleCheck, Plus, Delete } from '@element-plus/icons-vue'
+import { calculateDuration, formatDateNoTime, formatT } from '@/utils/formatTime'
+import { getStrDictOptions } from '@/utils/dict'
+import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import dayjs from 'dayjs'
+import { useDebounceFn } from '@vueuse/core'
+import { cloneDeep } from 'lodash-es'
+import { Base64 } from 'js-base64'
+
+const NON_PROD_FIELDS = [
+  { key: 'repairTime', label: '设备故障' },
+  { key: 'selfStopTime', label: '设备保养' },
+  { key: 'accidentTime', label: '工程质量' },
+  { key: 'complexityTime', label: '技术受限' },
+  { key: 'rectificationTime', label: '生产组织' },
+  { key: 'waitingStopTime', label: '不可抗力' },
+  { key: 'partyaDesign', label: '甲方设计' },
+  { key: 'partyaPrepare', label: '甲方准备' },
+  { key: 'partyaResource', label: '甲方资源' },
+  { key: 'relocationTime', label: '生产配合' },
+  { key: 'winterBreakTime', label: '待命' },
+  { key: 'otherNptTime', label: '其他非生产时间' }
+]
+
+const message = useMessage()
+
+const { t } = useI18n()
+
+interface Props {
+  visible: boolean
+  loadList: () => void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  visible: false
+})
+
+const emits = defineEmits(['update:visible'])
+const formType = ref<'edit' | 'approval' | 'detail' | 'time'>('edit')
+
+interface ExtProperty {
+  name: string
+  unit: string
+  dataType: 'double' | 'string'
+  actualValue: string | number
+  identifier: string
+  required?: number
+}
+
+interface Platform {
+  id: number
+  wellName: string
+  reportId: number
+  rdStatusLabel: string
+  techniqueNames: string
+  extProperty: Array<ExtProperty>
+}
+
+interface PlatformData {
+  rdStatus: string
+  techniqueIds: string[]
+  extProperty: Array<ExtProperty>
+  [key: (typeof NON_PROD_FIELDS)[number]['key']]: any
+}
+
+interface Fule {
+  deviceCode: string
+  deviceName: string
+  queryDate: number
+  zhbdFuel: number
+  customFuel: number
+}
+
+interface Data {
+  id: number
+  platformWell: number
+  platforms: Platform[]
+  finishedPlatforms: Platform[]
+  taskId: number
+  wellName: string
+  constructionStartDate: number
+  manufactureName: string
+  contractName: string
+  deptName: string
+  location: string
+  techniqueNames: string
+  workloadDesign: string
+  commencementDate: string
+  completionDate: string
+  constructionPeriod: string
+  idleTime: string
+  responsiblePersonNames: string
+  deviceNames: string
+  taskName: string
+  taskProgresses: {
+    createTime: string
+    rdStatusLabel: string
+  }[]
+  selectedDevices: {
+    deviceName: string
+    deviceCode: string
+    id: number
+  }[]
+  deviceIds: number[]
+  nextPlan: string
+  externalRental: string
+  malfunction: string
+  faultDowntime: number
+  startTime: number[]
+  endTime: number[]
+  dailyFuel: number
+  reportFuels: Fule[]
+  reportedFuels: Fule[]
+  createTime: number
+  auditStatus: number
+  status: number
+  opinion: string
+  companyId: number
+  deptId: number
+  reportDetails: Omit<ReportDetail, 'startTime' | 'endTime'> &
+    {
+      startTime: number[]
+      endTime: number[]
+    }[]
+  attachments: any[]
+  constructionBrief: string
+}
+
+interface ReportDetail {
+  startTime: string
+  endTime: string
+  constructionDetail: string
+  duration: number
+}
+
+interface Form {
+  timeRange: string[]
+  deviceIds: number[]
+  dailyFuel: number
+  nextPlan: string
+  externalRental: string
+  malfunction: string
+  faultDowntime: number
+  platformIds: number[]
+  reportFuels: Fule[]
+  reportDetails: ReportDetail[]
+  constructionBrief: string
+  attachments: any[]
+  [key: number]: PlatformData | any
+}
+
+const formRef = ref<FormInstance>()
+const rules = ref<FormRules<Form>>({
+  timeRange: [{ required: true, message: '请选择时间节点', trigger: 'change', type: 'array' }],
+  dailyFuel: [{ required: true, message: '请输入当日油耗', trigger: 'change' }],
+  nextPlan: [{ required: true, message: '请输入下计划', trigger: 'change' }],
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    {
+      required: formType.value === 'time',
+      message: '请填写施工简报',
+      type: 'string',
+      trigger: ['blur', 'change']
+    }
+  ]
+})
+
+function noProductionTimeRule(id: number) {
+  const wellName =
+    wellOptions.value.find((item) => item.value === id)?.label ?? data.value.wellName ?? ''
+  return {
+    validator: (_rule: any, _value: any, callback: any) => {
+      const currentRow = form.value[id]
+      if (!currentRow) {
+        callback()
+        return
+      }
+      let totalTime = 0
+      NON_PROD_FIELDS.forEach((field) => {
+        const val = parseFloat(currentRow[field.key])
+        if (!isNaN(val)) {
+          totalTime += val
+        }
+      })
+      const fixedTotal = Number(totalTime.toFixed(2))
+      if (fixedTotal > 24) {
+        callback(new Error(`【${wellName}】总时间(${fixedTotal}h)不能超过 24 小时`))
+      } else {
+        callback()
+      }
+    },
+    trigger: 'blur'
+  }
+}
+
+const handleRowValidate = (pid: number, key: string) => {
+  if (!formRef.value) return
+
+  const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
+
+  if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
+
+  formRef.value.validateField(propsToValidate)
+}
+
+const data = ref<Partial<Data>>({})
+
+const original = (): Form => ({
+  timeRange: [],
+  deviceIds: [],
+  dailyFuel: 0,
+  nextPlan: '',
+  externalRental: '',
+  malfunction: '',
+  faultDowntime: 0,
+  platformIds: [],
+  reportDetails: [],
+  reportFuels: [],
+  constructionBrief: '',
+  attachments: []
+})
+
+const opinion = ref('')
+
+const form = ref<Form>(original())
+
+function initPlatformData(reportId: number, sourceData: any) {
+  form.value[reportId] = {
+    rdStatus: sourceData.rdStatus,
+    techniqueIds: sourceData.techniqueIds || [],
+    extProperty: (sourceData.extProperty || []).map((item) => {
+      if (item.dataType === 'double') {
+        item.actualValue = Number(item.actualValue)
+      }
+      return item
+    }),
+    otherNptReason: sourceData.otherNptReason || ''
+  }
+  NON_PROD_FIELDS.forEach((field) => {
+    form.value[reportId][field.key] = sourceData[field.key] || 0
+  })
+}
+
+// const dailyFuel = ref(0)
+
+const initDailyFuel = () => {
+  const propVal = data.value.dailyFuel
+
+  const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
+
+  if (hasPropValue) {
+    // dailyFuel.value = propVal
+    form.value.dailyFuel = propVal
+  } else {
+    const list1 = data.value.reportFuels || []
+    const list2 = data.value.reportedFuels || []
+
+    const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
+
+    form.value.reportFuels = validList.map((v) => ({
+      ...v,
+      customFuel: Number(
+        Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
+      )
+    }))
+
+    let total = 0
+    form.value.reportFuels.forEach((item) => {
+      total += item.customFuel
+    })
+    // dailyFuel.value = total
+    form.value.dailyFuel = total
+  }
+}
+
+const loading = ref(false)
+async function loadDetail(id: number) {
+  loading.value = true
+  try {
+    const res = await IotRdDailyReportApi.getIotRdDailyReport(id)
+    data.value = res
+
+    opinion.value = data.value.opinion || ''
+
+    form.value.deviceIds = data.value.deviceIds || []
+    form.value.attachments = data.value.attachments || []
+    form.value.nextPlan = data.value.nextPlan || ''
+    form.value.externalRental = data.value.externalRental || ''
+    form.value.malfunction = data.value.malfunction || ''
+    form.value.faultDowntime = data.value.faultDowntime || 0
+    form.value.constructionBrief = data.value.constructionBrief || ''
+
+    form.value.reportDetails = (data.value.reportDetails || []).map((item) => ({
+      duration: item.duration || 0,
+      constructionDetail: item.constructionDetail || '',
+      startTime: formatT(item.startTime),
+      endTime: formatT(item.endTime)
+    }))
+
+    if (!form.value.reportDetails.length) {
+      addReportDetailRow()
+    }
+
+    if (data.value.startTime && data.value.endTime) {
+      form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
+    }
+
+    if (data.value.platformWell === 1) {
+      form.value.platformIds = data.value.platforms?.map((v) => v.reportId) ?? []
+      data.value.platforms?.forEach((p) => {
+        initPlatformData(p.reportId, p)
+      })
+    } else {
+      form.value.platformIds = [data.value.id!]
+      initPlatformData(data.value.id!, data.value)
+    }
+
+    initDailyFuel()
+  } finally {
+    loading.value = false
+  }
+}
+
+const formLoading = ref(false)
+
+const submitForm = useDebounceFn(async function submitForm() {
+  try {
+    formLoading.value = true
+
+    const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
+
+    deleteId.forEach((o) => {
+      delete form.value[o.value]
+    })
+
+    await formRef.value?.validate()
+
+    const copyForm = cloneDeep(form.value)
+
+    const responseData: any[] = []
+
+    form.value.platformIds.forEach((pid) => {
+      const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
+        item.bizId = pid
+        return item
+      })
+
+      let platformWell = data.value.platformWell
+
+      if (platformWell === 1) {
+        platformWell = pid === data.value.id ? 1 : 2
+      }
+
+      const platformData = {
+        id: pid,
+        timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
+        projectDepartment: '',
+        costCenter: '',
+        dynamicFields: {},
+        platformWell,
+        companyId: data.value.companyId,
+        deptId: data.value.deptId,
+        startTime: copyForm.timeRange[0],
+        endTime: copyForm.timeRange[1],
+        deviceIds: copyForm.deviceIds,
+        dailyFuel: copyForm.dailyFuel,
+        nextPlan: copyForm.nextPlan,
+        externalRental: copyForm.externalRental,
+        malfunction: copyForm.malfunction,
+        faultDowntime: copyForm.faultDowntime,
+        reportFuels: copyForm.reportFuels.map((item) => ({
+          ...item,
+          reportId: pid
+        })),
+        reportDetails: copyForm.reportDetails,
+        constructionBrief: copyForm.constructionBrief,
+        attachments: platformAttachments,
+        ...(data.value.platformWell === 1
+          ? {
+              platformId: data.value.platforms?.find((v) => v.reportId === pid)?.id
+            }
+          : {}),
+        extProperty: copyForm[pid].extProperty,
+        rdStatus: copyForm[pid].rdStatus,
+        techniqueIds: copyForm[pid].techniqueIds,
+        ...NON_PROD_FIELDS.reduce(
+          (acc, field) => {
+            acc[field.key] = copyForm[pid][field.key] ?? 0
+            return acc
+          },
+          {} as Record<string, number>
+        ),
+        otherNptReason: copyForm[pid].otherNptReason || '',
+        nonProduct: formType.value === 'time' ? 'Y' : ''
+      }
+
+      responseData.push(platformData)
+    })
+
+    await IotRdDailyReportApi.saveBatch(responseData)
+    message.success(t('common.updateSuccess'))
+    emits('update:visible', false)
+    props.loadList()
+  } catch (error) {
+    console.log('提交失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+})
+
+async function submitApprovalForm(auditStatus: number) {
+  try {
+    formLoading.value = true
+    await IotRdDailyReportApi.approveRdDailyReport({
+      ...data.value,
+      startTime: form.value.timeRange[0],
+      endTime: form.value.timeRange[1],
+      reportDetails: data.value.reportDetails?.map((item) => ({
+        ...item,
+        startTime: formatT(item.startTime),
+        endTime: formatT(item.endTime)
+      })),
+      id: data.value.id!,
+      auditStatus,
+      opinion: opinion.value
+    })
+    if (auditStatus === 20) {
+      message.success('审批通过')
+    } else {
+      message.success('审批驳回')
+    }
+    emits('update:visible', false)
+    props.loadList()
+  } catch (error) {
+    console.log('审批失败:', error)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
+  formType.value = type
+  form.value = original()
+  emits('update:visible', true)
+  loadDetail(id).then(() => {
+    nextTick(() => formRef.value?.clearValidate())
+  })
+}
+
+function handleCloseForm() {
+  emits('update:visible', false)
+}
+
+defineExpose({ handleOpenForm })
+
+const formDisabled = computed(() => (key?: string) => {
+  if (formType.value === 'approval' || formType.value === 'detail' || formType.value === 'time') {
+    if (formType.value === 'approval' && key === 'opinion') {
+      return data.value.auditStatus !== 10
+    }
+
+    if (formType.value === 'approval' && key === 'button') {
+      return data.value.auditStatus !== 10
+    }
+
+    if (
+      formType.value === 'time' &&
+      (NON_PROD_FIELDS.some((field) => field.key === key) ||
+        key === 'constructionBrief' ||
+        key === 'otherNptReason')
+    ) {
+      return false
+    }
+
+    if (formType.value === 'time' && key === 'button') {
+      return false
+    }
+
+    return true
+  }
+
+  if (formType.value === 'edit') {
+    if (
+      NON_PROD_FIELDS.some((field) => field.key === key) ||
+      key === 'constructionBrief' ||
+      key === 'otherNptReason'
+    ) {
+      return true
+    }
+
+    return data.value.status !== 0
+  }
+
+  return false
+})
+
+const header = computed(function () {
+  const suffix =
+    formType.value === 'edit'
+      ? '日报填报'
+      : formType.value === 'approval'
+        ? '日报审批'
+        : formType.value === 'time'
+          ? '日报时效'
+          : '日报详情'
+
+  let title: string = ''
+
+  if (data.value.platformWell === 1) {
+    const platformSource: Platform[] = data.value.platforms || data.value.finishedPlatforms || []
+
+    if (platformSource.length > 0) {
+      const isMainWellInPlatforms = platformSource.find(
+        (platform) => platform.id === data.value.taskId
+      )
+
+      if (!isMainWellInPlatforms) {
+        title = platformSource[0].wellName ?? ''
+      } else title = isMainWellInPlatforms.wellName ?? ''
+    }
+  } else title = data.value.wellName ?? ''
+
+  return { title, suffix, date: formatDateNoTime(data.value.constructionStartDate) }
+})
+
+const modeNotice = computed(() => {
+  if (formType.value === 'approval') {
+    return '审批模式:所有字段均为只读'
+  } else if (formType.value === 'detail') {
+    return '详情模式:所有字段均为只读'
+  } else if (formType.value === 'time') {
+    return '时效模式:非生产时间、当日生产简报可编辑'
+  }
+  return ''
+})
+
+const statusClass = computed(() => {
+  return formType.value === 'edit'
+    ? 'bg-blue-50 text-blue-500 border-blue-200'
+    : formType.value === 'approval'
+      ? 'bg-orange-50 text-orange-600 border-orange-200'
+      : formType.value === 'time'
+        ? 'bg-green-50 text-green-600 border-green-200'
+        : 'bg-gray-100 text-gray-500 border-gray-200'
+})
+
+const progressList = computed(() => {
+  return data.value.taskProgresses ?? []
+})
+
+const deviceOptions = computed(() => {
+  return data.value.selectedDevices ?? []
+})
+
+const noSelectedDevices = computed(() => {
+  if (!deviceOptions.value) return []
+
+  return deviceOptions.value.filter((item) => !form.value.deviceIds.includes(item.id))
+})
+
+const wellOptions = computed(() => {
+  return (
+    data.value.platforms?.map((v) => ({
+      label: v.wellName,
+      value: v.reportId
+    })) ?? []
+  )
+})
+
+const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS)
+const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY).map((v) => {
+  v.value = Number(v.value) as any
+  return v
+})
+
+function handleTechniqueChange(val: string[], platformId: number) {
+  if (!val || val.length === 0) {
+    if (form.value[platformId]) form.value[platformId].extProperty = []
+    return
+  }
+
+  IotDailyReportAttrsApi.dailyReportAttrs({ techniqueIds: val.join(',') }).then((res) => {
+    const newData = res || []
+    const currentExtProps = form.value[platformId].extProperty
+
+    const uniqueMap = new Map()
+    newData.forEach((item: any) => {
+      const key =
+        item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString()
+      uniqueMap.set(key, item)
+    })
+    const uniqueData = Array.from(uniqueMap.values())
+
+    const mergedData = uniqueData.map((newItem: any) => {
+      const newKey =
+        newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : ''
+      const oldItem = currentExtProps.find((old: any) => {
+        const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : ''
+        return newKey && oldKey && newKey === oldKey
+      })
+      return {
+        ...newItem,
+        actualValue: oldItem?.actualValue ?? newItem.actualValue
+      }
+    })
+
+    form.value[platformId].extProperty = mergedData
+  })
+}
+
+const { ZmTable, ZmTableColumn } = useTableComponents<Fule | ReportDetail | Platform>()
+
+const addReportDetailRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = []
+  }
+  form.value.reportDetails.push({
+    startTime: '',
+    endTime: '',
+    duration: 0,
+    constructionDetail: ''
+  })
+}
+
+const removeReportDetailRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+    return
+  }
+
+  form.value.reportDetails?.splice(index, 1)
+}
+
+const handleListChange = useDebounceFn(() => {
+  let total = 0
+  form.value.reportFuels.forEach((item) => {
+    total += item.customFuel
+  })
+  form.value.dailyFuel = total
+}, 500)
+
+const platformWorkloadData = computed(() => {
+  if (!data.value) return []
+  // 需要调整
+  return data.value.platforms || data.value.finishedPlatforms || []
+})
+
+const getWorkloadColumns = () => {
+  const dataSource = platformWorkloadData.value
+  if (!dataSource?.length) return []
+
+  const columnMap = new Map()
+
+  dataSource.forEach((platform) => {
+    platform.extProperty?.forEach((extProp) => {
+      const { identifier, name, unit } = extProp
+
+      if (!columnMap.has(identifier)) {
+        columnMap.set(identifier, {
+          prop: identifier,
+          label: unit ? `${name}(${unit})` : name
+        })
+      }
+    })
+  })
+
+  return Array.from(columnMap.values())
+}
+
+const getWorkloadValue = (platform: Platform, identifier: string) => {
+  if (!platform || !platform.extProperty) return ''
+  const prop = platform.extProperty.find((item) => item.identifier === identifier)
+  return prop ? prop.actualValue || '' : ''
+}
+
+const getFileType = (filename: string) => {
+  const ext = filename.split('.').pop()?.toLowerCase()
+  if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
+    return 'image'
+  } else if (['pdf'].includes(ext || '')) {
+    return 'pdf'
+  } else if (['doc', 'docx'].includes(ext || '')) {
+    return 'word'
+  } else if (['xls', 'xlsx'].includes(ext || '')) {
+    return 'excel'
+  } else {
+    return 'other'
+  }
+}
+
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 Bytes'
+  const k = 1024
+  const sizes = ['Bytes', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+const handleUploadSuccess = (result: any) => {
+  console.log('上传成功', result)
+
+  try {
+    if (!result.response) {
+      message.error('上传响应数据异常')
+      return
+    }
+
+    if (result.response.code !== 0) {
+      message.error(result.response.msg || '文件上传失败')
+      return
+    }
+
+    const responseData = result.response.data
+
+    if (!responseData) {
+      message.error('上传数据为空')
+      return
+    }
+
+    // 处理返回的文件列表
+    if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
+      responseData.files.forEach((file: any) => {
+        if (!file.filePath) {
+          console.warn('文件缺少 filePath:', file)
+          return
+        }
+
+        // 根据后端返回的数据结构构建附件对象
+        const attachment = {
+          id: undefined,
+          category: 'daily_report',
+          bizId: data.value.id,
+          type: 'attachment',
+          filename: file.name || '未知文件',
+          fileType: getFileType(file.name),
+          filePath: file.filePath, //使用正确的 filePath
+          fileSize: formatFileSize(file.size || 0),
+          remark: ''
+        }
+
+        // 添加到附件列表
+        if (!form.value.attachments) {
+          form.value.attachments = []
+        }
+        form.value.attachments.push(attachment)
+      })
+
+      message.success(`成功上传 ${responseData.files.length} 个文件`)
+    } else {
+      console.warn('上传成功但没有返回文件信息')
+      message.warning('上传成功但未获取到文件信息')
+    }
+  } catch (error) {
+    console.error('处理上传结果时发生错误:', error)
+    message.error('处理上传结果失败')
+  }
+}
+
+const removeAttachment = (index: number) => {
+  if (form.value.attachments && form.value.attachments.length > index) {
+    form.value.attachments.splice(index, 1)
+  }
+}
+
+const inContent = async (attachment) => {
+  if (!attachment || !attachment.filePath) {
+    message.error('附件路径不存在')
+    return
+  }
+
+  try {
+    const filePath = attachment.filePath
+    const encodedPath = encodeURIComponent(Base64.encode(filePath))
+
+    window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
+  } catch (error) {
+    console.error('预览附件失败:', error)
+    message.error('预览附件失败')
+  }
+}
+</script>
+
+<template>
+  <el-drawer
+    ref="DrawerRef"
+    :model-value="visible"
+    @update:model-value="emits('update:visible', $event)"
+    header-class="mb-0!"
+    :with-header="false"
+    size="50%"
+    :close-on-press-escape="false"
+    :close-on-click-modal="false"
+  >
+    <template #default>
+      <div id="rd-form-content" v-loading="loading" class="size-full flex flex-col gap-4">
+        <div class="flex justify-between items-start">
+          <div class="flex flex-col gap-1">
+            <div class="flex items-center gap-3">
+              <span class="text-xl font-bold text-gray-900 leading-tight">
+                {{ header.title ?? header.suffix }}
+              </span>
+              <div
+                v-if="header.title"
+                class="px-2 py-0.5 rounded text-xs font-medium border"
+                :class="statusClass"
+              >
+                {{ header.suffix }}
+              </div>
+            </div>
+            <div v-if="header.date" class="text-sm text-gray-400 font-medium">
+              {{ header.date }}
+            </div>
+          </div>
+
+          <el-button link @click="handleCloseForm">
+            <el-icon size="24"><Close /></el-icon>
+          </el-button>
+        </div>
+        <el-alert
+          class="min-h-12"
+          v-if="formType !== 'edit'"
+          :title="modeNotice"
+          type="info"
+          :closable="false"
+        />
+
+        <el-alert
+          class="min-h-12"
+          v-if="formType !== 'approval' && data.opinion"
+          :title="data.opinion"
+          type="warning"
+          :closable="false"
+        />
+        <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+
+        <div class="grid grid-cols-3 gap-y-8 gap-x-4">
+          <div class="info-item">
+            <label>甲方</label>
+            <div class="truncate">{{ data.manufactureName || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>合同号</label>
+            <div>{{ data.contractName || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>井号</label>
+            <div class="text-primary font-bold">
+              {{ data.wellName || data.taskName || '-' }}
+            </div>
+          </div>
+
+          <!-- 第二行 -->
+          <div class="info-item">
+            <label>施工队伍</label>
+            <div>{{ data.deptName || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>施工地点</label>
+            <div>{{ data.location || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>工艺</label>
+            <div>{{ data.techniqueNames || '-' }}</div>
+          </div>
+
+          <!-- 第三行 -->
+          <div class="info-item">
+            <label>设计工作量</label>
+            <div class="font-mono text-gray-700">{{ data.workloadDesign || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>开工日期</label>
+            <div class="font-mono text-gray-700">{{ data.commencementDate || '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>完工日期</label>
+            <div class="font-mono text-gray-700">{{ data.completionDate || '-' }}</div>
+          </div>
+
+          <!-- 第四行 -->
+          <div class="info-item">
+            <label>施工周期 (D)</label>
+            <div>{{ data.constructionPeriod ?? '-' }}</div>
+          </div>
+
+          <div class="info-item">
+            <label>停待时间 (D)</label>
+            <div>
+              {{ data.idleTime ?? '-' }}
+            </div>
+          </div>
+
+          <div class="info-item">
+            <label>带班干部</label>
+            <div>{{ data.responsiblePersonNames || '-' }}</div>
+          </div>
+
+          <div class="info-item col-span-3">
+            <label>设备配置</label>
+            <div>{{ data.deviceNames || '-' }}</div>
+          </div>
+        </div>
+        <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+        <div v-if="progressList.length > 0">
+          <h3 class="text-lg font-bold text-gray-800 mb-6 flex items-center gap-2">
+            <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+            任务进度
+          </h3>
+          <el-scrollbar class="h-24!" view-class="w-full flex items-start px-2">
+            <div
+              v-for="(item, index) in progressList"
+              :key="index"
+              class="group relative flex flex-col items-center flex-1 min-w-[160px] cursor-default select-none"
+            >
+              <div
+                v-if="index !== progressList.length - 1"
+                class="absolute top-[34px] left-1/2 w-full h-[2px] bg-gray-100 group-hover:bg-blue-50 transition-colors duration-300"
+              >
+              </div>
+
+              <span
+                class="text-xs font-medium text-gray-400 mb-3 font-mono transition-colors duration-300 group-hover:text-blue-500"
+              >
+                {{ item.createTime || '--' }}
+              </span>
+
+              <div
+                class="relative z-10 mb-3 transition-transform duration-300 group-hover:-translate-y-0.5"
+              >
+                <div
+                  class="w-4 h-4 rounded-full border-[3px] border-white shadow-[0_0_0_2px_rgba(229,231,235,1)] bg-blue-600 group-hover:shadow-[0_0_0_4px_rgba(219,234,254,1)] group-hover:bg-blue-500 transition-all duration-300"
+                >
+                </div>
+              </div>
+
+              <span
+                class="text-sm font-bold text-gray-700 px-2 text-center leading-relaxed transition-colors duration-300 group-hover:text-blue-600"
+              >
+                {{ item.rdStatusLabel || '未知状态' }}
+              </span>
+            </div>
+          </el-scrollbar>
+        </div>
+        <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
+        <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+          <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+          基础信息
+        </h3>
+        <el-form
+          ref="formRef"
+          size="default"
+          :rules="rules"
+          label-position="top"
+          :model="form"
+          require-asterisk-position="right"
+          class="flex flex-col"
+          :disabled="formDisabled()"
+        >
+          <div
+            class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
+          >
+            <el-form-item label="时间节点" prop="timeRange">
+              <el-time-picker
+                v-model="form.timeRange"
+                is-range
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                placeholder="选择时间范围"
+                clearable
+                format="HH:mm"
+                value-format="HH:mm"
+              />
+            </el-form-item>
+            <el-form-item label="当日油耗(L)" prop="dailyFuel">
+              <el-input-number
+                v-model="form.dailyFuel"
+                :min="0"
+                :controls="false"
+                align="left"
+                class="w-full!"
+                placeholder="请输入当日油耗"
+              >
+                <template #suffix>升(L)</template>
+              </el-input-number>
+            </el-form-item>
+            <el-form-item class="col-span-2" label="施工设备" prop="deviceIds">
+              <el-select
+                v-model="form.deviceIds"
+                multiple
+                placeholder="请选择施工设备"
+                clearable
+                filterable
+                tag-type="primary"
+              >
+                <el-option
+                  v-for="item in deviceOptions"
+                  :key="item.id"
+                  :label="item.deviceName"
+                  :value="item.id"
+                >
+                  <span class="font-medium">{{ item.deviceCode + ' - ' + item.deviceName }}</span>
+                </el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item class="col-span-2" label="闲置/未施工设备">
+              <div
+                class="w-full min-h-[40px] p-3 rounded bg-gray-50 border border-gray-200 border-dashed transition-all"
+              >
+                <template v-if="noSelectedDevices.length > 0">
+                  <div class="flex flex-wrap gap-2">
+                    <el-tag
+                      v-for="device in noSelectedDevices"
+                      :key="device.id"
+                      type="info"
+                      effect="plain"
+                      class="!border-gray-300"
+                    >
+                      {{ device.deviceName }}
+                    </el-tag>
+                  </div>
+                </template>
+                <template v-else>
+                  <div class="text-gray-400 text-sm flex items-center">
+                    <el-icon class="mr-1"><CircleCheck /></el-icon>
+                    所有设备均已投入施工
+                  </div>
+                </template>
+              </div>
+            </el-form-item>
+            <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
+              <el-input
+                v-model="form.nextPlan"
+                type="textarea"
+                :autosize="{ minRows: 2 }"
+                resize="none"
+                show-word-limit
+                :maxlength="1000"
+                placeholder="请输入下步工作计划"
+              />
+            </el-form-item>
+            <el-form-item class="col-span-2" label="外组设备" prop="externalRental">
+              <el-input
+                v-model="form.externalRental"
+                type="textarea"
+                :autosize="{ minRows: 2 }"
+                resize="none"
+                show-word-limit
+                :maxlength="1000"
+                placeholder="请输入外组设备"
+              />
+            </el-form-item>
+            <el-form-item class="col-span-2" label="故障情况" prop="malfunction">
+              <el-input
+                v-model="form.malfunction"
+                type="textarea"
+                :autosize="{ minRows: 2 }"
+                show-word-limit
+                resize="none"
+                :maxlength="1000"
+                placeholder="请输入故障情况"
+              />
+            </el-form-item>
+            <el-form-item label="故障误工(H)" prop="faultDowntime">
+              <el-input-number
+                v-model="form.faultDowntime"
+                :min="0"
+                :controls="false"
+                align="left"
+                class="w-full!"
+              >
+                <template #suffix>小时(H)</template>
+              </el-input-number>
+            </el-form-item>
+
+            <el-form-item label="附件">
+              <FileUpload
+                v-if="formType === 'edit'"
+                ref="fileUploadRef"
+                :device-id="undefined"
+                :show-folder-button="false"
+                @upload-success="handleUploadSuccess"
+              />
+
+              <div
+                v-if="form.attachments && form.attachments.length > 0"
+                class="attachment-container"
+              >
+                <div class="attachment-list">
+                  <div
+                    v-for="(attachment, index) in form.attachments"
+                    :key="attachment.id || index"
+                    class="attachment-item"
+                  >
+                    <a class="attachment-name" @click="inContent(attachment)">
+                      {{ attachment.filename }}
+                    </a>
+                    <el-button
+                      :disabled="formDisabled()"
+                      type="danger"
+                      link
+                      size="small"
+                      @click="removeAttachment(index)"
+                    >
+                      删除
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+              <div
+                v-else-if="!form.attachments || form.attachments.length === 0"
+                class="no-attachment"
+              >
+                无附件
+              </div>
+            </el-form-item>
+
+            <div class="col-span-2 flex items-center justify-between mb-6">
+              <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
+                <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+                生产动态
+              </h3>
+              <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
+                添加一行
+              </el-button>
+            </div>
+
+            <el-form-item prop="reportDetails" class="col-span-2">
+              <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
+                <ZmTableColumn
+                  :width="105"
+                  label="日期"
+                  cover-formatter
+                  :real-value="
+                    () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
+                  "
+                />
+                <ZmTableColumn :width="160" label="开始时间" prop="startTime">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.startTime`"
+                      :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
+                    >
+                      <el-time-picker
+                        v-model="row.startTime"
+                        placeholder="选择开始时间"
+                        clearable
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        class="w-full!"
+                        @change="calculateDuration(row)"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn :width="160" label="结束时间" prop="endTime">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.endTime`"
+                      :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
+                    >
+                      <el-time-picker
+                        v-model="row.endTime"
+                        placeholder="选择结束时间"
+                        clearable
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        class="w-full!"
+                        @change="calculateDuration(row)"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+                <ZmTableColumn label="施工详情" prop="constructionDetail">
+                  <template #default="{ row, $index }">
+                    <el-form-item
+                      v-if="$index >= 0"
+                      class="mb-0!"
+                      :prop="`reportDetails.${$index}.constructionDetail`"
+                      :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
+                    >
+                      <el-input
+                        v-model="row.constructionDetail"
+                        placeholder="输入施工详情"
+                        type="textarea"
+                        :autosize="{ minRows: 1 }"
+                        show-word-limit
+                        :maxlength="2000"
+                        class="w-full!"
+                      />
+                    </el-form-item>
+                  </template>
+                </ZmTableColumn>
+                <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
+                  <template #default="{ $index }">
+                    <el-button
+                      link
+                      type="danger"
+                      :icon="Delete"
+                      @click="removeReportDetailRow($index)"
+                    >
+                      删除
+                    </el-button>
+                  </template>
+                </ZmTableColumn>
+              </ZmTable>
+            </el-form-item>
+
+            <el-form-item class="col-span-2" label="当日施工简报" prop="constructionBrief">
+              <el-input
+                v-model="form.constructionBrief"
+                type="textarea"
+                :autosize="{ minRows: 2 }"
+                show-word-limit
+                resize="none"
+                :maxlength="1000"
+                placeholder="请输入当日施工简报"
+                :disabled="formDisabled('constructionBrief')"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item
+            class="mt-4 mb-0!"
+            v-if="data.platformWell === 1"
+            label="平台井"
+            prop="platformIds"
+          >
+            <el-select
+              v-model="form.platformIds"
+              multiple
+              :options="wellOptions"
+              placeholder="请选择平台井"
+              clearable
+              filterable
+              collapse-tags
+              collapse-tags-tooltip
+              :max-collapse-tags="5"
+              tag-type="primary"
+            />
+          </el-form-item>
+          <template v-for="pid in form.platformIds" :key="pid">
+            <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+            <div
+              class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
+            >
+              <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
+                <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+                {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
+              </h3>
+              <el-form-item
+                label="施工状态"
+                :prop="`${pid}.rdStatus`"
+                :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
+                class="col-span-2"
+              >
+                <el-select
+                  v-model="form[pid].rdStatus"
+                  :options="rdStatusOptions"
+                  placeholder="请选择"
+                  class="w-full"
+                  clearable
+                />
+              </el-form-item>
+
+              <el-form-item
+                label="施工工艺"
+                :prop="`${pid}.techniqueIds`"
+                :rules="{
+                  required: true,
+                  message: '请选择施工工艺',
+                  trigger: 'change',
+                  type: 'array'
+                }"
+                class="col-span-2"
+              >
+                <el-select
+                  v-model="form[pid].techniqueIds"
+                  :options="techniqueOptions"
+                  multiple
+                  collapse-tags
+                  collapse-tags-tooltip
+                  placeholder="请选择"
+                  class="w-full"
+                  @change="(val) => handleTechniqueChange(val, pid)"
+                  clearable
+                />
+              </el-form-item>
+
+              <template v-if="form[pid] && form[pid].extProperty">
+                <el-form-item
+                  v-for="(attr, idx) in form[pid].extProperty"
+                  :key="idx"
+                  :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
+                  :prop="`${pid}.extProperty.${idx}.actualValue`"
+                  :rules="
+                    attr.required === 1
+                      ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
+                      : []
+                  "
+                >
+                  <el-input-number
+                    v-if="attr.dataType === 'double'"
+                    v-model="attr.actualValue"
+                    :controls="false"
+                    class="w-full!"
+                    align="left"
+                    placeholder="请输入"
+                  />
+                  <el-input
+                    type="textarea"
+                    v-else
+                    v-model="attr.actualValue"
+                    placeholder="请输入"
+                  />
+                </el-form-item>
+              </template>
+
+              <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
+                非生产时间
+              </el-divider>
+
+              <el-form-item
+                v-for="field in NON_PROD_FIELDS"
+                :key="field.key"
+                :label="field.label"
+                :prop="`${pid}.${field.key}`"
+                :rules="noProductionTimeRule(pid)"
+              >
+                <el-input-number
+                  v-model="form[pid][field.key]"
+                  :min="0"
+                  :max="24"
+                  :controls="false"
+                  class="w-full!"
+                  align="left"
+                  @blur="handleRowValidate(pid, field.key)"
+                  :disabled="formDisabled(field.key)"
+                >
+                  <template #suffix>小时(H)</template>
+                </el-input-number>
+              </el-form-item>
+
+              <el-form-item
+                class="col-span-4"
+                label="其他非生产原因"
+                :prop="`${pid}.otherNptReason`"
+                :rules="
+                  form[pid].otherNptTime > 0
+                    ? { required: true, message: '请填写原因', trigger: 'change' }
+                    : {}
+                "
+              >
+                <el-input
+                  v-model="form[pid].otherNptReason"
+                  type="textarea"
+                  :autosize="{ minRows: 2 }"
+                  resize="none"
+                  show-word-limit
+                  :maxlength="1000"
+                  placeholder="当'其他非生产时间'大于0时必填"
+                  :disabled="formDisabled('otherNptReason')"
+                />
+              </el-form-item>
+            </div>
+          </template>
+
+          <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+          <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
+            <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+            油耗信息
+          </h3>
+
+          <ZmTable :data="form.reportFuels" :loading="false">
+            <ZmTableColumn label="设备编号" :width="160" prop="deviceCode" />
+            <ZmTableColumn label="设备名称" prop="deviceName" />
+            <ZmTableColumn
+              label="发生日期"
+              prop="queryDate"
+              :width="110"
+              cover-formatter
+              :real-value="
+                (row) => (row.queryDate ? dayjs(row.queryDate).format('YYYY-MM-DD') : '')
+              "
+            />
+            <ZmTableColumn label="中航北斗油耗(L)" :width="140" prop="zhbdFuel" />
+            <ZmTableColumn label="实际油耗(L)" prop="customFuel">
+              <template #default="{ row, $index }">
+                <el-form-item class="mb-0!" :prop="`reportFuels.${$index}.customFuel`">
+                  <el-input-number
+                    v-model="row.customFuel"
+                    :min="0"
+                    :controls="false"
+                    class="w-full!"
+                    align="left"
+                    @input="handleListChange"
+                  >
+                    <template #suffix> L </template>
+                  </el-input-number>
+                </el-form-item>
+              </template>
+            </ZmTableColumn>
+          </ZmTable>
+
+          <template v-if="platformWorkloadData.length > 0">
+            <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+            <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
+              <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
+              平台井工作量
+            </h3>
+
+            <ZmTable :data="platformWorkloadData" :loading="false">
+              <ZmTableColumn label="井号" prop="wellName" />
+              <ZmTableColumn label="施工状态" prop="rdStatusLabel" />
+              <ZmTableColumn label="施工工艺" prop="techniqueNames" />
+              <template v-for="{ prop, label } in getWorkloadColumns()" :key="prop">
+                <ZmTableColumn
+                  :label="label"
+                  :prop="prop"
+                  cover-formatter
+                  :real-value="(row) => getWorkloadValue(row, prop)"
+                />
+              </template>
+            </ZmTable>
+          </template>
+
+          <template v-if="formType === 'approval'">
+            <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
+
+            <el-form-item label="审批意见" prop="opinion">
+              <el-input
+                v-model="opinion"
+                type="textarea"
+                :autosize="{ minRows: 2 }"
+                resize="none"
+                show-word-limit
+                :maxlength="1000"
+                placeholder="请输入审批意见"
+                :disabled="formDisabled('opinion')"
+              />
+            </el-form-item>
+          </template>
+        </el-form>
+        <el-backtop target=".el-drawer__body" :right="100" :bottom="100" :visibility-height="40" />
+      </div>
+    </template>
+    <template #footer>
+      <div v-if="formType === 'edit' || formType === 'time'">
+        <el-button
+          size="default"
+          type="primary"
+          @click="submitForm"
+          :disabled="formDisabled('button')"
+          :loading="formLoading"
+        >
+          确 定
+        </el-button>
+        <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
+      </div>
+      <div v-if="formType === 'approval'">
+        <el-button
+          size="default"
+          type="primary"
+          @click="submitApprovalForm(20)"
+          :disabled="formDisabled('button')"
+          :loading="formLoading"
+        >
+          审批通过
+        </el-button>
+        <el-button
+          size="default"
+          type="danger"
+          @click="submitApprovalForm(30)"
+          :disabled="formDisabled('button')"
+          :loading="formLoading"
+        >
+          审批拒绝
+        </el-button>
+        <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<style scoped>
+.info-item {
+  display: flex;
+  flex-direction: column;
+  gap: 0.25rem;
+
+  label {
+    font-size: 0.75rem;
+    font-weight: 500;
+    line-height: 1rem;
+    color: #9ca3af;
+  }
+
+  > div {
+    min-height: 1.25rem;
+    font-size: 0.875rem;
+    font-weight: 600;
+    line-height: 1.25rem;
+    color: #1f2937;
+    word-break: break-all;
+  }
+}
+
+:deep(.el-form-item__label) {
+  font-weight: 500;
+}
+
+:deep(.el-scrollbar__bar.is-horizontal) {
+  height: 4px;
+}
+</style>

+ 3 - 3
src/views/pms/iotrddailyreport/summary.vue

@@ -79,7 +79,6 @@ const totalWorkKeys: [string, string | undefined, string, string, number][] = [
     2
   ],
   ['taici', undefined, '台次', 'i-material-symbols:check-circle-outline-rounded text-emerald', 0],
-
   [
     'utilizationRate',
     '%',
@@ -232,7 +231,8 @@ const chartData = ref<Record<string, number[]>>({
   cumulativeGasInjection: [],
   cumulativePowerConsumption: [],
   cumulativeWaterInjection: [],
-  transitTime: []
+  transitTime: [],
+  utilizationRate: []
 })
 
 let chartLoading = ref(false)
@@ -602,7 +602,7 @@ const { ZmTable, ZmTableColumn } = useTableComponents()
                         prop="utilizationRate"
                         cover-formatter
                         :real-value="
-                          (row: List) => `${Number(((row.utilizationRate || 0) * 100).toFixed(2))}%`
+                          (row: List) => (Number(row.utilizationRate ?? 0) * 100).toFixed(2) + '%'
                         "
                       />
                     </zm-table>

+ 100 - 575
src/views/pms/iotrhdailyreport/approval.vue

@@ -1,424 +1,13 @@
 <script lang="ts" setup>
 import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
 import rhForm from './rh-form.vue'
+import RhTable from './rh-table.vue'
 
-interface List {
-  createTime: number // 日期
-  deptName: string // 施工队伍
-  contractName: string // 项目
-  taskName: string // 任务
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  relocationDays: number // 搬迁安装天数
-  designInjection: string // 设计注气量(万方)
-  transitTime: number // 运行时效
-  dailyGasInjection: number // 注气量(万方)
-  dailyWaterInjection: number // 注水量(方)
-  dailyInjectGasTime: number // 注气时间(H)
-  dailyInjectWaterTime: number // 注水时间(H)
-  dailyPowerUsage: number // 日耗电量(度)
-  dailyOilUsage: number // 日耗油量(升)
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string // 其他非生产时间原因
-  nptReason: string // 非生产时间原因
-  constructionStartDate: number // 施工开始日期
-  constructionEndDate: number // 施工结束日期
-  productionStatus: string // 生产动态
-  constructionStatus: string // 施工状态
-  totalGasInjection: number // 注气量(万方)
-  totalWaterInjection: number // 注水量(方)
-  cumulativeCompletion: number // 完工井次
-  capacity: number // 产能(万方)
-  remark: string // 备注
-  auditStatus: number // 审核状态
-  status: number // 状态
-  opinion: string // 审核意见
-  gasElectricityRatio: number // 气电比
-  nonProductionRate: number // 非生产时效
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    fixed: 'left',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    fixed: 'left',
-    'min-width': '120px'
-  },
-  {
-    label: '任务',
-    prop: 'taskName',
-    fixed: 'left',
-    'min-width': '120px'
-  },
-  {
-    label: '施工状态',
-    prop: 'constructionStatus',
-    fixed: 'left',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '搬迁安装天数',
-    prop: 'relocationDays',
-    'min-width': '120px',
-    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
-  },
-  {
-    label: '设计注气量(万方)',
-    prop: 'designInjection',
-    'min-width': '120px',
-    formatter: (row: List) => row.designInjection || '0'
-  },
-  {
-    label: '运行时效',
-    prop: 'transitTime',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const capacity = Number(row?.capacity)
-      const dailyGasInjection = Number(row?.dailyGasInjection)
-
-      if (!capacity || !dailyGasInjection) {
-        return '0.00%'
-      }
-
-      return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '注气量(万方)',
-        prop: 'dailyGasInjection',
-        'min-width': '120px',
-        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
-      },
-      {
-        label: '注水量(方)',
-        prop: 'dailyWaterInjection',
-        'min-width': '120px'
-      },
-      {
-        label: '注气时间(H)',
-        prop: 'dailyInjectGasTime',
-        'min-width': '120px'
-      },
-      {
-        label: '注水时间(H)',
-        prop: 'dailyInjectWaterTime',
-        'min-width': '120px'
-      },
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(L)',
-        prop: 'dailyOilUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '气电比',
-        prop: 'gasElectricityRatio',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  },
-
-  // {
-  //   label: '非生产时间原因',
-  //   prop: 'nptReason',
-  //   'min-width': '120px',
-  //   isTag: true,
-  //   dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
-  // },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '累计',
-    children: [
-      {
-        label: '注气量(万方)',
-        prop: 'totalGasInjection',
-        'min-width': '120px',
-        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
-      },
-      {
-        label: '注水量(方)',
-        prop: 'totalWaterInjection',
-        'min-width': '120px'
-      },
-      {
-        label: '完工井次',
-        prop: 'cumulativeCompletion',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '产能(万方)',
-    prop: 'capacity',
-    'min-width': '120px',
-    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-// function checkTimeSumEquals24(row: List) {
-//   // 获取三个字段的值,转换为数字,如果为空则视为0
-//   const gasTime = row.dailyInjectGasTime || 0
-//   const waterTime = row.dailyInjectWaterTime || 0
-//   const nonProdTime = row.nonProductionTime || 0
-
-//   // 计算总和
-//   const sum = gasTime + waterTime + nonProdTime
-
-//   // 返回是否等于24(允许一定的浮点数误差)
-//   return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
-// }
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'transitTime') {
-    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
-    const capacity = Number(row?.capacity)
-    const dailyGasInjection = Number(row?.dailyGasInjection)
-
-    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
-    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
-    if (capacity && dailyGasInjection) {
-      const ratio = dailyGasInjection / capacity
-
-      // 3. 判断计算结果是否大于 1.2 (即 120%)
-      if (ratio > 1.2) {
-        return {
-          color: 'red',
-          fontWeight: 'bold'
-        }
-      }
-    }
-  }
-  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
-  // if (timeFields.includes(column.property)) {
-  //   if (!checkTimeSumEquals24(row)) {
-  //     return {
-  //       color: 'orange',
-  //       fontWeight: 'bold'
-  //     }
-  //   }
-  // }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+defineOptions({ name: 'IotRhDailyReportApproval' })
 
 const id = useUserStore().getUser.deptId
 
@@ -427,50 +16,48 @@ const deptId = id
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
+  createTime?: string[]
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ]
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
-}
-
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
 }
 
-const loading = ref(false)
+const query = ref<Query>({ ...initQuery })
 
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRhDailyReportApi.getIotRhDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -480,25 +67,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ]
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -527,143 +106,89 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="flex flex-col p-4 gap-2 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :deptId="deptId" :topId="157" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="157" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+    <rh-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-rh-daily-report:update']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                class="!w-220px"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-rh-daily-report:update']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.auditStatus === 10"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-rh-daily-report:update']"
-                      >
-                        审批
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <rh-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.auditStatus === 10"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-rh-daily-report:update']"
+        >
+          审批
+        </el-button>
+      </template>
+    </rh-table>
   </div>
+  <rh-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 669 - 0
src/views/pms/iotrhdailyreport/approval1.vue

@@ -0,0 +1,669 @@
+<script lang="ts" setup>
+import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import rhForm from './rh-form.vue'
+
+interface List {
+  createTime: number // 日期
+  deptName: string // 施工队伍
+  contractName: string // 项目
+  taskName: string // 任务
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  relocationDays: number // 搬迁安装天数
+  designInjection: string // 设计注气量(万方)
+  transitTime: number // 运行时效
+  dailyGasInjection: number // 注气量(万方)
+  dailyWaterInjection: number // 注水量(方)
+  dailyInjectGasTime: number // 注气时间(H)
+  dailyInjectWaterTime: number // 注水时间(H)
+  dailyPowerUsage: number // 日耗电量(度)
+  dailyOilUsage: number // 日耗油量(升)
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string // 其他非生产时间原因
+  nptReason: string // 非生产时间原因
+  constructionStartDate: number // 施工开始日期
+  constructionEndDate: number // 施工结束日期
+  productionStatus: string // 生产动态
+  constructionStatus: string // 施工状态
+  totalGasInjection: number // 注气量(万方)
+  totalWaterInjection: number // 注水量(方)
+  cumulativeCompletion: number // 完工井次
+  capacity: number // 产能(万方)
+  remark: string // 备注
+  auditStatus: number // 审核状态
+  status: number // 状态
+  opinion: string // 审核意见
+  gasElectricityRatio: number // 气电比
+  nonProductionRate: number // 非生产时效
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    fixed: 'left',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    fixed: 'left',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '搬迁安装天数',
+    prop: 'relocationDays',
+    'min-width': '120px',
+    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
+  },
+  {
+    label: '设计注气量(万方)',
+    prop: 'designInjection',
+    'min-width': '120px',
+    formatter: (row: List) => row.designInjection || '0'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const capacity = Number(row?.capacity)
+      const dailyGasInjection = Number(row?.dailyGasInjection)
+
+      if (!capacity || !dailyGasInjection) {
+        return '0.00%'
+      }
+
+      return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'dailyGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'dailyWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '注气时间(H)',
+        prop: 'dailyInjectGasTime',
+        'min-width': '120px'
+      },
+      {
+        label: '注水时间(H)',
+        prop: 'dailyInjectWaterTime',
+        'min-width': '120px'
+      },
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(L)',
+        prop: 'dailyOilUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '气电比',
+        prop: 'gasElectricityRatio',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  },
+
+  // {
+  //   label: '非生产时间原因',
+  //   prop: 'nptReason',
+  //   'min-width': '120px',
+  //   isTag: true,
+  //   dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
+  // },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '累计',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'totalGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'totalWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '完工井次',
+        prop: 'cumulativeCompletion',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '产能(万方)',
+    prop: 'capacity',
+    'min-width': '120px',
+    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+// function checkTimeSumEquals24(row: List) {
+//   // 获取三个字段的值,转换为数字,如果为空则视为0
+//   const gasTime = row.dailyInjectGasTime || 0
+//   const waterTime = row.dailyInjectWaterTime || 0
+//   const nonProdTime = row.nonProductionTime || 0
+
+//   // 计算总和
+//   const sum = gasTime + waterTime + nonProdTime
+
+//   // 返回是否等于24(允许一定的浮点数误差)
+//   return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+// }
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
+    const capacity = Number(row?.capacity)
+    const dailyGasInjection = Number(row?.dailyGasInjection)
+
+    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
+    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
+    if (capacity && dailyGasInjection) {
+      const ratio = dailyGasInjection / capacity
+
+      // 3. 判断计算结果是否大于 1.2 (即 120%)
+      if (ratio > 1.2) {
+        return {
+          color: 'red',
+          fontWeight: 'bold'
+        }
+      }
+    }
+  }
+  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
+  // if (timeFields.includes(column.property)) {
+  //   if (!checkTimeSumEquals24(row)) {
+  //     return {
+  //       color: 'orange',
+  //       fontWeight: 'bold'
+  //     }
+  //   }
+  // }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRhDailyReportApi.getIotRhDailyReportPage(query.value)
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ]
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="flex flex-col p-4 gap-2 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :deptId="deptId" :topId="157" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                class="!w-220px"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-rh-daily-report:update']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.auditStatus === 10"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-rh-daily-report:update']"
+                      >
+                        审批
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <rh-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 102 - 581
src/views/pms/iotrhdailyreport/fill.vue

@@ -1,477 +1,63 @@
 <script lang="ts" setup>
 import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
 import rhForm from './rh-form.vue'
+import RhTable from './rh-table.vue'
 
-interface List {
-  createTime: number // 日期
-  deptName: string // 施工队伍
-  contractName: string // 项目
-  taskName: string // 任务
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  relocationDays: number // 搬迁安装天数
-  designInjection: string // 设计注气量(万方)
-  transitTime: number // 运行时效
-  dailyGasInjection: number // 注气量(万方)
-  dailyWaterInjection: number // 注水量(方)
-  dailyInjectGasTime: number // 注气时间(H)
-  dailyInjectWaterTime: number // 注水时间(H)
-  dailyPowerUsage: number // 日耗电量(度)
-  dailyOilUsage: number // 日耗油量(升)
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string // 其他非生产时间原因
-  nptReason: string // 非生产时间原因
-  constructionStartDate: number // 施工开始日期
-  constructionEndDate: number // 施工结束日期
-  productionStatus: string // 生产动态
-  constructionStatus: string // 施工状态
-  totalGasInjection: number // 注气量(万方)
-  totalWaterInjection: number // 注水量(方)
-  cumulativeCompletion: number // 完工井次
-  capacity: number // 产能(万方)
-  remark: string // 备注
-  auditStatus: number // 审核状态
-  status: number // 状态
-  opinion: string // 审核意见
-  gasElectricityRatio: number // 气电比
-  nonProductionRate: number // 非生产时效
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    fixed: 'left',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    fixed: 'left',
-    'min-width': '120px'
-  },
-  {
-    label: '任务',
-    prop: 'taskName',
-    fixed: 'left',
-    'min-width': '120px'
-  },
-  {
-    label: '施工状态',
-    prop: 'constructionStatus',
-    fixed: 'left',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '搬迁安装天数',
-    prop: 'relocationDays',
-    'min-width': '120px',
-    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
-  },
-  {
-    label: '设计注气量(万方)',
-    prop: 'designInjection',
-    'min-width': '120px',
-    formatter: (row: List) => row.designInjection || '0'
-  },
-  {
-    label: '运行时效',
-    prop: 'transitTime',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const capacity = Number(row?.capacity)
-      const dailyGasInjection = Number(row?.dailyGasInjection)
-
-      if (!capacity || !dailyGasInjection) {
-        return '0.00%'
-      }
-
-      return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '注气量(万方)',
-        prop: 'dailyGasInjection',
-        'min-width': '120px',
-        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
-      },
-      {
-        label: '注水量(方)',
-        prop: 'dailyWaterInjection',
-        'min-width': '120px'
-      },
-      {
-        label: '注气时间(H)',
-        prop: 'dailyInjectGasTime',
-        'min-width': '120px'
-      },
-      {
-        label: '注水时间(H)',
-        prop: 'dailyInjectWaterTime',
-        'min-width': '120px'
-      },
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(L)',
-        prop: 'dailyOilUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '气电比',
-        prop: 'gasElectricityRatio',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  },
-
-  // {
-  //   label: '非生产时间原因',
-  //   prop: 'nptReason',
-  //   'min-width': '120px',
-  //   isTag: true,
-  //   dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
-  // },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '累计',
-    children: [
-      {
-        label: '注气量(万方)',
-        prop: 'totalGasInjection',
-        'min-width': '120px',
-        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
-      },
-      {
-        label: '注水量(方)',
-        prop: 'totalWaterInjection',
-        'min-width': '120px'
-      },
-      {
-        label: '完工井次',
-        prop: 'cumulativeCompletion',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '产能(万方)',
-    prop: 'capacity',
-    'min-width': '120px',
-    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-// function checkTimeSumEquals24(row: List) {
-//   // 获取三个字段的值,转换为数字,如果为空则视为0
-//   const gasTime = row.dailyInjectGasTime || 0
-//   const waterTime = row.dailyInjectWaterTime || 0
-//   const nonProdTime = row.nonProductionTime || 0
-
-//   // 计算总和
-//   const sum = gasTime + waterTime + nonProdTime
-
-//   // 返回是否等于24(允许一定的浮点数误差)
-//   return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
-// }
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'transitTime') {
-    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
-    const capacity = Number(row?.capacity)
-    const dailyGasInjection = Number(row?.dailyGasInjection)
-
-    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
-    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
-    if (capacity && dailyGasInjection) {
-      const ratio = dailyGasInjection / capacity
-
-      // 3. 判断计算结果是否大于 1.2 (即 120%)
-      if (ratio > 1.2) {
-        return {
-          color: 'red',
-          fontWeight: 'bold'
-        }
-      }
-    }
-  }
-
-  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
-  // if (timeFields.includes(column.property)) {
-  //   if (!checkTimeSumEquals24(row)) {
-  //     return {
-  //       color: 'orange',
-  //       fontWeight: 'bold'
-  //     }
-  //   }
-  // }
+defineOptions({ name: 'IotRhDailyReportFill' })
 
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
-
-const id = useUserStore().getUser.deptId ?? 157
+const id = useUserStore().getUser.deptId
 
 const deptId = id
 
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
+  createTime?: string[]
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ]
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
 }
 
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
+const query = ref<Query>({ ...initQuery })
 
-const loading = ref(false)
-
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRhDailyReportApi.getIotRhDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -481,25 +67,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ]
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -528,147 +106,90 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :top-id="157" :deptId="deptId" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="157" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+
+    <rh-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-rh-daily-report:query']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-                class="!w-220px"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-rh-daily-report:query']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.status === 0"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-rh-daily-report:create']"
-                      >
-                        编辑
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <rh-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.status === 0"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-rh-daily-report:create']"
+        >
+          编辑
+        </el-button>
+      </template>
+    </rh-table>
   </div>
+  <rh-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.el-input-number) {
-  width: 100%;
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 674 - 0
src/views/pms/iotrhdailyreport/fill1.vue

@@ -0,0 +1,674 @@
+<script lang="ts" setup>
+import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import rhForm from './rh-form.vue'
+
+interface List {
+  createTime: number // 日期
+  deptName: string // 施工队伍
+  contractName: string // 项目
+  taskName: string // 任务
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  relocationDays: number // 搬迁安装天数
+  designInjection: string // 设计注气量(万方)
+  transitTime: number // 运行时效
+  dailyGasInjection: number // 注气量(万方)
+  dailyWaterInjection: number // 注水量(方)
+  dailyInjectGasTime: number // 注气时间(H)
+  dailyInjectWaterTime: number // 注水时间(H)
+  dailyPowerUsage: number // 日耗电量(度)
+  dailyOilUsage: number // 日耗油量(升)
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string // 其他非生产时间原因
+  nptReason: string // 非生产时间原因
+  constructionStartDate: number // 施工开始日期
+  constructionEndDate: number // 施工结束日期
+  productionStatus: string // 生产动态
+  constructionStatus: string // 施工状态
+  totalGasInjection: number // 注气量(万方)
+  totalWaterInjection: number // 注水量(方)
+  cumulativeCompletion: number // 完工井次
+  capacity: number // 产能(万方)
+  remark: string // 备注
+  auditStatus: number // 审核状态
+  status: number // 状态
+  opinion: string // 审核意见
+  gasElectricityRatio: number // 气电比
+  nonProductionRate: number // 非生产时效
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    fixed: 'left',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    fixed: 'left',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '搬迁安装天数',
+    prop: 'relocationDays',
+    'min-width': '120px',
+    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
+  },
+  {
+    label: '设计注气量(万方)',
+    prop: 'designInjection',
+    'min-width': '120px',
+    formatter: (row: List) => row.designInjection || '0'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const capacity = Number(row?.capacity)
+      const dailyGasInjection = Number(row?.dailyGasInjection)
+
+      if (!capacity || !dailyGasInjection) {
+        return '0.00%'
+      }
+
+      return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'dailyGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'dailyWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '注气时间(H)',
+        prop: 'dailyInjectGasTime',
+        'min-width': '120px'
+      },
+      {
+        label: '注水时间(H)',
+        prop: 'dailyInjectWaterTime',
+        'min-width': '120px'
+      },
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(L)',
+        prop: 'dailyOilUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '气电比',
+        prop: 'gasElectricityRatio',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  },
+
+  // {
+  //   label: '非生产时间原因',
+  //   prop: 'nptReason',
+  //   'min-width': '120px',
+  //   isTag: true,
+  //   dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
+  // },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '累计',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'totalGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'totalWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '完工井次',
+        prop: 'cumulativeCompletion',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '产能(万方)',
+    prop: 'capacity',
+    'min-width': '120px',
+    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+// function checkTimeSumEquals24(row: List) {
+//   // 获取三个字段的值,转换为数字,如果为空则视为0
+//   const gasTime = row.dailyInjectGasTime || 0
+//   const waterTime = row.dailyInjectWaterTime || 0
+//   const nonProdTime = row.nonProductionTime || 0
+
+//   // 计算总和
+//   const sum = gasTime + waterTime + nonProdTime
+
+//   // 返回是否等于24(允许一定的浮点数误差)
+//   return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+// }
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
+    const capacity = Number(row?.capacity)
+    const dailyGasInjection = Number(row?.dailyGasInjection)
+
+    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
+    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
+    if (capacity && dailyGasInjection) {
+      const ratio = dailyGasInjection / capacity
+
+      // 3. 判断计算结果是否大于 1.2 (即 120%)
+      if (ratio > 1.2) {
+        return {
+          color: 'red',
+          fontWeight: 'bold'
+        }
+      }
+    }
+  }
+
+  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
+  // if (timeFields.includes(column.property)) {
+  //   if (!checkTimeSumEquals24(row)) {
+  //     return {
+  //       color: 'orange',
+  //       fontWeight: 'bold'
+  //     }
+  //   }
+  // }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId ?? 157
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRhDailyReportApi.getIotRhDailyReportPage(query.value)
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ]
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :top-id="157" :deptId="deptId" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+                class="!w-220px"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-rh-daily-report:query']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.status === 0"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-rh-daily-report:create']"
+                      >
+                        编辑
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <rh-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 306 - 1272
src/views/pms/iotrhdailyreport/index.vue

@@ -1,1339 +1,373 @@
-<template>
-  <el-row :gutter="20" class="h-full">
-    <el-col :span="4" :xs="24">
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
-        <DeptTreeSelect
-          :deptId="rootDeptId"
-          :top-id="157"
-          v-model="queryParams.deptId"
-          @node-click="handleDeptNodeClick"
-        />
-      </div>
-
-      <!-- <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" /> -->
-
-      <!-- </ContentWrap> -->
-    </el-col>
-    <el-col :span="20" :xs="24">
-      <ContentWrap>
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
-        >
-          <el-form-item label="项目" prop="contractName">
-            <el-input
-              v-model="queryParams.contractName"
-              placeholder="请输入项目"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item label="任务" prop="taskName">
-            <el-input
-              v-model="queryParams.taskName"
-              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')]"
-              :shortcuts="rangeShortcuts"
-              class="!w-220px"
-            />
-          </el-form-item>
-          <el-form-item label="非生产时效" prop="nonProductFlag">
-            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
-          </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-rh-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-rh-daily-report:export']"
-            >
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
-      </ContentWrap>
-
-      <!-- 数据统计区域 -->
-      <ContentWrap class="mb-15px">
-        <div class="statistics-container">
-          <div class="stat-item" :style="{ color: totalColor }">
-            <span>总数:</span>
-            <span>{{ statistics.total || '-' }}</span>
-          </div>
-          <div class="stat-item" :style="{ color: filledColor }">
-            <span>已填报:</span>
-            <span>{{ statistics.filled || '-' }}</span>
-          </div>
-          <div class="stat-item" :style="{ color: unFilledColor }">
-            <span>未填报:</span>
-            <span
-              class="unfilled-link"
-              @click="openUnfilledDialog"
-              :class="{ disabled: !queryParams.createTime || queryParams.createTime.length === 0 }"
-            >
-              {{ statistics.unFilled || '-' }}
-            </span>
-          </div>
-          <div class="stat-item" :style="{ color: '#0099CC' }">
-            <span>累计注水量(方):</span>
-            <span>{{ statistics.totalWaterInjection || '-' }}</span>
-          </div>
-          <div class="stat-item" :style="{ color: '#FF9900' }">
-            <span>累计注气量(万方):</span>
-            <span>{{ statistics.totalGasInjection || '-' }}</span>
-          </div>
-        </div>
-      </ContentWrap>
-
-      <ContentWrap class="mb-15px">
-        <div class="color-legend">
-          <div class="legend-item">
-            <span class="color-indicator red"></span>
-            <span
-              >运行时效=当日注气量/产能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过120%红色预警</span
-            >
-          </div>
-          <!-- <div class="legend-item">
-            <span class="color-indicator orange"></span>
-            <span
-              >当日注气时间+当日注水时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
-            >
-          </div> -->
-        </div>
-      </ContentWrap>
-
-      <!-- 列表 -->
-      <ContentWrap ref="tableContainerRef">
-        <div class="table-container">
-          <el-table
-            ref="tableRef"
-            v-loading="loading"
-            :data="list"
-            :stripe="true"
-            :style="{ width: '100%' }"
-            max-height="600"
-            :cell-style="cellStyle"
-            show-overflow-tooltip
-            border
-          >
-            <el-table-column
-              :label="t('iotDevice.serial')"
-              width="56px"
-              align="center"
-              fixed="left"
-            >
-              <template #default="scope">
-                {{ scope.$index + 1 }}
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="日期"
-              align="center"
-              prop="createTime"
-              :formatter="dateFormatter2"
-              :min-width="columnWidths.createTime.width"
-              resizable
-              fixed="left"
-            />
-            <el-table-column
-              label="施工队伍"
-              align="center"
-              prop="deptName"
-              :min-width="columnWidths.deptName.width"
-              resizable
-              fixed="left"
-            />
-            <el-table-column
-              label="任务"
-              align="center"
-              prop="taskName"
-              :min-width="columnWidths.taskName.width"
-              resizable
-              fixed="left"
-            />
-            <!-- <el-table-column label="施工状态" align="center" prop="constructionStatus" /> -->
-            <el-table-column
-              :label="t('project.status')"
-              align="center"
-              prop="constructionStatus"
-              :min-width="columnWidths.constructionStatus.width"
-              resizable
-              fixed="left"
-            >
-              <template #default="scope">
-                <dict-tag
-                  :type="DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE"
-                  :value="scope.row.constructionStatus"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="搬迁安装天数"
-              align="center"
-              prop="relocationDays"
-              :formatter="relocationDaysFormatter"
-              :min-width="columnWidths.relocationDays.width"
-              resizable
-            />
-            <el-table-column
-              label="设计注气量(万方)"
-              align="center"
-              prop="designInjection"
-              :min-width="columnWidths.designInjection.width"
-              resizable
-            />
-            <el-table-column
-              label="运行时效"
-              align="center"
-              prop="transitTime"
-              :formatter="percentageFormatter"
-              :min-width="columnWidths.transitTime.width"
-              resizable
-            />
-            <el-table-column label="当日" align="center">
-              <el-table-column
-                label="注气量(万方)"
-                align="center"
-                prop="dailyGasInjection"
-                :formatter="gasInjectionFormatter"
-                :min-width="columnWidths.dailyGasInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="注水量(方)"
-                align="center"
-                prop="dailyWaterInjection"
-                :min-width="columnWidths.dailyWaterInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="注气时间(H)"
-                align="center"
-                prop="dailyInjectGasTime"
-                :min-width="columnWidths.dailyInjectGasTime.width"
-                resizable
-              />
-              <el-table-column
-                label="注水时间(H)"
-                align="center"
-                prop="dailyInjectWaterTime"
-                :min-width="columnWidths.dailyInjectWaterTime.width"
-                resizable
-              />
-              <el-table-column
-                label="用电量(kWh)"
-                align="center"
-                prop="dailyPowerUsage"
-                :min-width="columnWidths.dailyPowerUsage.width"
-                resizable
-              />
-              <el-table-column
-                label="油耗(L)"
-                align="center"
-                prop="dailyOilUsage"
-                :min-width="columnWidths.dailyOilUsage.width"
-                resizable
-              />
-              <el-table-column
-                label="气电比"
-                align="center"
-                prop="gasElectricityRatio"
-                :min-width="columnWidths.gasElectricityRatio.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column
-              label="非生产时效"
-              align="center"
-              prop="nonProductionRate"
-              :formatter="nonProductionRateFormatter"
-              :min-width="columnWidths.nonProductionRate.width"
-              resizable
-            />
-            <el-table-column label="非生产时间" align="center">
-              <el-table-column
-                label="工程质量"
-                align="center"
-                prop="accidentTime"
-                :min-width="columnWidths.accidentTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备故障"
-                align="center"
-                prop="repairTime"
-                :min-width="columnWidths.repairTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备保养"
-                align="center"
-                prop="selfStopTime"
-                :min-width="columnWidths.selfStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="技术受限"
-                align="center"
-                prop="complexityTime"
-                :min-width="columnWidths.complexityTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产配合"
-                align="center"
-                prop="relocationTime"
-                :min-width="columnWidths.relocationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产组织"
-                align="center"
-                prop="rectificationTime"
-                :min-width="columnWidths.rectificationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="不可抗力"
-                align="center"
-                prop="waitingStopTime"
-                :min-width="columnWidths.waitingStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="待命"
-                align="center"
-                prop="winterBreakTime"
-                :min-width="columnWidths.winterBreakTime.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方设计"
-                align="center"
-                prop="partyaDesign"
-                :min-width="columnWidths.partyaDesign.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方准备"
-                align="center"
-                prop="partyaPrepare"
-                :min-width="columnWidths.partyaPrepare.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方资源"
-                align="center"
-                prop="partyaResource"
-                :min-width="columnWidths.partyaResource.width"
-                resizable
-              />
-              <el-table-column
-                label="其它非生产时间"
-                align="center"
-                prop="otherNptTime"
-                :min-width="columnWidths.otherNptTime.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column
-              label="其他非生产时间原因"
-              align="center"
-              prop="otherNptReason"
-              :min-width="columnWidths.otherNptReason.width"
-              resizable
-            />
-
-            <!-- <el-table-column
-              :label="t('project.nptReason')"
-              align="center"
-              prop="nptReason"
-              :min-width="columnWidths.nptReason.width"
-              resizable
-            >
-              <template #default="scope">
-                <dict-tag :type="DICT_TYPE.PMS_PROJECT_NPT_REASON" :value="scope.row.nptReason" />
-              </template>
-            </el-table-column> -->
-            <!-- <el-table-column
-              label="施工开始日期"
-              align="center"
-              prop="constructionStartDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionStartDate.width"
-              resizable
-            />
-            <el-table-column
-              label="施工结束日期"
-              align="center"
-              prop="constructionEndDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionEndDate.width"
-              resizable
-            /> -->
-            <el-table-column
-              label="生产动态"
-              align="center"
-              :min-width="columnWidths.productionStatus.width"
-              prop="productionStatus"
-              resizable
-            />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              class-name="contract-name-column"
-              :min-width="columnWidths.contractName.width"
-              resizable
-            />
-            <el-table-column label="井累计" align="center">
-              <el-table-column
-                label="注气量(万方)"
-                align="center"
-                prop="wellTotalGasInjection"
-                :formatter="gasInjectionFormatter"
-                :min-width="columnWidths.totalGasInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="注水量(方)"
-                align="center"
-                prop="wellTotalWaterInjection"
-                :min-width="columnWidths.totalWaterInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="用电量(MWh)"
-                align="center"
-                prop="wellTotalPower"
-                :formatter="gasInjectionFormatter"
-                :min-width="columnWidths.yearTotalPower.width"
-                resizable
-              />
-              <el-table-column
-                label="油耗(L)"
-                align="center"
-                prop="wellTotalFuel"
-                :min-width="columnWidths.cumulativeCompletion.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column label="年累计" align="center">
-              <el-table-column
-                label="注气量(万方)"
-                align="center"
-                prop="yearTotalGasInjection"
-                :formatter="gasInjectionFormatter"
-                :min-width="columnWidths.totalGasInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="注水量(方)"
-                align="center"
-                prop="yearTotalWaterInjection"
-                :min-width="columnWidths.totalWaterInjection.width"
-                resizable
-              />
-              <el-table-column
-                label="用电量(MWh)"
-                align="center"
-                prop="yearTotalPower"
-                :formatter="gasInjectionFormatter"
-                :min-width="columnWidths.yearTotalPower.width"
-                resizable
-              />
-              <el-table-column
-                label="油耗(L)"
-                align="center"
-                prop="yearTotalFuel"
-                :min-width="columnWidths.cumulativeCompletion.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column
-              label="产能(万方)"
-              align="center"
-              prop="capacity"
-              :formatter="gasInjectionFormatter"
-              :min-width="columnWidths.capacity.width"
-              resizable
-            />
-
-            <!-- <el-table-column label="操作" align="center" fixed="right">
-              <template #default="scope">
-                <el-button
-                  link
-                  type="primary"
-                  @click="openForm('update', scope.row.id, scope.row)"
-                  v-hasPermi="['pms:iot-rh-daily-report:update']"
-                >
-                  编辑
-                </el-button>
-                <el-button
-                  link
-                  type="danger"
-                  @click="handleDelete(scope.row.id)"
-                  v-hasPermi="['pms:iot-rh-daily-report:delete']"
-                >
-                  删除
-                </el-button>
-              </template>
-            </el-table-column> -->
-          </el-table>
-        </div>
-        <!-- 分页 -->
-        <Pagination
-          :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </ContentWrap>
-
-      <!-- 表单弹窗:添加/修改 -->
-      <IotRhDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData" />
-
-      <UnfilledReportDialog
-        ref="unfilledDialogRef"
-        :query-params="queryParams"
-        @close="handleUnfilledDialogClose"
-      />
-    </el-col>
-  </el-row>
-</template>
-
 <script setup lang="ts">
-import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
+import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { useUserStore } from '@/store/modules/user'
 import download from '@/utils/download'
-import { IotRhDailyReportApi, IotRhDailyReportVO } from '@/api/pms/iotrhdailyreport'
-import IotRhDailyReportForm from './IotRhDailyReportForm.vue'
-import UnfilledReportDialog from './UnfilledReportDialog.vue'
-import { DICT_TYPE } from '@/utils/dict'
-import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import UnfilledReportDialog from './UnfilledReportDialog.vue'
+import CountTo from '@/components/count-to1.vue'
 import dayjs from 'dayjs'
+import RhTable from './rh-table.vue'
 
-import { useUserStore } from '@/store/modules/user'
+defineOptions({ name: 'IotRhDailyReport' })
 
-dayjs.extend(quarterOfYear)
+const route = useRoute()
 
-/** 瑞恒日报 列表 */
-defineOptions({ name: 'IotRhDailyReport' })
+const message = useMessage()
 
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
+const id = useUserStore().getUser.deptId
 
-// 添加 selectedRowData 响应式变量
-const selectedRowData = ref<Record<string, any> | null>(null)
+const deptId = id
+
+interface Query {
+  deptId?: number
+  contractName?: string
+  taskName?: string
+  createTime?: string[]
+  pageNo: number
+  pageSize: number
+  nonProductFlag?: string
+}
 
-const loading = ref(true) // 列表的加载中
-const list = ref<IotRhDailyReportVO[]>([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-let queryParams = reactive({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
-  deptId: useUserStore().getUser.deptId,
-  contractName: undefined,
-  projectId: undefined,
-  taskName: undefined,
-  taskId: undefined,
-  projectClassification: undefined,
-  relocationDays: undefined,
-  transitTime: [],
-  dailyGasInjection: undefined,
-  dailyWaterInjection: undefined,
-  dailyPowerUsage: undefined,
-  dailyInjectGasTime: [],
-  dailyInjectWaterTime: [],
-  nonProductionTime: [],
-  nptReason: undefined,
-  constructionStartDate: [],
-  constructionEndDate: [],
-  productionStatus: undefined,
-  nextPlan: undefined,
-  constructionStatus: undefined,
-  personnel: undefined,
-  totalGasInjection: undefined,
-  totalWaterInjection: undefined,
-  cumulativeCompletion: undefined,
-  extProperty: undefined,
-  sort: undefined,
-  remark: undefined,
-  status: undefined,
-  processInstanceId: undefined,
-  auditStatus: undefined,
-  createTime: [
-    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  deptId: route.query.deptId ? Number(route.query.deptId) : id,
+  createTime: route.query.createTime
+    ? (route.query.createTime as string[])
+    : [...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))],
+  nonProductFlag: route.query.nonProductFlag ? (route.query.nonProductFlag as string) : undefined
+}
+
+const query = ref<Query>({ ...initQuery })
+
+const totalWorkKeys: [string, string, string, string, number][] = [
+  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
+  [
+    'alreadyReported',
+    '个',
+    '已填报',
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
   ],
-  nonProductFlag: 'N'
+  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose', 0],
+  ['totalGasInjection', '万方', '累计注气量', 'i-material-symbols:cloud-outline text-sky', 2],
+  [
+    'totalWaterInjection',
+    '万方',
+    '累计注水量',
+    'i-material-symbols:water-drop-outline-rounded text-sky',
+    2
+  ]
+  // [
+  //   'utilizationRate',
+  //   '%',
+  //   '设备利用率',
+  //   'i-material-symbols:check-circle-outline-rounded text-emerald',
+  //   0
+  // ],
+  // [
+  //   'totalPowerConsumption',
+  //   'MWh',
+  //   '累计用电量',
+  //   'i-material-symbols:electric-bolt-outline-rounded text-sky',
+  //   2
+  // ],
+  // [
+  //   'totalFuelConsumption',
+  //   '升',
+  //   '累计油耗',
+  //   'i-material-symbols:directions-car-outline-rounded text-sky',
+  //   2
+  // ],
+]
+
+const totalWork = ref({
+  totalCount: 0,
+  alreadyReported: 0,
+  notReported: 0,
+  totalFuelConsumption: 0,
+  totalPowerConsumption: 0,
+  totalWaterInjection: 0,
+  totalGasInjection: 0,
+  utilizationRate: 0
 })
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-// 添加弹窗引用
-const unfilledDialogRef = ref()
-
-const rootDeptId = ref(useUserStore().getUser.deptId)
 
-// 新增统计相关变量
-const statistics = ref({
-  total: '-',
-  filled: '-',
-  unFilled: '-',
-  totalWaterInjection: '-', // 新增累计注水量
-  totalGasInjection: '-' // 新增累计注气量
-})
+const totalLoading = ref(false)
 
-const totalColor = '#00DD99'
-const filledColor = '#0055BB'
-const unFilledColor = '#FF5500'
+const getTotal = useDebounceFn(async function () {
+  totalLoading.value = true
 
-// 表格引用
-const tableRef = ref()
-// 表格容器引用
-const tableContainerRef = ref()
-
-// 工作量统计相关变量
-const workloadStatistics = ref({
-  totalWaterInjection: '-',
-  totalGasInjection: '-'
-})
-
-// 列宽度配置
-const columnWidths = ref<
-  Record<
-    string,
-    { label: string; prop: string; width: string; fn?: (row: IotRhDailyReportVO) => string }
-  >
->({
-  createTime: {
-    label: '日期',
-    prop: 'createTime',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => dateFormatter2(null, null, row.createTime)
-  },
-  deptName: {
-    label: '施工队伍',
-    prop: 'deptName',
-    width: '120px'
-  },
-  contractName: {
-    label: '项目',
-    prop: 'contractName',
-    width: '120px'
-  },
-  taskName: {
-    label: '任务',
-    prop: 'taskName',
-    width: '120px'
-  },
-  constructionStatus: {
-    label: '施工状态',
-    prop: 'constructionStatus',
-    width: '120px'
-  },
-  relocationDays: {
-    label: '搬迁安装天数',
-    prop: 'relocationDays',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => relocationDaysFormatter(null, null, row.relocationDays, null)
-  },
-  designInjection: {
-    label: '设计注气量(万方)',
-    prop: 'designInjection',
-    width: '120px'
-  },
-  transitTime: {
-    label: '运行时效',
-    prop: 'transitTime',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => percentageFormatter(null, null, row.transitTime, null)
-  },
-  dailyGasInjection: {
-    label: '注气量(万方)',
-    prop: 'dailyGasInjection',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.dailyGasInjection, null)
-  },
-  dailyWaterInjection: {
-    label: '注水量(方)',
-    prop: 'dailyWaterInjection',
-    width: '120px'
-  },
-  dailyInjectGasTime: {
-    label: '注气时间(H)',
-    prop: 'dailyInjectGasTime',
-    width: '120px'
-  },
-  dailyInjectWaterTime: {
-    label: '注水时间(H)',
-    prop: 'dailyInjectWaterTime',
-    width: '120px'
-  },
-  dailyPowerUsage: {
-    label: '用电量(kWh)',
-    prop: 'dailyPowerUsage',
-    width: '120px'
-  },
-  dailyOilUsage: {
-    label: '油耗(L)',
-    prop: 'dailyOilUsage',
-    width: '120px'
-  },
-  gasElectricityRatio: {
-    label: '气电比',
-    prop: 'gasElectricityRatio',
-    width: '120px'
-  },
-  accidentTime: {
-    label: '工程质量',
-    prop: 'accidentTime',
-    width: '120px'
-  },
-  repairTime: {
-    label: '设备故障',
-    prop: 'repairTime',
-    width: '120px'
-  },
-  selfStopTime: {
-    label: '设备保养',
-    prop: 'selfStopTime',
-    width: '120px'
-  },
-  complexityTime: {
-    label: '技术受限',
-    prop: 'complexityTime',
-    width: '120px'
-  },
-  relocationTime: {
-    label: '生产配合',
-    prop: 'relocationTime',
-    width: '120px'
-  },
-  rectificationTime: {
-    label: '生产组织',
-    prop: 'rectificationTime',
-    width: '120px'
-  },
-  waitingStopTime: {
-    label: '不可抗力',
-    prop: 'waitingStopTime',
-    width: '120px'
-  },
-  winterBreakTime: {
-    label: '待命',
-    prop: 'winterBreakTime',
-    width: '120px'
-  },
-  partyaDesign: {
-    label: '甲方设计',
-    prop: 'partyaDesign',
-    width: '120px'
-  },
-  partyaPrepare: {
-    label: '甲方资源',
-    prop: 'partyaPrepare',
-    width: '120px'
-  },
-  partyaResource: {
-    label: '甲方准备',
-    prop: 'partyaResource',
-    width: '120px'
-  },
-  otherNptTime: {
-    label: '其它非生产时间',
-    prop: 'otherNptTime',
-    width: '120px'
-  },
-  otherNptReason: {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    width: '120px'
-  },
-  constructionStartDate: {
-    label: '施工开始日期',
-    prop: 'constructionStartDate',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
-  },
-  constructionEndDate: {
-    label: '施工结束日期',
-    prop: 'constructionEndDate',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
-  },
-  productionStatus: {
-    label: '生产动态',
-    prop: 'productionStatus',
-    width: '120px'
-  },
-  totalGasInjection: {
-    label: '注气量(万方)',
-    prop: 'totalGasInjection',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.totalGasInjection, null)
-  },
-  totalWaterInjection: {
-    label: '注水量(方)',
-    prop: 'totalWaterInjection',
-    width: '120px'
-  },
-  yearTotalPower: {
-    label: '用电量(万千瓦时)',
-    prop: 'yearTotalPower',
-    width: '120px'
-  },
-  cumulativeCompletion: {
-    label: '完工井次',
-    prop: 'cumulativeCompletion',
-    width: '120px'
-  },
-  capacity: {
-    label: '产能(万方)',
-    prop: 'capacity',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.capacity, null)
-  },
-  nonProductionRate: {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    width: '120px',
-    fn: (row: IotRhDailyReportVO) => nonProductionRateFormatter(row)
-  }
-})
-
-const nonProductionRateFormatter = (row: any) => {
-  const nonProductionRate = row?.nonProductionRate ?? 0
-
-  return (nonProductionRate * 100).toFixed(2) + '%'
-}
-
-// 计算文本宽度
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = useDebounceFn(() => {
-  if (!tableContainerRef.value?.$el) return
-  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
-    width =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(fn ? fn(v) : v[prop])
-              })
-            ]
-          ) +
-            (label === '施工状态' || label === '非生产时间原因'
-              ? 30
-              : label === '气电比'
-                ? 40
-                : 20),
-          200
-        ]
-      ) + 'px'
-
-    columnWidths.value[prop].width = width
-  })
-}, 1000)
-// 计算列宽度
-
-// 格式化设计井身结构文本
-const formatDesignWellStruct = (text: string | null | undefined) => {
-  if (!text) return '-'
-  // 如果文本长度超过30个字符,显示前30个字符并添加省略号
-  return text.length > 30 ? text.substring(0, 30) + '...' : text
-}
-
-// 百分比格式化函数
-const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
-  const capacity = Number(row?.capacity)
-  const dailyGasInjection = Number(row?.dailyGasInjection)
-
-  if (!capacity || !dailyGasInjection) {
-    return '0.00%'
-  }
-
-  return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
-
-  // if (cellValue === null || cellValue === undefined) return ''
-  // // 将小数转换为百分比,保留两位小数
-  // return `${(parseFloat(cellValue) * 100).toFixed(2)}%`
-}
-
-// 添加打开未填报弹窗的方法
-const openUnfilledDialog = () => {
-  // 检查是否选择了创建时间
-  if (!queryParams.createTime || queryParams.createTime.length === 0) {
-    message.warning('请先选择创建时间范围')
-    return
-  }
-
-  // 打开弹窗
-  unfilledDialogRef.value.open()
-}
-
-// 弹窗关闭回调
-const handleUnfilledDialogClose = () => {
-  // 可以在这里处理弹窗关闭后的逻辑
-  console.log('未填报弹窗已关闭')
-}
-
-// 新增获取统计数据的方法
-const getStatistics = async () => {
-  // 重置统计数据
-  statistics.value = {
-    total: '-',
-    filled: '-',
-    unFilled: '-'
-  }
-
-  // 如果没有选择时间范围,不调用接口
-  if (!queryParams.createTime || queryParams.createTime.length === 0) {
-    return
-  }
+  const { pageNo, pageSize, ...other } = query.value
 
   try {
-    const res = await IotRhDailyReportApi.rhDailyReportStatistics({
-      createTime: queryParams.createTime
-    })
+    let res1: any[]
+    if (query.value.createTime && query.value.createTime.length === 2) {
+      res1 = await IotRhDailyReportApi.rhDailyReportStatistics({
+        createTime: query.value.createTime,
+        deptId: query.value.deptId
+      })
+
+      totalWork.value.totalCount = res1[0].count
+      totalWork.value.alreadyReported = res1[1].count
+      totalWork.value.notReported = res1[2].count
+    }
 
-    // 处理统计数据
-    const statsMap = {}
-    res.forEach((item) => {
-      statsMap[item.groupName] = item.count
-    })
+    const res2 = await IotRhDailyReportApi.totalWorkload(other)
 
-    statistics.value = {
-      total: statsMap['总数'] || '-',
-      filled: statsMap['已填报'] || '-',
-      unFilled: statsMap['未填报'] || '-'
+    totalWork.value = {
+      ...totalWork.value,
+      ...res2,
+      totalPowerConsumption: (res2.totalPowerConsumption || 0) / 1000,
+      totalWaterInjection: (res2.totalWaterInjection || 0) / 10000,
+      totalGasInjection: (res2.totalGasInjection || 0) / 10000,
+      totalFuelConsumption: res2.totalFuelConsumption || 0,
+      utilizationRate: Number(((res2.utilizationRate || 0) * 100).toFixed(2))
     }
-  } catch (error) {
-    console.error('获取统计数据失败', error)
+  } finally {
+    totalLoading.value = false
   }
-}
+}, 500)
+
+const list = ref<any[]>([])
+const total = ref(0)
 
-/** 查询列表 */
-const getList = async () => {
+const loading = ref(false)
+
+const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
-    console.log('22 :>> ', 11)
-    const data = await IotRhDailyReportApi.getIotRhDailyReportPage(queryParams)
+    const data = await IotRhDailyReportApi.getIotRhDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-
-    // 获取统计数据
-    await getStatistics()
-
-    // 获取工作量统计数据
-    await getWorkloadStatistics()
-
-    // 获取数据后计算列宽
-    nextTick(() => {
-      calculateColumnWidths()
-    })
   } finally {
     loading.value = false
   }
-}
-
-// 搬迁安装天数格式化函数
-const relocationDaysFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
-  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+})
 
-  const value = parseFloat(cellValue)
-  // 如果值为负数,显示0,否则显示原值
-  return value < 0 ? '0' : String(value)
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
 }
 
-// 注气量格式化函数(单位转换:方 -> 万方)
-const gasInjectionFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
-  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
-  // 将方转换为万方,保留两位小数
-  const value = parseFloat(cellValue)
-  return (value / 10000).toFixed(2)
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+  getTotal()
 }
 
-// 检查三个时间字段之和是否为24
-const checkTimeSumEquals24 = (row: any) => {
-  // 获取三个字段的值,转换为数字,如果为空则视为0
-  const gasTime = parseFloat(row.dailyInjectGasTime) || 0
-  const waterTime = parseFloat(row.dailyInjectWaterTime) || 0
-  const nonProdTime = parseFloat(row.nonProductionTime) || 0
-
-  // 计算总和
-  const sum = gasTime + waterTime + nonProdTime
-
-  // 返回是否等于24(允许一定的浮点数误差)
-  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
-}
-
-// 单元格样式函数
-const cellStyle = ({
-  row,
-  column,
-  rowIndex,
-  columnIndex
-}: {
-  row: any
-  column: any
-  rowIndex: number
-  columnIndex: number
-}) => {
-  // 只针对 transitTime 列进行处理
-  if (column.property === 'transitTime') {
-    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
-    const capacity = Number(row?.capacity)
-    const dailyGasInjection = Number(row?.dailyGasInjection)
-
-    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
-    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
-    if (capacity && dailyGasInjection) {
-      const ratio = dailyGasInjection / capacity
-
-      // 3. 判断计算结果是否大于 1.2 (即 120%)
-      if (ratio > 1.2) {
-        return {
-          color: 'red',
-          fontWeight: 'bold'
-        }
-      }
-    }
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
   }
-
-  // 处理三个时间字段:当日注气时间、当日注水时间、非生产时间
-  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
-  // if (timeFields.includes(column.property)) {
-  //   // 检查三个时间字段之和是否不等于24
-  //   if (!checkTimeSumEquals24(row)) {
-  //     return {
-  //       color: 'orange',
-  //       fontWeight: 'bold'
-  //     }
-  //   }
-  // }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
+  loadList()
+  getTotal()
 }
 
-// 获取工作量统计数据的方法
-const getWorkloadStatistics = async () => {
-  // 重置工作量统计数据
-  statistics.value.totalWaterInjection = '-'
-  statistics.value.totalGasInjection = '-'
+function resetQuery() {
+  query.value = { ...initQuery }
 
-  try {
-    const res = await IotRhDailyReportApi.totalWorkload(queryParams)
-
-    // 处理工作量统计数据
-    if (res) {
-      // 累计注水量直接显示,单位:方
-      statistics.value.totalWaterInjection = res.totalWaterInjection || '-'
-
-      // 累计注气量需要转换:方 -> 万方 (除以10000)
-      if (res.totalGasInjection) {
-        const gasInjection = parseFloat(res.totalGasInjection)
-        statistics.value.totalGasInjection = (gasInjection / 10000).toFixed(2)
-      } else {
-        statistics.value.totalGasInjection = '-'
-      }
-    }
-  } catch (error) {
-    console.error('获取工作量统计数据失败', error)
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-const route = useRoute()
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  queryParams.deptId = useUserStore().getUser.deptId
-  // 重置后需要重新获取统计数据
-  getStatistics()
-  // 重新获取工作量统计数据
-  getWorkloadStatistics()
   handleQuery()
 }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number, row?: any) => {
-  // 保存当前行数据
-  if (row) {
-    selectedRowData.value = {
-      deptName: row.deptName,
-      contractName: row.contractName,
-      taskName: row.taskName,
-      relocationDays: row.relocationDays
-    }
-  } else {
-    selectedRowData.value = null
-  }
-
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await IotRhDailyReportApi.deleteIotRhDailyReport(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-// 响应式变量存储选中的部门
-const selectedDept = ref<{ id: number; name: string }>()
-/** 处理部门被点击 */
-const handleDeptNodeClick = async (row) => {
-  // 记录选中的部门信息
-  selectedDept.value = { id: row.id, name: row.name }
-  // queryParams.deptId = row.id
-  await getList()
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await IotRhDailyReportApi.exportIotRhDailyReport(queryParams)
-    download.excel(data, '瑞恒日报.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-// 声明 ResizeObserver 实例
-let resizeObserver: ResizeObserver | null = null
-
-/** 初始化 **/
-onMounted(() => {
-  if (Object.keys(route.query).length > 0) {
-    nextTick(() => {
-      queryParams.deptId = Number(route.query.deptId) as any
-      queryParams.createTime = route.query.createTime as string[]
-      queryParams.nonProductFlag = route.query.nonProductFlag as string
-      handleQuery()
-    })
-  } else 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,
+  [
+    () => query.value.deptId,
+    () => query.value.contractName,
+    () => query.value.taskName,
+    () => query.value.createTime,
+    () => query.value.nonProductFlag
+  ],
   () => {
-    nextTick(() => calculateColumnWidths())
+    handleQuery()
   },
-  { deep: true }
+  { immediate: true }
 )
-</script>
-
-<style scoped>
-/* 表格容器样式,确保水平滚动 */
-.table-container {
-  width: 100%;
-  overflow-x: auto;
-}
-
-/* 确保表格单元格内容不换行 */
-
-/* :deep(.el-table .cell) {
-  white-space: nowrap;
-} */
 
-/* 确保表格列标题不换行 */
+// const selectedRowData = ref<any>(null)
 
-/* :deep(.el-table th > .cell) {
-  white-space: nowrap;
-} */
+// const formRef = ref()
 
-/* 调整表格最小宽度,确保内容完全显示 */
-:deep(.el-table) {
-  min-width: 100%;
-}
-
-/* 强制显示所有内容,防止省略号 */
-
-/* :deep(.el-table td.el-table__cell),
-:deep(.el-table th.el-table__cell) {
-  overflow: visible !important;
-} */
-
-/* :deep(.el-table .cell) {
-  overflow: visible !important;
-  text-overflow: clip !important;
-} */
+// const openForm = (type: string, id?: number, row?: any) => {
+//   if (row) {
+//     selectedRowData.value = {
+//       deptName: row.deptName,
+//       contractName: row.contractName,
+//       taskName: row.taskName,
+//       relocationDays: row.relocationDays
+//     }
+//   } else {
+//     selectedRowData.value = null
+//   }
 
-/* :deep(.contract-name-column .cell) {
-  overflow: hidden !important;
-  text-overflow: ellipsis !important;
-  white-space: nowrap !important;
-} */
+//   formRef.value.open(type, id)
+// }
 
-/* 颜色说明区域样式 */
-.color-legend {
-  display: flex;
-  padding: 12px 16px;
-  background-color: #f8f9fa;
-  border-left: 4px solid #e6f7ff;
-  border-radius: 4px;
-  flex-direction: column;
-  gap: 8px;
-}
-
-.legend-item {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  font-size: 14px;
-}
+const exportLoading = ref(false)
 
-.color-indicator {
-  display: inline-block;
-  width: 12px;
-  height: 12px;
-  border-radius: 50%;
-}
+async function handleExport() {
+  try {
+    await message.exportConfirm()
 
-.color-indicator.red {
-  background-color: red;
-}
+    exportLoading.value = true
+    const res = await IotRhDailyReportApi.exportIotRhDailyReport(query.value)
 
-.color-indicator.orange {
-  background-color: orange;
+    download.excel(res, '瑞恒日报.xls')
+  } finally {
+    exportLoading.value = false
+  }
 }
 
-/* 统计区域未填报链接样式 */
-.unfilled-link {
-  color: #f50;
-  text-decoration: underline;
-  cursor: pointer;
-}
+const unfilledDialogRef = ref()
 
-.unfilled-link:hover {
-  color: #f73;
-}
+const openUnfilledDialog = () => {
+  if (!query.value.createTime || query.value.createTime.length === 0) {
+    message.warning('请先选择创建时间范围')
+    return
+  }
 
-.unfilled-link.disabled {
-  color: #ccc;
-  text-decoration: none;
-  cursor: not-allowed;
+  // 打开弹窗
+  unfilledDialogRef.value.open()
 }
-</style>
+</script>
 
-<style>
-/* 设计井身结构 tooltip 样式 - 保留换行符 */
-.design-well-struct-tooltip {
-  max-width: 500px;
-  line-height: 1.5;
-  white-space: pre-line;
-}
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[48px_auto_auto_1fr] gap-x-4 gap-y-3 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-4">
+      <DeptTreeSelect :top-id="157" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="query.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"
+            :shortcuts="rangeShortcuts"
+          />
+        </el-form-item>
+        <el-form-item label="非生产时效" prop="nonProductFlag">
+          <el-switch v-model="query.nonProductFlag" active-value="Y" inactive-value="N" />
+        </el-form-item>
+      </div>
+      <el-form-item>
+        <el-button type="primary" @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-rh-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-rh-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <div class="grid grid-cols-5 gap-3">
+      <div
+        v-for="info in totalWorkKeys"
+        :key="info[0]"
+        class="group relative bg-white dark:bg-[#1d1e1f] rounded-lg shadow-sm hover:shadow-md border border-gray-100 dark:border-gray-700 p-2.5 overflow-hidden transition-all duration-300"
+        :class="{
+          'cursor-pointer hover:border-blue-200 dark:hover:border-blue-800': info[2] === '未填报'
+        }"
+        @click="info[2] === '未填报' ? openUnfilledDialog() : ''"
+      >
+        <div class="relative z-10 flex flex-col h-full justify-center">
+          <span
+            class="text-[11px] text-gray-400 dark:text-gray-500 font-medium tracking-wide mb-0.5"
+          >
+            {{ info[2] }}
+          </span>
+          <div class="flex items-baseline gap-1">
+            <count-to
+              class="text-lg font-bold text-gray-800 dark:text-gray-100 leading-none tracking-tight font-sans"
+              :start-val="0"
+              :end-val="totalWork[info[0]]"
+              :decimals="info[4]"
+            />
+            <span class="text-[10px] text-gray-400 font-normal">{{ info[1] }}</span>
+          </div>
+        </div>
 
-/* 统计区域样式 */
-.statistics-container {
-  display: flex;
-  justify-content: space-around;
-  padding: 10px 0;
-}
+        <div
+          class="absolute -right-2 -bottom-3 opacity-40 dark:opacity-60 transform rotate-[-10deg] transition-transform duration-500 group-hover:scale-110 group-hover:rotate-0"
+        >
+          <div :class="info[3]" class="text-5xl"></div>
+        </div>
+      </div>
+    </div>
+    <div class="p-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow">
+      <el-alert
+        class="h-8!"
+        title="运行时效=当日注气量/产能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过120%红色预警"
+        type="error"
+        show-icon
+        :closable="false"
+      />
+    </div>
+    <rh-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      :show-action="false"
+      is-index
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    />
+  </div>
+
+  <UnfilledReportDialog ref="unfilledDialogRef" :query-params="query" />
+</template>
 
-.stat-item {
-  min-width: 0; /* 防止内容溢出 */
-  font-size: 16px;
-  font-weight: 500;
-  text-align: center;
-  flex: 1;
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
 }
 
-/* 确保统计项内容不换行 */
-.stat-item span {
-  white-space: nowrap;
+:deep(.zm-table) {
+  .el-table__cell {
+    height: 42px;
+  }
 }
 </style>

+ 1339 - 0
src/views/pms/iotrhdailyreport/index1.vue

@@ -0,0 +1,1339 @@
+<template>
+  <el-row :gutter="20" class="h-full">
+    <el-col :span="4" :xs="24">
+      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
+        <DeptTreeSelect
+          :deptId="rootDeptId"
+          :top-id="157"
+          v-model="queryParams.deptId"
+          @node-click="handleDeptNodeClick"
+        />
+      </div>
+
+      <!-- <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" /> -->
+
+      <!-- </ContentWrap> -->
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="80px"
+        >
+          <el-form-item label="项目" prop="contractName">
+            <el-input
+              v-model="queryParams.contractName"
+              placeholder="请输入项目"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input
+              v-model="queryParams.taskName"
+              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')]"
+              :shortcuts="rangeShortcuts"
+              class="!w-220px"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时效" prop="nonProductFlag">
+            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
+          </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-rh-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-rh-daily-report:export']"
+            >
+              <Icon icon="ep:download" class="mr-5px" /> 导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <!-- 数据统计区域 -->
+      <ContentWrap class="mb-15px">
+        <div class="statistics-container">
+          <div class="stat-item" :style="{ color: totalColor }">
+            <span>总数:</span>
+            <span>{{ statistics.total || '-' }}</span>
+          </div>
+          <div class="stat-item" :style="{ color: filledColor }">
+            <span>已填报:</span>
+            <span>{{ statistics.filled || '-' }}</span>
+          </div>
+          <div class="stat-item" :style="{ color: unFilledColor }">
+            <span>未填报:</span>
+            <span
+              class="unfilled-link"
+              @click="openUnfilledDialog"
+              :class="{ disabled: !queryParams.createTime || queryParams.createTime.length === 0 }"
+            >
+              {{ statistics.unFilled || '-' }}
+            </span>
+          </div>
+          <div class="stat-item" :style="{ color: '#0099CC' }">
+            <span>累计注水量(方):</span>
+            <span>{{ statistics.totalWaterInjection || '-' }}</span>
+          </div>
+          <div class="stat-item" :style="{ color: '#FF9900' }">
+            <span>累计注气量(万方):</span>
+            <span>{{ statistics.totalGasInjection || '-' }}</span>
+          </div>
+        </div>
+      </ContentWrap>
+
+      <ContentWrap class="mb-15px">
+        <div class="color-legend">
+          <div class="legend-item">
+            <span class="color-indicator red"></span>
+            <span
+              >运行时效=当日注气量/产能&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过120%红色预警</span
+            >
+          </div>
+          <!-- <div class="legend-item">
+            <span class="color-indicator orange"></span>
+            <span
+              >当日注气时间+当日注水时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
+            >
+          </div> -->
+        </div>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap ref="tableContainerRef">
+        <div class="table-container">
+          <el-table
+            ref="tableRef"
+            v-loading="loading"
+            :data="list"
+            :stripe="true"
+            :style="{ width: '100%' }"
+            max-height="600"
+            :cell-style="cellStyle"
+            show-overflow-tooltip
+            border
+          >
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
+              <template #default="scope">
+                {{ scope.$index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="日期"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter2"
+              :min-width="columnWidths.createTime.width"
+              resizable
+              fixed="left"
+            />
+            <el-table-column
+              label="施工队伍"
+              align="center"
+              prop="deptName"
+              :min-width="columnWidths.deptName.width"
+              resizable
+              fixed="left"
+            />
+            <el-table-column
+              label="任务"
+              align="center"
+              prop="taskName"
+              :min-width="columnWidths.taskName.width"
+              resizable
+              fixed="left"
+            />
+            <!-- <el-table-column label="施工状态" align="center" prop="constructionStatus" /> -->
+            <el-table-column
+              :label="t('project.status')"
+              align="center"
+              prop="constructionStatus"
+              :min-width="columnWidths.constructionStatus.width"
+              resizable
+              fixed="left"
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE"
+                  :value="scope.row.constructionStatus"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="搬迁安装天数"
+              align="center"
+              prop="relocationDays"
+              :formatter="relocationDaysFormatter"
+              :min-width="columnWidths.relocationDays.width"
+              resizable
+            />
+            <el-table-column
+              label="设计注气量(万方)"
+              align="center"
+              prop="designInjection"
+              :min-width="columnWidths.designInjection.width"
+              resizable
+            />
+            <el-table-column
+              label="运行时效"
+              align="center"
+              prop="transitTime"
+              :formatter="percentageFormatter"
+              :min-width="columnWidths.transitTime.width"
+              resizable
+            />
+            <el-table-column label="当日" align="center">
+              <el-table-column
+                label="注气量(万方)"
+                align="center"
+                prop="dailyGasInjection"
+                :formatter="gasInjectionFormatter"
+                :min-width="columnWidths.dailyGasInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="注水量(方)"
+                align="center"
+                prop="dailyWaterInjection"
+                :min-width="columnWidths.dailyWaterInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="注气时间(H)"
+                align="center"
+                prop="dailyInjectGasTime"
+                :min-width="columnWidths.dailyInjectGasTime.width"
+                resizable
+              />
+              <el-table-column
+                label="注水时间(H)"
+                align="center"
+                prop="dailyInjectWaterTime"
+                :min-width="columnWidths.dailyInjectWaterTime.width"
+                resizable
+              />
+              <el-table-column
+                label="用电量(kWh)"
+                align="center"
+                prop="dailyPowerUsage"
+                :min-width="columnWidths.dailyPowerUsage.width"
+                resizable
+              />
+              <el-table-column
+                label="油耗(L)"
+                align="center"
+                prop="dailyOilUsage"
+                :min-width="columnWidths.dailyOilUsage.width"
+                resizable
+              />
+              <el-table-column
+                label="气电比"
+                align="center"
+                prop="gasElectricityRatio"
+                :min-width="columnWidths.gasElectricityRatio.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column
+              label="非生产时效"
+              align="center"
+              prop="nonProductionRate"
+              :formatter="nonProductionRateFormatter"
+              :min-width="columnWidths.nonProductionRate.width"
+              resizable
+            />
+            <el-table-column label="非生产时间" align="center">
+              <el-table-column
+                label="工程质量"
+                align="center"
+                prop="accidentTime"
+                :min-width="columnWidths.accidentTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备故障"
+                align="center"
+                prop="repairTime"
+                :min-width="columnWidths.repairTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备保养"
+                align="center"
+                prop="selfStopTime"
+                :min-width="columnWidths.selfStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="技术受限"
+                align="center"
+                prop="complexityTime"
+                :min-width="columnWidths.complexityTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产配合"
+                align="center"
+                prop="relocationTime"
+                :min-width="columnWidths.relocationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产组织"
+                align="center"
+                prop="rectificationTime"
+                :min-width="columnWidths.rectificationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="不可抗力"
+                align="center"
+                prop="waitingStopTime"
+                :min-width="columnWidths.waitingStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="待命"
+                align="center"
+                prop="winterBreakTime"
+                :min-width="columnWidths.winterBreakTime.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方设计"
+                align="center"
+                prop="partyaDesign"
+                :min-width="columnWidths.partyaDesign.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方准备"
+                align="center"
+                prop="partyaPrepare"
+                :min-width="columnWidths.partyaPrepare.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方资源"
+                align="center"
+                prop="partyaResource"
+                :min-width="columnWidths.partyaResource.width"
+                resizable
+              />
+              <el-table-column
+                label="其它非生产时间"
+                align="center"
+                prop="otherNptTime"
+                :min-width="columnWidths.otherNptTime.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column
+              label="其他非生产时间原因"
+              align="center"
+              prop="otherNptReason"
+              :min-width="columnWidths.otherNptReason.width"
+              resizable
+            />
+
+            <!-- <el-table-column
+              :label="t('project.nptReason')"
+              align="center"
+              prop="nptReason"
+              :min-width="columnWidths.nptReason.width"
+              resizable
+            >
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.PMS_PROJECT_NPT_REASON" :value="scope.row.nptReason" />
+              </template>
+            </el-table-column> -->
+            <!-- <el-table-column
+              label="施工开始日期"
+              align="center"
+              prop="constructionStartDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionStartDate.width"
+              resizable
+            />
+            <el-table-column
+              label="施工结束日期"
+              align="center"
+              prop="constructionEndDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionEndDate.width"
+              resizable
+            /> -->
+            <el-table-column
+              label="生产动态"
+              align="center"
+              :min-width="columnWidths.productionStatus.width"
+              prop="productionStatus"
+              resizable
+            />
+            <el-table-column
+              label="项目"
+              align="center"
+              prop="contractName"
+              class-name="contract-name-column"
+              :min-width="columnWidths.contractName.width"
+              resizable
+            />
+            <el-table-column label="井累计" align="center">
+              <el-table-column
+                label="注气量(万方)"
+                align="center"
+                prop="wellTotalGasInjection"
+                :formatter="gasInjectionFormatter"
+                :min-width="columnWidths.totalGasInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="注水量(方)"
+                align="center"
+                prop="wellTotalWaterInjection"
+                :min-width="columnWidths.totalWaterInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="用电量(MWh)"
+                align="center"
+                prop="wellTotalPower"
+                :formatter="gasInjectionFormatter"
+                :min-width="columnWidths.yearTotalPower.width"
+                resizable
+              />
+              <el-table-column
+                label="油耗(L)"
+                align="center"
+                prop="wellTotalFuel"
+                :min-width="columnWidths.cumulativeCompletion.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column label="年累计" align="center">
+              <el-table-column
+                label="注气量(万方)"
+                align="center"
+                prop="yearTotalGasInjection"
+                :formatter="gasInjectionFormatter"
+                :min-width="columnWidths.totalGasInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="注水量(方)"
+                align="center"
+                prop="yearTotalWaterInjection"
+                :min-width="columnWidths.totalWaterInjection.width"
+                resizable
+              />
+              <el-table-column
+                label="用电量(MWh)"
+                align="center"
+                prop="yearTotalPower"
+                :formatter="gasInjectionFormatter"
+                :min-width="columnWidths.yearTotalPower.width"
+                resizable
+              />
+              <el-table-column
+                label="油耗(L)"
+                align="center"
+                prop="yearTotalFuel"
+                :min-width="columnWidths.cumulativeCompletion.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column
+              label="产能(万方)"
+              align="center"
+              prop="capacity"
+              :formatter="gasInjectionFormatter"
+              :min-width="columnWidths.capacity.width"
+              resizable
+            />
+
+            <!-- <el-table-column label="操作" align="center" fixed="right">
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  @click="openForm('update', scope.row.id, scope.row)"
+                  v-hasPermi="['pms:iot-rh-daily-report:update']"
+                >
+                  编辑
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  @click="handleDelete(scope.row.id)"
+                  v-hasPermi="['pms:iot-rh-daily-report:delete']"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column> -->
+          </el-table>
+        </div>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+
+      <!-- 表单弹窗:添加/修改 -->
+      <IotRhDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData" />
+
+      <UnfilledReportDialog
+        ref="unfilledDialogRef"
+        :query-params="queryParams"
+        @close="handleUnfilledDialogClose"
+      />
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRhDailyReportApi, IotRhDailyReportVO } from '@/api/pms/iotrhdailyreport'
+import IotRhDailyReportForm from './IotRhDailyReportForm.vue'
+import UnfilledReportDialog from './UnfilledReportDialog.vue'
+import { DICT_TYPE } from '@/utils/dict'
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { useDebounceFn } from '@vueuse/core'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import dayjs from 'dayjs'
+
+import { useUserStore } from '@/store/modules/user'
+
+dayjs.extend(quarterOfYear)
+
+/** 瑞恒日报 列表 */
+defineOptions({ name: 'IotRhDailyReport' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+// 添加 selectedRowData 响应式变量
+const selectedRowData = ref<Record<string, any> | null>(null)
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRhDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: useUserStore().getUser.deptId,
+  contractName: undefined,
+  projectId: undefined,
+  taskName: undefined,
+  taskId: undefined,
+  projectClassification: undefined,
+  relocationDays: undefined,
+  transitTime: [],
+  dailyGasInjection: undefined,
+  dailyWaterInjection: undefined,
+  dailyPowerUsage: undefined,
+  dailyInjectGasTime: [],
+  dailyInjectWaterTime: [],
+  nonProductionTime: [],
+  nptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  nextPlan: undefined,
+  constructionStatus: undefined,
+  personnel: undefined,
+  totalGasInjection: undefined,
+  totalWaterInjection: undefined,
+  cumulativeCompletion: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  nonProductFlag: 'N'
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+// 添加弹窗引用
+const unfilledDialogRef = ref()
+
+const rootDeptId = ref(useUserStore().getUser.deptId)
+
+// 新增统计相关变量
+const statistics = ref({
+  total: '-',
+  filled: '-',
+  unFilled: '-',
+  totalWaterInjection: '-', // 新增累计注水量
+  totalGasInjection: '-' // 新增累计注气量
+})
+
+const totalColor = '#00DD99'
+const filledColor = '#0055BB'
+const unFilledColor = '#FF5500'
+
+// 表格引用
+const tableRef = ref()
+// 表格容器引用
+const tableContainerRef = ref()
+
+// 工作量统计相关变量
+const workloadStatistics = ref({
+  totalWaterInjection: '-',
+  totalGasInjection: '-'
+})
+
+// 列宽度配置
+const columnWidths = ref<
+  Record<
+    string,
+    { label: string; prop: string; width: string; fn?: (row: IotRhDailyReportVO) => string }
+  >
+>({
+  createTime: {
+    label: '日期',
+    prop: 'createTime',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => dateFormatter2(null, null, row.createTime)
+  },
+  deptName: {
+    label: '施工队伍',
+    prop: 'deptName',
+    width: '120px'
+  },
+  contractName: {
+    label: '项目',
+    prop: 'contractName',
+    width: '120px'
+  },
+  taskName: {
+    label: '任务',
+    prop: 'taskName',
+    width: '120px'
+  },
+  constructionStatus: {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    width: '120px'
+  },
+  relocationDays: {
+    label: '搬迁安装天数',
+    prop: 'relocationDays',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => relocationDaysFormatter(null, null, row.relocationDays, null)
+  },
+  designInjection: {
+    label: '设计注气量(万方)',
+    prop: 'designInjection',
+    width: '120px'
+  },
+  transitTime: {
+    label: '运行时效',
+    prop: 'transitTime',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => percentageFormatter(null, null, row.transitTime, null)
+  },
+  dailyGasInjection: {
+    label: '注气量(万方)',
+    prop: 'dailyGasInjection',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.dailyGasInjection, null)
+  },
+  dailyWaterInjection: {
+    label: '注水量(方)',
+    prop: 'dailyWaterInjection',
+    width: '120px'
+  },
+  dailyInjectGasTime: {
+    label: '注气时间(H)',
+    prop: 'dailyInjectGasTime',
+    width: '120px'
+  },
+  dailyInjectWaterTime: {
+    label: '注水时间(H)',
+    prop: 'dailyInjectWaterTime',
+    width: '120px'
+  },
+  dailyPowerUsage: {
+    label: '用电量(kWh)',
+    prop: 'dailyPowerUsage',
+    width: '120px'
+  },
+  dailyOilUsage: {
+    label: '油耗(L)',
+    prop: 'dailyOilUsage',
+    width: '120px'
+  },
+  gasElectricityRatio: {
+    label: '气电比',
+    prop: 'gasElectricityRatio',
+    width: '120px'
+  },
+  accidentTime: {
+    label: '工程质量',
+    prop: 'accidentTime',
+    width: '120px'
+  },
+  repairTime: {
+    label: '设备故障',
+    prop: 'repairTime',
+    width: '120px'
+  },
+  selfStopTime: {
+    label: '设备保养',
+    prop: 'selfStopTime',
+    width: '120px'
+  },
+  complexityTime: {
+    label: '技术受限',
+    prop: 'complexityTime',
+    width: '120px'
+  },
+  relocationTime: {
+    label: '生产配合',
+    prop: 'relocationTime',
+    width: '120px'
+  },
+  rectificationTime: {
+    label: '生产组织',
+    prop: 'rectificationTime',
+    width: '120px'
+  },
+  waitingStopTime: {
+    label: '不可抗力',
+    prop: 'waitingStopTime',
+    width: '120px'
+  },
+  winterBreakTime: {
+    label: '待命',
+    prop: 'winterBreakTime',
+    width: '120px'
+  },
+  partyaDesign: {
+    label: '甲方设计',
+    prop: 'partyaDesign',
+    width: '120px'
+  },
+  partyaPrepare: {
+    label: '甲方资源',
+    prop: 'partyaPrepare',
+    width: '120px'
+  },
+  partyaResource: {
+    label: '甲方准备',
+    prop: 'partyaResource',
+    width: '120px'
+  },
+  otherNptTime: {
+    label: '其它非生产时间',
+    prop: 'otherNptTime',
+    width: '120px'
+  },
+  otherNptReason: {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    width: '120px'
+  },
+  constructionStartDate: {
+    label: '施工开始日期',
+    prop: 'constructionStartDate',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
+  },
+  constructionEndDate: {
+    label: '施工结束日期',
+    prop: 'constructionEndDate',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
+  },
+  productionStatus: {
+    label: '生产动态',
+    prop: 'productionStatus',
+    width: '120px'
+  },
+  totalGasInjection: {
+    label: '注气量(万方)',
+    prop: 'totalGasInjection',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.totalGasInjection, null)
+  },
+  totalWaterInjection: {
+    label: '注水量(方)',
+    prop: 'totalWaterInjection',
+    width: '120px'
+  },
+  yearTotalPower: {
+    label: '用电量(万千瓦时)',
+    prop: 'yearTotalPower',
+    width: '120px'
+  },
+  cumulativeCompletion: {
+    label: '完工井次',
+    prop: 'cumulativeCompletion',
+    width: '120px'
+  },
+  capacity: {
+    label: '产能(万方)',
+    prop: 'capacity',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => gasInjectionFormatter(null, null, row.capacity, null)
+  },
+  nonProductionRate: {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    width: '120px',
+    fn: (row: IotRhDailyReportVO) => nonProductionRateFormatter(row)
+  }
+})
+
+const nonProductionRateFormatter = (row: any) => {
+  const nonProductionRate = row?.nonProductionRate ?? 0
+
+  return (nonProductionRate * 100).toFixed(2) + '%'
+}
+
+// 计算文本宽度
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = useDebounceFn(() => {
+  if (!tableContainerRef.value?.$el) return
+  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
+    width =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(fn ? fn(v) : v[prop])
+              })
+            ]
+          ) +
+            (label === '施工状态' || label === '非生产时间原因'
+              ? 30
+              : label === '气电比'
+                ? 40
+                : 20),
+          200
+        ]
+      ) + 'px'
+
+    columnWidths.value[prop].width = width
+  })
+}, 1000)
+// 计算列宽度
+
+// 格式化设计井身结构文本
+const formatDesignWellStruct = (text: string | null | undefined) => {
+  if (!text) return '-'
+  // 如果文本长度超过30个字符,显示前30个字符并添加省略号
+  return text.length > 30 ? text.substring(0, 30) + '...' : text
+}
+
+// 百分比格式化函数
+const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
+  const capacity = Number(row?.capacity)
+  const dailyGasInjection = Number(row?.dailyGasInjection)
+
+  if (!capacity || !dailyGasInjection) {
+    return '0.00%'
+  }
+
+  return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
+
+  // if (cellValue === null || cellValue === undefined) return ''
+  // // 将小数转换为百分比,保留两位小数
+  // return `${(parseFloat(cellValue) * 100).toFixed(2)}%`
+}
+
+// 添加打开未填报弹窗的方法
+const openUnfilledDialog = () => {
+  // 检查是否选择了创建时间
+  if (!queryParams.createTime || queryParams.createTime.length === 0) {
+    message.warning('请先选择创建时间范围')
+    return
+  }
+
+  // 打开弹窗
+  unfilledDialogRef.value.open()
+}
+
+// 弹窗关闭回调
+const handleUnfilledDialogClose = () => {
+  // 可以在这里处理弹窗关闭后的逻辑
+  console.log('未填报弹窗已关闭')
+}
+
+// 新增获取统计数据的方法
+const getStatistics = async () => {
+  // 重置统计数据
+  statistics.value = {
+    total: '-',
+    filled: '-',
+    unFilled: '-'
+  }
+
+  // 如果没有选择时间范围,不调用接口
+  if (!queryParams.createTime || queryParams.createTime.length === 0) {
+    return
+  }
+
+  try {
+    const res = await IotRhDailyReportApi.rhDailyReportStatistics({
+      createTime: queryParams.createTime
+    })
+
+    // 处理统计数据
+    const statsMap = {}
+    res.forEach((item) => {
+      statsMap[item.groupName] = item.count
+    })
+
+    statistics.value = {
+      total: statsMap['总数'] || '-',
+      filled: statsMap['已填报'] || '-',
+      unFilled: statsMap['未填报'] || '-'
+    }
+  } catch (error) {
+    console.error('获取统计数据失败', error)
+  }
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    console.log('22 :>> ', 11)
+    const data = await IotRhDailyReportApi.getIotRhDailyReportPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+
+    // 获取统计数据
+    await getStatistics()
+
+    // 获取工作量统计数据
+    await getWorkloadStatistics()
+
+    // 获取数据后计算列宽
+    nextTick(() => {
+      calculateColumnWidths()
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 搬迁安装天数格式化函数
+const relocationDaysFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
+  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+
+  const value = parseFloat(cellValue)
+  // 如果值为负数,显示0,否则显示原值
+  return value < 0 ? '0' : String(value)
+}
+
+// 注气量格式化函数(单位转换:方 -> 万方)
+const gasInjectionFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
+  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+  // 将方转换为万方,保留两位小数
+  const value = parseFloat(cellValue)
+  return (value / 10000).toFixed(2)
+}
+
+// 检查三个时间字段之和是否为24
+const checkTimeSumEquals24 = (row: any) => {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const gasTime = parseFloat(row.dailyInjectGasTime) || 0
+  const waterTime = parseFloat(row.dailyInjectWaterTime) || 0
+  const nonProdTime = parseFloat(row.nonProductionTime) || 0
+
+  // 计算总和
+  const sum = gasTime + waterTime + nonProdTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+}
+
+// 单元格样式函数
+const cellStyle = ({
+  row,
+  column,
+  rowIndex,
+  columnIndex
+}: {
+  row: any
+  column: any
+  rowIndex: number
+  columnIndex: number
+}) => {
+  // 只针对 transitTime 列进行处理
+  if (column.property === 'transitTime') {
+    // 1. 获取参与计算的字段,逻辑与 formatter 保持一致
+    const capacity = Number(row?.capacity)
+    const dailyGasInjection = Number(row?.dailyGasInjection)
+
+    // 2. 只有当两个值都有效(且 capacity 不为 0)时才进行计算
+    // 对应 formatter 中的 if (!capacity || !dailyGasInjection) 返回 '0.00%' 的情况
+    if (capacity && dailyGasInjection) {
+      const ratio = dailyGasInjection / capacity
+
+      // 3. 判断计算结果是否大于 1.2 (即 120%)
+      if (ratio > 1.2) {
+        return {
+          color: 'red',
+          fontWeight: 'bold'
+        }
+      }
+    }
+  }
+
+  // 处理三个时间字段:当日注气时间、当日注水时间、非生产时间
+  // const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
+  // if (timeFields.includes(column.property)) {
+  //   // 检查三个时间字段之和是否不等于24
+  //   if (!checkTimeSumEquals24(row)) {
+  //     return {
+  //       color: 'orange',
+  //       fontWeight: 'bold'
+  //     }
+  //   }
+  // }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+// 获取工作量统计数据的方法
+const getWorkloadStatistics = async () => {
+  // 重置工作量统计数据
+  statistics.value.totalWaterInjection = '-'
+  statistics.value.totalGasInjection = '-'
+
+  try {
+    const res = await IotRhDailyReportApi.totalWorkload(queryParams)
+
+    // 处理工作量统计数据
+    if (res) {
+      // 累计注水量直接显示,单位:方
+      statistics.value.totalWaterInjection = res.totalWaterInjection || '-'
+
+      // 累计注气量需要转换:方 -> 万方 (除以10000)
+      if (res.totalGasInjection) {
+        const gasInjection = parseFloat(res.totalGasInjection)
+        statistics.value.totalGasInjection = (gasInjection / 10000).toFixed(2)
+      } else {
+        statistics.value.totalGasInjection = '-'
+      }
+    }
+  } catch (error) {
+    console.error('获取工作量统计数据失败', error)
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const route = useRoute()
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
+  // 重置后需要重新获取统计数据
+  getStatistics()
+  // 重新获取工作量统计数据
+  getWorkloadStatistics()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number, row?: any) => {
+  // 保存当前行数据
+  if (row) {
+    selectedRowData.value = {
+      deptName: row.deptName,
+      contractName: row.contractName,
+      taskName: row.taskName,
+      relocationDays: row.relocationDays
+    }
+  } else {
+    selectedRowData.value = null
+  }
+
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRhDailyReportApi.deleteIotRhDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+// 响应式变量存储选中的部门
+const selectedDept = ref<{ id: number; name: string }>()
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  // 记录选中的部门信息
+  selectedDept.value = { id: row.id, name: row.name }
+  // queryParams.deptId = row.id
+  await getList()
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotRhDailyReportApi.exportIotRhDailyReport(queryParams)
+    download.excel(data, '瑞恒日报.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 声明 ResizeObserver 实例
+let resizeObserver: ResizeObserver | null = null
+
+/** 初始化 **/
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    nextTick(() => {
+      queryParams.deptId = Number(route.query.deptId) as any
+      queryParams.createTime = route.query.createTime as string[]
+      queryParams.nonProductFlag = route.query.nonProductFlag as string
+      handleQuery()
+    })
+  } else 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>
+/* 表格容器样式,确保水平滚动 */
+.table-container {
+  width: 100%;
+  overflow-x: auto;
+}
+
+/* 确保表格单元格内容不换行 */
+
+/* :deep(.el-table .cell) {
+  white-space: nowrap;
+} */
+
+/* 确保表格列标题不换行 */
+
+/* :deep(.el-table th > .cell) {
+  white-space: nowrap;
+} */
+
+/* 调整表格最小宽度,确保内容完全显示 */
+:deep(.el-table) {
+  min-width: 100%;
+}
+
+/* 强制显示所有内容,防止省略号 */
+
+/* :deep(.el-table td.el-table__cell),
+:deep(.el-table th.el-table__cell) {
+  overflow: visible !important;
+} */
+
+/* :deep(.el-table .cell) {
+  overflow: visible !important;
+  text-overflow: clip !important;
+} */
+
+/* :deep(.contract-name-column .cell) {
+  overflow: hidden !important;
+  text-overflow: ellipsis !important;
+  white-space: nowrap !important;
+} */
+
+/* 颜色说明区域样式 */
+.color-legend {
+  display: flex;
+  padding: 12px 16px;
+  background-color: #f8f9fa;
+  border-left: 4px solid #e6f7ff;
+  border-radius: 4px;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+}
+
+.color-indicator {
+  display: inline-block;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+}
+
+.color-indicator.red {
+  background-color: red;
+}
+
+.color-indicator.orange {
+  background-color: orange;
+}
+
+/* 统计区域未填报链接样式 */
+.unfilled-link {
+  color: #f50;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.unfilled-link:hover {
+  color: #f73;
+}
+
+.unfilled-link.disabled {
+  color: #ccc;
+  text-decoration: none;
+  cursor: not-allowed;
+}
+</style>
+
+<style>
+/* 设计井身结构 tooltip 样式 - 保留换行符 */
+.design-well-struct-tooltip {
+  max-width: 500px;
+  line-height: 1.5;
+  white-space: pre-line;
+}
+
+/* 统计区域样式 */
+.statistics-container {
+  display: flex;
+  justify-content: space-around;
+  padding: 10px 0;
+}
+
+.stat-item {
+  min-width: 0; /* 防止内容溢出 */
+  font-size: 16px;
+  font-weight: 500;
+  text-align: center;
+  flex: 1;
+}
+
+/* 确保统计项内容不换行 */
+.stat-item span {
+  white-space: nowrap;
+}
+</style>

+ 7 - 3
src/views/pms/iotrhdailyreport/rh-form.vue

@@ -8,10 +8,12 @@ interface Props {
   visible: boolean
   type?: 'edit' | 'approval' | 'readonly'
   loadList: () => void
+  noValidateStatus?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  type: 'edit'
+  type: 'edit',
+  noValidateStatus: false
 })
 
 const emits = defineEmits(['update:visible'])
@@ -157,8 +159,10 @@ async function loadDetail(id: number) {
       }
     })
 
-    if (props.type === 'edit' && res.status !== 0) formType.value = 'readonly'
-    if (props.type === 'approval' && res.auditStatus !== 10) formType.value = 'readonly'
+    if (props.type === 'edit' && !props.noValidateStatus && res.status !== 0)
+      formType.value = 'readonly'
+    if (props.type === 'approval' && !props.noValidateStatus && res.auditStatus !== 10)
+      formType.value = 'readonly'
     if (!form.value.capacity) message.error('请维护增压机产能')
   } finally {
     loading.value = false

+ 318 - 0
src/views/pms/iotrhdailyreport/rh-table.vue

@@ -0,0 +1,318 @@
+<script setup lang="ts">
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import { DICT_TYPE, realValue } from '@/utils/dict'
+import dayjs from 'dayjs'
+import { TableColumnCtx } from 'element-plus'
+
+const { t } = useI18n()
+
+interface ListItem {
+  createTime: string
+  deptName: string
+  taskName: string
+  constructionStatus: string
+  relocationDays: number
+  designInjection: number
+  dailyGasInjection: number
+  dailyWaterInjection: number
+  dailyInjectGasTime: number
+  dailyInjectWaterTime: number
+  dailyPowerUsage: number
+  dailyOilUsage: number
+  gasElectricityRatio: number
+  nonProductionRate: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+  productionStatus: string
+  contractName: string
+  wellTotalGasInjection: number
+  wellTotalWaterInjection: number
+  wellTotalPower: number
+  wellTotalFuel: number
+  yearTotalGasInjection: number
+  yearTotalWaterInjection: number
+  yearTotalPower: number
+  yearTotalFuel: number
+  capacity: number
+}
+
+const props = defineProps({
+  list: {
+    type: Array as PropType<ListItem[]>,
+    default: () => []
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  total: {
+    type: Number,
+    default: 0
+  },
+  pageNo: {
+    type: Number,
+    default: 0
+  },
+  pageSize: {
+    type: Number,
+    default: 0
+  },
+  showAction: {
+    type: Boolean,
+    default: true
+  },
+  isIndex: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:pageNo', 'update:pageSize', 'sizeChange', 'currentChange'])
+
+const { list, loading, total, pageNo, pageSize, showAction, isIndex } = toRefs(props)
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
+
+function percentageFormatter(row: ListItem) {
+  const capacity = Number(row?.capacity)
+  const dailyGasInjection = Number(row?.dailyGasInjection)
+
+  if (!capacity || !dailyGasInjection) {
+    return '0.00%'
+  }
+
+  return ((dailyGasInjection / capacity) * 100).toFixed(2) + '%'
+}
+
+function unitformatter(
+  _row: ListItem,
+  _column: TableColumnCtx<ListItem>,
+  cellValue: any,
+  _index: number
+) {
+  if (cellValue === null || cellValue === undefined || cellValue == '') return ''
+  const value = parseFloat(cellValue)
+  return (value / 10000).toFixed(2)
+}
+
+const cellStyle = ({ row, column }: { row: any; column: any }) => {
+  if (column.property === 'transitTime') {
+    const capacity = Number(row?.capacity)
+    const dailyGasInjection = Number(row?.dailyGasInjection)
+    if (capacity && dailyGasInjection) {
+      const ratio = dailyGasInjection / capacity
+      if (ratio > 1.2) {
+        return {
+          color: 'red',
+          fontWeight: 'bold',
+          backgroundColor: 'var(--el-color-danger-light-9)'
+        }
+      }
+    }
+  }
+  return {}
+}
+
+function handleSizeChange(val: number) {
+  emits('sizeChange', val)
+}
+
+function handleCurrentChange(val: number) {
+  emits('currentChange', val)
+}
+</script>
+
+<template>
+  <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4">
+    <div class="flex-1 relative">
+      <el-auto-resizer class="absolute">
+        <template #default="{ width, height }">
+          <zm-table
+            :data="list"
+            :loading="loading"
+            :width="width"
+            :max-height="height"
+            :height="height"
+            show-border
+            :cell-style="cellStyle"
+          >
+            <zm-table-column
+              v-if="isIndex"
+              type="index"
+              :label="t('monitor.serial')"
+              :width="60"
+              fixed="left"
+            />
+            <zm-table-column
+              label="日期"
+              prop="createTime"
+              fixed="left"
+              cover-formatter
+              :real-value="(row: ListItem) => dayjs(row.createTime).format('YYYY-MM-DD')"
+            />
+            <zm-table-column label="施工队伍" prop="deptName" fixed="left" />
+            <zm-table-column label="任务" prop="taskName" fixed="left" />
+            <zm-table-column
+              prop="constructionStatus"
+              fixed="left"
+              :label="t('project.status')"
+              :real-value="
+                (row: ListItem) =>
+                  realValue(DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE, row.constructionStatus ?? '')
+              "
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE"
+                  :value="scope.row.constructionStatus ?? ''"
+                />
+              </template>
+            </zm-table-column>
+            <zm-table-column prop="auditStatus" label="审批状态" v-if="!isIndex">
+              <template #default="scope">
+                <el-tag v-if="scope.row.auditStatus === 0" type="info">
+                  {{ '待提交' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 10">
+                  {{ '待审批' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 20" type="success">
+                  {{ '审批通过' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 30" type="danger">
+                  {{ '审批拒绝' }}
+                </el-tag>
+              </template>
+            </zm-table-column>
+            <zm-table-column label="搬迁安装天数" prop="relocationDays" />
+            <zm-table-column label="设计注气量(万方)" prop="designInjection" />
+            <zm-table-column
+              label="运行时效"
+              prop="transitTime"
+              cover-formatter
+              :real-value="percentageFormatter"
+            />
+            <zm-table-column label="当日">
+              <zm-table-column
+                label="注气量(万方)"
+                prop="dailyGasInjection"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column label="注水量(方)" prop="dailyWaterInjection" />
+              <zm-table-column label="注气时间(H)" prop="dailyInjectGasTime" />
+              <zm-table-column label="注水时间(H)" prop="dailyInjectWaterTime" />
+              <zm-table-column label="用电量(kWh)" prop="dailyPowerUsage" />
+              <zm-table-column label="油耗(L)" prop="dailyOilUsage" />
+              <zm-table-column label="气电比" prop="gasElectricityRatio" />
+            </zm-table-column>
+            <zm-table-column
+              prop="nonProductionRate"
+              label="非生产时效"
+              cover-formatter
+              :real-value="(row) => (Number(row.nonProductionRate ?? 0) * 100).toFixed(2) + '%'"
+            />
+            <zm-table-column label="非生产时间">
+              <zm-table-column prop="accidentTime" label="工程质量" />
+              <zm-table-column prop="repairTime" label="设备故障" />
+              <zm-table-column prop="selfStopTime" label="设备保养" />
+              <zm-table-column prop="complexityTime" label="技术受限" />
+              <zm-table-column prop="relocationTime" label="生产配合" />
+              <zm-table-column prop="rectificationTime" label="生产组织" />
+              <zm-table-column prop="waitingStopTime" label="不可抗力" />
+              <zm-table-column prop="winterBreakTime" label="待命" />
+              <zm-table-column prop="partyaDesign" label="甲方设计" />
+              <zm-table-column prop="partyaPrepare" label="甲方准备" />
+              <zm-table-column prop="partyaResource" label="甲方资源" />
+              <zm-table-column prop="otherNptTime" label="其它非生产时间" />
+            </zm-table-column>
+            <zm-table-column prop="otherNptReason" label="其他非生产时间原因" />
+            <zm-table-column prop="productionStatus" label="生产动态" />
+            <zm-table-column prop="contractName" label="项目" />
+            <zm-table-column label="井累计" v-if="isIndex">
+              <zm-table-column
+                prop="wellTotalGasInjection"
+                label="注气量(万方)"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column prop="wellTotalWaterInjection" label="注水量(方)" />
+              <zm-table-column
+                prop="wellTotalPower"
+                label="用电量(MWh)"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column prop="wellTotalFuel" label="油耗(L)" />
+            </zm-table-column>
+            <zm-table-column label="年累计" v-if="isIndex">
+              <zm-table-column
+                prop="yearTotalGasInjection"
+                label="注气量(万方)"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column prop="yearTotalWaterInjection" label="注水量(方)" />
+              <zm-table-column
+                prop="yearTotalPower"
+                label="用电量(MWh)"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column prop="wellTotalFuel" label="油耗(L)" />
+            </zm-table-column>
+            <zm-table-column label="累计" v-if="!isIndex">
+              <zm-table-column
+                prop="totalGasInjection"
+                label="注气量(万方)"
+                cover-formatter
+                :real-value="unitformatter"
+              />
+              <zm-table-column prop="totalWaterInjection" label="注水量(方)" />
+              <zm-table-column prop="cumulativeCompletion" label="完工井次" />
+            </zm-table-column>
+            <zm-table-column
+              prop="capacity"
+              label="产能(万方)"
+              cover-formatter
+              :real-value="unitformatter"
+            />
+
+            <zm-table-column label="操作" :width="120" fixed="right" v-if="showAction">
+              <template #default="scope">
+                <slot name="action" :row="scope.row"></slot>
+              </template>
+            </zm-table-column>
+          </zm-table>
+        </template>
+      </el-auto-resizer>
+    </div>
+    <div class="h-8 mt-2 flex items-center justify-end">
+      <el-pagination
+        size="default"
+        v-show="total > 0"
+        :current-page="pageNo"
+        :page-size="pageSize"
+        :background="true"
+        :page-sizes="[10, 20, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped></style>

+ 28 - 8
src/views/pms/iotrhdailyreport/summary.vue

@@ -45,6 +45,13 @@ const totalWorkKeys: [string, string, string, string, number][] = [
     'i-material-symbols:water-drop-outline-rounded text-sky',
     2
   ],
+  [
+    'utilizationRate',
+    '%',
+    '设备利用率',
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
+  ],
   [
     'totalPowerConsumption',
     'MWh',
@@ -77,7 +84,8 @@ const totalWork = ref({
   totalFuelConsumption: 0,
   totalPowerConsumption: 0,
   totalWaterInjection: 0,
-  totalGasInjection: 0
+  totalGasInjection: 0,
+  utilizationRate: 0
 })
 
 const totalLoading = ref(false)
@@ -108,7 +116,8 @@ const getTotal = useDebounceFn(async () => {
       totalPowerConsumption: (res2.totalPowerConsumption || 0) / 1000,
       totalWaterInjection: (res2.totalWaterInjection || 0) / 10000,
       totalGasInjection: (res2.totalGasInjection || 0) / 10000,
-      totalFuelConsumption: res2.totalFuelConsumption || 0
+      totalFuelConsumption: res2.totalFuelConsumption || 0,
+      utilizationRate: Number(((res2.utilizationRate || 0) * 100).toFixed(2))
     }
   } finally {
     totalLoading.value = false
@@ -125,6 +134,7 @@ interface List {
   cumulativeFuelConsumption: number | null
   transitTime: number | null
   nonProductiveTime: number | null
+  utilizationRate: number | null
 }
 
 const list = ref<List[]>([])
@@ -190,7 +200,11 @@ const columns = (type: string) => {
             prop: 'sgTeamCount'
           }
         ]
-      : [])
+      : []),
+    {
+      label: '设备利用率(%)',
+      prop: 'utilizationRate'
+    }
   ]
 }
 
@@ -201,6 +215,8 @@ const formatter = (row: List, column: any) => {
     return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
   } else if (column.property === 'nonProductiveTime') {
     return (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%'
+  } else if (column.property === 'utilizationRate') {
+    return (Number(row.utilizationRate ?? 0) * 100).toFixed(2) + '%'
   } else return row[column.property] ?? 0
 }
 
@@ -222,7 +238,8 @@ const getList = useDebounceFn(async () => {
           cumulativeGasInjection: ((other.cumulativeGasInjection || 0) / 10000).toFixed(2),
           cumulativeWaterInjection: ((other.cumulativeWaterInjection || 0) / 10000).toFixed(2),
           cumulativePowerConsumption: ((other.cumulativePowerConsumption || 0) / 1000).toFixed(2),
-          cumulativeFuelConsumption: (other.cumulativeFuelConsumption || 0).toFixed(2)
+          cumulativeFuelConsumption: (other.cumulativeFuelConsumption || 0).toFixed(2),
+          utilizationRate: other.utilizationRate || 0
         }
       }
     )
@@ -264,7 +281,8 @@ const legend = ref<string[][]>([
   // ['累计用电量 (MWh)', 'cumulativePowerConsumption'],
   ['累计注水量 (方)', 'cumulativeWaterInjection'],
   // ['累计注水量 (万方)', 'cumulativeWaterInjection'],
-  ['平均时效 (%)', 'transitTime']
+  ['平均时效 (%)', 'transitTime'],
+  ['设备利用率 (%)', 'utilizationRate']
 ])
 
 const chartData = ref<Record<string, number[]>>({
@@ -272,7 +290,8 @@ const chartData = ref<Record<string, number[]>>({
   cumulativeGasInjection: [],
   cumulativePowerConsumption: [],
   cumulativeWaterInjection: [],
-  transitTime: []
+  transitTime: [],
+  utilizationRate: []
 })
 
 let chartLoading = ref(false)
@@ -291,7 +310,8 @@ const getChart = useDebounceFn(async () => {
       // cumulativePowerConsumption: res.map((item) => (item.cumulativePowerConsumption || 0) / 1000),
       cumulativeWaterInjection: res.map((item) => item.cumulativeWaterInjection || 0),
       // cumulativeWaterInjection: res.map((item) => (item.cumulativeWaterInjection || 0) / 10000),
-      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+      transitTime: res.map((item) => (item.transitTime || 0) * 100),
+      utilizationRate: res.map((item) => Number(((item.utilizationRate || 0) * 100).toFixed(2)))
     }
 
     xAxisData.value = res.map((item) => item.reportDate || '')
@@ -575,7 +595,7 @@ const { ZmTable, ZmTableColumn } = useTableComponents()
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
       </el-form-item>
     </el-form>
-    <div class="grid grid-cols-7 gap-8">
+    <div class="grid grid-cols-8 gap-8">
       <div
         v-for="info in totalWorkKeys"
         :key="info[0]"

+ 103 - 630
src/views/pms/iotrydailyreport/approval.vue

@@ -1,472 +1,13 @@
 <script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
-import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import ryForm from './ry-form.vue'
+import RyTable from './ry-table.vue'
 
-interface List {
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  projectClassification: string
-  relocationDays: number
-  latestWellDoneTime: number
-  currentDepth: number
-  dailyFootage: number
-  monthlyFootage: number
-  annualFootage: number
-  dailyPowerUsage: number
-  monthlyPowerUsage: number
-  dailyFuel: number
-  monthlyFuel: number
-  dailyOilVolume: number
-  remainDieselVolume: number
-  productionTime: number
-  nonProductionTime: number
-  ryNptReason: string
-  drillingWorkingTime: number
-  otherProductionTime: number
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string // 其他非生产时间原因
-  constructionStartDate: number
-  constructionEndDate: number
-  productionStatus: string
-  currentOperation: string
-  nextPlan: string
-  rigStatus: string
-  repairStatus: string
-  personnel: string
-  totalStaffNum: number
-  leaveStaffNum: number
-  mudDensity: number
-  mudViscosity: number
-  lateralLength: number
-  wellInclination: number
-  azimuth: number
-  remark: string
-  status: number
-  processInstanceId: string
-  auditStatus: number
-  opinion: string
-  createTime: number
-  deptName: string
-  contractName: string
-  taskName: string
-  designWellDepth: number
-  designWellStruct: number
-  totalConstructionWells: number
-  completedWells: number
-  equipmentType: string
-  transitTime: number
-  lastCurrentDepth: number
-  nonProductionRate: number
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
-    fixed: 'left'
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '任务',
-    prop: 'taskName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '施工状态',
-    prop: 'rigStatus',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
-    fixed: 'left'
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '设备型号',
-    prop: 'equipmentType',
-    'min-width': '120px'
-  },
-  {
-    label: '上井次完井时间',
-    prop: 'latestWellDoneTime',
-    'min-width': '120px',
-    formatter: (row: List) =>
-      row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
-  },
-  {
-    label: '井深(m)',
-    children: [
-      {
-        label: '设计',
-        prop: 'designWellDepth',
-        'min-width': '120px'
-      },
-      {
-        label: '当前',
-        prop: 'currentDepth',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '进尺(m)',
-    children: [
-      {
-        label: '日',
-        prop: 'dailyFootage',
-        'min-width': '120px'
-      },
-      {
-        label: '月',
-        prop: 'monthlyFootage',
-        'min-width': '120px'
-      },
-      {
-        label: '年累计',
-        prop: 'annualFootage',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    'min-width': '120px'
-  },
-  {
-    label: '完工井数',
-    prop: 'completedWells',
-    'min-width': '120px'
-  },
-  {
-    label: '泥浆性能',
-    children: [
-      {
-        label: '密度(g/cm³)',
-        prop: 'mudDensity',
-        'min-width': '120px'
-      },
-      {
-        label: '粘度(S)',
-        prop: 'mudViscosity',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(升)',
-        prop: 'dailyFuel',
-        'min-width': '120px'
-      }
-    ]
-  },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '水平段长度(m)',
-    prop: 'lateralLength',
-    'min-width': '120px'
-  },
-  {
-    label: '井斜(°)',
-    prop: 'wellInclination',
-    'min-width': '120px'
-  },
-  {
-    label: '方位(°)',
-    prop: 'azimuth',
-    'min-width': '120px'
-  },
-  {
-    label: '设计井身结构',
-    prop: 'designWellStruct',
-    'min-width': '120px'
-  },
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '进尺工作时间(H)',
-    prop: 'drillingWorkingTime',
-    'min-width': '120px'
-  },
-  {
-    label: '其它生产时间(H)',
-    prop: 'otherProductionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-const nptFields = [
-  'accidentTime', // 工程质量
-  'repairTime', // 设备故障
-  'selfStopTime', // 设备保养
-  'complexityTime', // 技术受限
-  'relocationTime', // 生产配合
-  'rectificationTime', // 生产组织
-  'waitingStopTime', // 不可抗力
-  'winterBreakTime', // 待命
-  'partyaDesign', // 甲方设计
-  'partyaPrepare', // 甲方资源
-  'partyaResource', // 甲方准备
-  'otherNptTime' // 其它非生产时间
-]
-
-function checkTimeSumEquals24(row: List) {
-  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
-  // 对应你代码中的 waterTime
-  const waterTime = parseFloat(row.otherProductionTime + '') || 0
-
-  // 3. 计算所有非生产时间之和
-  const nonProdTime = nptFields.reduce((sum, field) => {
-    const val = parseFloat(row[field])
-    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
-    return sum + (isNaN(val) ? 0 : val)
-  }, 0)
-
-  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
-  const totalSum = gasTime + waterTime + nonProdTime
-
-  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
-  return Math.abs(totalSum - 24) < 0.01
-}
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'dailyFuel') {
-    const originalValue = row.dailyFuel ?? 0
-
-    if (originalValue > 9000)
-      return {
-        color: 'red',
-        fontWeight: 'bold'
-      }
-  }
-
-  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
-  if (timeFields.includes(column.property)) {
-    if (!checkTimeSumEquals24(row)) {
-      return {
-        color: 'orange',
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+defineOptions({ name: 'IotRyDailyReportApproval' })
 
 const id = useUserStore().getUser.deptId
 
@@ -475,14 +16,14 @@ const deptId = id
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
-  projectClassification: '1' | '2'
+  createTime?: string[]
+  projectClassification?: string
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
@@ -490,37 +31,35 @@ const query = ref<Query>({
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
   projectClassification: '1'
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
 }
 
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
+const query = ref<Query>({ ...initQuery })
 
-const loading = ref(false)
-
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -530,26 +69,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ],
-    projectClassification: '1'
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -578,147 +108,90 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] >"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="flex flex-col p-4 gap-2 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :deptId="deptId" :topId="158" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+
+    <ry-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-ry-daily-report:update']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                class="!w-220px"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-ry-daily-report:update']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.auditStatus === 10"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-ry-daily-report:update']"
-                      >
-                        审批
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <ry-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.auditStatus === 10"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:update']"
+        >
+          审批
+        </el-button>
+      </template>
+    </ry-table>
   </div>
+  <ry-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.el-input-number) {
-  width: 100%;
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 724 - 0
src/views/pms/iotrydailyreport/approval1.vue

@@ -0,0 +1,724 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import ryForm from './ry-form.vue'
+
+interface List {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  projectClassification: string
+  relocationDays: number
+  latestWellDoneTime: number
+  currentDepth: number
+  dailyFootage: number
+  monthlyFootage: number
+  annualFootage: number
+  dailyPowerUsage: number
+  monthlyPowerUsage: number
+  dailyFuel: number
+  monthlyFuel: number
+  dailyOilVolume: number
+  remainDieselVolume: number
+  productionTime: number
+  nonProductionTime: number
+  ryNptReason: string
+  drillingWorkingTime: number
+  otherProductionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string // 其他非生产时间原因
+  constructionStartDate: number
+  constructionEndDate: number
+  productionStatus: string
+  currentOperation: string
+  nextPlan: string
+  rigStatus: string
+  repairStatus: string
+  personnel: string
+  totalStaffNum: number
+  leaveStaffNum: number
+  mudDensity: number
+  mudViscosity: number
+  lateralLength: number
+  wellInclination: number
+  azimuth: number
+  remark: string
+  status: number
+  processInstanceId: string
+  auditStatus: number
+  opinion: string
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  designWellDepth: number
+  designWellStruct: number
+  totalConstructionWells: number
+  completedWells: number
+  equipmentType: string
+  transitTime: number
+  lastCurrentDepth: number
+  nonProductionRate: number
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
+    fixed: 'left'
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '施工状态',
+    prop: 'rigStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
+    fixed: 'left'
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '设备型号',
+    prop: 'equipmentType',
+    'min-width': '120px'
+  },
+  {
+    label: '上井次完井时间',
+    prop: 'latestWellDoneTime',
+    'min-width': '120px',
+    formatter: (row: List) =>
+      row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
+  },
+  {
+    label: '井深(m)',
+    children: [
+      {
+        label: '设计',
+        prop: 'designWellDepth',
+        'min-width': '120px'
+      },
+      {
+        label: '当前',
+        prop: 'currentDepth',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '进尺(m)',
+    children: [
+      {
+        label: '日',
+        prop: 'dailyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '月',
+        prop: 'monthlyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '年累计',
+        prop: 'annualFootage',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    'min-width': '120px'
+  },
+  {
+    label: '完工井数',
+    prop: 'completedWells',
+    'min-width': '120px'
+  },
+  {
+    label: '泥浆性能',
+    children: [
+      {
+        label: '密度(g/cm³)',
+        prop: 'mudDensity',
+        'min-width': '120px'
+      },
+      {
+        label: '粘度(S)',
+        prop: 'mudViscosity',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(升)',
+        prop: 'dailyFuel',
+        'min-width': '120px'
+      }
+    ]
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '水平段长度(m)',
+    prop: 'lateralLength',
+    'min-width': '120px'
+  },
+  {
+    label: '井斜(°)',
+    prop: 'wellInclination',
+    'min-width': '120px'
+  },
+  {
+    label: '方位(°)',
+    prop: 'azimuth',
+    'min-width': '120px'
+  },
+  {
+    label: '设计井身结构',
+    prop: 'designWellStruct',
+    'min-width': '120px'
+  },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '进尺工作时间(H)',
+    prop: 'drillingWorkingTime',
+    'min-width': '120px'
+  },
+  {
+    label: '其它生产时间(H)',
+    prop: 'otherProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+const nptFields = [
+  'accidentTime', // 工程质量
+  'repairTime', // 设备故障
+  'selfStopTime', // 设备保养
+  'complexityTime', // 技术受限
+  // 'relocationTime', // 生产配合
+  'rectificationTime', // 生产组织
+  'waitingStopTime', // 不可抗力
+  'winterBreakTime', // 待命
+  'partyaDesign', // 甲方设计
+  'partyaPrepare', // 甲方资源
+  'partyaResource', // 甲方准备
+  'otherNptTime' // 其它非生产时间
+]
+
+function checkTimeSumEquals24(row: List) {
+  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
+  // 对应你代码中的 waterTime
+  const waterTime = parseFloat(row.otherProductionTime + '') || 0
+
+  // 3. 计算所有非生产时间之和
+  const nonProdTime = nptFields.reduce((sum, field) => {
+    const val = parseFloat(row[field])
+    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
+  const totalSum = gasTime + waterTime + nonProdTime
+
+  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
+  return Math.abs(totalSum - 24) < 0.01
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 9000)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+  projectClassification: '1' | '2'
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  projectClassification: '1'
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ],
+    projectClassification: '1'
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] >"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="flex flex-col p-4 gap-2 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :deptId="deptId" :topId="158" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                class="!w-220px"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-ry-daily-report:update']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.auditStatus === 10"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-ry-daily-report:update']"
+                      >
+                        审批
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <ry-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 103 - 630
src/views/pms/iotrydailyreport/fill.vue

@@ -1,472 +1,13 @@
 <script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
-import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import ryForm from './ry-form.vue'
+import RyTable from './ry-table.vue'
 
-interface List {
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  projectClassification: string
-  relocationDays: number
-  latestWellDoneTime: number
-  currentDepth: number
-  dailyFootage: number
-  monthlyFootage: number
-  annualFootage: number
-  dailyPowerUsage: number
-  monthlyPowerUsage: number
-  dailyFuel: number
-  monthlyFuel: number
-  dailyOilVolume: number
-  remainDieselVolume: number
-  productionTime: number
-  nonProductionTime: number
-  ryNptReason: string
-  drillingWorkingTime: number
-  otherProductionTime: number
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string
-  constructionStartDate: number
-  constructionEndDate: number
-  productionStatus: string
-  currentOperation: string
-  nextPlan: string
-  rigStatus: string
-  repairStatus: string
-  personnel: string
-  totalStaffNum: number
-  leaveStaffNum: number
-  mudDensity: number
-  mudViscosity: number
-  lateralLength: number
-  wellInclination: number
-  azimuth: number
-  remark: string
-  status: number
-  processInstanceId: string
-  auditStatus: number
-  opinion: string
-  createTime: number
-  deptName: string
-  contractName: string
-  taskName: string
-  designWellDepth: number
-  designWellStruct: number
-  totalConstructionWells: number
-  completedWells: number
-  equipmentType: string
-  transitTime: number
-  lastCurrentDepth: number
-  nonProductionRate: number
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
-    fixed: 'left'
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '任务',
-    prop: 'taskName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '施工状态',
-    prop: 'rigStatus',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
-    fixed: 'left'
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '设备型号',
-    prop: 'equipmentType',
-    'min-width': '120px'
-  },
-  {
-    label: '上井次完井时间',
-    prop: 'latestWellDoneTime',
-    'min-width': '120px',
-    formatter: (row: List) =>
-      row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
-  },
-  {
-    label: '井深(m)',
-    children: [
-      {
-        label: '设计',
-        prop: 'designWellDepth',
-        'min-width': '120px'
-      },
-      {
-        label: '当前',
-        prop: 'currentDepth',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '进尺(m)',
-    children: [
-      {
-        label: '日',
-        prop: 'dailyFootage',
-        'min-width': '120px'
-      },
-      {
-        label: '月',
-        prop: 'monthlyFootage',
-        'min-width': '120px'
-      },
-      {
-        label: '年累计',
-        prop: 'annualFootage',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    'min-width': '120px'
-  },
-  {
-    label: '完工井数',
-    prop: 'completedWells',
-    'min-width': '120px'
-  },
-  {
-    label: '泥浆性能',
-    children: [
-      {
-        label: '密度(g/cm³)',
-        prop: 'mudDensity',
-        'min-width': '120px'
-      },
-      {
-        label: '粘度(S)',
-        prop: 'mudViscosity',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(升)',
-        prop: 'dailyFuel',
-        'min-width': '120px'
-      }
-    ]
-  },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '水平段长度(m)',
-    prop: 'lateralLength',
-    'min-width': '120px'
-  },
-  {
-    label: '井斜(°)',
-    prop: 'wellInclination',
-    'min-width': '120px'
-  },
-  {
-    label: '方位(°)',
-    prop: 'azimuth',
-    'min-width': '120px'
-  },
-  {
-    label: '设计井身结构',
-    prop: 'designWellStruct',
-    'min-width': '120px'
-  },
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '进尺工作时间(H)',
-    prop: 'drillingWorkingTime',
-    'min-width': '120px'
-  },
-  {
-    label: '其它生产时间(H)',
-    prop: 'otherProductionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-const nptFields = [
-  'accidentTime', // 工程质量
-  'repairTime', // 设备故障
-  'selfStopTime', // 设备保养
-  'complexityTime', // 技术受限
-  'relocationTime', // 生产配合
-  'rectificationTime', // 生产组织
-  'waitingStopTime', // 不可抗力
-  'winterBreakTime', // 待命
-  'partyaDesign', // 甲方设计
-  'partyaPrepare', // 甲方资源
-  'partyaResource', // 甲方准备
-  'otherNptTime' // 其它非生产时间
-]
-
-function checkTimeSumEquals24(row: List) {
-  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
-  // 对应你代码中的 waterTime
-  const waterTime = parseFloat(row.otherProductionTime + '') || 0
-
-  // 3. 计算所有非生产时间之和
-  const nonProdTime = nptFields.reduce((sum, field) => {
-    const val = parseFloat(row[field])
-    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
-    return sum + (isNaN(val) ? 0 : val)
-  }, 0)
-
-  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
-  const totalSum = gasTime + waterTime + nonProdTime
-
-  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
-  return Math.abs(totalSum - 24) < 0.01
-}
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'dailyFuel') {
-    const originalValue = row.dailyFuel ?? 0
-
-    if (originalValue > 9000)
-      return {
-        color: 'red',
-        fontWeight: 'bold'
-      }
-  }
-
-  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
-  if (timeFields.includes(column.property)) {
-    if (!checkTimeSumEquals24(row)) {
-      return {
-        color: 'orange',
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+defineOptions({ name: 'IotRyDailyReportFill' })
 
 const id = useUserStore().getUser.deptId
 
@@ -475,14 +16,14 @@ const deptId = id
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
-  projectClassification: '1' | '2'
+  createTime?: string[]
+  projectClassification?: string
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
@@ -490,37 +31,35 @@ const query = ref<Query>({
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
   projectClassification: '1'
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
 }
 
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
+const query = ref<Query>({ ...initQuery })
 
-const loading = ref(false)
-
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -530,26 +69,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ],
-    projectClassification: '1'
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -578,147 +108,90 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+
+    <ry-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-ry-daily-report:query']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                class="!w-220px"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-ry-daily-report:query']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.status === 0"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-ry-daily-report:create']"
-                      >
-                        编辑
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <ry-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.status === 0"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:create']"
+        >
+          编辑
+        </el-button>
+      </template>
+    </ry-table>
   </div>
+  <ry-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.el-input-number) {
-  width: 100%;
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 724 - 0
src/views/pms/iotrydailyreport/fill1.vue

@@ -0,0 +1,724 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import ryForm from './ry-form.vue'
+
+interface List {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  projectClassification: string
+  relocationDays: number
+  latestWellDoneTime: number
+  currentDepth: number
+  dailyFootage: number
+  monthlyFootage: number
+  annualFootage: number
+  dailyPowerUsage: number
+  monthlyPowerUsage: number
+  dailyFuel: number
+  monthlyFuel: number
+  dailyOilVolume: number
+  remainDieselVolume: number
+  productionTime: number
+  nonProductionTime: number
+  ryNptReason: string
+  drillingWorkingTime: number
+  otherProductionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+  constructionStartDate: number
+  constructionEndDate: number
+  productionStatus: string
+  currentOperation: string
+  nextPlan: string
+  rigStatus: string
+  repairStatus: string
+  personnel: string
+  totalStaffNum: number
+  leaveStaffNum: number
+  mudDensity: number
+  mudViscosity: number
+  lateralLength: number
+  wellInclination: number
+  azimuth: number
+  remark: string
+  status: number
+  processInstanceId: string
+  auditStatus: number
+  opinion: string
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  designWellDepth: number
+  designWellStruct: number
+  totalConstructionWells: number
+  completedWells: number
+  equipmentType: string
+  transitTime: number
+  lastCurrentDepth: number
+  nonProductionRate: number
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
+    fixed: 'left'
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '施工状态',
+    prop: 'rigStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
+    fixed: 'left'
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '设备型号',
+    prop: 'equipmentType',
+    'min-width': '120px'
+  },
+  {
+    label: '上井次完井时间',
+    prop: 'latestWellDoneTime',
+    'min-width': '120px',
+    formatter: (row: List) =>
+      row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
+  },
+  {
+    label: '井深(m)',
+    children: [
+      {
+        label: '设计',
+        prop: 'designWellDepth',
+        'min-width': '120px'
+      },
+      {
+        label: '当前',
+        prop: 'currentDepth',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '进尺(m)',
+    children: [
+      {
+        label: '日',
+        prop: 'dailyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '月',
+        prop: 'monthlyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '年累计',
+        prop: 'annualFootage',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    'min-width': '120px'
+  },
+  {
+    label: '完工井数',
+    prop: 'completedWells',
+    'min-width': '120px'
+  },
+  {
+    label: '泥浆性能',
+    children: [
+      {
+        label: '密度(g/cm³)',
+        prop: 'mudDensity',
+        'min-width': '120px'
+      },
+      {
+        label: '粘度(S)',
+        prop: 'mudViscosity',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(升)',
+        prop: 'dailyFuel',
+        'min-width': '120px'
+      }
+    ]
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '水平段长度(m)',
+    prop: 'lateralLength',
+    'min-width': '120px'
+  },
+  {
+    label: '井斜(°)',
+    prop: 'wellInclination',
+    'min-width': '120px'
+  },
+  {
+    label: '方位(°)',
+    prop: 'azimuth',
+    'min-width': '120px'
+  },
+  {
+    label: '设计井身结构',
+    prop: 'designWellStruct',
+    'min-width': '120px'
+  },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '进尺工作时间(H)',
+    prop: 'drillingWorkingTime',
+    'min-width': '120px'
+  },
+  {
+    label: '其它生产时间(H)',
+    prop: 'otherProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+const nptFields = [
+  'accidentTime', // 工程质量
+  'repairTime', // 设备故障
+  'selfStopTime', // 设备保养
+  'complexityTime', // 技术受限
+  // 'relocationTime', // 生产配合
+  'rectificationTime', // 生产组织
+  'waitingStopTime', // 不可抗力
+  'winterBreakTime', // 待命
+  'partyaDesign', // 甲方设计
+  'partyaPrepare', // 甲方资源
+  'partyaResource', // 甲方准备
+  'otherNptTime' // 其它非生产时间
+]
+
+function checkTimeSumEquals24(row: List) {
+  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
+  // 对应你代码中的 waterTime
+  const waterTime = parseFloat(row.otherProductionTime + '') || 0
+
+  // 3. 计算所有非生产时间之和
+  const nonProdTime = nptFields.reduce((sum, field) => {
+    const val = parseFloat(row[field])
+    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
+  const totalSum = gasTime + waterTime + nonProdTime
+
+  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
+  return Math.abs(totalSum - 24) < 0.01
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 9000)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+  projectClassification: '1' | '2'
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  projectClassification: '1'
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ],
+    projectClassification: '1'
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                class="!w-220px"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-ry-daily-report:query']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.status === 0"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-ry-daily-report:create']"
+                      >
+                        编辑
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <ry-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 212 - 1099
src/views/pms/iotrydailyreport/index.vue

@@ -1,1153 +1,266 @@
-<template>
-  <el-row :gutter="20">
-    <el-col :span="4" :xs="24">
-      <!-- <ContentWrap class="h-1/1">
-        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
-      </ContentWrap> -->
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
-        <DeptTreeSelect
-          :deptId="rootDeptId"
-          :top-id="158"
-          v-model="queryParams.deptId"
-          @node-click="handleDeptNodeClick"
-        />
-      </div>
-    </el-col>
-    <el-col :span="20" :xs="24">
-      <ContentWrap>
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
-        >
-          <el-form-item label="项目" prop="contractName">
-            <el-input
-              v-model="queryParams.contractName"
-              placeholder="请输入项目"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item label="任务" prop="taskName">
-            <el-input
-              v-model="queryParams.taskName"
-              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"
-              :shortcuts="rangeShortcuts"
-            />
-          </el-form-item>
-          <el-form-item label="非生产时效" prop="nonProductFlag">
-            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
-          </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-ry-daily-report:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> 新增
-            </el-button> -->
-            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
-      </ContentWrap>
-
-      <ContentWrap class="mb-15px">
-        <div class="color-legend">
-          <div class="legend-item">
-            <span class="color-indicator red"></span>
-            <span>当日油耗大于9000升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;红色预警</span>
-          </div>
-          <div class="legend-item">
-            <span class="color-indicator orange"></span>
-            <span
-              >进尺工作时间+其它生产时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
-            >
-          </div>
-        </div>
-      </ContentWrap>
-
-      <!-- 列表 -->
-      <ContentWrap ref="tableContainerRef">
-        <div class="table-container">
-          <el-table
-            ref="tableRef"
-            v-loading="loading"
-            :data="list"
-            :stripe="true"
-            :style="{ width: '100%' }"
-            max-height="600"
-            :cell-style="cellStyle"
-            show-overflow-tooltip
-            border
-          >
-            <el-table-column
-              :label="t('iotDevice.serial')"
-              width="56px"
-              align="center"
-              fixed="left"
-            >
-              <template #default="scope">
-                {{ scope.$index + 1 }}
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="日期"
-              align="center"
-              prop="createTime"
-              :formatter="dateFormatter2"
-              :min-width="columnWidths.createTime.width"
-              resizable
-              fixed="left"
-            />
-            <el-table-column
-              label="施工队伍"
-              align="center"
-              prop="deptName"
-              :min-width="columnWidths.deptName.width"
-              resizable
-              fixed="left"
-            />
-
-            <el-table-column
-              label="任务"
-              align="center"
-              prop="taskName"
-              :min-width="columnWidths.taskName.width"
-              resizable
-              fixed="left"
-            />
-
-            <el-table-column
-              :label="t('project.status')"
-              align="center"
-              prop="rigStatus"
-              :min-width="columnWidths.rigStatus.width"
-              resizable
-              fixed="left"
-            >
-              <template #default="scope">
-                <dict-tag
-                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE"
-                  :value="scope.row.rigStatus"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="设备型号"
-              align="center"
-              prop="equipmentType"
-              :min-width="columnWidths.equipmentType.width"
-              resizable
-            />
-            <el-table-column
-              label="上井次完井时间"
-              align="center"
-              prop="latestWellDoneTime"
-              :min-width="columnWidths.latestWellDoneTime.width"
-              :formatter="dateFormatter2"
-              resizable
-            />
-            <el-table-column align="center" label="井深(m)">
-              <el-table-column
-                label="设计"
-                align="center"
-                prop="designWellDepth"
-                :min-width="columnWidths.designWellDepth.width"
-                resizable
-              />
-              <el-table-column
-                label="当前"
-                align="center"
-                prop="currentDepth"
-                :min-width="columnWidths.currentDepth.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column align="center" label="进尺(m)">
-              <el-table-column
-                label="日"
-                align="center"
-                prop="dailyFootage"
-                :min-width="columnWidths.dailyFootage.width"
-                resizable
-              />
-              <el-table-column
-                label="月"
-                align="center"
-                prop="monthlyFootage"
-                :min-width="columnWidths.monthlyFootage.width"
-                resizable
-              />
-              <el-table-column
-                label="年累计"
-                align="center"
-                prop="annualFootage"
-                :min-width="columnWidths.annualFootage.width"
-                resizable
-              />
-            </el-table-column>
-
-            <el-table-column
-              label="总施工井数"
-              align="center"
-              prop="totalConstructionWells"
-              :min-width="columnWidths.totalConstructionWells.width"
-              resizable
-            />
-            <el-table-column
-              label="完工井数"
-              align="center"
-              prop="completedWells"
-              :min-width="columnWidths.completedWells.width"
-              resizable
-            />
-            <el-table-column align="center" label="泥浆性能">
-              <el-table-column
-                label="密度(g/cm³)"
-                align="center"
-                prop="mudDensity"
-                :min-width="columnWidths.mudDensity.width"
-                resizable
-              />
-              <el-table-column
-                label="粘度(S)"
-                align="center"
-                prop="mudViscosity"
-                :min-width="columnWidths.mudViscosity.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column align="center" label="当日">
-              <el-table-column
-                label="用电量(kWh)"
-                align="center"
-                prop="dailyPowerUsage"
-                :min-width="columnWidths.dailyPowerUsage.width"
-                resizable
-              />
-              <el-table-column
-                label="油耗(升)"
-                align="center"
-                prop="dailyFuel"
-                :min-width="columnWidths.dailyFuel.width"
-                resizable
-              >
-                <template #default="scope">
-                  <span :class="{ 'fuel-warning': shouldShowFuelWarning(scope.row) }">
-                    {{ scope.row.dailyFuel }}
-                  </span>
-                </template>
-              </el-table-column>
-            </el-table-column>
-
-            <!-- <el-table-column
-              label="施工开始日期"
-              align="center"
-              prop="constructionStartDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionStartDate.width"
-              resizable
-            />
-            <el-table-column
-              label="施工结束日期"
-              align="center"
-              prop="constructionEndDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionEndDate.width"
-              resizable
-            /> -->
-            <el-table-column
-              label="水平段长度(m)"
-              align="center"
-              prop="lateralLength"
-              :min-width="columnWidths.lateralLength.width"
-              resizable
-            />
-            <el-table-column
-              label="井斜(°)"
-              align="center"
-              prop="wellInclination"
-              :min-width="columnWidths.wellInclination.width"
-              resizable
-            />
-            <el-table-column
-              label="方位(°)"
-              align="center"
-              prop="azimuth"
-              :min-width="columnWidths.azimuth.width"
-              resizable
-            />
-            <el-table-column
-              label="设计井身结构"
-              align="center"
-              prop="designWellStruct"
-              :min-width="columnWidths.designWellStruct.width"
-              resizable
-            />
-            <el-table-column
-              label="生产动态"
-              align="center"
-              prop="productionStatus"
-              :width="columnWidths.productionStatus.width"
-            />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              :min-width="columnWidths.contractName.width"
-              resizable
-            />
-            <el-table-column
-              label="进尺工作时间(H)"
-              align="center"
-              prop="drillingWorkingTime"
-              :min-width="columnWidths.drillingWorkingTime.width"
-              resizable
-            />
-            <el-table-column
-              label="其它生产时间(H)"
-              align="center"
-              prop="otherProductionTime"
-              :min-width="columnWidths.otherProductionTime.width"
-              resizable
-            />
-            <el-table-column
-              label="非生产时效"
-              align="center"
-              prop="nonProductionRate"
-              :formatter="nonProductionRateFormatter"
-              :min-width="columnWidths.nonProductionRate.width"
-              resizable
-            />
-            <el-table-column label="非生产时间" align="center">
-              <el-table-column
-                label="工程质量"
-                align="center"
-                prop="accidentTime"
-                :min-width="columnWidths.accidentTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备故障"
-                align="center"
-                prop="repairTime"
-                :min-width="columnWidths.repairTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备保养"
-                align="center"
-                prop="selfStopTime"
-                :min-width="columnWidths.selfStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="技术受限"
-                align="center"
-                prop="complexityTime"
-                :min-width="columnWidths.complexityTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产配合"
-                align="center"
-                prop="relocationTime"
-                :min-width="columnWidths.relocationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产组织"
-                align="center"
-                prop="rectificationTime"
-                :min-width="columnWidths.rectificationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="不可抗力"
-                align="center"
-                prop="waitingStopTime"
-                :min-width="columnWidths.waitingStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="待命"
-                align="center"
-                prop="winterBreakTime"
-                :min-width="columnWidths.winterBreakTime.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方设计"
-                align="center"
-                prop="partyaDesign"
-                :min-width="columnWidths.partyaDesign.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方准备"
-                align="center"
-                prop="partyaPrepare"
-                :min-width="columnWidths.partyaPrepare.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方资源"
-                align="center"
-                prop="partyaResource"
-                :min-width="columnWidths.partyaResource.width"
-                resizable
-              />
-              <el-table-column
-                label="其它非生产时间"
-                align="center"
-                prop="otherNptTime"
-                :min-width="columnWidths.otherNptTime.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column
-              label="其他非生产时间原因"
-              align="center"
-              prop="otherNptReason"
-              :min-width="columnWidths.otherNptReason.width"
-              resizable
-            />
-
-            <el-table-column label="操作" align="center" fixed="right">
-              <template #default="scope">
-                <el-button
-                  link
-                  type="primary"
-                  @click="openForm('update', scope.row.id, scope.row)"
-                  v-hasPermi="['pms:iot-ry-daily-report:update']"
-                >
-                  编辑
-                </el-button>
-                <el-button
-                  link
-                  type="danger"
-                  @click="handleDelete(scope.row.id)"
-                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
-                >
-                  删除
-                </el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-        <!-- 分页 -->
-        <Pagination
-          :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </ContentWrap>
-
-      <!-- 表单弹窗:添加/修改 -->
-      <IotRyDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData" />
-    </el-col>
-  </el-row>
-</template>
-
-<script setup lang="ts">
-import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
-import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
-import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
-import { DICT_TYPE } from '@/utils/dict'
-import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-
-import dayjs from 'dayjs'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
-import { useDebounceFn } from '@vueuse/core'
-
+<script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useUserStore } from '@/store/modules/user'
 import download from '@/utils/download'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import ryForm from './ry-form.vue'
+import dayjs from 'dayjs'
+import RyTable from './ry-table.vue'
 
-dayjs.extend(quarterOfYear)
-
-/** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyDailyReport' })
 
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-// 添加 selectedRowData 响应式变量
-const selectedRowData = ref<Record<string, any> | null>(null)
-
-const rootDeptId = ref(useUserStore().getUser.deptId)
-
-const loading = ref(true) // 列表的加载中
-const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-let queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  deptId: useUserStore().getUser.deptId,
-  contractName: undefined,
-  projectId: undefined,
-  taskName: undefined,
-  taskId: undefined,
-  projectClassification: '1',
-  relocationDays: undefined,
-  latestWellDoneTime: [],
-  currentDepth: undefined,
-  dailyFootage: undefined,
-  monthlyFootage: undefined,
-  annualFootage: undefined,
-  dailyPowerUsage: undefined,
-  monthlyPowerUsage: undefined,
-  dailyFuel: undefined,
-  monthlyFuel: undefined,
-  nonProductionTime: [],
-  nptReason: undefined,
-  constructionStartDate: [],
-  constructionEndDate: [],
-  productionStatus: undefined,
-  nextPlan: undefined,
-  rigStatus: undefined,
-  personnel: undefined,
-  mudDensity: undefined,
-  mudViscosity: undefined,
-  lateralLength: undefined,
-  wellInclination: undefined,
-  azimuth: undefined,
-  extProperty: undefined,
-  sort: undefined,
-  remark: undefined,
-  status: undefined,
-  processInstanceId: undefined,
-  auditStatus: undefined,
-  createTime: [
-    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-  ],
-  nonProductFlag: 'N'
-})
-const queryFormRef = ref() // 搜索的表单
-
-// 表格引用
-const tableRef = ref()
-// 表格容器引用
-const tableContainerRef = ref()
-
-const columnWidths = ref<
-  Record<
-    string,
-    { label: string; prop: string; width: string; fn?: (row: IotRyDailyReportVO) => string }
-  >
->({
-  createTime: {
-    label: '日期',
-    prop: 'createTime',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.createTime)
-  },
-  deptName: {
-    label: '施工队伍',
-    prop: 'deptName',
-    width: '120px'
-  },
-  contractName: {
-    label: '项目',
-    prop: 'contractName',
-    width: '120px'
-  },
-  taskName: {
-    label: '任务',
-    prop: 'taskName',
-    width: '120px'
-  },
-  equipmentType: {
-    label: '设备型号',
-    prop: 'equipmentType',
-    width: '120px'
-  },
-  rigStatus: {
-    label: '施工状态',
-    prop: 'rigStatus',
-    width: '120px'
-  },
-  latestWellDoneTime: {
-    label: '上井次完井时间',
-    prop: 'latestWellDoneTime',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.latestWellDoneTime)
-  },
-  designWellDepth: {
-    label: '设计',
-    prop: 'designWellDepth',
-    width: '120px'
-  },
-  currentDepth: {
-    label: '当前',
-    prop: 'currentDepth',
-    width: '120px'
-  },
-  dailyFootage: {
-    label: '日',
-    prop: 'dailyFootage',
-    width: '120px'
-  },
-  monthlyFootage: {
-    label: '月',
-    prop: 'monthlyFootage',
-    width: '120px'
-  },
-  annualFootage: {
-    label: '年累计',
-    prop: 'annualFootage',
-    width: '120px'
-  },
-  totalConstructionWells: {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    width: '120px'
-  },
-  completedWells: {
-    label: '完工井数',
-    prop: 'completedWells',
-    width: '120px'
-  },
-  mudDensity: {
-    label: '密度(g/cm³)',
-    prop: 'mudDensity',
-    width: '120px'
-  },
-  mudViscosity: {
-    label: '粘度(S)',
-    prop: 'mudViscosity',
-    width: '120px'
-  },
-  dailyPowerUsage: {
-    label: '用电量(kWh)',
-    prop: 'dailyPowerUsage',
-    width: '120px'
-  },
-  dailyFuel: {
-    label: '油耗(升)',
-    prop: 'dailyFuel',
-    width: '120px'
-  },
-  constructionStartDate: {
-    label: '施工开始日期',
-    prop: 'constructionStartDate',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
-  },
-  constructionEndDate: {
-    label: '施工结束日期',
-    prop: 'constructionEndDate',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
-  },
-  lateralLength: {
-    label: '水平段长度(m)',
-    prop: 'lateralLength',
-    width: '120px'
-  },
-  wellInclination: {
-    label: '井斜(°)',
-    prop: 'wellInclination',
-    width: '120px'
-  },
-  azimuth: {
-    label: '方位(°)',
-    prop: 'azimuth',
-    width: '120px'
-  },
-  designWellStruct: {
-    label: '设计井身结构',
-    prop: 'designWellStruct',
-    width: '120px'
-  },
-  productionStatus: {
-    label: '生产动态',
-    prop: 'productionStatus',
-    width: '120px'
-  },
-  drillingWorkingTime: {
-    label: '进尺工作时间(H)',
-    prop: 'drillingWorkingTime',
-    width: '120px'
-  },
-  otherProductionTime: {
-    label: '其它生产时间(H)',
-    prop: 'otherProductionTime',
-    width: '120px'
-  },
-  accidentTime: {
-    label: '工程质量',
-    prop: 'accidentTime',
-    width: '120px'
-  },
-  repairTime: {
-    label: '设备故障',
-    prop: 'repairTime',
-    width: '120px'
-  },
-  selfStopTime: {
-    label: '设备保养',
-    prop: 'selfStopTime',
-    width: '120px'
-  },
-  complexityTime: {
-    label: '技术受限',
-    prop: 'complexityTime',
-    width: '120px'
-  },
-  relocationTime: {
-    label: '生产配合',
-    prop: 'relocationTime',
-    width: '120px'
-  },
-  rectificationTime: {
-    label: '生产组织',
-    prop: 'rectificationTime',
-    width: '120px'
-  },
-  waitingStopTime: {
-    label: '不可抗力',
-    prop: 'waitingStopTime',
-    width: '120px'
-  },
-  winterBreakTime: {
-    label: '待命',
-    prop: 'winterBreakTime',
-    width: '120px'
-  },
-  partyaDesign: {
-    label: '甲方设计',
-    prop: 'partyaDesign',
-    width: '120px'
-  },
-  partyaPrepare: {
-    label: '甲方资源',
-    prop: 'partyaPrepare',
-    width: '120px'
-  },
-  partyaResource: {
-    label: '甲方准备',
-    prop: 'partyaResource',
-    width: '120px'
-  },
-  otherNptTime: {
-    label: '其它非生产时间',
-    prop: 'otherNptTime',
-    width: '120px'
-  },
-  otherNptReason: {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    width: '120px'
-  },
-  nonProductionRate: {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    width: '120px',
-    fn: (row: any) => nonProductionRateFormatter(row)
-  }
-})
+const { t } = useI18n()
 
-const nonProductionRateFormatter = (row: any) => {
-  const nonProductionRate = row?.nonProductionRate ?? 0
+const route = useRoute()
 
-  return (nonProductionRate * 100).toFixed(2) + '%'
-}
+const message = useMessage()
 
-// 计算文本宽度
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
+const id = useUserStore().getUser.deptId
 
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
+const deptId = id
 
-  return width
+interface Query {
+  deptId?: number
+  contractName?: string
+  taskName?: string
+  createTime?: string[]
+  pageNo: number
+  pageSize: number
+  nonProductFlag?: string
+  projectClassification: string
 }
 
-const calculateColumnWidths = useDebounceFn(() => {
-  if (!tableContainerRef.value?.$el) return
-  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
-    width =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(fn ? fn(v) : v[prop])
-              })
-            ]
-          ) + (label === '施工状态' ? 30 : 20),
-          200
-        ]
-      ) + 'px'
-
-    columnWidths.value[prop].width = width
-  })
-}, 1000)
-
-const nptFields = [
-  'accidentTime', // 工程质量
-  'repairTime', // 设备故障
-  'selfStopTime', // 设备保养
-  'complexityTime', // 技术受限
-  'relocationTime', // 生产配合
-  'rectificationTime', // 生产组织
-  'waitingStopTime', // 不可抗力
-  'winterBreakTime', // 待命
-  'partyaDesign', // 甲方设计
-  'partyaPrepare', // 甲方资源
-  'partyaResource', // 甲方准备
-  'otherNptTime' // 其它非生产时间
-]
-
-function checkTimeSumEquals24(row) {
-  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
-  // 对应你代码中的 waterTime
-  const waterTime = parseFloat(row.otherProductionTime + '') || 0
-
-  // 3. 计算所有非生产时间之和
-  const nonProdTime = nptFields.reduce((sum, field) => {
-    const val = parseFloat(row[field])
-    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
-    return sum + (isNaN(val) ? 0 : val)
-  }, 0)
-
-  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
-  const totalSum = gasTime + waterTime + nonProdTime
-
-  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
-  return Math.abs(totalSum - 24) < 0.01
+const initQuery: Query = {
+  pageNo: 1,
+  pageSize: 10,
+  projectClassification: '1',
+  deptId: route.query.deptId ? Number(route.query.deptId) : id,
+  createTime: route.query.createTime
+    ? (route.query.createTime as string[])
+    : [...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))],
+  nonProductFlag: route.query.nonProductFlag ? (route.query.nonProductFlag as string) : undefined
 }
 
-// 单元格样式函数
-const cellStyle = ({
-  row,
-  column,
-  rowIndex,
-  columnIndex
-}: {
-  row: any
-  column: any
-  rowIndex: number
-  columnIndex: number
-}) => {
-  // 处理当日油耗预警
-  if (column.property === 'dailyFuel') {
-    if (shouldShowFuelWarning(row)) {
-      return {
-        color: 'red',
-        fontWeight: 'bold',
-        backgroundColor: '#fff5f5' // 可选:添加背景色突出显示
-      }
-    }
-  }
+const query = ref<Query>({ ...initQuery })
 
-  // 处理三个时间字段:当日注气时间、当日注水时间、非生产时间
-  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
-  if (timeFields.includes(column.property)) {
-    if (!checkTimeSumEquals24(row)) {
-      return {
-        color: 'orange',
-        fontWeight: 'bold'
-      }
-    }
-  }
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+const list = ref<any[]>([])
+const total = ref(0)
 
-// 可伸缩列配置
+const loading = ref(false)
 
-/** 查询列表 */
-const getList = async () => {
+const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
-    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(queryParams)
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-    // 获取数据后计算列宽
-    nextTick(() => {
-      calculateColumnWidths()
-    })
   } finally {
     loading.value = false
   }
-}
-
-// 在 cellStyle 函数附近添加油耗预警判断函数
-const shouldShowFuelWarning = (row: any): boolean => {
-  const dailyFuel = parseFloat(row.dailyFuel)
-  return !isNaN(dailyFuel) && dailyFuel > 9000
-}
-
-// 计算列宽度
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
+})
 
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  queryParams.deptId = useUserStore().getUser.deptId
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
   handleQuery()
 }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number, row?: any) => {
-  // 保存当前行数据
-  if (row) {
-    selectedRowData.value = {
-      deptName: row.deptName,
-      contractName: row.contractName,
-      taskName: row.taskName,
-      designWellDepth: row.designWellDepth,
-      designWellStruct: row.designWellStruct,
-      totalConstructionWells: row.totalConstructionWells,
-      completedWells: row.completedWells
-    }
-  } else {
-    selectedRowData.value = null
-  }
-
-  formRef.value.open(type, id)
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
 }
 
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-// 响应式变量存储选中的部门
-const selectedDept = ref<{ id: number; name: string }>()
-/** 处理部门被点击 */
-const handleDeptNodeClick = async (row) => {
-  // 记录选中的部门信息
-  selectedDept.value = { id: row.id, name: row.name }
-  // queryParams.deptId = row.id
-  await getList()
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
 }
 
-const exportLoading = ref(false)
-const handleExport = async () => {
-  const res = await IotRyDailyReportApi.exportIotRyDailyReport({
-    createTime: queryParams.createTime,
-    contractName: queryParams.contractName,
-    taskName: queryParams.taskName,
-    // pageNo: queryParams.pageNo,
-    // pageSize: queryParams.pageSize,
-    deptId: queryParams.deptId,
-    projectClassification: queryParams.projectClassification
-  })
+function resetQuery() {
+  query.value = { ...initQuery }
 
-  download.excel(res, '瑞鹰钻井日报.xlsx')
+  handleQuery()
 }
-// 声明 ResizeObserver 实例
-let resizeObserver: ResizeObserver | null = null
-
-const route = useRoute()
-
-/** 初始化 **/
-onMounted(() => {
-  if (Object.keys(route.query).length > 0) {
-    nextTick(() => {
-      queryParams.deptId = Number(route.query.deptId) as any
-      queryParams.createTime = route.query.createTime as string[]
-      queryParams.nonProductFlag = route.query.nonProductFlag as string
-      handleQuery()
-    })
-  } else 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,
+  [
+    () => query.value.deptId,
+    () => query.value.contractName,
+    () => query.value.taskName,
+    () => query.value.createTime,
+    () => query.value.nonProductFlag
+  ],
   () => {
-    nextTick(calculateColumnWidths)
+    handleQuery()
   },
-  { deep: true }
+  { immediate: true }
 )
-</script>
-
-<style scoped>
-/* 表格容器样式,确保水平滚动 */
-.table-container {
-  width: 100%;
-  overflow-x: auto;
-}
 
-/* 确保表格单元格内容不换行 */
-
-/* :deep(.el-table .cell) {
-  white-space: nowrap;
-} */
-
-/* 确保表格列标题不换行 */
-
-/* :deep(.el-table th > .cell) {
-  white-space: nowrap;
-} */
-
-/* 调整表格最小宽度,确保内容完全显示 */
-:deep(.el-table) {
-  min-width: 100%;
-}
-
-/* 强制显示所有内容,防止省略号 */
-
-/* :deep(.el-table td.el-table__cell),
-:deep(.el-table th.el-table__cell) {
-  overflow: visible !important;
-} */
-
-/* :deep(.el-table .cell) {
-  overflow: visible !important;
-  text-overflow: clip !important;
-} */
-
-/* 设计井身结构文本样式 - 多行显示并添加省略号 */
-.design-well-struct-text {
-  display: -webkit-box;
-  max-height: 3em; /* 两行文本的高度 */
-  overflow: hidden;
-  line-height: 1.5;
-  text-overflow: ellipsis;
-  -webkit-box-orient: vertical;
-  -webkit-line-clamp: 2;
-}
+const exportLoading = ref(false)
 
-/* 确保设计井身结构列不参与自动调整 */
-:deep(.el-table__header-wrapper .el-table__cell.fixed-width),
-:deep(.el-table__body-wrapper .el-table__cell.fixed-width) {
-  flex-shrink: 0;
-  flex-grow: 0;
-}
+async function handleExport() {
+  try {
+    await message.exportConfirm()
 
-/* 颜色说明区域样式 */
-.color-legend {
-  display: flex;
-  padding: 12px 16px;
-  background-color: #f8f9fa;
-  border-left: 4px solid #e6f7ff;
-  border-radius: 4px;
-  flex-direction: column;
-  gap: 8px;
-}
+    exportLoading.value = true
+    const res = await IotRyDailyReportApi.exportIotRyDailyReport(query.value)
 
-.legend-item {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  font-size: 14px;
+    download.excel(res, '瑞鹰钻井日报.xlsx')
+  } finally {
+    exportLoading.value = false
+  }
 }
 
-.color-indicator {
-  display: inline-block;
-  width: 12px;
-  height: 12px;
-  border-radius: 50%;
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
+    message.success(t('common.delSuccess'))
+    await loadList()
+  } catch {}
 }
 
-.color-indicator.red {
-  background-color: red;
-}
+const visible = ref(false)
 
-.color-indicator.orange {
-  background-color: orange;
-}
-</style>
+const formRef = ref()
 
-<style>
-/* 设计井身结构 tooltip 样式 - 保留换行符 */
-.design-well-struct-tooltip {
-  max-width: 500px;
-  line-height: 1.5;
-  white-space: pre-line;
-}
-.color-indicator.red {
-  background-color: red;
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
 }
+</script>
 
-/* 当日油耗预警样式 */
-.fuel-warning {
-  color: red !important;
-  font-weight: bold;
-  animation: pulse 1.5s infinite;
-}
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[48px_auto_1fr] gap-x-4 gap-y-3 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-3">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="query.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"
+            :shortcuts="rangeShortcuts"
+          />
+        </el-form-item>
+        <el-form-item label="非生产时效" prop="nonProductFlag">
+          <el-switch v-model="query.nonProductFlag" active-value="Y" inactive-value="N" />
+        </el-form-item>
+      </div>
+      <el-form-item>
+        <el-button type="primary" @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-ry-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-rh-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="p-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow flex flex-col gap-2">
+      <el-alert
+        class="h-8!"
+        title="当日油耗大于9000升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;红色预警"
+        type="error"
+        show-icon
+        :closable="false"
+      />
+      <el-alert
+        class="h-8!"
+        title="进尺工作时间+其它生产时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警"
+        type="warning"
+        show-icon
+        :closable="false"
+      />
+    </div>
+    <ry-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      is-index
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:update']"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="danger"
+          @click="handleDelete(row.id)"
+          v-hasPermi="['pms:iot-ry-daily-report:delete']"
+        >
+          删除
+        </el-button>
+      </template>
+    </ry-table>
+  </div>
+
+  <ry-form
+    v-model:visible="visible"
+    type="edit"
+    ref="formRef"
+    :load-list="loadList"
+    no-validate-status
+  />
+</template>
 
-/* 确保表格中的预警样式不被覆盖 */
-:deep(.el-table .cell .fuel-warning) {
-  color: red !important;
-  font-weight: bold !important;
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
 }
 </style>

+ 1169 - 0
src/views/pms/iotrydailyreport/index1.vue

@@ -0,0 +1,1169 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="4" :xs="24">
+      <!-- <ContentWrap class="h-1/1">
+        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
+      </ContentWrap> -->
+      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
+        <DeptTreeSelect
+          :deptId="rootDeptId"
+          :top-id="158"
+          v-model="queryParams.deptId"
+          @node-click="handleDeptNodeClick"
+        />
+      </div>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="80px"
+        >
+          <el-form-item label="项目" prop="contractName">
+            <el-input
+              v-model="queryParams.contractName"
+              placeholder="请输入项目"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input
+              v-model="queryParams.taskName"
+              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"
+              :shortcuts="rangeShortcuts"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时效" prop="nonProductFlag">
+            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
+          </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-ry-daily-report:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 新增
+            </el-button> -->
+            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+              <Icon icon="ep:download" class="mr-5px" /> 导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <ContentWrap class="mb-15px">
+        <div class="color-legend">
+          <div class="legend-item">
+            <span class="color-indicator red"></span>
+            <span>当日油耗大于9000升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;红色预警</span>
+          </div>
+          <div class="legend-item">
+            <span class="color-indicator orange"></span>
+            <span
+              >进尺工作时间+其它生产时间+非生产时间=24H&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
+            >
+          </div>
+        </div>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap ref="tableContainerRef">
+        <div class="table-container">
+          <el-table
+            ref="tableRef"
+            v-loading="loading"
+            :data="list"
+            :stripe="true"
+            :style="{ width: '100%' }"
+            max-height="600"
+            :cell-style="cellStyle"
+            show-overflow-tooltip
+            border
+          >
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
+              <template #default="scope">
+                {{ scope.$index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="日期"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter2"
+              :min-width="columnWidths.createTime.width"
+              resizable
+              fixed="left"
+            />
+            <el-table-column
+              label="施工队伍"
+              align="center"
+              prop="deptName"
+              :min-width="columnWidths.deptName.width"
+              resizable
+              fixed="left"
+            />
+
+            <el-table-column
+              label="任务"
+              align="center"
+              prop="taskName"
+              :min-width="columnWidths.taskName.width"
+              resizable
+              fixed="left"
+            />
+
+            <el-table-column
+              :label="t('project.status')"
+              align="center"
+              prop="rigStatus"
+              :min-width="columnWidths.rigStatus.width"
+              resizable
+              fixed="left"
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE"
+                  :value="scope.row.rigStatus"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="设备型号"
+              align="center"
+              prop="equipmentType"
+              :min-width="columnWidths.equipmentType.width"
+              resizable
+            />
+            <el-table-column
+              label="上井次完井时间"
+              align="center"
+              prop="latestWellDoneTime"
+              :min-width="columnWidths.latestWellDoneTime.width"
+              :formatter="dateFormatter2"
+              resizable
+            />
+            <el-table-column align="center" label="井深(m)">
+              <el-table-column
+                label="设计"
+                align="center"
+                prop="designWellDepth"
+                :min-width="columnWidths.designWellDepth.width"
+                resizable
+              />
+              <el-table-column
+                label="当前"
+                align="center"
+                prop="currentDepth"
+                :min-width="columnWidths.currentDepth.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column align="center" label="进尺(m)">
+              <el-table-column
+                label="日"
+                align="center"
+                prop="dailyFootage"
+                :min-width="columnWidths.dailyFootage.width"
+                resizable
+              />
+              <el-table-column
+                label="月"
+                align="center"
+                prop="monthlyFootage"
+                :min-width="columnWidths.monthlyFootage.width"
+                resizable
+              />
+              <el-table-column
+                label="年累计"
+                align="center"
+                prop="annualFootage"
+                :min-width="columnWidths.annualFootage.width"
+                resizable
+              />
+            </el-table-column>
+
+            <el-table-column
+              label="总施工井数"
+              align="center"
+              prop="totalConstructionWells"
+              :min-width="columnWidths.totalConstructionWells.width"
+              resizable
+            />
+            <el-table-column
+              label="完工井数"
+              align="center"
+              prop="completedWells"
+              :min-width="columnWidths.completedWells.width"
+              resizable
+            />
+            <el-table-column align="center" label="泥浆性能">
+              <el-table-column
+                label="密度(g/cm³)"
+                align="center"
+                prop="mudDensity"
+                :min-width="columnWidths.mudDensity.width"
+                resizable
+              />
+              <el-table-column
+                label="粘度(S)"
+                align="center"
+                prop="mudViscosity"
+                :min-width="columnWidths.mudViscosity.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column align="center" label="当日">
+              <el-table-column
+                label="用电量(kWh)"
+                align="center"
+                prop="dailyPowerUsage"
+                :min-width="columnWidths.dailyPowerUsage.width"
+                resizable
+              />
+              <el-table-column
+                label="油耗(升)"
+                align="center"
+                prop="dailyFuel"
+                :min-width="columnWidths.dailyFuel.width"
+                resizable
+              >
+                <template #default="scope">
+                  <span :class="{ 'fuel-warning': shouldShowFuelWarning(scope.row) }">
+                    {{ scope.row.dailyFuel }}
+                  </span>
+                </template>
+              </el-table-column>
+            </el-table-column>
+
+            <!-- <el-table-column
+              label="施工开始日期"
+              align="center"
+              prop="constructionStartDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionStartDate.width"
+              resizable
+            />
+            <el-table-column
+              label="施工结束日期"
+              align="center"
+              prop="constructionEndDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionEndDate.width"
+              resizable
+            /> -->
+            <el-table-column
+              label="水平段长度(m)"
+              align="center"
+              prop="lateralLength"
+              :min-width="columnWidths.lateralLength.width"
+              resizable
+            />
+            <el-table-column
+              label="井斜(°)"
+              align="center"
+              prop="wellInclination"
+              :min-width="columnWidths.wellInclination.width"
+              resizable
+            />
+            <el-table-column
+              label="方位(°)"
+              align="center"
+              prop="azimuth"
+              :min-width="columnWidths.azimuth.width"
+              resizable
+            />
+            <el-table-column
+              label="设计井身结构"
+              align="center"
+              prop="designWellStruct"
+              :min-width="columnWidths.designWellStruct.width"
+              resizable
+            />
+            <el-table-column
+              label="生产动态"
+              align="center"
+              prop="productionStatus"
+              :width="columnWidths.productionStatus.width"
+            />
+            <el-table-column
+              label="项目"
+              align="center"
+              prop="contractName"
+              :min-width="columnWidths.contractName.width"
+              resizable
+            />
+            <el-table-column
+              label="进尺工作时间(H)"
+              align="center"
+              prop="drillingWorkingTime"
+              :min-width="columnWidths.drillingWorkingTime.width"
+              resizable
+            />
+            <el-table-column
+              label="其它生产时间(H)"
+              align="center"
+              prop="otherProductionTime"
+              :min-width="columnWidths.otherProductionTime.width"
+              resizable
+            />
+            <el-table-column
+              label="非生产时效"
+              align="center"
+              prop="nonProductionRate"
+              :formatter="nonProductionRateFormatter"
+              :min-width="columnWidths.nonProductionRate.width"
+              resizable
+            />
+            <el-table-column label="非生产时间" align="center">
+              <el-table-column
+                label="工程质量"
+                align="center"
+                prop="accidentTime"
+                :min-width="columnWidths.accidentTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备故障"
+                align="center"
+                prop="repairTime"
+                :min-width="columnWidths.repairTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备保养"
+                align="center"
+                prop="selfStopTime"
+                :min-width="columnWidths.selfStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="技术受限"
+                align="center"
+                prop="complexityTime"
+                :min-width="columnWidths.complexityTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产配合"
+                align="center"
+                prop="relocationTime"
+                :min-width="columnWidths.relocationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产组织"
+                align="center"
+                prop="rectificationTime"
+                :min-width="columnWidths.rectificationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="不可抗力"
+                align="center"
+                prop="waitingStopTime"
+                :min-width="columnWidths.waitingStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="待命"
+                align="center"
+                prop="winterBreakTime"
+                :min-width="columnWidths.winterBreakTime.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方设计"
+                align="center"
+                prop="partyaDesign"
+                :min-width="columnWidths.partyaDesign.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方准备"
+                align="center"
+                prop="partyaPrepare"
+                :min-width="columnWidths.partyaPrepare.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方资源"
+                align="center"
+                prop="partyaResource"
+                :min-width="columnWidths.partyaResource.width"
+                resizable
+              />
+              <el-table-column
+                label="其它非生产时间"
+                align="center"
+                prop="otherNptTime"
+                :min-width="columnWidths.otherNptTime.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column
+              label="其他非生产时间原因"
+              align="center"
+              prop="otherNptReason"
+              :min-width="columnWidths.otherNptReason.width"
+              resizable
+            />
+
+            <el-table-column label="操作" align="center" fixed="right">
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  @click="handleOpenForm(scope.row.id, 'edit')"
+                  v-hasPermi="['pms:iot-ry-daily-report:update']"
+                >
+                  编辑
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  @click="handleDelete(scope.row.id)"
+                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+
+      <!-- 表单弹窗:添加/修改 -->
+      <ry-form
+        v-model:visible="visible"
+        type="edit"
+        ref="formRef"
+        :load-list="getList"
+        no-validate-status
+      />
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import ryForm from './ry-form.vue'
+import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
+import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
+import { DICT_TYPE } from '@/utils/dict'
+import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
+
+import dayjs from 'dayjs'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+import { useDebounceFn } from '@vueuse/core'
+
+import { useUserStore } from '@/store/modules/user'
+import download from '@/utils/download'
+
+dayjs.extend(quarterOfYear)
+
+/** 瑞鹰日报 列表 */
+defineOptions({ name: 'IotRyDailyReport' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+// 添加 selectedRowData 响应式变量
+const selectedRowData = ref<Record<string, any> | null>(null)
+
+const rootDeptId = ref(useUserStore().getUser.deptId)
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: useUserStore().getUser.deptId,
+  contractName: undefined,
+  projectId: undefined,
+  taskName: undefined,
+  taskId: undefined,
+  projectClassification: '1',
+  relocationDays: undefined,
+  latestWellDoneTime: [],
+  currentDepth: undefined,
+  dailyFootage: undefined,
+  monthlyFootage: undefined,
+  annualFootage: undefined,
+  dailyPowerUsage: undefined,
+  monthlyPowerUsage: undefined,
+  dailyFuel: undefined,
+  monthlyFuel: undefined,
+  nonProductionTime: [],
+  nptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  nextPlan: undefined,
+  rigStatus: undefined,
+  personnel: undefined,
+  mudDensity: undefined,
+  mudViscosity: undefined,
+  lateralLength: undefined,
+  wellInclination: undefined,
+  azimuth: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  nonProductFlag: 'N'
+})
+const queryFormRef = ref() // 搜索的表单
+
+// 表格引用
+const tableRef = ref()
+// 表格容器引用
+const tableContainerRef = ref()
+
+const columnWidths = ref<
+  Record<
+    string,
+    { label: string; prop: string; width: string; fn?: (row: IotRyDailyReportVO) => string }
+  >
+>({
+  createTime: {
+    label: '日期',
+    prop: 'createTime',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.createTime)
+  },
+  deptName: {
+    label: '施工队伍',
+    prop: 'deptName',
+    width: '120px'
+  },
+  contractName: {
+    label: '项目',
+    prop: 'contractName',
+    width: '120px'
+  },
+  taskName: {
+    label: '任务',
+    prop: 'taskName',
+    width: '120px'
+  },
+  equipmentType: {
+    label: '设备型号',
+    prop: 'equipmentType',
+    width: '120px'
+  },
+  rigStatus: {
+    label: '施工状态',
+    prop: 'rigStatus',
+    width: '120px'
+  },
+  latestWellDoneTime: {
+    label: '上井次完井时间',
+    prop: 'latestWellDoneTime',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.latestWellDoneTime)
+  },
+  designWellDepth: {
+    label: '设计',
+    prop: 'designWellDepth',
+    width: '120px'
+  },
+  currentDepth: {
+    label: '当前',
+    prop: 'currentDepth',
+    width: '120px'
+  },
+  dailyFootage: {
+    label: '日',
+    prop: 'dailyFootage',
+    width: '120px'
+  },
+  monthlyFootage: {
+    label: '月',
+    prop: 'monthlyFootage',
+    width: '120px'
+  },
+  annualFootage: {
+    label: '年累计',
+    prop: 'annualFootage',
+    width: '120px'
+  },
+  totalConstructionWells: {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    width: '120px'
+  },
+  completedWells: {
+    label: '完工井数',
+    prop: 'completedWells',
+    width: '120px'
+  },
+  mudDensity: {
+    label: '密度(g/cm³)',
+    prop: 'mudDensity',
+    width: '120px'
+  },
+  mudViscosity: {
+    label: '粘度(S)',
+    prop: 'mudViscosity',
+    width: '120px'
+  },
+  dailyPowerUsage: {
+    label: '用电量(kWh)',
+    prop: 'dailyPowerUsage',
+    width: '120px'
+  },
+  dailyFuel: {
+    label: '油耗(升)',
+    prop: 'dailyFuel',
+    width: '120px'
+  },
+  constructionStartDate: {
+    label: '施工开始日期',
+    prop: 'constructionStartDate',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
+  },
+  constructionEndDate: {
+    label: '施工结束日期',
+    prop: 'constructionEndDate',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
+  },
+  lateralLength: {
+    label: '水平段长度(m)',
+    prop: 'lateralLength',
+    width: '120px'
+  },
+  wellInclination: {
+    label: '井斜(°)',
+    prop: 'wellInclination',
+    width: '120px'
+  },
+  azimuth: {
+    label: '方位(°)',
+    prop: 'azimuth',
+    width: '120px'
+  },
+  designWellStruct: {
+    label: '设计井身结构',
+    prop: 'designWellStruct',
+    width: '120px'
+  },
+  productionStatus: {
+    label: '生产动态',
+    prop: 'productionStatus',
+    width: '120px'
+  },
+  drillingWorkingTime: {
+    label: '进尺工作时间(H)',
+    prop: 'drillingWorkingTime',
+    width: '120px'
+  },
+  otherProductionTime: {
+    label: '其它生产时间(H)',
+    prop: 'otherProductionTime',
+    width: '120px'
+  },
+  accidentTime: {
+    label: '工程质量',
+    prop: 'accidentTime',
+    width: '120px'
+  },
+  repairTime: {
+    label: '设备故障',
+    prop: 'repairTime',
+    width: '120px'
+  },
+  selfStopTime: {
+    label: '设备保养',
+    prop: 'selfStopTime',
+    width: '120px'
+  },
+  complexityTime: {
+    label: '技术受限',
+    prop: 'complexityTime',
+    width: '120px'
+  },
+  relocationTime: {
+    label: '生产配合',
+    prop: 'relocationTime',
+    width: '120px'
+  },
+  rectificationTime: {
+    label: '生产组织',
+    prop: 'rectificationTime',
+    width: '120px'
+  },
+  waitingStopTime: {
+    label: '不可抗力',
+    prop: 'waitingStopTime',
+    width: '120px'
+  },
+  winterBreakTime: {
+    label: '待命',
+    prop: 'winterBreakTime',
+    width: '120px'
+  },
+  partyaDesign: {
+    label: '甲方设计',
+    prop: 'partyaDesign',
+    width: '120px'
+  },
+  partyaPrepare: {
+    label: '甲方资源',
+    prop: 'partyaPrepare',
+    width: '120px'
+  },
+  partyaResource: {
+    label: '甲方准备',
+    prop: 'partyaResource',
+    width: '120px'
+  },
+  otherNptTime: {
+    label: '其它非生产时间',
+    prop: 'otherNptTime',
+    width: '120px'
+  },
+  otherNptReason: {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    width: '120px'
+  },
+  nonProductionRate: {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    width: '120px',
+    fn: (row: any) => nonProductionRateFormatter(row)
+  }
+})
+
+const nonProductionRateFormatter = (row: any) => {
+  const nonProductionRate = row?.nonProductionRate ?? 0
+
+  return (nonProductionRate * 100).toFixed(2) + '%'
+}
+
+// 计算文本宽度
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = useDebounceFn(() => {
+  if (!tableContainerRef.value?.$el) return
+  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
+    width =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(fn ? fn(v) : v[prop])
+              })
+            ]
+          ) + (label === '施工状态' ? 30 : 20),
+          200
+        ]
+      ) + 'px'
+
+    columnWidths.value[prop].width = width
+  })
+}, 1000)
+
+const nptFields = [
+  'accidentTime', // 工程质量
+  'repairTime', // 设备故障
+  'selfStopTime', // 设备保养
+  'complexityTime', // 技术受限
+  // 'relocationTime', // 生产配合
+  'rectificationTime', // 生产组织
+  'waitingStopTime', // 不可抗力
+  'winterBreakTime', // 待命
+  'partyaDesign', // 甲方设计
+  'partyaPrepare', // 甲方资源
+  'partyaResource', // 甲方准备
+  'otherNptTime' // 其它非生产时间
+]
+
+function checkTimeSumEquals24(row) {
+  const gasTime = parseFloat(row.drillingWorkingTime + '') || 0
+  // 对应你代码中的 waterTime
+  const waterTime = parseFloat(row.otherProductionTime + '') || 0
+
+  // 3. 计算所有非生产时间之和
+  const nonProdTime = nptFields.reduce((sum, field) => {
+    const val = parseFloat(row[field])
+    // 如果值是数字则累加,如果是 NaN 或 null/undefined 则加 0
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  // 4. 计算总和:纯钻进 + 其他生产 + 所有非生产时间
+  const totalSum = gasTime + waterTime + nonProdTime
+
+  // 5. 返回是否等于 24(允许 0.01 的浮点数误差)
+  return Math.abs(totalSum - 24) < 0.01
+}
+
+// 单元格样式函数
+const cellStyle = ({
+  row,
+  column,
+  rowIndex,
+  columnIndex
+}: {
+  row: any
+  column: any
+  rowIndex: number
+  columnIndex: number
+}) => {
+  // 处理当日油耗预警
+  if (column.property === 'dailyFuel') {
+    if (shouldShowFuelWarning(row)) {
+      return {
+        color: 'red',
+        fontWeight: 'bold',
+        backgroundColor: '#fff5f5' // 可选:添加背景色突出显示
+      }
+    }
+  }
+
+  // 处理三个时间字段:当日注气时间、当日注水时间、非生产时间
+  const timeFields = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+// 可伸缩列配置
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+    // 获取数据后计算列宽
+    nextTick(() => {
+      calculateColumnWidths()
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 在 cellStyle 函数附近添加油耗预警判断函数
+const shouldShowFuelWarning = (row: any): boolean => {
+  const dailyFuel = parseFloat(row.dailyFuel)
+  return !isNaN(dailyFuel) && dailyFuel > 9000
+}
+
+// 计算列宽度
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
+  handleQuery()
+}
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+/** 添加/修改操作 */
+// const formRef = ref()
+// const openForm = (type: string, id?: number, row?: any) => {
+//   // 保存当前行数据
+//   if (row) {
+//     selectedRowData.value = {
+//       deptName: row.deptName,
+//       contractName: row.contractName,
+//       taskName: row.taskName,
+//       designWellDepth: row.designWellDepth,
+//       designWellStruct: row.designWellStruct,
+//       totalConstructionWells: row.totalConstructionWells,
+//       completedWells: row.completedWells
+//     }
+//   } else {
+//     selectedRowData.value = null
+//   }
+
+//   formRef.value.open(type, id)
+// }
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+// 响应式变量存储选中的部门
+const selectedDept = ref<{ id: number; name: string }>()
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  // 记录选中的部门信息
+  selectedDept.value = { id: row.id, name: row.name }
+  // queryParams.deptId = row.id
+  await getList()
+}
+
+const exportLoading = ref(false)
+const handleExport = async () => {
+  const res = await IotRyDailyReportApi.exportIotRyDailyReport({
+    createTime: queryParams.createTime,
+    contractName: queryParams.contractName,
+    taskName: queryParams.taskName,
+    // pageNo: queryParams.pageNo,
+    // pageSize: queryParams.pageSize,
+    deptId: queryParams.deptId,
+    projectClassification: queryParams.projectClassification
+  })
+
+  download.excel(res, '瑞鹰钻井日报.xlsx')
+}
+// 声明 ResizeObserver 实例
+let resizeObserver: ResizeObserver | null = null
+
+const route = useRoute()
+
+/** 初始化 **/
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    nextTick(() => {
+      queryParams.deptId = Number(route.query.deptId) as any
+      queryParams.createTime = route.query.createTime as string[]
+      queryParams.nonProductFlag = route.query.nonProductFlag as string
+      handleQuery()
+    })
+  } else 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>
+/* 表格容器样式,确保水平滚动 */
+.table-container {
+  width: 100%;
+  overflow-x: auto;
+}
+
+/* 确保表格单元格内容不换行 */
+
+/* :deep(.el-table .cell) {
+  white-space: nowrap;
+} */
+
+/* 确保表格列标题不换行 */
+
+/* :deep(.el-table th > .cell) {
+  white-space: nowrap;
+} */
+
+/* 调整表格最小宽度,确保内容完全显示 */
+:deep(.el-table) {
+  min-width: 100%;
+}
+
+/* 强制显示所有内容,防止省略号 */
+
+/* :deep(.el-table td.el-table__cell),
+:deep(.el-table th.el-table__cell) {
+  overflow: visible !important;
+} */
+
+/* :deep(.el-table .cell) {
+  overflow: visible !important;
+  text-overflow: clip !important;
+} */
+
+/* 设计井身结构文本样式 - 多行显示并添加省略号 */
+.design-well-struct-text {
+  display: -webkit-box;
+  max-height: 3em; /* 两行文本的高度 */
+  overflow: hidden;
+  line-height: 1.5;
+  text-overflow: ellipsis;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+}
+
+/* 确保设计井身结构列不参与自动调整 */
+:deep(.el-table__header-wrapper .el-table__cell.fixed-width),
+:deep(.el-table__body-wrapper .el-table__cell.fixed-width) {
+  flex-shrink: 0;
+  flex-grow: 0;
+}
+
+/* 颜色说明区域样式 */
+.color-legend {
+  display: flex;
+  padding: 12px 16px;
+  background-color: #f8f9fa;
+  border-left: 4px solid #e6f7ff;
+  border-radius: 4px;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+}
+
+.color-indicator {
+  display: inline-block;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+}
+
+.color-indicator.red {
+  background-color: red;
+}
+
+.color-indicator.orange {
+  background-color: orange;
+}
+</style>
+
+<style>
+/* 设计井身结构 tooltip 样式 - 保留换行符 */
+.design-well-struct-tooltip {
+  max-width: 500px;
+  line-height: 1.5;
+  white-space: pre-line;
+}
+.color-indicator.red {
+  background-color: red;
+}
+
+/* 当日油耗预警样式 */
+.fuel-warning {
+  color: red !important;
+  font-weight: bold;
+  animation: pulse 1.5s infinite;
+}
+
+/* 确保表格中的预警样式不被覆盖 */
+:deep(.el-table .cell .fuel-warning) {
+  color: red !important;
+  font-weight: bold !important;
+}
+</style>

+ 290 - 23
src/views/pms/iotrydailyreport/ry-form.vue

@@ -1,17 +1,24 @@
 <script lang="ts" setup generic="T">
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import { FormInstance, FormRules } from 'element-plus'
 import { computed, reactive, ref, watch, nextTick } from 'vue'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import dayjs from 'dayjs'
+import { calculateDuration, formatT } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
 
 interface Props {
   visible: boolean
   type?: 'edit' | 'approval' | 'readonly'
   loadList: () => void
+  noValidateStatus?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  type: 'edit'
+  type: 'edit',
+  noValidateStatus: false
 })
 
 const emits = defineEmits(['update:visible'])
@@ -32,6 +39,15 @@ const NON_PROD_FIELDS = [
   { key: 'otherNptTime', label: '其他非生产时间' }
 ] as const
 
+interface ReportDetail {
+  startTime: string
+  endTime: string
+  duration: number
+  currentDepth: number
+  currentOperation: string
+  constructionDetail: string
+}
+
 interface FormOriginal {
   id: number
   deptId: number
@@ -72,8 +88,9 @@ interface FormOriginal {
 
   // 其他非生产时间原因(仅作为备注字段存在)
   otherNptReason: string
+  constructionBrief: string
 
-  productionStatus: string
+  reportDetails: ReportDetail[]
   remark: string
   createTime: string
   opinion: string
@@ -105,11 +122,10 @@ const FORM_KEYS: (keyof FormOriginal)[] = [
   'drillingWorkingTime',
   'otherProductionTime',
   'lastCurrentDepth',
-
-  'productionStatus',
+  'constructionBrief',
   'remark',
   'createTime',
-
+  'reportDetails',
   'opinion',
   'repairTime',
   'selfStopTime',
@@ -169,15 +185,61 @@ async function loadDetail(id: number) {
       }
     })
 
-    if (props.type === 'edit' && res.status !== 0) formType.value = 'readonly'
-    if (props.type === 'approval' && res.auditStatus !== 10) formType.value = 'readonly'
+    form.value.reportDetails = (res.reportDetails ? (res.reportDetails as any[]) : []).map(
+      (item) => ({
+        startTime: formatT(item.startTime),
+        endTime: formatT(item.endTime),
+        duration: item.duration,
+        currentDepth: item.currentDepth,
+        currentOperation: item.currentOperation,
+        constructionDetail: item.constructionDetail
+      })
+    )
+
+    if (!form.value.reportDetails.length) {
+      addProductionStatusRow()
+    }
+
+    if (props.type === 'edit') {
+      form.value.currentDepth = form.value.reportDetails.at(-1)?.currentDepth
+    }
+
+    if (props.type === 'edit' && !props.noValidateStatus && res.status !== 0)
+      formType.value = 'readonly'
+    if (props.type === 'approval' && !props.noValidateStatus && res.auditStatus !== 10)
+      formType.value = 'readonly'
   } finally {
     loading.value = false
   }
 }
 
+const addProductionStatusRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = []
+  }
+  form.value.reportDetails.push({
+    startTime: '',
+    endTime: '',
+    duration: 0,
+    currentDepth: 0,
+    currentOperation: '',
+    constructionDetail: ''
+  })
+}
+
+const removeProductionStatusRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+    return
+  }
+  form.value.reportDetails?.splice(index, 1)
+}
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ReportDetail>()
+
 function handleOpenForm(id: number, type: 'edit' | 'readonly') {
   formType.value = type
+  form.value.reportDetails = []
   emits('update:visible', true)
   loadDetail(id).then(() => {
     nextTick(() => formRef.value?.clearValidate())
@@ -253,7 +315,11 @@ const rules = reactive<FormRules>({
     { required: true, message: '请输入当前深度', trigger: ['change', 'blur'] },
     { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
   ],
-  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
+  // productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+  ],
 
   // 生产时间绑定校验
   drillingWorkingTime: [
@@ -302,6 +368,9 @@ const submitForm = async () => {
     FORM_KEYS.forEach((key) => (submitData[key] = form.value[key]))
     submitData.fillOrderCreateTime = form.value.createTime
     submitData.projectClassification = '1'
+    if (props.type === 'edit' && props.noValidateStatus) {
+      submitData.editFlag = 'Y'
+    }
 
     await IotRyDailyReportApi.createIotRyDailyReport(submitData)
     message.success(t('common.updateSuccess'))
@@ -318,10 +387,14 @@ const handleAudit = async (auditStatus: 20 | 30) => {
   if (!formRef.value) return
   try {
     formLoading.value = true
+
+    await formRef.value.validateField('constructionBrief')
+
     await IotRyDailyReportApi.approvalIotRyDailyReport({
       id: form.value.id!,
       auditStatus,
-      opinion: form.value.opinion!
+      opinion: form.value.opinion!,
+      constructionBrief: form.value.constructionBrief
     })
     message.success(auditStatus === 20 ? '通过成功' : '拒绝成功')
     emits('update:visible', false)
@@ -346,6 +419,12 @@ const orange = computed(() => {
   if (Math.abs(total - 24) > 0.01) return true
   return false
 })
+
+const inputCurrentDepth = useDebounceFn(function inputCurrentDepth(val: any, index: number) {
+  if (form.value.reportDetails && index === form.value.reportDetails.length - 1) {
+    form.value.currentDepth = val
+  }
+}, 300)
 </script>
 
 <template>
@@ -353,7 +432,9 @@ const orange = computed(() => {
     :model-value="visible"
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
-    size="50%"
+    size="60%"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
   >
     <template #header>
       <span class="text-xl font-bold text-[var(--el-text-color-primary)]">
@@ -456,6 +537,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="当日用电量(kWh)" prop="dailyPowerUsage">
@@ -467,6 +549,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="当日油耗(升)" prop="dailyFuel">
@@ -479,6 +562,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="泥浆密度(g/cm³)" prop="mudDensity">
@@ -490,6 +574,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="泥浆粘度(S)" prop="mudViscosity">
@@ -501,6 +586,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="水平段长度(m)" prop="lateralLength">
@@ -512,6 +598,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="井斜(°)" prop="wellInclination">
@@ -523,6 +610,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="方位(°)" prop="azimuth">
@@ -534,6 +622,7 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
             :controls="false"
             align="left"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="设计井身结构" prop="designWellStruct">
@@ -555,22 +644,200 @@ const orange = computed(() => {
             :disabled="isMainFieldDisabled"
           />
         </el-form-item>
-        <el-form-item class="col-span-2" label="生产动态" prop="productionStatus">
+        <el-form-item class="col-span-2" label="备注" prop="remark">
           <el-input
-            v-model="form.productionStatus"
+            v-model="form.remark"
             type="textarea"
             autosize
             maxlength="1000"
             :disabled="isMainFieldDisabled"
           />
         </el-form-item>
-        <el-form-item class="col-span-2" label="备注" prop="remark">
+
+        <div class="col-span-2">
+          <div class="flex items-center justify-between mb-6">
+            <div class="flex items-center gap-2">
+              <div class="bg-[var(--el-color-primary)] w-1 h-5 rounded-full"></div>
+              <div class="text-lg font-medium text-[var(--el-text-color-primary)]">生产动态</div>
+            </div>
+            <el-button
+              type="primary"
+              link
+              :icon="Plus"
+              @click="addProductionStatusRow"
+              :disabled="isMainFieldDisabled"
+            >
+              添加一行
+            </el-button>
+          </div>
+          <el-form-item prop="reportDetails">
+            <ZmTable :data="form.reportDetails!" :loading="false" class="mb-4">
+              <ZmTableColumn
+                label="日期"
+                :width="105"
+                cover-formatter
+                :real-value="
+                  () => (form.createTime ? dayjs(form.createTime).format('YYYY-MM-DD') : '')
+                "
+              />
+
+              <ZmTableColumn :width="130" label="开始时间" prop="startTime">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.startTime`"
+                    :rules="{
+                      required: true,
+                      message: '请选择开始时间',
+                      trigger: ['change', 'blur']
+                    }"
+                  >
+                    <el-time-picker
+                      v-model="row.startTime"
+                      placeholder="选择开始时间"
+                      clearable
+                      format="HH:mm"
+                      value-format="HH:mm"
+                      class="w-full!"
+                      @change="calculateDuration(row)"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :width="130" label="结束时间" prop="endTime">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.endTime`"
+                    :rules="{
+                      required: true,
+                      message: '请选择结束时间',
+                      trigger: ['change', 'blur']
+                    }"
+                  >
+                    <el-time-picker
+                      v-model="row.endTime"
+                      placeholder="选择结束时间"
+                      clearable
+                      format="HH:mm"
+                      value-format="HH:mm"
+                      class="w-full!"
+                      @change="calculateDuration(row)"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+
+              <ZmTableColumn label="工况" min-width="140">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.currentOperation`"
+                    :rules="{ required: true, message: '请输入工况', trigger: ['change', 'blur'] }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.currentOperation"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入工况"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="结束井深(m)" min-width="80">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.currentDepth`"
+                    :rules="[
+                      {
+                        required: true,
+                        message: '请输入结束井深',
+                        trigger: ['blur']
+                      },
+                      { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
+                    ]"
+                    class="mb-0!"
+                  >
+                    <el-input-number
+                      v-model="row.currentDepth"
+                      :min="0"
+                      :controls="false"
+                      class="!w-full"
+                      align="left"
+                      placeholder="请输入结束井深"
+                      :disabled="isMainFieldDisabled"
+                      @input="(val) => inputCurrentDepth(val, $index)"
+                    >
+                      <template #suffix> m </template>
+                    </el-input-number>
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="详细描述" min-width="200" align="center">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.constructionDetail`"
+                    :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.constructionDetail"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入详细描述"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="操作" width="80" fixed="right" align="center">
+                <template #default="{ $index }">
+                  <el-button
+                    link
+                    type="danger"
+                    :icon="Delete"
+                    @click="removeProductionStatusRow($index)"
+                    :disabled="isMainFieldDisabled"
+                  >
+                    删除
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </el-form-item>
+        </div>
+
+        <el-form-item
+          v-if="isApproval"
+          class="col-span-2"
+          label="当日施工简报"
+          prop="constructionBrief"
+        >
           <el-input
-            v-model="form.remark"
+            v-model="form.constructionBrief"
             type="textarea"
-            autosize
-            maxlength="1000"
-            :disabled="isMainFieldDisabled"
+            :autosize="{ minRows: 2 }"
+            show-word-limit
+            resize="none"
+            :maxlength="1000"
+            placeholder="请输入当日施工简报"
+            :disabled="formType === 'readonly'"
           />
         </el-form-item>
 
@@ -661,8 +928,8 @@ const orange = computed(() => {
           @click="submitForm"
           :loading="formLoading"
           :disabled="formType === 'readonly'"
-          >确 定</el-button
-        >
+          >确 定
+        </el-button>
         <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
       </div>
       <div v-if="isApproval">
@@ -672,16 +939,16 @@ const orange = computed(() => {
           @click="handleAudit(20)"
           :loading="formLoading"
           :disabled="formType === 'readonly'"
-          >审批通过</el-button
-        >
+          >审批通过
+        </el-button>
         <el-button
           size="default"
           type="danger"
           @click="handleAudit(30)"
           :loading="formLoading"
           :disabled="formType === 'readonly'"
-          >审批拒绝</el-button
-        >
+          >审批拒绝
+        </el-button>
         <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
       </div>
     </template>

+ 432 - 0
src/views/pms/iotrydailyreport/ry-table.vue

@@ -0,0 +1,432 @@
+<script setup lang="ts">
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import { DICT_TYPE, realValue } from '@/utils/dict'
+import { formatT } from '@/utils/formatTime'
+import dayjs from 'dayjs'
+
+const { t } = useI18n()
+
+interface ListItem {
+  createTime: number
+  deptName: string
+  taskName: string
+  rigStatus: string
+  equipmentType: string
+  latestWellDoneTime: number
+  designWellDepth: number
+  currentDepth: number
+  dailyFootage: number
+  monthlyFootage: number
+  annualFootage: number
+  totalConstructionWells: number
+  completedWells: number
+  mudDensity: number
+  mudViscosity: number
+  dailyPowerUsage: number
+  dailyFuel: number
+  lateralLength: number
+  wellInclination: number
+  azimuth: number
+  designWellStruct: string
+  productionStatus: string
+  contractName: string
+  drillingWorkingTime: number
+  otherProductionTime: number
+  nonProductionRate: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+}
+
+const props = defineProps({
+  list: {
+    type: Array as PropType<ListItem[]>,
+    default: () => []
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  total: {
+    type: Number,
+    default: 0
+  },
+  pageNo: {
+    type: Number,
+    default: 0
+  },
+  pageSize: {
+    type: Number,
+    default: 0
+  },
+  showAction: {
+    type: Boolean,
+    default: true
+  },
+  isIndex: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:pageNo', 'update:pageSize', 'sizeChange', 'currentChange'])
+
+const { list, loading, total, pageNo, pageSize, showAction, isIndex } = toRefs(props)
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
+
+const cellStyle = ({ row, column }: { row: ListItem; column: any }) => {
+  // 1. 红色预警:当日油耗大于 9000 升
+  if (column.property === 'dailyFuel') {
+    const dailyFuel = Number(row.dailyFuel || 0)
+    if (dailyFuel > 9000) {
+      return {
+        color: 'red',
+        fontWeight: 'bold',
+        backgroundColor: 'var(--el-color-danger-light-9)'
+      }
+    }
+  }
+
+  // 2. 橙色预警:进尺工作时间 + 其它生产时间 + 非生产时间 != 24H
+  // 定义所有非生产时间(NPT)的字段名
+  const nptFields = [
+    'accidentTime',
+    'repairTime',
+    'selfStopTime',
+    'complexityTime',
+    // 'relocationTime',
+    'rectificationTime',
+    'waitingStopTime',
+    'winterBreakTime',
+    'partyaDesign',
+    'partyaPrepare',
+    'partyaResource',
+    'otherNptTime'
+  ]
+  // 定义所有需要进行时间校验高亮的列(进尺 + 其它 + NPT明细)
+  const timeCheckCols = ['drillingWorkingTime', 'otherProductionTime', ...nptFields]
+
+  if (timeCheckCols.includes(column.property)) {
+    const drilling = Number(row.drillingWorkingTime || 0)
+    const other = Number(row.otherProductionTime || 0)
+    // 计算非生产时间总和
+    const nptTotal = nptFields.reduce((sum, field) => {
+      return sum + Number(row[field as keyof ListItem] || 0)
+    }, 0)
+
+    const totalTime = drilling + other + nptTotal
+
+    // 如果总时长不等于 24 (使用 0.01 误差范围处理浮点数计算)
+    if (Math.abs(totalTime - 24) > 0.01) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold',
+        backgroundColor: 'var(--el-color-warning-light-9)'
+      }
+    }
+  }
+
+  return {}
+}
+
+function handleSizeChange(val: number) {
+  emits('sizeChange', val)
+}
+
+function handleCurrentChange(val: number) {
+  emits('currentChange', val)
+}
+</script>
+
+<template>
+  <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4">
+    <div class="flex-1 relative">
+      <el-auto-resizer class="absolute">
+        <template #default="{ width, height }">
+          <zm-table
+            :data="list"
+            :loading="loading"
+            :width="width"
+            :max-height="height"
+            :height="height"
+            show-border
+            :cell-style="cellStyle"
+          >
+            <zm-table-column
+              type="index"
+              :label="t('monitor.serial')"
+              :width="60"
+              fixed="left"
+              v-if="isIndex"
+            />
+            <zm-table-column
+              label="日期"
+              prop="createTime"
+              fixed="left"
+              cover-formatter
+              :real-value="(row: ListItem) => dayjs(row.createTime).format('YYYY-MM-DD')"
+            />
+            <zm-table-column label="施工队伍" prop="deptName" fixed="left" />
+            <zm-table-column label="任务" prop="taskName" fixed="left" />
+            <zm-table-column
+              prop="rigStatus"
+              fixed="left"
+              :label="t('project.status')"
+              :real-value="
+                (row: ListItem) =>
+                  realValue(DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE, row.rigStatus ?? '')
+              "
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE"
+                  :value="scope.row.rigStatus ?? ''"
+                />
+              </template>
+            </zm-table-column>
+            <zm-table-column prop="auditStatus" label="审批状态" v-if="!isIndex">
+              <template #default="scope">
+                <el-tag v-if="scope.row.auditStatus === 0" type="info">
+                  {{ '待提交' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 10">
+                  {{ '待审批' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 20" type="success">
+                  {{ '审批通过' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 30" type="danger">
+                  {{ '审批拒绝' }}
+                </el-tag>
+              </template>
+            </zm-table-column>
+            <zm-table-column label="设备型号" prop="equipmentType" />
+            <zm-table-column
+              label="上井次完井时间"
+              prop="latestWellDoneTime"
+              cover-formatter
+              :real-value="
+                (row: ListItem) =>
+                  row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
+              "
+            />
+            <zm-table-column label="井深(m)">
+              <zm-table-column label="设计" prop="designWellDepth" />
+              <zm-table-column label="当前" prop="currentDepth" />
+            </zm-table-column>
+            <zm-table-column label="进尺(m)">
+              <zm-table-column label="日" prop="dailyFootage" />
+              <zm-table-column label="月" prop="monthlyFootage" />
+              <zm-table-column label="年" prop="annualFootage" />
+            </zm-table-column>
+            <zm-table-column label="总施工井数" prop="totalConstructionWells" />
+            <zm-table-column label="完工井数" prop="completedWells" />
+            <zm-table-column label="泥浆性能">
+              <zm-table-column label="密度(g/cm³)" prop="mudDensity" />
+              <zm-table-column label="粘度(S)" prop="mudViscosity" />
+            </zm-table-column>
+            <zm-table-column label="当日">
+              <zm-table-column label="用电量(kWh)" prop="dailyPowerUsage" />
+              <zm-table-column label="油耗(升)" prop="dailyFuel" />
+            </zm-table-column>
+            <zm-table-column label="水平段长度(m)" prop="lateralLength" />
+            <zm-table-column label="井斜(°)" prop="wellInclination" />
+            <zm-table-column label="方位(°)" prop="azimuth" />
+            <zm-table-column label="设计井身结构" prop="designWellStruct" />
+            <zm-table-column
+              prop="reportDetails"
+              label="生产动态"
+              width="320"
+              :show-overflow-tooltip="false"
+            >
+              <template #default="{ row }">
+                <div v-if="row.reportDetails && row.reportDetails.length > 0" class="py-2">
+                  <el-popover
+                    placement="right"
+                    trigger="hover"
+                    width="320"
+                    popper-class="!p-0"
+                    :disabled="row.reportDetails.length <= 1"
+                  >
+                    <template #reference>
+                      <el-badge :value="row.reportDetails.length" type="primary" class="max-w-full">
+                        <div
+                          class="w-72 flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                        >
+                          <div class="flex items-center gap-x-2">
+                            <div class="flex items-center">
+                              <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                              <div class="font-medium mr-2">{{
+                                dayjs(row.createTime).format('YYYY-MM-DD')
+                              }}</div>
+                              <div class="flex items-center">
+                                <span>{{ formatT(row.reportDetails[0].startTime) }}</span>
+                                <span class="mx-1">-</span>
+                                <span>{{ formatT(row.reportDetails[0].endTime) }}</span>
+                              </div>
+                            </div>
+
+                            <div class="ml-auto group-hover:text-blue-600 font-medium">
+                              {{ row.reportDetails[0].duration }} H
+                            </div>
+                          </div>
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">结束井深(m):</div>
+                            <span
+                              class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >{{ row.reportDetails[0].currentDepth || '-' }} m
+                            </span>
+                          </div>
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">工况:</div>
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].currentOperation"
+                              placement="top"
+                              popper-class="max-w-100"
+                            >
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].currentOperation || '-' }}
+                              </span>
+                            </el-tooltip>
+                          </div>
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">详细描述:</div>
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].constructionDetail"
+                              placement="top"
+                              popper-class="max-w-100"
+                            >
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].constructionDetail || '-' }}
+                              </span>
+                            </el-tooltip>
+                          </div>
+                        </div>
+                      </el-badge>
+                    </template>
+                    <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                      <div
+                        v-for="(item, index) in row.reportDetails"
+                        :key="index"
+                        class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                      >
+                        <div class="flex items-center gap-x-2">
+                          <div class="flex items-center">
+                            <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                            <div class="font-medium mr-2">{{
+                              dayjs(row.createTime).format('YYYY-MM-DD')
+                            }}</div>
+                            <div class="flex items-center">
+                              <span>{{ formatT(item.startTime) }}</span>
+                              <span class="mx-1">-</span>
+                              <span>{{ formatT(item.endTime) }}</span>
+                            </div>
+                          </div>
+
+                          <div class="ml-auto group-hover:text-blue-600 font-medium">
+                            {{ item.duration }} H
+                          </div>
+                        </div>
+
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">结束井深(m):</div>
+                          <span
+                            class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                            >{{ item.currentDepth || '-' }} m
+                          </span>
+                        </div>
+
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">工况:</div>
+                          <span class="font-medium group-hover:text-blue-600 transition-colors">
+                            {{ item.currentOperation || '-' }}
+                          </span>
+                        </div>
+
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">详细描述:</div>
+                          <span
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                          >
+                            {{ item.constructionDetail || '-' }}
+                          </span>
+                        </div>
+                      </div>
+                    </el-scrollbar>
+                  </el-popover>
+                </div>
+                <span v-else class="text-gray-300">-</span>
+              </template>
+            </zm-table-column>
+            <zm-table-column prop="contractName" label="项目" />
+            <zm-table-column prop="drillingWorkingTime" label="进尺工作时间(H)" />
+            <zm-table-column prop="otherProductionTime" label="其它生产时间(H)" />
+            <zm-table-column
+              prop="nonProductionRate"
+              label="非生产时效"
+              cover-formatter
+              :real-value="(row) => (Number(row.nonProductionRate ?? 0) * 100).toFixed(2) + '%'"
+            />
+            <zm-table-column label="非生产时间">
+              <zm-table-column prop="accidentTime" label="工程质量" />
+              <zm-table-column prop="repairTime" label="设备故障" />
+              <zm-table-column prop="selfStopTime" label="设备保养" />
+              <zm-table-column prop="complexityTime" label="技术受限" />
+              <zm-table-column prop="relocationTime" label="生产配合" />
+              <zm-table-column prop="rectificationTime" label="生产组织" />
+              <zm-table-column prop="waitingStopTime" label="不可抗力" />
+              <zm-table-column prop="winterBreakTime" label="待命" />
+              <zm-table-column prop="partyaDesign" label="甲方设计" />
+              <zm-table-column prop="partyaPrepare" label="甲方准备" />
+              <zm-table-column prop="partyaResource" label="甲方资源" />
+              <zm-table-column prop="otherNptTime" label="其它非生产时间" />
+            </zm-table-column>
+            <zm-table-column prop="otherNptReason" label="其他非生产时间原因" />
+
+            <zm-table-column label="操作" :width="120" fixed="right" v-if="showAction">
+              <template #default="scope">
+                <slot name="action" :row="scope.row"></slot>
+              </template>
+            </zm-table-column>
+          </zm-table>
+        </template>
+      </el-auto-resizer>
+    </div>
+    <div class="h-8 mt-2 flex items-center justify-end">
+      <el-pagination
+        size="default"
+        v-show="total > 0"
+        :current-page="pageNo"
+        :page-size="pageSize"
+        :background="true"
+        :page-sizes="[10, 20, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped></style>

+ 238 - 16
src/views/pms/iotrydailyreport/ry-xj-form.vue

@@ -1,17 +1,23 @@
 <script lang="ts" setup generic="T">
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { calculateDuration, formatT } from '@/utils/formatTime'
 import { FormInstance, FormRules } from 'element-plus'
 import { computed, reactive, ref, watch, nextTick } from 'vue'
+import { Delete, Plus } from '@element-plus/icons-vue'
+import dayjs from 'dayjs'
 
 interface Props {
   visible: boolean
   type?: 'edit' | 'approval' | 'readonly'
   loadList: () => void
+  noValidateStatus?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  type: 'edit'
+  type: 'edit',
+  noValidateStatus: false
 })
 
 const emits = defineEmits(['update:visible'])
@@ -32,6 +38,14 @@ const NON_PROD_FIELDS = [
   { key: 'otherNptTime', label: '其他非生产时间' }
 ] as const
 
+interface ReportDetail {
+  startTime: string
+  endTime: string
+  duration: number
+  currentOperation: string
+  constructionDetail: string
+}
+
 interface FormOriginal {
   id: number
   deptId: number
@@ -71,8 +85,9 @@ interface FormOriginal {
 
   // 其他非生产时间原因(仅作为备注字段存在)
   otherNptReason: string
+  constructionBrief: string
 
-  productionStatus: string
+  reportDetails: ReportDetail[]
   remark: string
   createTime: string
   opinion: string
@@ -103,7 +118,8 @@ const FORM_KEYS: (keyof FormOriginal)[] = [
   'totalStaffNum',
   'onDutyStaffNum',
   'leaveStaffNum',
-  'productionStatus',
+  'constructionBrief',
+  'reportDetails',
   'remark',
   'createTime',
   'opinion',
@@ -165,13 +181,52 @@ async function loadDetail(id: number) {
       }
     })
 
-    if (props.type === 'edit' && res.status !== 0) formType.value = 'readonly'
-    if (props.type === 'approval' && res.auditStatus !== 10) formType.value = 'readonly'
+    form.value.reportDetails = (res.reportDetails ? (res.reportDetails as any[]) : []).map(
+      (item) => ({
+        startTime: formatT(item.startTime),
+        endTime: formatT(item.endTime),
+        duration: item.duration,
+        currentOperation: item.currentOperation,
+        constructionDetail: item.constructionDetail
+      })
+    )
+
+    if (!form.value.reportDetails.length) {
+      addProductionStatusRow()
+    }
+
+    if (props.type === 'edit' && !props.noValidateStatus && res.status !== 0)
+      formType.value = 'readonly'
+    if (props.type === 'approval' && !props.noValidateStatus && res.auditStatus !== 10)
+      formType.value = 'readonly'
   } finally {
     loading.value = false
   }
 }
 
+const addProductionStatusRow = () => {
+  if (!form.value.reportDetails) {
+    form.value.reportDetails = []
+  }
+  form.value.reportDetails.push({
+    startTime: '',
+    endTime: '',
+    duration: 0,
+    currentOperation: '',
+    constructionDetail: ''
+  })
+}
+
+const removeProductionStatusRow = (index: number) => {
+  if (index === 0) {
+    message.warning('至少填写一条生产动态')
+    return
+  }
+  form.value.reportDetails?.splice(index, 1)
+}
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ReportDetail>()
+
 function handleOpenForm(id: number, type: 'edit' | 'readonly') {
   formType.value = type
   emits('update:visible', true)
@@ -244,7 +299,10 @@ const validateOtherReason = (_rule: any, value: any, callback: any) => {
 // 动态构建校验规则
 const rules = reactive<FormRules>({
   repairStatus: [{ required: true, message: '请选择施工状态', trigger: ['change', 'blur'] }],
-  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['blur', 'change'] }],
+  reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
+  constructionBrief: [
+    { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
+  ],
 
   // 生产时间绑定校验
   ratedProductionTime: [
@@ -293,6 +351,9 @@ const submitForm = async () => {
     FORM_KEYS.forEach((key) => (submitData[key] = form.value[key]))
     submitData.fillOrderCreateTime = form.value.createTime
     submitData.projectClassification = '2'
+    if (props.type === 'edit' && props.noValidateStatus) {
+      submitData.editFlag = 'Y'
+    }
 
     await IotRyDailyReportApi.createIotRyDailyReport(submitData)
     message.success(t('common.updateSuccess'))
@@ -309,10 +370,14 @@ const handleAudit = async (auditStatus: 20 | 30) => {
   if (!formRef.value) return
   try {
     formLoading.value = true
+
+    await formRef.value.validateField('constructionBrief')
+
     await IotRyDailyReportApi.approvalIotRyDailyReport({
       id: form.value.id!,
       auditStatus,
-      opinion: form.value.opinion!
+      opinion: form.value.opinion!,
+      constructionBrief: form.value.constructionBrief
     })
     message.success(auditStatus === 20 ? '通过成功' : '拒绝成功')
     emits('update:visible', false)
@@ -344,7 +409,7 @@ const orange = computed(() => {
     :model-value="visible"
     @update:model-value="emits('update:visible', $event)"
     header-class="mb-0!"
-    size="50%"
+    size="60%"
   >
     <template #header>
       <span class="text-xl font-bold text-[var(--el-text-color-primary)]">
@@ -521,10 +586,17 @@ const orange = computed(() => {
             :min="0"
             v-model="form.totalStaffNum"
             placeholder="请输入全员数量"
+            class="w-full!"
           />
         </el-form-item>
         <el-form-item label="在岗人数" prop="onDutyStaffNum">
-          <el-input-number :min="0" v-model="onDutyStaffNum" placeholder="" disabled />
+          <el-input-number
+            :min="0"
+            v-model="onDutyStaffNum"
+            placeholder=""
+            disabled
+            class="w-full!"
+          />
         </el-form-item>
         <el-form-item label="休假人员数量" prop="leaveStaffNum">
           <el-input-number
@@ -534,24 +606,174 @@ const orange = computed(() => {
             :min="0"
             v-model="form.leaveStaffNum"
             placeholder="请输入休假人员数量"
+            class="w-full!"
           />
         </el-form-item>
-        <el-form-item class="col-span-2" label="生产动态" prop="productionStatus">
+        <el-form-item class="col-span-2" label="备注" prop="remark">
           <el-input
-            v-model="form.productionStatus"
+            v-model="form.remark"
             type="textarea"
             autosize
             maxlength="1000"
             :disabled="isMainFieldDisabled"
           />
         </el-form-item>
-        <el-form-item class="col-span-2" label="备注" prop="remark">
+
+        <div class="col-span-2">
+          <div class="flex items-center justify-between mb-6">
+            <div class="flex items-center gap-2">
+              <div class="bg-[var(--el-color-primary)] w-1 h-5 rounded-full"></div>
+              <div class="text-lg font-medium text-[var(--el-text-color-primary)]">生产动态</div>
+            </div>
+            <el-button
+              type="primary"
+              link
+              :icon="Plus"
+              @click="addProductionStatusRow"
+              :disabled="isMainFieldDisabled"
+            >
+              添加一行
+            </el-button>
+          </div>
+          <el-form-item prop="reportDetails">
+            <ZmTable :data="form.reportDetails!" :loading="false" class="mb-4">
+              <ZmTableColumn
+                label="日期"
+                :width="105"
+                cover-formatter
+                :real-value="
+                  () => (form.createTime ? dayjs(form.createTime).format('YYYY-MM-DD') : '')
+                "
+              />
+
+              <ZmTableColumn :width="130" label="开始时间" prop="startTime">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.startTime`"
+                    :rules="{
+                      required: true,
+                      message: '请选择开始时间',
+                      trigger: ['change', 'blur']
+                    }"
+                  >
+                    <el-time-picker
+                      v-model="row.startTime"
+                      placeholder="选择开始时间"
+                      clearable
+                      format="HH:mm"
+                      value-format="HH:mm"
+                      class="w-full!"
+                      @change="calculateDuration(row)"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :width="130" label="结束时间" prop="endTime">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    class="mb-0!"
+                    :prop="`reportDetails.${$index}.endTime`"
+                    :rules="{
+                      required: true,
+                      message: '请选择结束时间',
+                      trigger: ['change', 'blur']
+                    }"
+                  >
+                    <el-time-picker
+                      v-model="row.endTime"
+                      placeholder="选择结束时间"
+                      clearable
+                      format="HH:mm"
+                      value-format="HH:mm"
+                      class="w-full!"
+                      @change="calculateDuration(row)"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+              <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
+
+              <ZmTableColumn label="工况" min-width="140">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.currentOperation`"
+                    :rules="{ required: true, message: '请输入工况', trigger: ['change', 'blur'] }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.currentOperation"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      resize="none"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入工况"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="详细描述" min-width="200" align="center">
+                <template #default="{ row, $index }">
+                  <el-form-item
+                    v-if="$index >= 0"
+                    :prop="`reportDetails.${$index}.constructionDetail`"
+                    :rules="{ required: true, message: '请输入详细描述', trigger: 'blur' }"
+                    class="mb-0!"
+                  >
+                    <el-input
+                      v-model="row.constructionDetail"
+                      type="textarea"
+                      :autosize="{ minRows: 1 }"
+                      resize="none"
+                      show-word-limit
+                      :maxlength="1000"
+                      placeholder="请输入详细描述"
+                      :disabled="isMainFieldDisabled"
+                    />
+                  </el-form-item>
+                </template>
+              </ZmTableColumn>
+
+              <ZmTableColumn label="操作" width="80" fixed="right" align="center">
+                <template #default="{ $index }">
+                  <el-button
+                    link
+                    type="danger"
+                    :icon="Delete"
+                    @click="removeProductionStatusRow($index)"
+                    :disabled="isMainFieldDisabled"
+                  >
+                    删除
+                  </el-button>
+                </template>
+              </ZmTableColumn>
+            </ZmTable>
+          </el-form-item>
+        </div>
+
+        <el-form-item
+          v-if="isApproval"
+          class="col-span-2"
+          label="当日施工简报"
+          prop="constructionBrief"
+        >
           <el-input
-            v-model="form.remark"
+            v-model="form.constructionBrief"
             type="textarea"
-            autosize
-            maxlength="1000"
-            :disabled="isMainFieldDisabled"
+            :autosize="{ minRows: 2 }"
+            show-word-limit
+            resize="none"
+            :maxlength="1000"
+            placeholder="请输入当日施工简报"
+            :disabled="formType === 'readonly'"
           />
         </el-form-item>
 

+ 475 - 0
src/views/pms/iotrydailyreport/ry-xj-table.vue

@@ -0,0 +1,475 @@
+<script setup lang="ts">
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import { DICT_TYPE, realValue } from '@/utils/dict'
+import { formatT } from '@/utils/formatTime'
+import dayjs from 'dayjs'
+import { TableColumnCtx } from 'element-plus'
+
+const { t } = useI18n()
+
+interface ListItem {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  repairStatus: string
+  totalConstructionWells: number
+  completedWells: number
+  technique: string
+  wellCategory: string
+  designWellDepth: number
+  casingPipeSize: string
+  wellControlLevel: string
+  dailyPowerUsage: number
+  dailyFuel: number
+  constructionStartDate: number
+  constructionEndDate: number
+  currentOperation: string
+  nextPlan: string
+  transitTime: number
+  ratedProductionTime: number
+  productionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+  ryNptReason: string
+  productionStatus: string
+  totalStaffNum: number
+  onDutyStaffNum: number
+  leaveStaffNum: number
+  status: number
+  auditStatus: number
+  opinion: string
+  offDutyStaffNum: number
+  remark: string
+  nonProductionRate: number
+}
+const props = defineProps({
+  list: {
+    type: Array as PropType<ListItem[]>,
+    default: () => []
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  total: {
+    type: Number,
+    default: 0
+  },
+  pageNo: {
+    type: Number,
+    default: 0
+  },
+  pageSize: {
+    type: Number,
+    default: 0
+  },
+  showAction: {
+    type: Boolean,
+    default: true
+  },
+  isIndex: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:pageNo', 'update:pageSize', 'sizeChange', 'currentChange'])
+
+const { list, loading, total, pageNo, pageSize, showAction, isIndex } = toRefs(props)
+
+const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
+
+const transitTime = (row: ListItem) => {
+  const { ratedProductionTime = 0, productionTime = 0 } = row
+
+  return (
+    (ratedProductionTime === 0 ? 0 : (productionTime / ratedProductionTime) * 100).toFixed(2) + '%'
+  )
+}
+
+const nptFields = [
+  'accidentTime',
+  'repairTime',
+  'selfStopTime',
+  'complexityTime',
+  'relocationTime',
+  'rectificationTime',
+  'waitingStopTime',
+  'winterBreakTime',
+  'partyaDesign',
+  'partyaPrepare',
+  'partyaResource',
+  'otherNptTime'
+]
+
+function checkTimeSumEquals24(row: ListItem) {
+  const ratedProductionTime = parseFloat(row.ratedProductionTime + '') || 0
+  const productionTime = parseFloat(row.productionTime + '') || 0
+
+  const totalNonProductionTime = nptFields.reduce((sum, field) => {
+    const val = parseFloat(row[field])
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  const currentSum = productionTime + totalNonProductionTime
+
+  return Math.abs(currentSum - ratedProductionTime) < 0.01
+}
+
+function cellStyle(data: {
+  row: ListItem
+  column: TableColumnCtx<ListItem>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    const originalValue = Number(row.transitTime ?? 0)
+
+    if (originalValue >= 1)
+      return {
+        color: 'red',
+        fontWeight: 'bold',
+        backgroundColor: 'var(--el-color-danger-light-9)'
+      }
+  }
+
+  if (column.property === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 3500)
+      return {
+        color: 'blue',
+        fontWeight: 'bold',
+        backgroundColor: 'var(--el-color-primary-light-9)'
+      }
+  }
+
+  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold',
+        backgroundColor: 'var(--el-color-warning-light-9)'
+      }
+    }
+  }
+
+  return {}
+}
+
+function handleSizeChange(val: number) {
+  emits('sizeChange', val)
+}
+
+function handleCurrentChange(val: number) {
+  emits('currentChange', val)
+}
+</script>
+
+<template>
+  <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4">
+    <div class="flex-1 relative">
+      <el-auto-resizer class="absolute">
+        <template #default="{ width, height }">
+          <zm-table
+            :data="list"
+            :loading="loading"
+            :width="width"
+            :max-height="height"
+            :height="height"
+            show-border
+            :cell-style="cellStyle"
+          >
+            <zm-table-column
+              type="index"
+              :label="t('monitor.serial')"
+              :width="60"
+              fixed="left"
+              v-if="isIndex"
+            />
+            <zm-table-column
+              label="日期"
+              prop="createTime"
+              fixed="left"
+              cover-formatter
+              :real-value="(row: ListItem) => dayjs(row.createTime).format('YYYY-MM-DD')"
+            />
+            <zm-table-column label="施工队伍" prop="deptName" fixed="left" />
+            <zm-table-column label="任务" prop="taskName" fixed="left" />
+            <zm-table-column
+              prop="repairStatus"
+              fixed="left"
+              :label="t('project.status')"
+              :real-value="
+                (row: ListItem) =>
+                  realValue(DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE, row.repairStatus ?? '')
+              "
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE"
+                  :value="scope.row.repairStatus ?? ''"
+                />
+              </template>
+            </zm-table-column>
+            <zm-table-column prop="auditStatus" label="审批状态" v-if="!isIndex">
+              <template #default="scope">
+                <el-tag v-if="scope.row.auditStatus === 0" type="info">
+                  {{ '待提交' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 10">
+                  {{ '待审批' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 20" type="success">
+                  {{ '审批通过' }}
+                </el-tag>
+                <el-tag v-else-if="scope.row.auditStatus === 30" type="danger">
+                  {{ '审批拒绝' }}
+                </el-tag>
+              </template>
+            </zm-table-column>
+            <zm-table-column label="总施工井数" prop="totalConstructionWells" />
+            <zm-table-column label="完工井数" prop="completedWells" />
+            <zm-table-column
+              prop="technique"
+              :label="t('project.technology')"
+              :real-value="
+                (row: ListItem) =>
+                  realValue(DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY, row.technique ?? '')
+              "
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY"
+                  :value="scope.row.technique ?? ''"
+                />
+              </template>
+            </zm-table-column>
+            <zm-table-column label="井别" prop="wellCategory" />
+            <zm-table-column label="井深(m)" prop="designWellDepth" />
+            <zm-table-column label="套生段产管尺寸(mm)" prop="casingPipeSize" />
+            <zm-table-column label="井控级别" prop="wellControlLevel" />
+            <zm-table-column label="当日">
+              <zm-table-column label="用电量(kWh)" prop="dailyPowerUsage" />
+              <zm-table-column label="油耗(升)" prop="dailyFuel" />
+            </zm-table-column>
+            <!-- <zm-table-column label="施工开始日期" prop="constructionStartDate" />
+            <zm-table-column label="施工结束日期" prop="constructionEndDate" /> -->
+            <zm-table-column :label="t('project.currentOperation')" prop="currentOperation" />
+            <zm-table-column :label="t('project.nextPlan')" prop="nextPlan" />
+            <zm-table-column
+              :label="t('project.transitTime')"
+              prop="transitTime"
+              cover-formatter
+              :real-value="transitTime"
+            />
+            <zm-table-column label="额定生产时间(H)" prop="ratedProductionTime" />
+            <zm-table-column label="生产时间(H)" prop="productionTime" />
+            <zm-table-column
+              prop="nonProductionRate"
+              label="非生产时效"
+              cover-formatter
+              :real-value="(row) => (Number(row.nonProductionRate ?? 0) * 100).toFixed(2) + '%'"
+            />
+            <zm-table-column label="非生产时间">
+              <zm-table-column prop="accidentTime" label="工程质量" />
+              <zm-table-column prop="repairTime" label="设备故障" />
+              <zm-table-column prop="selfStopTime" label="设备保养" />
+              <zm-table-column prop="complexityTime" label="技术受限" />
+              <zm-table-column prop="relocationTime" label="生产配合" />
+              <zm-table-column prop="rectificationTime" label="生产组织" />
+              <zm-table-column prop="waitingStopTime" label="不可抗力" />
+              <zm-table-column prop="winterBreakTime" label="待命" />
+              <zm-table-column prop="partyaDesign" label="甲方设计" />
+              <zm-table-column prop="partyaPrepare" label="甲方准备" />
+              <zm-table-column prop="partyaResource" label="甲方资源" />
+              <zm-table-column prop="otherNptTime" label="其它非生产时间" />
+            </zm-table-column>
+            <zm-table-column prop="otherNptReason" label="其他非生产时间原因" />
+            <zm-table-column
+              prop="reportDetails"
+              label="生产动态"
+              width="320"
+              :show-overflow-tooltip="false"
+            >
+              <template #default="{ row }">
+                <div v-if="row.reportDetails && row.reportDetails.length > 0" class="py-2">
+                  <el-popover
+                    placement="right"
+                    trigger="hover"
+                    width="320"
+                    popper-class="p-0!"
+                    :disabled="row.reportDetails.length <= 1"
+                  >
+                    <template #reference>
+                      <el-badge :value="row.reportDetails.length" type="primary" class="max-w-full">
+                        <div
+                          class="w-72 flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                        >
+                          <div class="flex items-center gap-x-2">
+                            <div class="flex items-center">
+                              <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                              <div class="font-medium mr-2">{{
+                                dayjs(row.createTime).format('YYYY-MM-DD')
+                              }}</div>
+                              <div class="flex items-center">
+                                <span>{{ formatT(row.reportDetails[0].startTime) }}</span>
+                                <span class="mx-1">-</span>
+                                <span>{{ formatT(row.reportDetails[0].endTime) }}</span>
+                              </div>
+                            </div>
+
+                            <div class="ml-auto group-hover:text-blue-600 font-medium">
+                              {{ row.reportDetails[0].duration }} H
+                            </div>
+                          </div>
+
+                          <!-- <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">结束井深(m):</div>
+                            <span
+                              class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >{{ row.reportDetails[0].currentDepth || '-' }} m
+                            </span>
+                          </div> -->
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">工况:</div>
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].currentOperation"
+                              placement="top"
+                              popper-class="max-w-100"
+                            >
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].currentOperation || '-' }}
+                              </span>
+                            </el-tooltip>
+                          </div>
+
+                          <div class="flex items-center">
+                            <div class="font-medium flex-shrink-0">详细描述:</div>
+                            <el-tooltip
+                              effect="dark"
+                              :content="row.reportDetails[0].constructionDetail"
+                              placement="top"
+                              popper-class="max-w-100"
+                            >
+                              <span
+                                class="font-medium truncate group-hover:text-blue-600 transition-colors"
+                              >
+                                {{ row.reportDetails[0].constructionDetail || '-' }}
+                              </span>
+                            </el-tooltip>
+                          </div>
+                        </div>
+                      </el-badge>
+                    </template>
+                    <el-scrollbar max-height="480px" view-class="!p-3 flex flex-col gap-y-2">
+                      <div
+                        v-for="(item, index) in row.reportDetails"
+                        :key="index"
+                        class="flex flex-col bg-gray-200/80 hover:bg-blue-100 transition-colors relative group cursor-pointer p-2 rounded gap-y-2"
+                      >
+                        <div class="flex items-center gap-x-2">
+                          <div class="flex items-center">
+                            <div class="i-carbon-calendar mr-1 -translate-y-[0.5px]"></div>
+                            <div class="font-medium mr-2">{{
+                              dayjs(row.createTime).format('YYYY-MM-DD')
+                            }}</div>
+                            <div class="flex items-center">
+                              <span>{{ formatT(item.startTime) }}</span>
+                              <span class="mx-1">-</span>
+                              <span>{{ formatT(item.endTime) }}</span>
+                            </div>
+                          </div>
+
+                          <div class="ml-auto group-hover:text-blue-600 font-medium">
+                            {{ item.duration }} H
+                          </div>
+                        </div>
+
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">工况:</div>
+                          <span
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                          >
+                            {{ item.currentOperation || '-' }}
+                          </span>
+                        </div>
+
+                        <div class="flex items-center">
+                          <div class="font-medium flex-shrink-0">详细描述:</div>
+                          <span
+                            class="font-medium break-all whitespace-pre-wrap group-hover:text-blue-600 transition-colors"
+                          >
+                            {{ item.constructionDetail || '-' }}
+                          </span>
+                        </div>
+                      </div>
+                    </el-scrollbar>
+                  </el-popover>
+                </div>
+                <span v-else class="text-gray-300">-</span>
+              </template>
+            </zm-table-column>
+            <zm-table-column prop="contractName" label="项目" />
+            <zm-table-column prop="totalStaffNum" label="全员数量" />
+            <zm-table-column
+              prop="onDutyStaffNum"
+              label="在岗人数"
+              cover-formatter
+              :real-value="
+                (row: ListItem) =>
+                  (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
+              "
+            />
+            <zm-table-column prop="leaveStaffNum" label="休假人员数量" />
+
+            <zm-table-column label="操作" :width="120" fixed="right" v-if="showAction">
+              <template #default="scope">
+                <slot name="action" :row="scope.row"></slot>
+              </template>
+            </zm-table-column>
+          </zm-table>
+        </template>
+      </el-auto-resizer>
+    </div>
+    <div class="h-8 mt-2 flex items-center justify-end">
+      <el-pagination
+        size="default"
+        v-show="total > 0"
+        :current-page="pageNo"
+        :page-size="pageSize"
+        :background="true"
+        :page-sizes="[10, 20, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped></style>

+ 27 - 7
src/views/pms/iotrydailyreport/summary.vue

@@ -39,6 +39,13 @@ const query = ref<Query>({
 
 const totalWorkKeys: [string, string, string, string, number][] = [
   ['totalFootage', 'M', '累计进尺', 'i-solar:ruler-bold text-sky', 2],
+  [
+    'utilizationRate',
+    '%',
+    '设备利用率',
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
+  ],
   [
     'totalPowerConsumption',
     'MWh',
@@ -78,7 +85,8 @@ const totalWork = ref({
   totalFuelConsumption: 0,
   totalPowerConsumption: 0,
   totalFootage: 0,
-  averageFuelConsumption: 0
+  averageFuelConsumption: 0,
+  utilizationRate: 0
 })
 
 const totalLoading = ref(false)
@@ -110,7 +118,8 @@ const getTotal = useDebounceFn(async () => {
       totalPowerConsumption: (res2.totalPowerConsumption || 0) / 1000,
       totalGasInjection: (res2.totalGasInjection || 0) / 10000,
       totalFuelConsumption: res2.totalFuelConsumption || 0,
-      averageFuelConsumption: res2.averageFuelConsumption || 0
+      averageFuelConsumption: res2.averageFuelConsumption || 0,
+      utilizationRate: Number(((res2.utilizationRate || 0) * 100).toFixed(2))
     }
   } finally {
     totalLoading.value = false
@@ -128,6 +137,7 @@ interface List {
   transitTime: number | null
   nonProductiveTime: number | null
   averageFuelConsumption: number | null
+  utilizationRate: number | null
 }
 
 const list = ref<List[]>([])
@@ -163,6 +173,10 @@ const columns = (type: string) => {
     {
       label: '非生产时效(%)',
       prop: 'nonProductiveTime'
+    },
+    {
+      label: '设备利用率(%)',
+      prop: 'utilizationRate'
     }
   ]
 }
@@ -174,6 +188,8 @@ const formatter = (row: List, column: any) => {
     return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
   } else if (column.property === 'nonProductiveTime') {
     return (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%'
+  } else if (column.property === 'utilizationRate') {
+    return (Number(row.utilizationRate ?? 0) * 100).toFixed(2) + '%'
   } else return row[column.property] ?? 0
 }
 
@@ -193,7 +209,8 @@ const getList = useDebounceFn(async () => {
         ...other,
         cumulativePowerConsumption: ((other.cumulativePowerConsumption || 0) / 1000).toFixed(2),
         cumulativeFuelConsumption: (other.cumulativeFuelConsumption || 0).toFixed(2),
-        averageFuelConsumption: (other.averageFuelConsumption || 0).toFixed(2)
+        averageFuelConsumption: (other.averageFuelConsumption || 0).toFixed(2),
+        utilizationRate: other.utilizationRate || 0
       })
     )
   } finally {
@@ -231,14 +248,16 @@ const legend = ref<string[][]>([
   ['进尺 (M)', 'cumulativeFootage'],
   ['用电量 (KWh)', 'cumulativePowerConsumption'],
   // ['累计用电量 (MWh)', 'cumulativePowerConsumption'],
-  ['平均时效 (%)', 'transitTime']
+  ['平均时效 (%)', 'transitTime'],
+  ['设备利用率 (%)', 'utilizationRate']
 ])
 
 const chartData = ref<Record<string, number[]>>({
   cumulativeFuelConsumption: [],
   cumulativeFootage: [],
   cumulativePowerConsumption: [],
-  transitTime: []
+  transitTime: [],
+    utilizationRate: []
 })
 
 let chartLoading = ref(false)
@@ -266,7 +285,8 @@ const getChart = useDebounceFn(async () => {
       cumulativeFootage: res.map((item) => item.cumulativeFootage || 0),
       cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
       // cumulativePowerConsumption: res.map((item) => (item.cumulativePowerConsumption || 0) / 1000),
-      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+      transitTime: res.map((item) => (item.transitTime || 0) * 100),
+      utilizationRate: res.map((item) => Number(((item.utilizationRate || 0) * 100).toFixed(2)))
     }
 
     xAxisData.value = res.map((item) => item.reportDate || '')
@@ -544,7 +564,7 @@ const tolist = (id: number, non: boolean = false) => {
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
       </el-form-item>
     </el-form>
-    <div class="grid grid-cols-7 gap-8">
+    <div class="grid grid-cols-8 gap-8">
       <div
         v-for="info in totalWorkKeys"
         :key="info[0]"

+ 102 - 629
src/views/pms/iotrydailyreport/xapproval.vue

@@ -1,443 +1,13 @@
 <script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
-import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import ryXjForm from './ry-xj-form.vue'
+import RyXjTable from './ry-xj-table.vue'
 
-interface List {
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  createTime: number
-  deptName: string
-  contractName: string
-  taskName: string
-  repairStatus: string
-  totalConstructionWells: number
-  completedWells: number
-  technique: string
-  wellCategory: string
-  designWellDepth: number
-  casingPipeSize: string
-  wellControlLevel: string
-  dailyPowerUsage: number
-  dailyFuel: number
-  constructionStartDate: number
-  constructionEndDate: number
-  currentOperation: string
-  nextPlan: string
-  transitTime: number
-  ratedProductionTime: number
-  productionTime: number
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string
-  ryNptReason: string
-  productionStatus: string
-  totalStaffNum: number
-  onDutyStaffNum: number
-  leaveStaffNum: number
-  status: number
-  auditStatus: number
-  opinion: string
-  offDutyStaffNum: number
-  remark: string
-  nonProductionRate: number
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
-    fixed: 'left'
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-
-  {
-    label: '任务',
-    prop: 'taskName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '施工状态',
-    prop: 'repairStatus',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE,
-    fixed: 'left'
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    'min-width': '120px'
-  },
-  {
-    label: '完工井数',
-    prop: 'completedWells',
-    'min-width': '120px'
-  },
-  {
-    label: '施工工艺',
-    prop: 'technique',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY
-  },
-  {
-    label: '井别',
-    prop: 'wellCategory',
-    'min-width': '120px'
-  },
-  {
-    label: '井深(m)',
-    prop: 'designWellDepth',
-    'min-width': '120px'
-  },
-  {
-    label: '套生段产管尺寸(mm)',
-    prop: 'casingPipeSize',
-    'min-width': '120px'
-  },
-  {
-    label: '井控级别',
-    prop: 'wellControlLevel',
-    'min-width': '120px'
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(升)',
-        prop: 'dailyFuel',
-        'min-width': '120px'
-      }
-    ]
-  },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '目前工序',
-    prop: 'currentOperation',
-    'min-width': '120px'
-  },
-  {
-    label: '下步工序',
-    prop: 'nextPlan',
-    'min-width': '120px'
-  },
-  {
-    label: '运行时效',
-    prop: 'transitTime',
-    'min-width': '120px',
-    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
-  },
-  {
-    label: '额定生产时间(H)',
-    prop: 'ratedProductionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '生产时间(H)',
-    prop: 'productionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  },
-
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '全员数量',
-    prop: 'totalStaffNum',
-    'min-width': '120px'
-  },
-  {
-    label: '在岗人数',
-    prop: 'onDutyStaffNum',
-    'min-width': '120px',
-    formatter: (row: List) => (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
-  },
-  {
-    label: '休假人员数量',
-    prop: 'leaveStaffNum',
-    'min-width': '120px'
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-const nptFields = [
-  'accidentTime', // 工程质量
-  'repairTime', // 设备故障
-  'selfStopTime', // 设备保养
-  'complexityTime', // 技术受限
-  'relocationTime', // 生产配合
-  'rectificationTime', // 生产组织
-  'waitingStopTime', // 不可抗力
-  'winterBreakTime', // 待命
-  'partyaDesign', // 甲方设计
-  'partyaPrepare', // 甲方资源
-  'partyaResource', // 甲方准备
-  'otherNptTime' // 其它非生产时间
-]
-
-function checkTimeSumEquals24(row: List) {
-  const ratedProductionTime = parseFloat(row.ratedProductionTime + '') || 0
-  const productionTime = parseFloat(row.productionTime + '') || 0
-
-  // 3. 计算所有非生产时间细项的总和
-  const totalNonProductionTime = nptFields.reduce((sum, field) => {
-    // 获取字段值,转为浮点数,如果是 NaN 则视为 0
-    const val = parseFloat(row[field])
-    return sum + (isNaN(val) ? 0 : val)
-  }, 0)
-
-  // 4. 计算实际总投入时间:生产时间 + 所有非生产时间之和
-  const currentSum = productionTime + totalNonProductionTime
-
-  // 5. 校验逻辑:判断 (生产时间 + 非生产时间总和) 是否等于 额定生产时间
-  // 允许 0.01 的浮点数误差
-  return Math.abs(currentSum - ratedProductionTime) < 0.01
-}
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'transitTime') {
-    const originalValue = Number(row.transitTime ?? 0)
-
-    if (originalValue >= 1)
-      return {
-        color: 'red',
-        fontWeight: 'bold'
-      }
-  }
-
-  if (column.property === 'dailyFuel') {
-    const originalValue = row.dailyFuel ?? 0
-
-    if (originalValue > 3500)
-      return {
-        color: 'blue',
-        fontWeight: 'bold'
-      }
-  }
-
-  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
-  if (timeFields.includes(column.property)) {
-    if (!checkTimeSumEquals24(row)) {
-      return {
-        color: 'orange',
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+defineOptions({ name: 'IotRyDailyReportApproval' })
 
 const id = useUserStore().getUser.deptId
 
@@ -446,14 +16,14 @@ const deptId = id
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
-  projectClassification: '1' | '2'
+  createTime?: string[]
+  projectClassification?: string
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
@@ -461,44 +31,35 @@ const query = ref<Query>({
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
   projectClassification: '2'
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
 }
 
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
-
-const loading = ref(false)
+const query = ref<Query>({ ...initQuery })
 
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
-
-    data.list.forEach((v) => {
-      const { ratedProductionTime = 0, productionTime = 0 } = v
-
-      v.transitTime = ratedProductionTime === 0 ? 0 : productionTime / ratedProductionTime
-    })
-
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -508,26 +69,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ],
-    projectClassification: '2'
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -556,168 +108,89 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+    <ry-xj-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-ry-daily-report:query']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                class="!w-220px"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-ry-daily-report:query']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.auditStatus === 10"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-ry-daily-report:update']"
-                      >
-                        审批
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <ry-xj-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.auditStatus === 10"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:update']"
+        >
+          审批
+        </el-button>
+      </template>
+    </ry-xj-table>
   </div>
+  <ry-xj-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.warning-input) {
-  .el-input__inner {
-    color: red !important;
-    -webkit-text-fill-color: red !important;
-  }
-}
-
-:deep(.blue-input) {
-  .el-input__inner {
-    color: blue !important;
-    -webkit-text-fill-color: blue !important;
-  }
-}
-
-:deep(.orange-input) {
-  .el-input__inner {
-    color: orange !important;
-    -webkit-text-fill-color: orange !important;
-  }
-}
-
-:deep(.el-input-number) {
-  width: 100%;
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 723 - 0
src/views/pms/iotrydailyreport/xapproval1.vue

@@ -0,0 +1,723 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import ryXjForm from './ry-xj-form.vue'
+
+interface List {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  repairStatus: string
+  totalConstructionWells: number
+  completedWells: number
+  technique: string
+  wellCategory: string
+  designWellDepth: number
+  casingPipeSize: string
+  wellControlLevel: string
+  dailyPowerUsage: number
+  dailyFuel: number
+  constructionStartDate: number
+  constructionEndDate: number
+  currentOperation: string
+  nextPlan: string
+  transitTime: number
+  ratedProductionTime: number
+  productionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+  ryNptReason: string
+  productionStatus: string
+  totalStaffNum: number
+  onDutyStaffNum: number
+  leaveStaffNum: number
+  status: number
+  auditStatus: number
+  opinion: string
+  offDutyStaffNum: number
+  remark: string
+  nonProductionRate: number
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
+    fixed: 'left'
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '施工状态',
+    prop: 'repairStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE,
+    fixed: 'left'
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    'min-width': '120px'
+  },
+  {
+    label: '完工井数',
+    prop: 'completedWells',
+    'min-width': '120px'
+  },
+  {
+    label: '施工工艺',
+    prop: 'technique',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY
+  },
+  {
+    label: '井别',
+    prop: 'wellCategory',
+    'min-width': '120px'
+  },
+  {
+    label: '井深(m)',
+    prop: 'designWellDepth',
+    'min-width': '120px'
+  },
+  {
+    label: '套生段产管尺寸(mm)',
+    prop: 'casingPipeSize',
+    'min-width': '120px'
+  },
+  {
+    label: '井控级别',
+    prop: 'wellControlLevel',
+    'min-width': '120px'
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(升)',
+        prop: 'dailyFuel',
+        'min-width': '120px'
+      }
+    ]
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '目前工序',
+    prop: 'currentOperation',
+    'min-width': '120px'
+  },
+  {
+    label: '下步工序',
+    prop: 'nextPlan',
+    'min-width': '120px'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
+  },
+  {
+    label: '额定生产时间(H)',
+    prop: 'ratedProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '生产时间(H)',
+    prop: 'productionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  },
+
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '全员数量',
+    prop: 'totalStaffNum',
+    'min-width': '120px'
+  },
+  {
+    label: '在岗人数',
+    prop: 'onDutyStaffNum',
+    'min-width': '120px',
+    formatter: (row: List) => (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
+  },
+  {
+    label: '休假人员数量',
+    prop: 'leaveStaffNum',
+    'min-width': '120px'
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+const nptFields = [
+  'accidentTime', // 工程质量
+  'repairTime', // 设备故障
+  'selfStopTime', // 设备保养
+  'complexityTime', // 技术受限
+  'relocationTime', // 生产配合
+  'rectificationTime', // 生产组织
+  'waitingStopTime', // 不可抗力
+  'winterBreakTime', // 待命
+  'partyaDesign', // 甲方设计
+  'partyaPrepare', // 甲方资源
+  'partyaResource', // 甲方准备
+  'otherNptTime' // 其它非生产时间
+]
+
+function checkTimeSumEquals24(row: List) {
+  const ratedProductionTime = parseFloat(row.ratedProductionTime + '') || 0
+  const productionTime = parseFloat(row.productionTime + '') || 0
+
+  // 3. 计算所有非生产时间细项的总和
+  const totalNonProductionTime = nptFields.reduce((sum, field) => {
+    // 获取字段值,转为浮点数,如果是 NaN 则视为 0
+    const val = parseFloat(row[field])
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  // 4. 计算实际总投入时间:生产时间 + 所有非生产时间之和
+  const currentSum = productionTime + totalNonProductionTime
+
+  // 5. 校验逻辑:判断 (生产时间 + 非生产时间总和) 是否等于 额定生产时间
+  // 允许 0.01 的浮点数误差
+  return Math.abs(currentSum - ratedProductionTime) < 0.01
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    const originalValue = Number(row.transitTime ?? 0)
+
+    if (originalValue >= 1)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  if (column.property === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 3500)
+      return {
+        color: 'blue',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+  projectClassification: '1' | '2'
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  projectClassification: '2'
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
+
+    data.list.forEach((v) => {
+      const { ratedProductionTime = 0, productionTime = 0 } = v
+
+      v.transitTime = ratedProductionTime === 0 ? 0 : productionTime / ratedProductionTime
+    })
+
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ],
+    projectClassification: '2'
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                class="!w-220px"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-ry-daily-report:query']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.auditStatus === 10"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-ry-daily-report:update']"
+                      >
+                        审批
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <ry-xj-form v-model:visible="visible" type="approval" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.warning-input) {
+  .el-input__inner {
+    color: red !important;
+    -webkit-text-fill-color: red !important;
+  }
+}
+
+:deep(.blue-input) {
+  .el-input__inner {
+    color: blue !important;
+    -webkit-text-fill-color: blue !important;
+  }
+}
+
+:deep(.orange-input) {
+  .el-input__inner {
+    color: orange !important;
+    -webkit-text-fill-color: orange !important;
+  }
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 102 - 636
src/views/pms/iotrydailyreport/xfill.vue

@@ -1,450 +1,13 @@
 <script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useUserStore } from '@/store/modules/user'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
 import dayjs from 'dayjs'
-import { DICT_TYPE } from '@/utils/dict'
-import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { useUserStore } from '@/store/modules/user'
-import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import ryXjForm from './ry-xj-form.vue'
+import RyXjTable from './ry-xj-table.vue'
 
-interface List {
-  id: number
-  deptId: number
-  projectId: number
-  taskId: number
-  createTime: number
-  deptName: string
-  contractName: string
-  taskName: string
-  repairStatus: string
-  totalConstructionWells: number
-  completedWells: number
-  technique: string
-  wellCategory: string
-  designWellDepth: number
-  casingPipeSize: string
-  wellControlLevel: string
-  dailyPowerUsage: number
-  dailyFuel: number
-  constructionStartDate: number
-  constructionEndDate: number
-  currentOperation: string
-  nextPlan: string
-  transitTime: number
-  ratedProductionTime: number
-  productionTime: number
-  accidentTime: number
-  repairTime: number
-  selfStopTime: number
-  complexityTime: number
-  relocationTime: number
-  rectificationTime: number
-  waitingStopTime: number
-  winterBreakTime: number
-  partyaDesign: number
-  partyaPrepare: number
-  partyaResource: number
-  otherNptTime: number
-  otherNptReason: string
-  ryNptReason: string
-  productionStatus: string
-  totalStaffNum: number
-  onDutyStaffNum: number
-  leaveStaffNum: number
-  status: number
-  auditStatus: number
-  opinion: string
-  offDutyStaffNum: number
-  remark: string
-  nonProductionRate: number
-}
-
-interface Column {
-  prop?: keyof List
-  label: string
-  'min-width'?: string
-  isTag?: boolean
-  fixed?: 'left' | 'right'
-  formatter?: (row: List) => any
-  children?: Column[]
-  dictType?: string
-}
-
-const columns = ref<Column[]>([
-  {
-    label: '日期',
-    prop: 'createTime',
-    'min-width': '120px',
-    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
-    fixed: 'left'
-  },
-  {
-    label: '施工队伍',
-    prop: 'deptName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-
-  {
-    label: '任务',
-    prop: 'taskName',
-    'min-width': '120px',
-    fixed: 'left'
-  },
-  {
-    label: '施工状态',
-    prop: 'repairStatus',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE,
-    fixed: 'left'
-  },
-  {
-    label: '审批状态',
-    prop: 'auditStatus',
-    'min-width': '120px',
-    isTag: true,
-    formatter: (row: List) => {
-      switch (row.auditStatus) {
-        case 0:
-          return '待提交'
-        case 10:
-          return '待审批'
-        case 20:
-          return '审批通过'
-        case 30:
-          return '审批拒绝'
-        default:
-          return ''
-      }
-    }
-  },
-  {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    'min-width': '120px'
-  },
-  {
-    label: '完工井数',
-    prop: 'completedWells',
-    'min-width': '120px'
-  },
-  {
-    label: '施工工艺',
-    prop: 'technique',
-    'min-width': '120px',
-    isTag: true,
-    dictType: DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY
-  },
-  {
-    label: '井别',
-    prop: 'wellCategory',
-    'min-width': '120px'
-  },
-  {
-    label: '井深(m)',
-    prop: 'designWellDepth',
-    'min-width': '120px'
-  },
-  {
-    label: '套生段产管尺寸(mm)',
-    prop: 'casingPipeSize',
-    'min-width': '120px'
-  },
-  {
-    label: '井控级别',
-    prop: 'wellControlLevel',
-    'min-width': '120px'
-  },
-  {
-    label: '当日',
-    children: [
-      {
-        label: '用电量(kWh)',
-        prop: 'dailyPowerUsage',
-        'min-width': '120px'
-      },
-      {
-        label: '油耗(升)',
-        prop: 'dailyFuel',
-        'min-width': '120px'
-      }
-    ]
-  },
-  // {
-  //   label: '施工开始日期',
-  //   prop: 'constructionStartDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  // {
-  //   label: '施工结束日期',
-  //   prop: 'constructionEndDate',
-  //   'min-width': '120px',
-  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
-  // },
-  {
-    label: '目前工序',
-    prop: 'currentOperation',
-    'min-width': '120px'
-  },
-  {
-    label: '下步工序',
-    prop: 'nextPlan',
-    'min-width': '120px'
-  },
-  {
-    label: '运行时效',
-    prop: 'transitTime',
-    'min-width': '120px',
-    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
-  },
-  {
-    label: '额定生产时间(H)',
-    prop: 'ratedProductionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '生产时间(H)',
-    prop: 'productionTime',
-    'min-width': '120px'
-  },
-  {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    'min-width': '120px',
-    formatter: (row: List) => {
-      const nonProductionRate = row?.nonProductionRate ?? 0
-
-      return (nonProductionRate * 100).toFixed(2) + '%'
-    }
-  },
-  {
-    label: '非生产时间',
-    children: [
-      {
-        label: '工程质量',
-        prop: 'accidentTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备故障',
-        prop: 'repairTime',
-        'min-width': '120px'
-      },
-      {
-        label: '设备保养',
-        prop: 'selfStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '技术受限',
-        prop: 'complexityTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产配合',
-        prop: 'relocationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '生产组织',
-        prop: 'rectificationTime',
-        'min-width': '120px'
-      },
-      {
-        label: '不可抗力',
-        prop: 'waitingStopTime',
-        'min-width': '120px'
-      },
-      {
-        label: '待命',
-        prop: 'winterBreakTime',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方设计',
-        prop: 'partyaDesign',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方准备',
-        prop: 'partyaPrepare',
-        'min-width': '120px'
-      },
-      {
-        label: '甲方资源',
-        prop: 'partyaResource',
-        'min-width': '120px'
-      },
-      {
-        label: '其它非生产时间',
-        prop: 'otherNptTime',
-        'min-width': '120px'
-      }
-    ]
-  },
-  {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    'min-width': '120px'
-  },
-
-  // {
-  //   label: '非生产时间原因',
-  //   prop: 'ryNptReason',
-  //   'min-width': '120px',
-  //   isTag: true,
-  //   dictType: DICT_TYPE.PMS_PROJECT_RY_NPT_REASON
-  // },
-  {
-    label: '生产动态',
-    prop: 'productionStatus',
-    'min-width': '120px'
-  },
-  {
-    label: '项目',
-    prop: 'contractName',
-    'min-width': '120px'
-  },
-  {
-    label: '全员数量',
-    prop: 'totalStaffNum',
-    'min-width': '120px'
-  },
-  {
-    label: '在岗人数',
-    prop: 'onDutyStaffNum',
-    'min-width': '120px',
-    formatter: (row: List) => (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
-  },
-  {
-    label: '休假人员数量',
-    prop: 'leaveStaffNum',
-    'min-width': '120px'
-  }
-])
-
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
-
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
-
-  return width
-}
-
-const calculateColumnWidths = (colums: Column[]) => {
-  for (const col of colums) {
-    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
-
-    if (children && children.length > 0) {
-      calculateColumnWidths(children)
-      continue
-    }
-
-    minWidth =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(formatter ? formatter(v) : v[prop!])
-              })
-            ]
-          ) + (isTag ? 40 : 20),
-          200
-        ]
-      ) + 'px'
-
-    col['min-width'] = minWidth
-  }
-}
-
-const nptFields = [
-  'accidentTime', // 工程质量
-  'repairTime', // 设备故障
-  'selfStopTime', // 设备保养
-  'complexityTime', // 技术受限
-  'relocationTime', // 生产配合
-  'rectificationTime', // 生产组织
-  'waitingStopTime', // 不可抗力
-  'winterBreakTime', // 待命
-  'partyaDesign', // 甲方设计
-  'partyaPrepare', // 甲方资源
-  'partyaResource', // 甲方准备
-  'otherNptTime' // 其它非生产时间
-]
-
-function checkTimeSumEquals24(row: List) {
-  const ratedProductionTime = parseFloat(row.ratedProductionTime + '') || 0
-  const productionTime = parseFloat(row.productionTime + '') || 0
-
-  // 3. 计算所有非生产时间细项的总和
-  const totalNonProductionTime = nptFields.reduce((sum, field) => {
-    // 获取字段值,转为浮点数,如果是 NaN 则视为 0
-    const val = parseFloat(row[field])
-    return sum + (isNaN(val) ? 0 : val)
-  }, 0)
-
-  // 4. 计算实际总投入时间:生产时间 + 所有非生产时间之和
-  const currentSum = productionTime + totalNonProductionTime
-
-  // 5. 校验逻辑:判断 (生产时间 + 非生产时间总和) 是否等于 额定生产时间
-  // 允许 0.01 的浮点数误差
-  return Math.abs(currentSum - ratedProductionTime) < 0.01
-}
-
-function cellStyle(data: {
-  row: List
-  column: TableColumnCtx<List>
-  rowIndex: number
-  columnIndex: number
-}) {
-  const { row, column } = data
-
-  if (column.property === 'transitTime') {
-    const originalValue = Number(row.transitTime ?? 0)
-
-    if (originalValue >= 1)
-      return {
-        color: 'red',
-        fontWeight: 'bold'
-      }
-  }
-
-  if (column.property === 'dailyFuel') {
-    const originalValue = row.dailyFuel ?? 0
-
-    if (originalValue > 3500)
-      return {
-        color: 'blue',
-        fontWeight: 'bold'
-      }
-  }
-
-  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
-  if (timeFields.includes(column.property)) {
-    if (!checkTimeSumEquals24(row)) {
-      return {
-        color: 'orange',
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+defineOptions({ name: 'IotRyDailyReportFill' })
 
 const id = useUserStore().getUser.deptId
 
@@ -453,14 +16,14 @@ const deptId = id
 interface Query {
   pageNo: number
   pageSize: number
-  deptId: number
+  deptId?: number
   contractName?: string
   taskName?: string
-  createTime: string[]
-  projectClassification: '1' | '2'
+  createTime?: string[]
+  projectClassification?: string
 }
 
-const query = ref<Query>({
+const initQuery: Query = {
   pageNo: 1,
   pageSize: 10,
   deptId: id,
@@ -468,44 +31,35 @@ const query = ref<Query>({
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
   projectClassification: '2'
-})
-
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
 }
 
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
-
-const loading = ref(false)
+const query = ref<Query>({ ...initQuery })
 
-const list = ref<List[]>([])
+const list = ref<any[]>([])
 const total = ref(0)
 
+const loading = ref(false)
+
 const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
     const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
-
-    data.list.forEach((v) => {
-      const { ratedProductionTime = 0, productionTime = 0 } = v
-
-      v.transitTime = ratedProductionTime === 0 ? 0 : productionTime / ratedProductionTime
-    })
-
     list.value = data.list
     total.value = data.total
-
-    nextTick(() => {
-      calculateColumnWidths(columns.value)
-    })
   } finally {
     loading.value = false
   }
-}, 500)
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
 
 function handleQuery(setPage = true) {
   if (setPage) {
@@ -515,26 +69,17 @@ function handleQuery(setPage = true) {
 }
 
 function resetQuery() {
-  query.value = {
-    pageNo: 1,
-    pageSize: 10,
-    deptId: 157,
-    contractName: '',
-    taskName: '',
-    createTime: [
-      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-    ],
-    projectClassification: '2'
-  }
+  query.value = { ...initQuery }
+
   handleQuery()
 }
 
 watch(
   [
-    () => query.value.createTime,
     () => query.value.deptId,
+    () => query.value.contractName,
     () => query.value.taskName,
-    () => query.value.contractName
+    () => query.value.createTime
   ],
   () => {
     handleQuery()
@@ -563,168 +108,89 @@ onMounted(() => {
 
 <template>
   <div
-    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
-    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
-      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
-        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
       </div>
-      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
-        <el-form
-          size="default"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+      <el-form-item>
+        <el-button type="primary" @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-form-item>
+    </el-form>
+    <ry-xj-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="success"
+          @click="handleOpenForm(row.id, 'readonly')"
+          v-hasPermi="['pms:iot-ry-daily-report:query']"
         >
-          <div class="flex items-center gap-8">
-            <el-form-item label="项目">
-              <el-input
-                v-model="query.contractName"
-                placeholder="请输入项目"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="任务">
-              <el-input
-                v-model="query.taskName"
-                placeholder="请输入任务"
-                clearable
-                @keyup.enter="handleQuery()"
-                class="!w-240px"
-              />
-            </el-form-item>
-            <el-form-item label="创建时间">
-              <el-date-picker
-                v-model="query.createTime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :shortcuts="rangeShortcuts"
-                class="!w-220px"
-                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              />
-            </el-form-item>
-          </div>
-          <el-form-item>
-            <el-button type="primary" @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-form-item>
-        </el-form>
-        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
-          <div class="flex-1 relative">
-            <el-auto-resizer class="absolute">
-              <template #default="{ width, height }">
-                <el-table
-                  :data="list"
-                  v-loading="loading"
-                  stripe
-                  class="absolute"
-                  :max-height="height"
-                  show-overflow-tooltip
-                  :width="width"
-                  :cell-style="cellStyle"
-                  border
-                >
-                  <DailyTableColumn :columns="columns" />
-                  <el-table-column label="操作" width="120px" align="center" fixed="right">
-                    <template #default="{ row }">
-                      <el-button
-                        link
-                        type="success"
-                        @click="handleOpenForm(row.id, 'readonly')"
-                        v-hasPermi="['pms:iot-ry-daily-report:query']"
-                      >
-                        查看
-                      </el-button>
-                      <el-button
-                        v-show="row.status === 0"
-                        link
-                        type="primary"
-                        @click="handleOpenForm(row.id, 'edit')"
-                        v-hasPermi="['pms:iot-ry-daily-report:create']"
-                      >
-                        编辑
-                      </el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
-              </template>
-            </el-auto-resizer>
-          </div>
-          <div class="h-10 mt-4 flex items-center justify-end">
-            <el-pagination
-              size="default"
-              v-show="total > 0"
-              v-model:current-page="query.pageNo"
-              v-model:page-size="query.pageSize"
-              :background="true"
-              :page-sizes="[10, 20, 30, 50, 100]"
-              :total="total"
-              layout="total, sizes, prev, pager, next, jumper"
-              @size-change="handleSizeChange"
-              @current-change="handleCurrentChange"
-            />
-          </div>
-        </div>
-      </div>
-    </div>
-    <ry-xj-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+          查看
+        </el-button>
+        <el-button
+          v-show="row.status === 0"
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:create']"
+        >
+          编辑
+        </el-button>
+      </template>
+    </ry-xj-table>
   </div>
+  <ry-xj-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
 </template>
 
 <style scoped>
 :deep(.el-form-item) {
   margin-bottom: 0;
 }
-
-:deep(.el-table) {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-
-  .el-table__cell {
-    height: 40px;
-  }
-
-  .el-table__header-wrapper {
-    .el-table__cell {
-      background: var(--el-fill-color-light);
-    }
-  }
-}
-
-:deep(.warning-input) {
-  .el-input__inner {
-    color: red !important;
-    -webkit-text-fill-color: red !important;
-  }
-}
-
-:deep(.blue-input) {
-  .el-input__inner {
-    color: blue !important;
-    -webkit-text-fill-color: blue !important;
-  }
-}
-
-:deep(.orange-input) {
-  .el-input__inner {
-    color: orange !important;
-    -webkit-text-fill-color: orange !important;
-  }
-}
-
-:deep(.el-input-number) {
-  width: 100%;
-}
-
-:deep(.el-input-number__decrease) {
-  display: none !important;
-}
-
-:deep(.el-input-number__increase) {
-  display: none !important;
-}
 </style>

+ 730 - 0
src/views/pms/iotrydailyreport/xfill1.vue

@@ -0,0 +1,730 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import ryXjForm from './ry-xj-form.vue'
+
+interface List {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  repairStatus: string
+  totalConstructionWells: number
+  completedWells: number
+  technique: string
+  wellCategory: string
+  designWellDepth: number
+  casingPipeSize: string
+  wellControlLevel: string
+  dailyPowerUsage: number
+  dailyFuel: number
+  constructionStartDate: number
+  constructionEndDate: number
+  currentOperation: string
+  nextPlan: string
+  transitTime: number
+  ratedProductionTime: number
+  productionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  partyaDesign: number
+  partyaPrepare: number
+  partyaResource: number
+  otherNptTime: number
+  otherNptReason: string
+  ryNptReason: string
+  productionStatus: string
+  totalStaffNum: number
+  onDutyStaffNum: number
+  leaveStaffNum: number
+  status: number
+  auditStatus: number
+  opinion: string
+  offDutyStaffNum: number
+  remark: string
+  nonProductionRate: number
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
+    fixed: 'left'
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '施工状态',
+    prop: 'repairStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE,
+    fixed: 'left'
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    'min-width': '120px'
+  },
+  {
+    label: '完工井数',
+    prop: 'completedWells',
+    'min-width': '120px'
+  },
+  {
+    label: '施工工艺',
+    prop: 'technique',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY
+  },
+  {
+    label: '井别',
+    prop: 'wellCategory',
+    'min-width': '120px'
+  },
+  {
+    label: '井深(m)',
+    prop: 'designWellDepth',
+    'min-width': '120px'
+  },
+  {
+    label: '套生段产管尺寸(mm)',
+    prop: 'casingPipeSize',
+    'min-width': '120px'
+  },
+  {
+    label: '井控级别',
+    prop: 'wellControlLevel',
+    'min-width': '120px'
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(升)',
+        prop: 'dailyFuel',
+        'min-width': '120px'
+      }
+    ]
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '目前工序',
+    prop: 'currentOperation',
+    'min-width': '120px'
+  },
+  {
+    label: '下步工序',
+    prop: 'nextPlan',
+    'min-width': '120px'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
+  },
+  {
+    label: '额定生产时间(H)',
+    prop: 'ratedProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '生产时间(H)',
+    prop: 'productionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    'min-width': '120px',
+    formatter: (row: List) => {
+      const nonProductionRate = row?.nonProductionRate ?? 0
+
+      return (nonProductionRate * 100).toFixed(2) + '%'
+    }
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '工程质量',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备故障',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '设备保养',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '技术受限',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产配合',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '生产组织',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '不可抗力',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '待命',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方设计',
+        prop: 'partyaDesign',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方准备',
+        prop: 'partyaPrepare',
+        'min-width': '120px'
+      },
+      {
+        label: '甲方资源',
+        prop: 'partyaResource',
+        'min-width': '120px'
+      },
+      {
+        label: '其它非生产时间',
+        prop: 'otherNptTime',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    'min-width': '120px'
+  },
+
+  // {
+  //   label: '非生产时间原因',
+  //   prop: 'ryNptReason',
+  //   'min-width': '120px',
+  //   isTag: true,
+  //   dictType: DICT_TYPE.PMS_PROJECT_RY_NPT_REASON
+  // },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '全员数量',
+    prop: 'totalStaffNum',
+    'min-width': '120px'
+  },
+  {
+    label: '在岗人数',
+    prop: 'onDutyStaffNum',
+    'min-width': '120px',
+    formatter: (row: List) => (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
+  },
+  {
+    label: '休假人员数量',
+    prop: 'leaveStaffNum',
+    'min-width': '120px'
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(formatter ? formatter(v) : v[prop!])
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          200
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+const nptFields = [
+  'accidentTime', // 工程质量
+  'repairTime', // 设备故障
+  'selfStopTime', // 设备保养
+  'complexityTime', // 技术受限
+  'relocationTime', // 生产配合
+  'rectificationTime', // 生产组织
+  'waitingStopTime', // 不可抗力
+  'winterBreakTime', // 待命
+  'partyaDesign', // 甲方设计
+  'partyaPrepare', // 甲方资源
+  'partyaResource', // 甲方准备
+  'otherNptTime' // 其它非生产时间
+]
+
+function checkTimeSumEquals24(row: List) {
+  const ratedProductionTime = parseFloat(row.ratedProductionTime + '') || 0
+  const productionTime = parseFloat(row.productionTime + '') || 0
+
+  // 3. 计算所有非生产时间细项的总和
+  const totalNonProductionTime = nptFields.reduce((sum, field) => {
+    // 获取字段值,转为浮点数,如果是 NaN 则视为 0
+    const val = parseFloat(row[field])
+    return sum + (isNaN(val) ? 0 : val)
+  }, 0)
+
+  // 4. 计算实际总投入时间:生产时间 + 所有非生产时间之和
+  const currentSum = productionTime + totalNonProductionTime
+
+  // 5. 校验逻辑:判断 (生产时间 + 非生产时间总和) 是否等于 额定生产时间
+  // 允许 0.01 的浮点数误差
+  return Math.abs(currentSum - ratedProductionTime) < 0.01
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    const originalValue = Number(row.transitTime ?? 0)
+
+    if (originalValue >= 1)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  if (column.property === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 3500)
+      return {
+        color: 'blue',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  createTime: string[]
+  projectClassification: '1' | '2'
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  projectClassification: '2'
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
+
+    data.list.forEach((v) => {
+      const { ratedProductionTime = 0, productionTime = 0 } = v
+
+      v.transitTime = ratedProductionTime === 0 ? 0 : productionTime / ratedProductionTime
+    })
+
+    list.value = data.list
+    total.value = data.total
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ],
+    projectClassification: '2'
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+</script>
+
+<template>
+  <div
+    class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="grid grid-cols-[15%_1fr] gap-4 h-full">
+      <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+        <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" />
+      </div>
+      <div class="grid grid-rows-[62px_1fr] h-full gap-4">
+        <el-form
+          size="default"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
+        >
+          <div class="flex items-center gap-8">
+            <el-form-item label="项目">
+              <el-input
+                v-model="query.contractName"
+                placeholder="请输入项目"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="任务">
+              <el-input
+                v-model="query.taskName"
+                placeholder="请输入任务"
+                clearable
+                @keyup.enter="handleQuery()"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="query.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :shortcuts="rangeShortcuts"
+                class="!w-220px"
+                :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              />
+            </el-form-item>
+          </div>
+          <el-form-item>
+            <el-button type="primary" @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-form-item>
+        </el-form>
+        <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col">
+          <div class="flex-1 relative">
+            <el-auto-resizer class="absolute">
+              <template #default="{ width, height }">
+                <el-table
+                  :data="list"
+                  v-loading="loading"
+                  stripe
+                  class="absolute"
+                  :max-height="height"
+                  show-overflow-tooltip
+                  :width="width"
+                  :cell-style="cellStyle"
+                  border
+                >
+                  <DailyTableColumn :columns="columns" />
+                  <el-table-column label="操作" width="120px" align="center" fixed="right">
+                    <template #default="{ row }">
+                      <el-button
+                        link
+                        type="success"
+                        @click="handleOpenForm(row.id, 'readonly')"
+                        v-hasPermi="['pms:iot-ry-daily-report:query']"
+                      >
+                        查看
+                      </el-button>
+                      <el-button
+                        v-show="row.status === 0"
+                        link
+                        type="primary"
+                        @click="handleOpenForm(row.id, 'edit')"
+                        v-hasPermi="['pms:iot-ry-daily-report:create']"
+                      >
+                        编辑
+                      </el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </template>
+            </el-auto-resizer>
+          </div>
+          <div class="h-10 mt-4 flex items-center justify-end">
+            <el-pagination
+              size="default"
+              v-show="total > 0"
+              v-model:current-page="query.pageNo"
+              v-model:page-size="query.pageSize"
+              :background="true"
+              :page-sizes="[10, 20, 30, 50, 100]"
+              :total="total"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <ry-xj-form v-model:visible="visible" type="edit" ref="formRef" :load-list="loadList" />
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 40px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+
+:deep(.warning-input) {
+  .el-input__inner {
+    color: red !important;
+    -webkit-text-fill-color: red !important;
+  }
+}
+
+:deep(.blue-input) {
+  .el-input__inner {
+    color: blue !important;
+    -webkit-text-fill-color: blue !important;
+  }
+}
+
+:deep(.orange-input) {
+  .el-input__inner {
+    color: orange !important;
+    -webkit-text-fill-color: orange !important;
+  }
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 218 - 1217
src/views/pms/iotrydailyreport/xjindex.vue

@@ -1,1271 +1,272 @@
-<template>
-  <el-row :gutter="20">
-    <el-col :span="4" :xs="24">
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
-        <DeptTreeSelect
-          :deptId="rootDeptId"
-          :top-id="158"
-          v-model="queryParams.deptId"
-          @node-click="handleDeptNodeClick"
-        />
-      </div>
-      <!-- <ContentWrap class="h-1/1">
-        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
-      </ContentWrap> -->
-    </el-col>
-    <el-col :span="20" :xs="24">
-      <ContentWrap>
-        <!-- 搜索工作栏 -->
-        <el-form
-          class="-mb-15px"
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
-        >
-          <el-form-item label="项目" prop="contractName">
-            <el-input
-              v-model="queryParams.contractName"
-              placeholder="请输入项目"
-              clearable
-              @keyup.enter="handleQuery"
-              class="!w-240px"
-            />
-          </el-form-item>
-          <el-form-item label="任务" prop="taskName">
-            <el-input
-              v-model="queryParams.taskName"
-              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"
-              :shortcuts="rangeShortcuts"
-            />
-          </el-form-item>
-          <el-form-item label="非生产时效" prop="nonProductFlag">
-            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
-          </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-rh-daily-report:create']"
-            >
-              <Icon icon="ep:plus" class="mr-5px" /> 新增
-            </el-button> -->
-            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
-              <Icon icon="ep:download" class="mr-5px" /> 导出
-            </el-button>
-          </el-form-item>
-        </el-form>
-      </ContentWrap>
-
-      <ContentWrap class="mb-15px">
-        <div class="color-legend">
-          <div class="legend-item">
-            <span class="color-indicator red"></span>
-            <span
-              >运行时效=生产时间/额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过100%红色预警</span
-            >
-          </div>
-          <div class="legend-item">
-            <span class="color-indicator orange"></span>
-            <span
-              >生产时间+非生产时间=额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
-            >
-          </div>
-          <div class="legend-item">
-            <span class="color-indicator yellow"></span>
-            <span>当日油耗大于3500升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;蓝色预警</span>
-          </div>
-        </div>
-      </ContentWrap>
-
-      <!-- 列表 -->
-      <ContentWrap ref="tableContainerRef">
-        <div class="table-container">
-          <el-table
-            ref="tableRef"
-            v-loading="loading"
-            :data="list"
-            :stripe="true"
-            :style="{ width: '100%' }"
-            max-height="600"
-            :cell-style="cellStyle"
-            show-overflow-tooltip
-            border
-          >
-            <el-table-column
-              :label="t('iotDevice.serial')"
-              width="56px"
-              align="center"
-              fixed="left"
-            >
-              <template #default="scope">
-                {{ scope.$index + 1 }}
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="日期"
-              align="center"
-              prop="createTime"
-              :formatter="dateFormatter2"
-              :min-width="columnWidths.createTime.width"
-              resizable
-              fixed="left"
-            />
-            <el-table-column
-              label="施工队伍"
-              align="center"
-              prop="deptName"
-              :min-width="columnWidths.deptName.width"
-              resizable
-              fixed="left"
-            />
-
-            <el-table-column
-              label="任务"
-              align="center"
-              prop="taskName"
-              :min-width="columnWidths.taskName.width"
-              resizable
-              fixed="left"
-            />
-            <el-table-column
-              :label="t('project.status')"
-              align="center"
-              prop="repairStatus"
-              :min-width="columnWidths.repairStatus.width"
-              resizable
-              fixed="left"
-            >
-              <template #default="scope">
-                <dict-tag
-                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE"
-                  :value="scope.row.repairStatus"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="总施工井数"
-              align="center"
-              prop="totalConstructionWells"
-              :min-width="columnWidths.totalConstructionWells.width"
-              resizable
-            />
-            <el-table-column
-              label="完工井数"
-              align="center"
-              prop="completedWells"
-              :min-width="columnWidths.completedWells.width"
-              resizable
-            />
-            <el-table-column
-              :label="t('project.technology')"
-              align="center"
-              prop="technique"
-              :min-width="columnWidths.technique.width"
-              resizable
-            >
-              <template #default="scope">
-                <dict-tag
-                  :type="DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY"
-                  :value="scope.row.technique"
-                />
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="井别"
-              align="center"
-              prop="wellCategory"
-              :min-width="columnWidths.wellCategory.width"
-              resizable
-            />
-            <el-table-column
-              label="井深(m)"
-              align="center"
-              prop="designWellDepth"
-              :min-width="columnWidths.designWellDepth.width"
-              resizable
-            />
-            <el-table-column
-              label="套生段产管尺寸(mm)"
-              align="center"
-              prop="casingPipeSize"
-              :min-width="columnWidths.casingPipeSize.width"
-              resizable
-            />
-            <el-table-column
-              label="井控级别"
-              align="center"
-              prop="wellControlLevel"
-              :min-width="columnWidths.wellControlLevel.width"
-              resizable
-            />
-            <el-table-column align="center" label="当日">
-              <el-table-column
-                label="用电量(kWh)"
-                align="center"
-                prop="dailyPowerUsage"
-                :min-width="columnWidths.dailyPowerUsage.width"
-                resizable
-              />
-              <el-table-column
-                label="油耗(升)"
-                align="center"
-                prop="dailyFuel"
-                :min-width="columnWidths.dailyFuel.width"
-                resizable
-              />
-            </el-table-column>
-
-            <el-table-column
-              label="施工开始日期"
-              align="center"
-              prop="constructionStartDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionStartDate.width"
-              resizable
-            />
-            <el-table-column
-              label="施工结束日期"
-              align="center"
-              prop="constructionEndDate"
-              :formatter="dateFormatter"
-              :min-width="columnWidths.constructionEndDate.width"
-              resizable
-            />
-
-            <el-table-column
-              :label="t('project.currentOperation')"
-              align="center"
-              prop="currentOperation"
-              :min-width="columnWidths.currentOperation.width"
-              resizable
-            />
-            <el-table-column
-              :label="t('project.nextPlan')"
-              align="center"
-              prop="nextPlan"
-              :min-width="columnWidths.nextPlan.width"
-              resizable
-            />
-            <el-table-column
-              :label="t('project.transitTime')"
-              align="center"
-              prop="transitTime"
-              :min-width="columnWidths.transitTime.width"
-              resizable
-              :formatter="percentageFormatter"
-            />
-            <el-table-column
-              label="额定生产时间(H)"
-              align="center"
-              prop="ratedProductionTime"
-              :min-width="columnWidths.ratedProductionTime.width"
-              resizable
-            />
-            <el-table-column
-              label="生产时间(H)"
-              align="center"
-              prop="productionTime"
-              :min-width="columnWidths.productionTime.width"
-              resizable
-            />
-            <el-table-column
-              label="非生产时效"
-              align="center"
-              prop="nonProductionRate"
-              :formatter="nonProductionRateFormatter"
-              :min-width="columnWidths.nonProductionRate.width"
-              resizable
-            />
-            <el-table-column label="非生产时间" align="center">
-              <el-table-column
-                label="工程质量"
-                align="center"
-                prop="accidentTime"
-                :min-width="columnWidths.accidentTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备故障"
-                align="center"
-                prop="repairTime"
-                :min-width="columnWidths.repairTime.width"
-                resizable
-              />
-              <el-table-column
-                label="设备保养"
-                align="center"
-                prop="selfStopTime"
-                :min-width="columnWidths.selfStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="技术受限"
-                align="center"
-                prop="complexityTime"
-                :min-width="columnWidths.complexityTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产配合"
-                align="center"
-                prop="relocationTime"
-                :min-width="columnWidths.relocationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="生产组织"
-                align="center"
-                prop="rectificationTime"
-                :min-width="columnWidths.rectificationTime.width"
-                resizable
-              />
-              <el-table-column
-                label="不可抗力"
-                align="center"
-                prop="waitingStopTime"
-                :min-width="columnWidths.waitingStopTime.width"
-                resizable
-              />
-              <el-table-column
-                label="待命"
-                align="center"
-                prop="winterBreakTime"
-                :min-width="columnWidths.winterBreakTime.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方设计"
-                align="center"
-                prop="partyaDesign"
-                :min-width="columnWidths.partyaDesign.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方准备"
-                align="center"
-                prop="partyaPrepare"
-                :min-width="columnWidths.partyaPrepare.width"
-                resizable
-              />
-              <el-table-column
-                label="甲方资源"
-                align="center"
-                prop="partyaResource"
-                :min-width="columnWidths.partyaResource.width"
-                resizable
-              />
-              <el-table-column
-                label="其它非生产时间"
-                align="center"
-                prop="otherNptTime"
-                :min-width="columnWidths.otherNptTime.width"
-                resizable
-              />
-            </el-table-column>
-            <el-table-column
-              label="其他非生产时间原因"
-              align="center"
-              prop="otherNptReason"
-              :min-width="columnWidths.otherNptReason.width"
-              resizable
-            />
-
-            <!-- <el-table-column
-              :label="t('project.nptReason')"
-              align="center"
-              prop="ryNptReason"
-              :min-width="columnWidths.ryNptReason.width"
-              resizable
-            >
-              <template #default="scope">
-                <dict-tag
-                  :type="DICT_TYPE.PMS_PROJECT_RY_NPT_REASON"
-                  :value="scope.row.ryNptReason"
-                />
-              </template>
-            </el-table-column> -->
-            <el-table-column
-              label="生产动态"
-              align="center"
-              prop="productionStatus"
-              :min-width="columnWidths.productionStatus.width"
-              resizable
-            />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              :min-width="columnWidths.contractName.width"
-              resizable
-            />
-            <el-table-column
-              label="全员数量"
-              align="center"
-              prop="totalStaffNum"
-              :min-width="columnWidths.totalStaffNum.width"
-              resizable
-            />
-            <!--
-            <el-table-column label="在岗人数" align="center" prop="onDutyStaffNum" :min-width="columnWidths.onDutyStaffNum"/> -->
-            <el-table-column
-              label="在岗人数"
-              align="center"
-              prop="onDutyStaffNum"
-              :min-width="columnWidths.onDutyStaffNum.width"
-              resizable
-            >
-              <template #default="scope">
-                <!-- 动态计算:在岗人数 = 全员数量 - 休假人员数量 -->
-                {{
-                  (Number(scope.row.totalStaffNum) || 0) - (Number(scope.row.leaveStaffNum) || 0)
-                }}
-              </template>
-            </el-table-column>
-            <el-table-column
-              label="休假人员数量"
-              align="center"
-              prop="leaveStaffNum"
-              :min-width="columnWidths.leaveStaffNum.width"
-              resizable
-            />
-            <el-table-column label="操作" align="center" fixed="right">
-              <template #default="scope">
-                <el-button
-                  link
-                  type="primary"
-                  @click="openForm('update', scope.row.id, scope.row)"
-                  v-hasPermi="['pms:iot-ry-daily-report:update']"
-                >
-                  编辑
-                </el-button>
-                <el-button
-                  link
-                  type="danger"
-                  @click="handleDelete(scope.row.id)"
-                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
-                >
-                  删除
-                </el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-        <!-- 分页 -->
-        <Pagination
-          :total="total"
-          v-model:page="queryParams.pageNo"
-          v-model:limit="queryParams.pageSize"
-          @pagination="getList"
-        />
-      </ContentWrap>
-
-      <!-- 表单弹窗:添加/修改 -->
-      <IotRyXjDailyReportForm ref="formRef" @success="getList" :row-data="selectedRowData" />
-    </el-col>
-  </el-row>
-</template>
-
-<script setup lang="ts">
-import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
+<script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { useUserStore } from '@/store/modules/user'
 import download from '@/utils/download'
-import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
-import IotRyXjDailyReportForm from './IotRyXjDailyReportForm.vue'
-import { DICT_TYPE } from '@/utils/dict'
-import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
+import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
-
-import { useUserStore } from '@/store/modules/user'
-
+import ryXjForm from './ry-xj-form.vue'
 import dayjs from 'dayjs'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
-
-dayjs.extend(quarterOfYear)
+import RyXjTable from './ry-xj-table.vue'
 
-/** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyXjDailyReport' })
 
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-// 添加 selectedRowData 响应式变量
-const selectedRowData = ref<Record<string, any> | null>(null)
-
-const loading = ref(true) // 列表的加载中
-const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-let queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  deptId: useUserStore().getUser.deptId,
-  contractName: undefined,
-  projectId: undefined,
-  taskName: undefined,
-  taskId: undefined,
-  projectClassification: '2',
-  relocationDays: undefined,
-  latestWellDoneTime: [],
-  currentDepth: undefined,
-  dailyFootage: undefined,
-  monthlyFootage: undefined,
-  annualFootage: undefined,
-  dailyPowerUsage: undefined,
-  monthlyPowerUsage: undefined,
-  dailyFuel: undefined,
-  monthlyFuel: undefined,
-  nonProductionTime: [],
-  nptReason: undefined,
-  constructionStartDate: [],
-  constructionEndDate: [],
-  productionStatus: undefined,
-  nextPlan: undefined,
-  rigStatus: undefined,
-  personnel: undefined,
-  mudDensity: undefined,
-  mudViscosity: undefined,
-  lateralLength: undefined,
-  wellInclination: undefined,
-  azimuth: undefined,
-  extProperty: undefined,
-  sort: undefined,
-  remark: undefined,
-  status: undefined,
-  processInstanceId: undefined,
-  auditStatus: undefined,
-  createTime: [
-    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
-  ],
-  nonProductFlag: 'N'
-})
-const queryFormRef = ref() // 搜索的表单
-
-const rootDeptId = ref(useUserStore().getUser.deptId)
-
-// 表格引用
-const tableRef = ref()
-// 表格容器引用
-const tableContainerRef = ref()
+const { t } = useI18n()
 
-// 列宽度配置
-const columnWidths = ref<
-  Record<
-    string,
-    {
-      label: string
-      prop: string
-      width: string
-      fn?: (row: IotRyDailyReportVO) => string | number
-    }
-  >
->({
-  createTime: {
-    label: '日期',
-    prop: 'createTime',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.createTime)
-  },
-  deptName: {
-    label: '施工队伍',
-    prop: 'deptName',
-    width: '120px'
-  },
-  contractName: {
-    label: '项目',
-    prop: 'contractName',
-    width: '120px'
-  },
-  taskName: {
-    label: '任务',
-    prop: 'taskName',
-    width: '120px'
-  },
-  equipmentType: {
-    label: '设备型号',
-    prop: 'equipmentType',
-    width: '120px'
-  },
-  repairStatus: {
-    label: '施工状态',
-    prop: 'repairStatus',
-    width: '120px'
-  },
-  totalConstructionWells: {
-    label: '总施工井数',
-    prop: 'totalConstructionWells',
-    width: '120px'
-  },
-  completedWells: {
-    label: '完工井数',
-    prop: 'completedWells',
-    width: '120px'
-  },
-  technique: {
-    label: '施工工艺',
-    prop: 'technique',
-    width: '120px'
-  },
-  wellCategory: {
-    label: '井别',
-    prop: 'wellCategory',
-    width: '120px'
-  },
-  designWellDepth: {
-    label: '井深(m)',
-    prop: 'designWellDepth',
-    width: '120px'
-  },
-  casingPipeSize: {
-    label: '套生段产管尺寸(mm)',
-    prop: 'casingPipeSize',
-    width: '120px'
-  },
-  wellControlLevel: {
-    label: '井控级别',
-    prop: 'wellControlLevel',
-    width: '120px'
-  },
-  dailyPowerUsage: {
-    label: '用电量(kWh)',
-    prop: 'dailyPowerUsage',
-    width: '120px'
-  },
-  dailyFuel: {
-    label: '油耗(升)',
-    prop: 'dailyFuel',
-    width: '120px'
-  },
-  constructionStartDate: {
-    label: '施工开始日期',
-    prop: 'constructionStartDate',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
-  },
-  constructionEndDate: {
-    label: '施工结束日期',
-    prop: 'constructionEndDate',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
-  },
-  currentOperation: {
-    label: '目前',
-    prop: 'currentOperation',
-    width: '120px'
-  },
-  nextPlan: {
-    label: '下步',
-    prop: 'nextPlan',
-    width: '120px'
-  },
-  transitTime: {
-    label: '运行时效',
-    prop: 'transitTime',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) => percentageFormatter(null, null, row.transitTime, null)
-  },
-  ratedProductionTime: {
-    label: '额定生产时间(H)',
-    prop: 'ratedProductionTime',
-    width: '120px'
-  },
-  productionTime: {
-    label: '生产时间(H)',
-    prop: 'productionTime',
-    width: '120px'
-  },
-  accidentTime: {
-    label: '工程质量',
-    prop: 'accidentTime',
-    width: '120px'
-  },
-  repairTime: {
-    label: '设备故障',
-    prop: 'repairTime',
-    width: '120px'
-  },
-  selfStopTime: {
-    label: '设备保养',
-    prop: 'selfStopTime',
-    width: '120px'
-  },
-  complexityTime: {
-    label: '技术受限',
-    prop: 'complexityTime',
-    width: '120px'
-  },
-  relocationTime: {
-    label: '生产配合',
-    prop: 'relocationTime',
-    width: '120px'
-  },
-  rectificationTime: {
-    label: '生产组织',
-    prop: 'rectificationTime',
-    width: '120px'
-  },
-  waitingStopTime: {
-    label: '不可抗力',
-    prop: 'waitingStopTime',
-    width: '120px'
-  },
-  winterBreakTime: {
-    label: '待命',
-    prop: 'winterBreakTime',
-    width: '120px'
-  },
-  partyaDesign: {
-    label: '甲方设计',
-    prop: 'partyaDesign',
-    width: '120px'
-  },
-  partyaPrepare: {
-    label: '甲方资源',
-    prop: 'partyaPrepare',
-    width: '120px'
-  },
-  partyaResource: {
-    label: '甲方准备',
-    prop: 'partyaResource',
-    width: '120px'
-  },
-  otherNptTime: {
-    label: '其它非生产时间',
-    prop: 'otherNptTime',
-    width: '120px'
-  },
-  otherNptReason: {
-    label: '其他非生产时间原因',
-    prop: 'otherNptReason',
-    width: '120px'
-  },
-  ryNptReason: {
-    label: '非生产时间原因',
-    prop: 'ryNptReason',
-    width: '120px'
-  },
-  drillingWorkingTime: {
-    label: '进尺工作时间(H)',
-    prop: 'drillingWorkingTime',
-    width: '120px'
-  },
-  otherProductionTime: {
-    label: '其它生产时间(H)',
-    prop: 'otherProductionTime',
-    width: '120px'
-  },
-  productionStatus: {
-    label: '生产动态',
-    prop: 'productionStatus',
-    width: '120px'
-  },
-  totalStaffNum: {
-    label: '全员数量',
-    prop: 'totalStaffNum',
-    width: '120px'
-  },
-  onDutyStaffNum: {
-    label: '在岗人数',
-    prop: 'onDutyStaffNum',
-    width: '120px',
-    fn: (row: IotRyDailyReportVO) =>
-      (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
-  },
-  leaveStaffNum: {
-    label: '休假人员数量',
-    prop: 'leaveStaffNum',
-    width: '120px'
-  },
-  nonProductionRate: {
-    label: '非生产时效',
-    prop: 'nonProductionRate',
-    width: '120px',
-    fn: (row: any) => nonProductionRateFormatter(row)
-  }
-})
-
-const nonProductionRateFormatter = (row: any) => {
-  const nonProductionRate = row?.nonProductionRate ?? 0
+const route = useRoute()
 
-  return (nonProductionRate * 100).toFixed(2) + '%'
-}
+const message = useMessage()
 
-// 计算文本宽度
-const getTextWidth = (text: string, fontSize = 12) => {
-  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 = 'PingFang SC'
-  span.innerText = text
+const id = useUserStore().getUser.deptId
 
-  document.body.appendChild(span)
-  const width = span.offsetWidth
-  document.body.removeChild(span)
+const deptId = id
 
-  return width
+interface Query {
+  deptId?: number
+  contractName?: string
+  taskName?: string
+  createTime?: string[]
+  pageNo: number
+  pageSize: number
+  nonProductFlag?: string
+  projectClassification: string
 }
 
-const calculateColumnWidths = useDebounceFn(() => {
-  if (!tableContainerRef.value?.$el) return
-  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
-    width =
-      Math.min(
-        ...[
-          Math.max(
-            ...[
-              getTextWidth(label),
-              ...list.value.map((v) => {
-                return getTextWidth(fn ? fn(v) : v[prop])
-              })
-            ]
-          ) +
-            (label === '施工状态' || label === '施工工艺' || label === '非生产时间原因' ? 35 : 20),
-          200
-        ]
-      ) + 'px'
-
-    columnWidths.value[prop].width = width
-  })
-}, 1000)
-
-// 检查10个时间字段之和是否为24H
-const checkTimeSumEquals24 = (row: any) => {
-  // 获取三个字段的值,转换为数字,如果为空则视为0
-  const drillingWorkingTime = parseFloat(row.drillingWorkingTime) || 0
-  const otherProductionTime = parseFloat(row.otherProductionTime) || 0
-  const accidentTime = parseFloat(row.accidentTime) || 0
-  const repairTime = parseFloat(row.repairTime) || 0
-  const selfStopTime = parseFloat(row.selfStopTime) || 0
-  const complexityTime = parseFloat(row.complexityTime) || 0
-  const relocationTime = parseFloat(row.relocationTime) || 0
-  const rectificationTime = parseFloat(row.rectificationTime) || 0
-  const waitingStopTime = parseFloat(row.waitingStopTime) || 0
-  const winterBreakTime = parseFloat(row.winterBreakTime) || 0
-
-  // 计算总和
-  const sum =
-    drillingWorkingTime +
-    otherProductionTime +
-    accidentTime +
-    repairTime +
-    selfStopTime +
-    complexityTime +
-    relocationTime +
-    rectificationTime +
-    waitingStopTime +
-    winterBreakTime
-
-  // 返回是否等于24(允许一定的浮点数误差)
-  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+const initQuery: Query = {
+  pageNo: 1,
+  pageSize: 10,
+  projectClassification: '2',
+  deptId: route.query.deptId ? Number(route.query.deptId) : id,
+  createTime: route.query.createTime
+    ? (route.query.createTime as string[])
+    : [...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))],
+  nonProductFlag: route.query.nonProductFlag ? (route.query.nonProductFlag as string) : undefined
 }
 
-// 单元格样式函数
-const cellStyle = ({
-  row,
-  column,
-  rowIndex,
-  columnIndex
-}: {
-  row: any
-  column: any
-  rowIndex: number
-  columnIndex: number
-}) => {
-  // 当日油耗预警逻辑
-  if (column.property === 'dailyFuel') {
-    const dailyFuel = parseFloat(row.dailyFuel) || 0
-    if (dailyFuel > 3500) {
-      return {
-        backgroundColor: '#e6f8ff', // 浅黄色背景
-        color: '#0a35c4', // 橙色文字
-        fontWeight: 'bold',
-        border: '1px solid #ffd591' // 可选:添加边框突出显示
-      }
-    }
-  }
-
-  const nptFields = [
-    'accidentTime', // 工程质量
-    'repairTime', // 设备故障
-    'selfStopTime', // 设备保养
-    'complexityTime', // 技术受限
-    'relocationTime', // 生产配合
-    'rectificationTime', // 生产组织
-    'waitingStopTime', // 不可抗力
-    'winterBreakTime', // 待命
-    'partyaDesign', // 甲方设计
-    'partyaPrepare', // 甲方资源
-    'partyaResource', // 甲方准备
-    'otherNptTime' // 其它非生产时间
-  ]
-
-  // 定义所有涉及校验的列(额定、生产 + 所有非生产细分字段)
-  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
-
-  if (timeFields.includes(column.property)) {
-    // 辅助函数:判断值是否为空
-    const isEmpty = (val) => val === null || val === undefined || val === ''
-
-    // 1. 检查是否有空值
-    // 先检查额定时间和生产时间
-    let hasEmptyField = isEmpty(row.ratedProductionTime) || isEmpty(row.productionTime)
-
-    // 如果主要字段不为空,继续检查所有非生产时间细分字段
-    if (!hasEmptyField) {
-      hasEmptyField = nptFields.some((field) => isEmpty(row[field]))
-    }
-
-    // 如果有空字段,应用警告样式(红色)
-    if (hasEmptyField) {
-      return {
-        backgroundColor: '#fff2f0', // 浅红色背景
-        color: '#d46b08', // 深红色文字
-        fontWeight: 'bold',
-        border: '1px solid #ffa39e'
-      }
-    }
-
-    // 2. 获取数值进行计算
-    const ratedTime = parseFloat(row.ratedProductionTime) || 0
-    const prodTime = parseFloat(row.productionTime) || 0
-
-    // 计算所有非生产时间细分字段的总和
-    const totalNonProdTime = nptFields.reduce((sum, field) => {
-      return sum + (parseFloat(row[field]) || 0)
-    }, 0)
-
-    // 3. 校验逻辑:额定生产时间 = 生产时间 + 所有非生产时间细项之和
-    // 使用容差比较,避免浮点数精度问题
-    if (Math.abs(ratedTime - (prodTime + totalNonProdTime)) > 0.01) {
-      return {
-        backgroundColor: '#fffbe6', // 浅黄色背景
-        color: '#d46b08', // 橙色文字
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 2. 处理运行时效字段的颜色
-  if (column.property === 'transitTime') {
-    const transitTime = row.transitTime
-    // 将运行时效转为数字(处理原逻辑中 toFixed(4) 生成的字符串)
-    const transitTimeNum = parseFloat(transitTime)
-    if (
-      transitTime === null ||
-      transitTime === undefined ||
-      isNaN(transitTimeNum) ||
-      transitTimeNum === 0 ||
-      transitTimeNum >= 1
-    ) {
-      return {
-        backgroundColor: '#fff2f0', // 浅红色背景(与数据不完整警告样式统一)
-        color: '#ff4d4f', // 红色文字
-        fontWeight: 'bold'
-      }
-    }
-  }
-
-  // 3. 处理“在岗人数”列:动态计算 + 负数黄色背景
-  if (column.label === '在岗人数') {
-    // 按列名匹配(避免prop绑定问题)
-    // 步骤1:计算在岗人数(处理空值/非数字情况,默认设为0)
-    const totalStaff = Number(row.totalStaffNum) || 0
-    const leaveStaff = Number(row.leaveStaffNum) || 0
-    const onDutyStaff = totalStaff - leaveStaff
+const query = ref<Query>({ ...initQuery })
 
-    // 步骤2:若计算值为负数,设置黄色提醒样式
-    if (onDutyStaff < 0) {
-      return {
-        backgroundColor: '#fff9e6', // 浅黄色背景
-        fontWeight: 'bold'
-      }
-    }
-  }
+const list = ref<any[]>([])
+const total = ref(0)
 
-  // 默认返回空对象,不应用特殊样式
-  return {}
-}
+const loading = ref(false)
 
-/** 查询列表 */
-const getList = async () => {
+const loadList = useDebounceFn(async function () {
   loading.value = true
   try {
-    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(queryParams)
-    // 计算运行时效
-    data.list.forEach((item: any) => {
-      const ratedTime = parseFloat(item.ratedProductionTime) || 0
-      const productionTime = parseFloat(item.productionTime) || 0
-
-      if (ratedTime > 0 && !isNaN(productionTime)) {
-        // 计算运行时效并保留4位小数用于计算
-        item.transitTime = (productionTime / ratedTime).toFixed(4)
-      } else {
-        item.transitTime = null
-      }
-    })
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(query.value)
     list.value = data.list
     total.value = data.total
-    // 获取数据后计算列宽
-    nextTick(() => {
-      calculateColumnWidths()
-    })
   } finally {
     loading.value = false
   }
-}
-
-// 百分比格式化函数
-const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
-  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
-  console.log('cellValue :>> ', cellValue)
-  const value = parseFloat(cellValue)
-  if (isNaN(value)) return '-'
-  // 将小数转换为百分比,保留两位小数
-  return `${(value * 100).toFixed(2)}%`
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
+})
 
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  queryParams.deptId = useUserStore().getUser.deptId
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
   handleQuery()
 }
 
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number, row?: any) => {
-  // 保存当前行数据
-  if (row) {
-    selectedRowData.value = {
-      deptName: row.deptName,
-      contractName: row.contractName,
-      taskName: row.taskName,
-      designWellDepth: row.designWellDepth,
-      technique: row.technique,
-      wellCategory: row.wellCategory,
-      wellControlLevel: row.wellControlLevel,
-      casingPipeSize: row.casingPipeSize
-    }
-  } else {
-    selectedRowData.value = null
-  }
-
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
 }
 
-// 响应式变量存储选中的部门
-const selectedDept = ref<{ id: number; name: string }>()
-/** 处理部门被点击 */
-const handleDeptNodeClick = async (row) => {
-  // 记录选中的部门信息
-  selectedDept.value = { id: row.id, name: row.name }
-  queryParams.deptId = row.id
-  await getList()
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
 }
 
-const exportLoading = ref(false)
-const handleExport = async () => {
-  const res = await IotRyDailyReportApi.exportIotRyDailyReport({
-    createTime: queryParams.createTime,
-    contractName: queryParams.contractName,
-    taskName: queryParams.taskName,
-    // pageNo: queryParams.pageNo,
-    // pageSize: queryParams.pageSize,
-    deptId: queryParams.deptId,
-    projectClassification: queryParams.projectClassification
-  })
+function resetQuery() {
+  query.value = { ...initQuery }
 
-  download.excel(res, '瑞鹰修井日报.xlsx')
+  handleQuery()
 }
-// 声明 ResizeObserver 实例
-let resizeObserver: ResizeObserver | null = null
-
-const route = useRoute()
-
-/** 初始化 **/
-onMounted(() => {
-  if (Object.keys(route.query).length > 0) {
-    nextTick(() => {
-      queryParams.deptId = Number(route.query.deptId) as any
-      queryParams.createTime = route.query.createTime as string[]
-      queryParams.nonProductFlag = route.query.nonProductFlag as string
-      handleQuery()
-    })
-  } else 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,
+  [
+    () => query.value.deptId,
+    () => query.value.contractName,
+    () => query.value.taskName,
+    () => query.value.createTime,
+    () => query.value.nonProductFlag
+  ],
   () => {
-    nextTick(calculateColumnWidths)
+    handleQuery()
   },
-  { deep: true }
+  { immediate: true }
 )
-</script>
-
-<style scoped>
-/* 表格容器样式,确保水平滚动 */
-.table-container {
-  width: 100%;
-  overflow-x: auto;
-}
-
-/* 确保表格单元格内容不换行 */
-
-/* :deep(.el-table .cell) {
-  white-space: nowrap;
-} */
-
-/* 确保表格列标题不换行 */
-
-/* :deep(.el-table th > .cell) {
-  white-space: nowrap;
-} */
-
-/* 调整表格最小宽度,确保内容完全显示 */
-:deep(.el-table) {
-  min-width: 100%;
-}
-
-/* 强制显示所有内容,防止省略号 */
 
-/* :deep(.el-table td.el-table__cell),
-:deep(.el-table th.el-table__cell) {
-  overflow: visible !important;
-} */
-
-/* :deep(.el-table .cell) {
-  overflow: visible !important;
-  text-overflow: clip !important;
-} */
-
-/* 设计井身结构文本样式 - 多行显示并添加省略号 */
-.design-well-struct-text {
-  display: -webkit-box;
-  max-height: 3em; /* 两行文本的高度 */
-  overflow: hidden;
-  line-height: 1.5;
-  text-overflow: ellipsis;
-  -webkit-box-orient: vertical;
-  -webkit-line-clamp: 2;
-}
+const exportLoading = ref(false)
 
-/* 确保设计井身结构列不参与自动调整 */
-:deep(.el-table__header-wrapper .el-table__cell.fixed-width),
-:deep(.el-table__body-wrapper .el-table__cell.fixed-width) {
-  flex-shrink: 0;
-  flex-grow: 0;
-}
+async function handleExport() {
+  try {
+    await message.exportConfirm()
 
-/* 颜色说明区域样式 */
-.color-legend {
-  display: flex;
-  padding: 12px 16px;
-  background-color: #f8f9fa;
-  border-left: 4px solid #e6f7ff;
-  border-radius: 4px;
-  flex-direction: column;
-  gap: 8px;
+    exportLoading.value = true
+    const res = await IotRyDailyReportApi.exportIotRyDailyReport(query.value)
+    download.excel(res, '瑞鹰修井日报.xlsx')
+  } finally {
+    exportLoading.value = false
+  }
 }
 
-.legend-item {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  font-size: 14px;
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
+    message.success(t('common.delSuccess'))
+    await loadList()
+  } catch {}
 }
 
-.color-indicator {
-  display: inline-block;
-  width: 12px;
-  height: 12px;
-  border-radius: 50%;
-}
+const visible = ref(false)
 
-.color-indicator.red {
-  background-color: red;
-}
+const formRef = ref()
 
-.color-indicator.orange {
-  background-color: orange;
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
 }
+</script>
 
-/* 在原有样式基础上添加黄色指示器 */
-.color-indicator.yellow {
-  background-color: #0a35c4; /* 黄色,与警告颜色一致 */
-}
-</style>
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[48px_auto_1fr] gap-x-4 gap-y-3 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-3">
+      <DeptTreeSelect :top-id="158" :deptId="deptId" v-model="query.deptId" :show-title="false" />
+    </div>
+    <el-form
+      size="default"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-6 gap-8 flex items-center justify-between"
+    >
+      <div class="flex items-center gap-8">
+        <el-form-item label="项目">
+          <el-input
+            v-model="query.contractName"
+            placeholder="请输入项目"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-180px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="query.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"
+            :shortcuts="rangeShortcuts"
+          />
+        </el-form-item>
+        <el-form-item label="非生产时效" prop="nonProductFlag">
+          <el-switch v-model="query.nonProductFlag" active-value="Y" inactive-value="N" />
+        </el-form-item>
+      </div>
+      <el-form-item>
+        <el-button type="primary" @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-ry-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-rh-daily-report:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="p-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow flex flex-col gap-2">
+      <el-alert
+        class="h-8!"
+        title="运行时效=生产时间/额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过100%红色预警"
+        type="error"
+        show-icon
+        :closable="false"
+      />
+      <el-alert
+        class="h-8!"
+        title="生产时间+非生产时间=额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警"
+        type="warning"
+        show-icon
+        :closable="false"
+      />
+      <el-alert
+        class="h-8!"
+        title="当日油耗大于3500升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;蓝色预警"
+        type="primary"
+        show-icon
+        :closable="false"
+      />
+    </div>
+    <ry-xj-table
+      :list="list"
+      :total="total"
+      :loading="loading"
+      :page-no="query.pageNo"
+      :page-size="query.pageSize"
+      is-index
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          @click="handleOpenForm(row.id, 'edit')"
+          v-hasPermi="['pms:iot-ry-daily-report:update']"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="danger"
+          @click="handleDelete(row.id)"
+          v-hasPermi="['pms:iot-ry-daily-report:delete']"
+        >
+          删除
+        </el-button>
+      </template>
+    </ry-xj-table>
+  </div>
+
+  <ry-xj-form
+    v-model:visible="visible"
+    type="edit"
+    ref="formRef"
+    :load-list="loadList"
+    no-validate-status
+  />
+</template>
 
-<style>
-/* 设计井身结构 tooltip 样式 - 保留换行符 */
-.design-well-struct-tooltip {
-  max-width: 500px;
-  line-height: 1.5;
-  white-space: pre-line;
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
 }
 </style>

+ 1265 - 0
src/views/pms/iotrydailyreport/xjindex1.vue

@@ -0,0 +1,1265 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="4" :xs="24">
+      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 h-full">
+        <DeptTreeSelect
+          :deptId="rootDeptId"
+          :top-id="158"
+          v-model="queryParams.deptId"
+          @node-click="handleDeptNodeClick"
+        />
+      </div>
+      <!-- <ContentWrap class="h-1/1">
+        <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
+      </ContentWrap> -->
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="80px"
+        >
+          <el-form-item label="项目" prop="contractName">
+            <el-input
+              v-model="queryParams.contractName"
+              placeholder="请输入项目"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input
+              v-model="queryParams.taskName"
+              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"
+              :shortcuts="rangeShortcuts"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时效" prop="nonProductFlag">
+            <el-switch v-model="queryParams.nonProductFlag" active-value="Y" inactive-value="N" />
+          </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-rh-daily-report:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 新增
+            </el-button> -->
+            <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+              <Icon icon="ep:download" class="mr-5px" /> 导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <ContentWrap class="mb-15px">
+        <div class="color-legend">
+          <div class="legend-item">
+            <span class="color-indicator red"></span>
+            <span
+              >运行时效=生产时间/额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超过100%红色预警</span
+            >
+          </div>
+          <div class="legend-item">
+            <span class="color-indicator orange"></span>
+            <span
+              >生产时间+非生产时间=额定生产时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;否则橙色预警</span
+            >
+          </div>
+          <div class="legend-item">
+            <span class="color-indicator yellow"></span>
+            <span>当日油耗大于3500升&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;蓝色预警</span>
+          </div>
+        </div>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap ref="tableContainerRef">
+        <div class="table-container">
+          <el-table
+            ref="tableRef"
+            v-loading="loading"
+            :data="list"
+            :stripe="true"
+            :style="{ width: '100%' }"
+            max-height="600"
+            :cell-style="cellStyle"
+            show-overflow-tooltip
+            border
+          >
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
+              <template #default="scope">
+                {{ scope.$index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="日期"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter2"
+              :min-width="columnWidths.createTime.width"
+              resizable
+              fixed="left"
+            />
+            <el-table-column
+              label="施工队伍"
+              align="center"
+              prop="deptName"
+              :min-width="columnWidths.deptName.width"
+              resizable
+              fixed="left"
+            />
+
+            <el-table-column
+              label="任务"
+              align="center"
+              prop="taskName"
+              :min-width="columnWidths.taskName.width"
+              resizable
+              fixed="left"
+            />
+            <el-table-column
+              :label="t('project.status')"
+              align="center"
+              prop="repairStatus"
+              :min-width="columnWidths.repairStatus.width"
+              resizable
+              fixed="left"
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE"
+                  :value="scope.row.repairStatus"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="总施工井数"
+              align="center"
+              prop="totalConstructionWells"
+              :min-width="columnWidths.totalConstructionWells.width"
+              resizable
+            />
+            <el-table-column
+              label="完工井数"
+              align="center"
+              prop="completedWells"
+              :min-width="columnWidths.completedWells.width"
+              resizable
+            />
+            <el-table-column
+              :label="t('project.technology')"
+              align="center"
+              prop="technique"
+              :min-width="columnWidths.technique.width"
+              resizable
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY"
+                  :value="scope.row.technique"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="井别"
+              align="center"
+              prop="wellCategory"
+              :min-width="columnWidths.wellCategory.width"
+              resizable
+            />
+            <el-table-column
+              label="井深(m)"
+              align="center"
+              prop="designWellDepth"
+              :min-width="columnWidths.designWellDepth.width"
+              resizable
+            />
+            <el-table-column
+              label="套生段产管尺寸(mm)"
+              align="center"
+              prop="casingPipeSize"
+              :min-width="columnWidths.casingPipeSize.width"
+              resizable
+            />
+            <el-table-column
+              label="井控级别"
+              align="center"
+              prop="wellControlLevel"
+              :min-width="columnWidths.wellControlLevel.width"
+              resizable
+            />
+            <el-table-column align="center" label="当日">
+              <el-table-column
+                label="用电量(kWh)"
+                align="center"
+                prop="dailyPowerUsage"
+                :min-width="columnWidths.dailyPowerUsage.width"
+                resizable
+              />
+              <el-table-column
+                label="油耗(升)"
+                align="center"
+                prop="dailyFuel"
+                :min-width="columnWidths.dailyFuel.width"
+                resizable
+              />
+            </el-table-column>
+
+            <el-table-column
+              label="施工开始日期"
+              align="center"
+              prop="constructionStartDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionStartDate.width"
+              resizable
+            />
+            <el-table-column
+              label="施工结束日期"
+              align="center"
+              prop="constructionEndDate"
+              :formatter="dateFormatter"
+              :min-width="columnWidths.constructionEndDate.width"
+              resizable
+            />
+
+            <el-table-column
+              :label="t('project.currentOperation')"
+              align="center"
+              prop="currentOperation"
+              :min-width="columnWidths.currentOperation.width"
+              resizable
+            />
+            <el-table-column
+              :label="t('project.nextPlan')"
+              align="center"
+              prop="nextPlan"
+              :min-width="columnWidths.nextPlan.width"
+              resizable
+            />
+            <el-table-column
+              :label="t('project.transitTime')"
+              align="center"
+              prop="transitTime"
+              :min-width="columnWidths.transitTime.width"
+              resizable
+              :formatter="percentageFormatter"
+            />
+            <el-table-column
+              label="额定生产时间(H)"
+              align="center"
+              prop="ratedProductionTime"
+              :min-width="columnWidths.ratedProductionTime.width"
+              resizable
+            />
+            <el-table-column
+              label="生产时间(H)"
+              align="center"
+              prop="productionTime"
+              :min-width="columnWidths.productionTime.width"
+              resizable
+            />
+            <el-table-column
+              label="非生产时效"
+              align="center"
+              prop="nonProductionRate"
+              :formatter="nonProductionRateFormatter"
+              :min-width="columnWidths.nonProductionRate.width"
+              resizable
+            />
+            <el-table-column label="非生产时间" align="center">
+              <el-table-column
+                label="工程质量"
+                align="center"
+                prop="accidentTime"
+                :min-width="columnWidths.accidentTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备故障"
+                align="center"
+                prop="repairTime"
+                :min-width="columnWidths.repairTime.width"
+                resizable
+              />
+              <el-table-column
+                label="设备保养"
+                align="center"
+                prop="selfStopTime"
+                :min-width="columnWidths.selfStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="技术受限"
+                align="center"
+                prop="complexityTime"
+                :min-width="columnWidths.complexityTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产配合"
+                align="center"
+                prop="relocationTime"
+                :min-width="columnWidths.relocationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="生产组织"
+                align="center"
+                prop="rectificationTime"
+                :min-width="columnWidths.rectificationTime.width"
+                resizable
+              />
+              <el-table-column
+                label="不可抗力"
+                align="center"
+                prop="waitingStopTime"
+                :min-width="columnWidths.waitingStopTime.width"
+                resizable
+              />
+              <el-table-column
+                label="待命"
+                align="center"
+                prop="winterBreakTime"
+                :min-width="columnWidths.winterBreakTime.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方设计"
+                align="center"
+                prop="partyaDesign"
+                :min-width="columnWidths.partyaDesign.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方准备"
+                align="center"
+                prop="partyaPrepare"
+                :min-width="columnWidths.partyaPrepare.width"
+                resizable
+              />
+              <el-table-column
+                label="甲方资源"
+                align="center"
+                prop="partyaResource"
+                :min-width="columnWidths.partyaResource.width"
+                resizable
+              />
+              <el-table-column
+                label="其它非生产时间"
+                align="center"
+                prop="otherNptTime"
+                :min-width="columnWidths.otherNptTime.width"
+                resizable
+              />
+            </el-table-column>
+            <el-table-column
+              label="其他非生产时间原因"
+              align="center"
+              prop="otherNptReason"
+              :min-width="columnWidths.otherNptReason.width"
+              resizable
+            />
+
+            <!-- <el-table-column
+              :label="t('project.nptReason')"
+              align="center"
+              prop="ryNptReason"
+              :min-width="columnWidths.ryNptReason.width"
+              resizable
+            >
+              <template #default="scope">
+                <dict-tag
+                  :type="DICT_TYPE.PMS_PROJECT_RY_NPT_REASON"
+                  :value="scope.row.ryNptReason"
+                />
+              </template>
+            </el-table-column> -->
+            <el-table-column
+              label="生产动态"
+              align="center"
+              prop="productionStatus"
+              :min-width="columnWidths.productionStatus.width"
+              resizable
+            />
+            <el-table-column
+              label="项目"
+              align="center"
+              prop="contractName"
+              :min-width="columnWidths.contractName.width"
+              resizable
+            />
+            <el-table-column
+              label="全员数量"
+              align="center"
+              prop="totalStaffNum"
+              :min-width="columnWidths.totalStaffNum.width"
+              resizable
+            />
+            <!--
+            <el-table-column label="在岗人数" align="center" prop="onDutyStaffNum" :min-width="columnWidths.onDutyStaffNum"/> -->
+            <el-table-column
+              label="在岗人数"
+              align="center"
+              prop="onDutyStaffNum"
+              :min-width="columnWidths.onDutyStaffNum.width"
+              resizable
+            >
+              <template #default="scope">
+                <!-- 动态计算:在岗人数 = 全员数量 - 休假人员数量 -->
+                {{
+                  (Number(scope.row.totalStaffNum) || 0) - (Number(scope.row.leaveStaffNum) || 0)
+                }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="休假人员数量"
+              align="center"
+              prop="leaveStaffNum"
+              :min-width="columnWidths.leaveStaffNum.width"
+              resizable
+            />
+            <el-table-column label="操作" align="center" fixed="right">
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  @click="handleOpenForm(scope.row.id, 'edit')"
+                  v-hasPermi="['pms:iot-ry-daily-report:update']"
+                >
+                  编辑
+                </el-button>
+                <el-button
+                  link
+                  type="danger"
+                  @click="handleDelete(scope.row.id)"
+                  v-hasPermi="['pms:iot-ry-daily-report:delete']"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+
+      <!-- 表单弹窗:添加/修改 -->
+      <ry-xj-form
+        v-model:visible="visible"
+        type="edit"
+        ref="formRef"
+        :load-list="getList"
+        no-validate-status
+      />
+    </el-col>
+  </el-row>
+</template>
+
+<script setup lang="ts">
+import ryXjForm from './ry-xj-form.vue'
+import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
+import { DICT_TYPE } from '@/utils/dict'
+import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
+import { useDebounceFn } from '@vueuse/core'
+
+import { useUserStore } from '@/store/modules/user'
+
+import dayjs from 'dayjs'
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
+
+dayjs.extend(quarterOfYear)
+
+/** 瑞鹰日报 列表 */
+defineOptions({ name: 'IotRyXjDailyReport' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+// 添加 selectedRowData 响应式变量
+const selectedRowData = ref<Record<string, any> | null>(null)
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: useUserStore().getUser.deptId,
+  contractName: undefined,
+  projectId: undefined,
+  taskName: undefined,
+  taskId: undefined,
+  projectClassification: '2',
+  relocationDays: undefined,
+  latestWellDoneTime: [],
+  currentDepth: undefined,
+  dailyFootage: undefined,
+  monthlyFootage: undefined,
+  annualFootage: undefined,
+  dailyPowerUsage: undefined,
+  monthlyPowerUsage: undefined,
+  dailyFuel: undefined,
+  monthlyFuel: undefined,
+  nonProductionTime: [],
+  nptReason: undefined,
+  constructionStartDate: [],
+  constructionEndDate: [],
+  productionStatus: undefined,
+  nextPlan: undefined,
+  rigStatus: undefined,
+  personnel: undefined,
+  mudDensity: undefined,
+  mudViscosity: undefined,
+  lateralLength: undefined,
+  wellInclination: undefined,
+  azimuth: undefined,
+  extProperty: undefined,
+  sort: undefined,
+  remark: undefined,
+  status: undefined,
+  processInstanceId: undefined,
+  auditStatus: undefined,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
+  nonProductFlag: 'N'
+})
+const queryFormRef = ref() // 搜索的表单
+
+const rootDeptId = ref(useUserStore().getUser.deptId)
+
+// 表格引用
+const tableRef = ref()
+// 表格容器引用
+const tableContainerRef = ref()
+
+// 列宽度配置
+const columnWidths = ref<
+  Record<
+    string,
+    {
+      label: string
+      prop: string
+      width: string
+      fn?: (row: IotRyDailyReportVO) => string | number
+    }
+  >
+>({
+  createTime: {
+    label: '日期',
+    prop: 'createTime',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter2(null, null, row.createTime)
+  },
+  deptName: {
+    label: '施工队伍',
+    prop: 'deptName',
+    width: '120px'
+  },
+  contractName: {
+    label: '项目',
+    prop: 'contractName',
+    width: '120px'
+  },
+  taskName: {
+    label: '任务',
+    prop: 'taskName',
+    width: '120px'
+  },
+  equipmentType: {
+    label: '设备型号',
+    prop: 'equipmentType',
+    width: '120px'
+  },
+  repairStatus: {
+    label: '施工状态',
+    prop: 'repairStatus',
+    width: '120px'
+  },
+  totalConstructionWells: {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    width: '120px'
+  },
+  completedWells: {
+    label: '完工井数',
+    prop: 'completedWells',
+    width: '120px'
+  },
+  technique: {
+    label: '施工工艺',
+    prop: 'technique',
+    width: '120px'
+  },
+  wellCategory: {
+    label: '井别',
+    prop: 'wellCategory',
+    width: '120px'
+  },
+  designWellDepth: {
+    label: '井深(m)',
+    prop: 'designWellDepth',
+    width: '120px'
+  },
+  casingPipeSize: {
+    label: '套生段产管尺寸(mm)',
+    prop: 'casingPipeSize',
+    width: '120px'
+  },
+  wellControlLevel: {
+    label: '井控级别',
+    prop: 'wellControlLevel',
+    width: '120px'
+  },
+  dailyPowerUsage: {
+    label: '用电量(kWh)',
+    prop: 'dailyPowerUsage',
+    width: '120px'
+  },
+  dailyFuel: {
+    label: '油耗(升)',
+    prop: 'dailyFuel',
+    width: '120px'
+  },
+  constructionStartDate: {
+    label: '施工开始日期',
+    prop: 'constructionStartDate',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionStartDate)
+  },
+  constructionEndDate: {
+    label: '施工结束日期',
+    prop: 'constructionEndDate',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => dateFormatter(null, null, row.constructionEndDate)
+  },
+  currentOperation: {
+    label: '目前',
+    prop: 'currentOperation',
+    width: '120px'
+  },
+  nextPlan: {
+    label: '下步',
+    prop: 'nextPlan',
+    width: '120px'
+  },
+  transitTime: {
+    label: '运行时效',
+    prop: 'transitTime',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) => percentageFormatter(null, null, row.transitTime, null)
+  },
+  ratedProductionTime: {
+    label: '额定生产时间(H)',
+    prop: 'ratedProductionTime',
+    width: '120px'
+  },
+  productionTime: {
+    label: '生产时间(H)',
+    prop: 'productionTime',
+    width: '120px'
+  },
+  accidentTime: {
+    label: '工程质量',
+    prop: 'accidentTime',
+    width: '120px'
+  },
+  repairTime: {
+    label: '设备故障',
+    prop: 'repairTime',
+    width: '120px'
+  },
+  selfStopTime: {
+    label: '设备保养',
+    prop: 'selfStopTime',
+    width: '120px'
+  },
+  complexityTime: {
+    label: '技术受限',
+    prop: 'complexityTime',
+    width: '120px'
+  },
+  relocationTime: {
+    label: '生产配合',
+    prop: 'relocationTime',
+    width: '120px'
+  },
+  rectificationTime: {
+    label: '生产组织',
+    prop: 'rectificationTime',
+    width: '120px'
+  },
+  waitingStopTime: {
+    label: '不可抗力',
+    prop: 'waitingStopTime',
+    width: '120px'
+  },
+  winterBreakTime: {
+    label: '待命',
+    prop: 'winterBreakTime',
+    width: '120px'
+  },
+  partyaDesign: {
+    label: '甲方设计',
+    prop: 'partyaDesign',
+    width: '120px'
+  },
+  partyaPrepare: {
+    label: '甲方资源',
+    prop: 'partyaPrepare',
+    width: '120px'
+  },
+  partyaResource: {
+    label: '甲方准备',
+    prop: 'partyaResource',
+    width: '120px'
+  },
+  otherNptTime: {
+    label: '其它非生产时间',
+    prop: 'otherNptTime',
+    width: '120px'
+  },
+  otherNptReason: {
+    label: '其他非生产时间原因',
+    prop: 'otherNptReason',
+    width: '120px'
+  },
+  ryNptReason: {
+    label: '非生产时间原因',
+    prop: 'ryNptReason',
+    width: '120px'
+  },
+  drillingWorkingTime: {
+    label: '进尺工作时间(H)',
+    prop: 'drillingWorkingTime',
+    width: '120px'
+  },
+  otherProductionTime: {
+    label: '其它生产时间(H)',
+    prop: 'otherProductionTime',
+    width: '120px'
+  },
+  productionStatus: {
+    label: '生产动态',
+    prop: 'productionStatus',
+    width: '120px'
+  },
+  totalStaffNum: {
+    label: '全员数量',
+    prop: 'totalStaffNum',
+    width: '120px'
+  },
+  onDutyStaffNum: {
+    label: '在岗人数',
+    prop: 'onDutyStaffNum',
+    width: '120px',
+    fn: (row: IotRyDailyReportVO) =>
+      (Number(row.totalStaffNum) || 0) - (Number(row.offDutyStaffNum) || 0)
+  },
+  leaveStaffNum: {
+    label: '休假人员数量',
+    prop: 'leaveStaffNum',
+    width: '120px'
+  },
+  nonProductionRate: {
+    label: '非生产时效',
+    prop: 'nonProductionRate',
+    width: '120px',
+    fn: (row: any) => nonProductionRateFormatter(row)
+  }
+})
+
+const nonProductionRateFormatter = (row: any) => {
+  const nonProductionRate = row?.nonProductionRate ?? 0
+
+  return (nonProductionRate * 100).toFixed(2) + '%'
+}
+
+// 计算文本宽度
+const getTextWidth = (text: string, fontSize = 12) => {
+  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 = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = useDebounceFn(() => {
+  if (!tableContainerRef.value?.$el) return
+  Object.values(columnWidths.value).forEach(({ fn, prop, label, width }) => {
+    width =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                return getTextWidth(fn ? fn(v) : v[prop])
+              })
+            ]
+          ) +
+            (label === '施工状态' || label === '施工工艺' || label === '非生产时间原因' ? 35 : 20),
+          200
+        ]
+      ) + 'px'
+
+    columnWidths.value[prop].width = width
+  })
+}, 1000)
+
+// 检查10个时间字段之和是否为24H
+const checkTimeSumEquals24 = (row: any) => {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const drillingWorkingTime = parseFloat(row.drillingWorkingTime) || 0
+  const otherProductionTime = parseFloat(row.otherProductionTime) || 0
+  const accidentTime = parseFloat(row.accidentTime) || 0
+  const repairTime = parseFloat(row.repairTime) || 0
+  const selfStopTime = parseFloat(row.selfStopTime) || 0
+  const complexityTime = parseFloat(row.complexityTime) || 0
+  const relocationTime = parseFloat(row.relocationTime) || 0
+  const rectificationTime = parseFloat(row.rectificationTime) || 0
+  const waitingStopTime = parseFloat(row.waitingStopTime) || 0
+  const winterBreakTime = parseFloat(row.winterBreakTime) || 0
+
+  // 计算总和
+  const sum =
+    drillingWorkingTime +
+    otherProductionTime +
+    accidentTime +
+    repairTime +
+    selfStopTime +
+    complexityTime +
+    relocationTime +
+    rectificationTime +
+    waitingStopTime +
+    winterBreakTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+}
+
+// 单元格样式函数
+const cellStyle = ({
+  row,
+  column,
+  rowIndex,
+  columnIndex
+}: {
+  row: any
+  column: any
+  rowIndex: number
+  columnIndex: number
+}) => {
+  // 当日油耗预警逻辑
+  if (column.property === 'dailyFuel') {
+    const dailyFuel = parseFloat(row.dailyFuel) || 0
+    if (dailyFuel > 3500) {
+      return {
+        backgroundColor: '#e6f8ff', // 浅黄色背景
+        color: '#0a35c4', // 橙色文字
+        fontWeight: 'bold',
+        border: '1px solid #ffd591' // 可选:添加边框突出显示
+      }
+    }
+  }
+
+  const nptFields = [
+    'accidentTime', // 工程质量
+    'repairTime', // 设备故障
+    'selfStopTime', // 设备保养
+    'complexityTime', // 技术受限
+    'relocationTime', // 生产配合
+    'rectificationTime', // 生产组织
+    'waitingStopTime', // 不可抗力
+    'winterBreakTime', // 待命
+    'partyaDesign', // 甲方设计
+    'partyaPrepare', // 甲方资源
+    'partyaResource', // 甲方准备
+    'otherNptTime' // 其它非生产时间
+  ]
+
+  // 定义所有涉及校验的列(额定、生产 + 所有非生产细分字段)
+  const timeFields = ['ratedProductionTime', 'productionTime', ...nptFields]
+
+  if (timeFields.includes(column.property)) {
+    // 辅助函数:判断值是否为空
+    const isEmpty = (val) => val === null || val === undefined || val === ''
+
+    // 1. 检查是否有空值
+    // 先检查额定时间和生产时间
+    let hasEmptyField = isEmpty(row.ratedProductionTime) || isEmpty(row.productionTime)
+
+    // 如果主要字段不为空,继续检查所有非生产时间细分字段
+    if (!hasEmptyField) {
+      hasEmptyField = nptFields.some((field) => isEmpty(row[field]))
+    }
+
+    // 如果有空字段,应用警告样式(红色)
+    if (hasEmptyField) {
+      return {
+        backgroundColor: '#fff2f0', // 浅红色背景
+        color: '#d46b08', // 深红色文字
+        fontWeight: 'bold',
+        border: '1px solid #ffa39e'
+      }
+    }
+
+    // 2. 获取数值进行计算
+    const ratedTime = parseFloat(row.ratedProductionTime) || 0
+    const prodTime = parseFloat(row.productionTime) || 0
+
+    // 计算所有非生产时间细分字段的总和
+    const totalNonProdTime = nptFields.reduce((sum, field) => {
+      return sum + (parseFloat(row[field]) || 0)
+    }, 0)
+
+    // 3. 校验逻辑:额定生产时间 = 生产时间 + 所有非生产时间细项之和
+    // 使用容差比较,避免浮点数精度问题
+    if (Math.abs(ratedTime - (prodTime + totalNonProdTime)) > 0.01) {
+      return {
+        backgroundColor: '#fffbe6', // 浅黄色背景
+        color: '#d46b08', // 橙色文字
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 2. 处理运行时效字段的颜色
+  if (column.property === 'transitTime') {
+    const transitTime = row.transitTime
+    // 将运行时效转为数字(处理原逻辑中 toFixed(4) 生成的字符串)
+    const transitTimeNum = parseFloat(transitTime)
+    if (
+      transitTime === null ||
+      transitTime === undefined ||
+      isNaN(transitTimeNum) ||
+      transitTimeNum === 0 ||
+      transitTimeNum >= 1
+    ) {
+      return {
+        backgroundColor: '#fff2f0', // 浅红色背景(与数据不完整警告样式统一)
+        color: '#ff4d4f', // 红色文字
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 3. 处理“在岗人数”列:动态计算 + 负数黄色背景
+  if (column.label === '在岗人数') {
+    // 按列名匹配(避免prop绑定问题)
+    // 步骤1:计算在岗人数(处理空值/非数字情况,默认设为0)
+    const totalStaff = Number(row.totalStaffNum) || 0
+    const leaveStaff = Number(row.leaveStaffNum) || 0
+    const onDutyStaff = totalStaff - leaveStaff
+
+    // 步骤2:若计算值为负数,设置黄色提醒样式
+    if (onDutyStaff < 0) {
+      return {
+        backgroundColor: '#fff9e6', // 浅黄色背景
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotRyDailyReportApi.getIotRyDailyReportPage(queryParams)
+    // 计算运行时效
+    data.list.forEach((item: any) => {
+      const ratedTime = parseFloat(item.ratedProductionTime) || 0
+      const productionTime = parseFloat(item.productionTime) || 0
+
+      if (ratedTime > 0 && !isNaN(productionTime)) {
+        // 计算运行时效并保留4位小数用于计算
+        item.transitTime = (productionTime / ratedTime).toFixed(4)
+      } else {
+        item.transitTime = null
+      }
+    })
+    list.value = data.list
+    total.value = data.total
+    // 获取数据后计算列宽
+    nextTick(() => {
+      calculateColumnWidths()
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 百分比格式化函数
+const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
+  if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+  console.log('cellValue :>> ', cellValue)
+  const value = parseFloat(cellValue)
+  if (isNaN(value)) return '-'
+  // 将小数转换为百分比,保留两位小数
+  return `${(value * 100).toFixed(2)}%`
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
+  handleQuery()
+}
+
+const visible = ref(false)
+
+const formRef = ref()
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  if (formRef.value) {
+    formRef.value.handleOpenForm(id, type)
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await IotRyDailyReportApi.deleteIotRyDailyReport(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+// 响应式变量存储选中的部门
+const selectedDept = ref<{ id: number; name: string }>()
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  // 记录选中的部门信息
+  selectedDept.value = { id: row.id, name: row.name }
+  queryParams.deptId = row.id
+  await getList()
+}
+
+const exportLoading = ref(false)
+const handleExport = async () => {
+  const res = await IotRyDailyReportApi.exportIotRyDailyReport({
+    createTime: queryParams.createTime,
+    contractName: queryParams.contractName,
+    taskName: queryParams.taskName,
+    // pageNo: queryParams.pageNo,
+    // pageSize: queryParams.pageSize,
+    deptId: queryParams.deptId,
+    projectClassification: queryParams.projectClassification
+  })
+
+  download.excel(res, '瑞鹰修井日报.xlsx')
+}
+// 声明 ResizeObserver 实例
+let resizeObserver: ResizeObserver | null = null
+
+const route = useRoute()
+
+/** 初始化 **/
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    nextTick(() => {
+      queryParams.deptId = Number(route.query.deptId) as any
+      queryParams.createTime = route.query.createTime as string[]
+      queryParams.nonProductFlag = route.query.nonProductFlag as string
+      handleQuery()
+    })
+  } else 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>
+/* 表格容器样式,确保水平滚动 */
+.table-container {
+  width: 100%;
+  overflow-x: auto;
+}
+
+/* 确保表格单元格内容不换行 */
+
+/* :deep(.el-table .cell) {
+  white-space: nowrap;
+} */
+
+/* 确保表格列标题不换行 */
+
+/* :deep(.el-table th > .cell) {
+  white-space: nowrap;
+} */
+
+/* 调整表格最小宽度,确保内容完全显示 */
+:deep(.el-table) {
+  min-width: 100%;
+}
+
+/* 强制显示所有内容,防止省略号 */
+
+/* :deep(.el-table td.el-table__cell),
+:deep(.el-table th.el-table__cell) {
+  overflow: visible !important;
+} */
+
+/* :deep(.el-table .cell) {
+  overflow: visible !important;
+  text-overflow: clip !important;
+} */
+
+/* 设计井身结构文本样式 - 多行显示并添加省略号 */
+.design-well-struct-text {
+  display: -webkit-box;
+  max-height: 3em; /* 两行文本的高度 */
+  overflow: hidden;
+  line-height: 1.5;
+  text-overflow: ellipsis;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+}
+
+/* 确保设计井身结构列不参与自动调整 */
+:deep(.el-table__header-wrapper .el-table__cell.fixed-width),
+:deep(.el-table__body-wrapper .el-table__cell.fixed-width) {
+  flex-shrink: 0;
+  flex-grow: 0;
+}
+
+/* 颜色说明区域样式 */
+.color-legend {
+  display: flex;
+  padding: 12px 16px;
+  background-color: #f8f9fa;
+  border-left: 4px solid #e6f7ff;
+  border-radius: 4px;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+}
+
+.color-indicator {
+  display: inline-block;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+}
+
+.color-indicator.red {
+  background-color: red;
+}
+
+.color-indicator.orange {
+  background-color: orange;
+}
+
+/* 在原有样式基础上添加黄色指示器 */
+.color-indicator.yellow {
+  background-color: #0a35c4; /* 黄色,与警告颜色一致 */
+}
+</style>
+
+<style>
+/* 设计井身结构 tooltip 样式 - 保留换行符 */
+.design-well-struct-tooltip {
+  max-width: 500px;
+  line-height: 1.5;
+  white-space: pre-line;
+}
+</style>

+ 27 - 7
src/views/pms/iotrydailyreport/xsummary.vue

@@ -40,6 +40,13 @@ const query = ref<Query>({
 const totalWorkKeys: [string, string, string, string, number][] = [
   ['constructionWells', '个', '累计施工井数', 'i-mdi:progress-wrench text-sky', 0],
   ['completedWells', '个', '累计完工井数', 'i-mdi:wrench-check-outline text-emerald', 0],
+  [
+    'utilizationRate',
+    '%',
+    '设备利用率',
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
+  ],
   [
     'totalPowerConsumption',
     'MWh',
@@ -80,7 +87,8 @@ const totalWork = ref({
   totalPowerConsumption: 0,
   constructionWells: 0,
   completedWells: 0,
-  averageFuelConsumption: 0
+  averageFuelConsumption: 0,
+  utilizationRate: 0
 })
 
 const totalLoading = ref(false)
@@ -112,7 +120,8 @@ const getTotal = useDebounceFn(async () => {
       ...res2,
       totalPowerConsumption: (res2.totalPowerConsumption || 0) / 1000,
       totalFuelConsumption: res2.totalFuelConsumption || 0,
-      averageFuelConsumption: res2.averageFuelConsumption || 0
+      averageFuelConsumption: res2.averageFuelConsumption || 0,
+      utilizationRate: Number(((res2.utilizationRate || 0) * 100).toFixed(2))
     }
   } finally {
     totalLoading.value = false
@@ -130,6 +139,7 @@ interface List {
   transitTime: number | null
   nonProductiveTime: number | null
   averageFuelConsumption: number | null
+  utilizationRate: number | null
 }
 
 const list = ref<List[]>([])
@@ -169,6 +179,10 @@ const columns = (type: string) => {
     {
       label: '非生产时效(%)',
       prop: 'nonProductiveTime'
+    },
+    {
+      label: '设备利用率(%)',
+      prop: 'utilizationRate'
     }
   ]
 }
@@ -180,6 +194,8 @@ const formatter = (row: List, column: any) => {
     return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
   } else if (column.property === 'nonProductiveTime') {
     return (Number(row.nonProductiveTime ?? 0) * 100).toFixed(2) + '%'
+  } else if (column.property === 'utilizationRate') {
+    return (Number(row.utilizationRate ?? 0) * 100).toFixed(2) + '%'
   } else return row[column.property] ?? 0
 }
 
@@ -199,7 +215,8 @@ const getList = useDebounceFn(async () => {
         ...other,
         cumulativePowerConsumption: ((other.cumulativePowerConsumption || 0) / 1000).toFixed(2),
         cumulativeFuelConsumption: (other.cumulativeFuelConsumption || 0).toFixed(2),
-        averageFuelConsumption: (other.averageFuelConsumption || 0).toFixed(2)
+        averageFuelConsumption: (other.averageFuelConsumption || 0).toFixed(2),
+        utilizationRate: other.utilizationRate || 0
       })
     )
   } finally {
@@ -238,7 +255,8 @@ const legend = ref<string[][]>([
   ['油耗 (升)', 'cumulativeFuelConsumption'],
   // ['累计用电量 (MWh)', 'cumulativePowerConsumption'],
   ['用电量 (KWh)', 'cumulativePowerConsumption'],
-  ['平均时效 (%)', 'transitTime']
+  ['平均时效 (%)', 'transitTime'],
+  ['设备利用率 (%)', 'utilizationRate']
 ])
 
 const chartData = ref<Record<string, number[]>>({
@@ -246,7 +264,8 @@ const chartData = ref<Record<string, number[]>>({
   cumulativeConstructWells: [],
   cumulativeCompletedWells: [],
   cumulativePowerConsumption: [],
-  transitTime: []
+  transitTime: [],
+  utilizationRate: []
 })
 
 let chartLoading = ref(false)
@@ -264,7 +283,8 @@ const getChart = useDebounceFn(async () => {
       cumulativeCompletedWells: res.map((item) => item.cumulativeCompletedWells || 0),
       cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
       // cumulativePowerConsumption: res.map((item) => (item.cumulativePowerConsumption || 0) / 1000),
-      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+      transitTime: res.map((item) => (item.transitTime || 0) * 100),
+      utilizationRate: res.map((item) => Number(((item.utilizationRate || 0) * 100).toFixed(2)))
     }
 
     xAxisData.value = res.map((item) => item.reportDate || '')
@@ -542,7 +562,7 @@ const tolist = (id: number, non: boolean = false) => {
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
       </el-form-item>
     </el-form>
-    <div class="grid grid-cols-8 gap-8">
+    <div class="grid grid-cols-9 gap-8">
       <div
         v-for="info in totalWorkKeys"
         :key="info[0]"

+ 13 - 0
src/views/pms/maintain/index.vue

@@ -50,6 +50,19 @@
               :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
             />
           </el-form-item>
+          <el-form-item label="创建时间" label-width="70px" prop="createTime">
+            <el-date-picker
+              size="small"
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-220px"
+              :clearable="true"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            />
+          </el-form-item>
           <el-form-item :label="t('maintain.status')" label-width="40px" prop="status">
             <el-select
               v-model="queryParams.status"

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

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

+ 74 - 22
src/views/pms/monitor/index.vue

@@ -49,25 +49,38 @@
               :lg="6"
               class="mt-2"
             >
-              <el-card shadow="hover" class="device-card" style="border: 0">
+              <el-card shadow="hover" class="device-card hhh rounded-sm">
                 <div class="card-header">
                   <div class="flex justify-between w-full">
                     <div>
                       <div class="card-title">{{ item.name }}</div>
                       <div class="card-dept">{{ item.deptName }}</div>
                     </div>
-                    <el-image
-                      v-if="item.type === '1'"
-                      :src="img1"
-                      style="width: 40px; height: 40px"
-                    />
-                    <el-image v-else :src="img2" style="width: 40px; height: 40px" />
+                    <el-tag
+                      :type="getOnlineStatusTagType(item.ifOnline)"
+                      size="small"
+                      class="status-tag"
+                    >
+                      {{ item.ifOnline ? '在线' : '离线' }}
+                    </el-tag>
                   </div>
                 </div>
-                <div class="card-body">
+                <div class="card-body" style="margin-top: -20px">
                   <div class="card-row">
                     <span class="muted">成套类型:</span>
-                    <dict-tag :type="DICT_TYPE.DEVICE_GROUP_TYPE" :value="item.type" />
+                    <span class="type-with-icon">
+                      <dict-tag :type="DICT_TYPE.DEVICE_GROUP_TYPE" :value="item.type" />
+                      <el-image
+                        v-if="item.type === '1'"
+                        :src="img1"
+                        style="width: 40px; height: 40px; margin-left: 40px"
+                      />
+                      <el-image
+                        v-else
+                        :src="img2"
+                        style="width: 40px; height: 40px; margin-left: 40px"
+                      />
+                    </span>
                   </div>
                   <div class="card-row"
                     ><span class="muted">设备数量:</span>
@@ -83,10 +96,18 @@
                         : '无'
                     }}
                   </div>
+                  <div class="card-row">
+                    <span class="muted">在线状态:</span>
+                    <el-tag :type="getOnlineStatusTagType(item.ifOnline)" size="small">
+                      {{ item.ifOnline ? '在线' : '离线' }}
+                    </el-tag>
+                  </div>
                   <!-- <div class="card-row"><span class="muted">描述:</span> {{ item.remark }}</div> -->
                 </div>
                 <template #footer>
-                  <el-button type="primary" link @click="handleEdit(item)"> 查看详情 </el-button>
+                  <el-button type="primary" link @click="handleEdit(item)"
+                    ><Icon icon="ep:view" class="mr-5px" /> 查看详情
+                  </el-button>
                 </template>
               </el-card>
             </el-col>
@@ -153,6 +174,11 @@ const formData = ref({
   remark: ''
 })
 
+// 获取在线状态的标签类型
+const getOnlineStatusTagType = (ifOnline: boolean) => {
+  return ifOnline ? 'success' : 'info'
+}
+
 // 表单验证规则
 const formRules = {
   name: [{ required: true, message: '成套名称不能为空', trigger: 'blur' }],
@@ -391,7 +417,17 @@ onMounted(async () => {
 })
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+::v-deep .el-card {
+  border-radius: 5px !important;
+}
+
+::v-deep .hhh.el-card {
+  background-color: transparent !important;
+  border-radius: 5px !important;
+  overflow: hidden;
+  box-shadow: 0 6px 18px rgba(28, 39, 63, 0.06);
+}
 .transfer-container {
   display: flex;
   flex-direction: column;
@@ -429,11 +465,15 @@ onMounted(async () => {
   border-radius: 12px;
   overflow: hidden;
   box-shadow: 0 6px 18px rgba(28, 39, 63, 0.06);
+  /* 渐变蓝背景 */
+  background: linear-gradient(to top, #fff 0%, #effaff 100%);
+  color: #717e9d !important;
 }
 
 /* 移除 el-card 默认内边距,统一由子元素控制 */
 .device-card ::v-deep .el-card__body {
   padding: 0;
+  color: #717e9d !important;
 }
 
 .device-card .card-header {
@@ -441,20 +481,24 @@ onMounted(async () => {
   justify-content: space-between;
   align-items: center;
   padding: 18px 16px;
-  background: linear-gradient(90deg, #2dd4bf 0%, #0ea5e9 100%);
-  color: #fff;
 }
 
 .device-card .card-title {
   font-weight: 700;
   font-size: 16px;
   line-height: 1.2;
+  color: #34475c;
 }
 
 .device-card .card-dept {
   font-size: 12px;
-  opacity: 0.9;
+  opacity: 0.95;
   margin-top: 6px;
+  color: #34475c;
+}
+
+.device-card .status-tag {
+  margin-bottom: 8px;
 }
 
 .device-card .card-status {
@@ -466,15 +510,16 @@ onMounted(async () => {
 }
 
 .device-card .card-body {
-  background: #fff;
+  background: transparent;
   padding: 14px 16px;
+  font-size: 14px;
+  color: #34475c;
 }
 
 .device-card .card-row {
   display: flex;
   align-items: center;
-  padding: 12px 0;
-  border-bottom: 1px solid #f5f7fa;
+  padding: 10px 0;
 }
 
 .device-card .card-row:last-child {
@@ -482,13 +527,18 @@ onMounted(async () => {
 }
 
 .device-card .card-row .muted {
-  color: #97a0b5;
+  color: #34475c;
   width: 86px;
   flex-shrink: 0;
 }
 
+.device-card .type-with-icon {
+  display: inline-flex;
+  align-items: center;
+}
+
 .device-card .card-row .value {
-  color: #2b3a4a;
+  color: #fff;
   flex: 1;
 }
 
@@ -497,16 +547,18 @@ onMounted(async () => {
   justify-content: space-between;
   align-items: center;
   padding: 12px 16px 16px;
-  background: #fff;
+  background: transparent;
+  color: #fff;
 }
 
 .device-card .detail-link {
-  color: var(--el-color-primary, #409eff);
+  color: #fff;
   font-weight: 500;
 }
 
 .device-card .id-label {
-  color: #97a0b5;
+  color: rgba(255, 255, 255, 0.85);
+  font-size: 12px;
   font-size: 12px;
 }
 </style>

+ 614 - 0
src/views/pms/qhse/certificate.vue

@@ -0,0 +1,614 @@
+<!-- src/views/pms/qhse/certificate.vue -->
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+          <el-form-item label="证书类型" prop="type">
+            <el-select v-model="queryParams.type" placeholder="请选择证书类型" style="width: 150px">
+              <el-option label="个人证书" value="personal" />
+              <el-option label="组织证书" value="organization" />
+              <el-option label="其他" value="other" />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item label="证书类别" prop="classify">
+            <el-select
+              v-model="queryParams.classify"
+              placeholder="证书类别"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.PERSON_CERT).concat(
+                  getStrDictOptions(DICT_TYPE.ORG_CERT)
+                )"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button @click="handleAdd" type="primary"
+              ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+            >
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+            >
+            <el-button @click="handleExport" type="success" plain :loading="exportLoading"
+              ><Icon icon="ep:download" class="mr-5px" /> 导出Excel</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="t('monitor.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="证书类型" align="center" prop="type">
+            <template #default="scope">
+              {{ getCertificateTypeText(scope.row.type) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="证书类别" align="center" width="150" prop="classify">
+            <template #default="scope">
+              <dict-tag
+                v-if="scope.row.type === 'organization'"
+                :type="DICT_TYPE.ORG_CERT"
+                :value="scope.row.classify"
+              />
+              <dict-tag v-else :type="DICT_TYPE.PERSON_CERT" :value="scope.row.classify" />
+            </template>
+          </el-table-column>
+
+          <el-table-column label="所属公司/人" align="center" prop="certBelong" />
+
+          <el-table-column label="颁发机构" align="center" prop="certOrg" />
+
+          <el-table-column label="证书标准" align="center" prop="certStandard" />
+
+          <el-table-column label="颁发时间" align="center" prop="certIssue">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.certIssue) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="有效期" align="center">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.certExpire) }}
+            </template>
+          </el-table-column>
+
+          <el-table-column label="到期提醒" align="center">
+            <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
+          </el-table-column>
+
+          <el-table-column label="备注" align="center" prop="remark" />
+          <el-table-column
+            :label="t('devicePerson.operation')"
+            align="center"
+            fixed="right"
+            min-width="150px"
+          >
+            <template #default="scope">
+              <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
+              <el-button link type="success" @click="handleViewImage(scope.row.certPic)">
+                查看
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 新增/编辑证书对话框 -->
+  <el-dialog
+    :title="dialogTitle"
+    v-model="dialogVisible"
+    width="600px"
+    destroy-on-close
+    @close="closeDialog"
+  >
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="证书类型" prop="type">
+        <el-select
+          v-model="formData.type"
+          placeholder="请选择证书类型"
+          @change="formData.classify = ''"
+        >
+          <el-option label="个人证书" value="personal" />
+          <el-option label="组织证书" value="organization" />
+          <el-option label="其他" value="other" />
+        </el-select>
+      </el-form-item>
+
+      <span class="absolute left-16 text-red" v-if="formData.type !== 'other'">*</span>
+      <el-form-item label="证书类别" prop="classify" v-show="formData.type !== 'other'">
+        <el-select
+          v-if="formData.type === 'personal'"
+          v-model="formData.classify"
+          placeholder="证书类别"
+          clearable
+        >
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.PERSON_CERT)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+        <el-select v-else v-model="formData.classify" placeholder="证书类别" clearable>
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.ORG_CERT)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="所在部门" prop="deptId">
+        <el-tree-select
+          clearable
+          v-model="formData.deptId"
+          :data="deptList2"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          filterable
+          placeholder="请选择所在部门"
+          @change="handleDeptChange"
+        />
+      </el-form-item>
+
+      <el-form-item label="所属人" prop="userId">
+        <el-select v-model="formData.userId" placeholder="请选择所属人" clearable>
+          <el-option
+            v-for="dict in userList"
+            :key="dict.id"
+            :label="dict.nickname"
+            :value="dict.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="颁发机构" prop="certOrg">
+        <el-input v-model="formData.certOrg" placeholder="请输入颁发机构" />
+      </el-form-item>
+
+      <el-form-item label="证书标准" prop="certStandard">
+        <el-input v-model="formData.certStandard" placeholder="如国标、API等" />
+      </el-form-item>
+
+      <el-form-item label="颁发时间" prop="certIssue">
+        <el-date-picker
+          v-model="formData.certIssue"
+          type="date"
+          value-format="x"
+          placeholder="请选择颁发时间"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="有效期" prop="certExpire">
+        <el-date-picker
+          v-model="formData.certExpire"
+          type="date"
+          value-format="x"
+          placeholder="请选择有效期"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="到期前提醒" prop="noticeBefore">
+        <el-input-number
+          v-model="formData.noticeBefore"
+          :min="0"
+          :max="365"
+          placeholder="请输入提前多少天提醒"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          type="textarea"
+          v-model="formData.remark"
+          :rows="2"
+          placeholder="请输入备注"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="证书图片" prop="certPic">
+        <UploadImage v-model="formData.certPic" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="closeDialog">取 消</el-button>
+      <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+    </template>
+  </el-dialog>
+
+  <!-- 查看证书图片对话框 -->
+  <el-dialog :title="imageDialogTitle" v-model="imageDialogVisible" width="800px" center>
+    <img :src="imagePreviewUrl" alt="证书图片" style="max-width: 100%; max-height: 80vh" />
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { IotMeasureCertApi } from '@/api/pms/qhse/index'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { ElMessageBox, ElMessage } from 'element-plus'
+const deptList = ref<Tree[]>([]) // 树形结构
+const deptList2 = ref<Tree[]>([]) // 树形结构
+import { formatDate } from '@/utils/formatTime'
+import UploadImage from '@/components/UploadFile/src/UploadImg.vue'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { defaultProps } from '@/utils/tree'
+import { selectedDeptsEmployee } from '@/api/system/user'
+
+defineOptions({ name: 'IotQHSECertificate' })
+
+const loading = ref(true) // 列表的加载中
+const formLoading = ref(false) // 表单加载中
+const submitLoading = ref(false) // 提交按钮加载中
+const exportLoading = ref(false) // 导出按钮加载中
+
+const { t } = useI18n()
+
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  type: undefined,
+  classify: undefined,
+  deptId: ''
+})
+const queryFormRef = ref(null) // 搜索的表单
+
+const contentSpan = ref(20)
+const treeShow = ref(true)
+
+// 对话框相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+
+// 图片查看对话框
+const imageDialogVisible = ref(false)
+const imageDialogTitle = ref('证书图片')
+const imagePreviewUrl = ref('')
+
+// 表单相关
+const formRef = ref()
+const formData = ref({
+  type: '', // 证书类型
+  classify: '', // 证书类别
+  userId: '',
+
+  certOrg: '', // 证书颁发机构
+  certStandard: '', // 证书标准
+  certIssue: '', // 证书颁发时间
+  certExpire: '', // 证书有效期
+  noticeBefore: '', // 到期前提醒
+  certPic: '', // 证书图片上传
+  remark: '', // 备注
+  deptId: '' // 部门id
+})
+
+// 获取证书类型文本
+const getCertificateTypeText = (type: string) => {
+  const map: Record<string, string> = {
+    personal: '个人证书',
+    organization: '组织证书',
+    other: '其他'
+  }
+  return map[type] || type
+}
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+// 表单验证规则
+const formRules = {
+  type: [{ required: true, message: '证书类型不能为空', trigger: 'blur' }],
+  classify: [
+    {
+      required: false, // 默认设为非必填
+      validator: (rule, value, callback) => {
+        // 只有当证书类型不是 "other" 时才验证
+        if (formData.value.type !== 'other') {
+          if (!value) {
+            callback(new Error('证书类别不能为空'))
+          } else {
+            callback()
+          }
+        } else {
+          callback() // other 类型时不需要验证
+        }
+      },
+      trigger: ['blur', 'change']
+    }
+  ],
+  deptId: [{ required: true, message: '所在部门不能为空', trigger: 'blur' }],
+
+  certOrg: [{ required: true, message: '颁发机构不能为空', trigger: 'blur' }],
+  certIssue: [{ required: true, message: '颁发时间不能为空', trigger: 'blur' }],
+  certExpire: [{ required: true, message: '有效期不能为空', trigger: 'blur' }]
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureCertApi.getIotMeasureCertPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    const response = await IotMeasureCertApi.exportIotMeasureCert(queryParams)
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 首页处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.deptId = ''
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+// 显示新增对话框
+const handleAdd = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增证书'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 显示编辑对话框
+const handleEdit = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑证书'
+
+  formData.value = {
+    ...row,
+    // 确保日期字段正确处理
+    issueDate: row.issueDate ? ensureMillisecondTimestamp(row.issueDate) : null,
+    validityPeriod: row.validityPeriod ? ensureMillisecondTimestamp(row.validityPeriod) : null
+  }
+
+  dialogVisible.value = true
+}
+
+// 查看证书图片
+const handleViewImage = (imageUrl: string) => {
+  imagePreviewUrl.value = imageUrl
+  imageDialogTitle.value = '证书图片'
+  imageDialogVisible.value = true
+}
+
+// 确保时间戳是毫秒级的
+const ensureMillisecondTimestamp = (timestamp) => {
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 秒级时间戳转为毫秒级
+    return time * 1000
+  }
+  return time
+}
+
+//删除证书
+const handleDelete = async (id: number) => {
+  ElMessageBox.confirm('确定要删除该证书吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await IotMeasureCertApi.deleteIotMeasureCert(id)
+        ElMessage.success('删除成功')
+        getList()
+      } catch (error) {
+        console.error(error)
+      }
+    })
+    .catch(() => {
+      // 取消操作
+    })
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.value = {
+    type: '', // 证书类型
+    classify: '',
+    certOrg: '', // 证书颁发机构
+    certStandard: '', // 证书标准
+    certIssue: '', // 证书颁发时间
+    certExpire: '', // 证书有效期
+    noticeBefore: '', // 到期前提醒
+    certPic: '', // 证书图片上传
+    remark: '', // 备注
+    deptId: '', // 部门id
+    userId: ''
+  }
+  formRef.value?.clearValidate()
+}
+
+// 关闭对话框
+const closeDialog = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 准备提交数据
+    const submitData = {
+      ...formData.value,
+      // 确保日期字段以正确的格式提交
+      certIssue: formData.value.certIssue,
+      certExpire: formData.value.certExpire
+    }
+
+    if (isEdit.value) {
+      // 编辑
+      await IotMeasureCertApi.updateIotMeasureCert(submitData)
+      ElMessage.success('编辑成功')
+    } else {
+      // 新增
+      await IotMeasureCertApi.createIotMeasureCert(submitData)
+      ElMessage.success('新增成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error(error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+// 下载文件函数
+const downloadFile = (response: any) => {
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  let fileName = '证书台账.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  document.body.appendChild(link)
+  link.click()
+
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+let userList = ref([])
+const handleDeptChange = async (value) => {
+  const res = await selectedDeptsEmployee({
+    deptIds: value
+  })
+
+  userList.value = res
+  console.log('value>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', userList.value)
+}
+
+onMounted(async () => {
+  getList()
+
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+::deep(.el-tree--highlight-current) {
+  height: 200px !important;
+}
+::deep(.el-transfer-panel__body) {
+  height: 700px !important;
+}
+.image-preview {
+  margin-top: 10px;
+  display: flex;
+  justify-content: center;
+}
+</style>

+ 680 - 0
src/views/pms/qhse/factor/index.vue

@@ -0,0 +1,680 @@
+<template>
+  <div class="factor-matrix">
+    <!-- 筛选表单 -->
+    <ContentWrap style="border: 0">
+      <el-form
+        class="pt-2"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="80px"
+      >
+        <el-form-item label="工序" prop="process">
+          <el-input
+            v-model="queryParams.process"
+            placeholder="请输入工序"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item label="步骤分解" prop="stepBreak">
+          <el-input
+            v-model="queryParams.stepBreak"
+            placeholder="请输入步骤分解"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item label="环境因素" prop="environmentElement">
+          <el-input
+            v-model="queryParams.environmentElement"
+            placeholder="请输入环境因素"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </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" @click="openAddDialog" color="#626aef"
+            ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+          >
+          <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+            <Icon icon="ep:download" class="mr-5px" /> 导出
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 表格 -->
+    <ContentWrap style="border: 0">
+      <el-table
+        :data="tableData"
+        border
+        stripe
+        style="width: 100%"
+        :header-cell-style="{ background: '#f5f7fa', color: '#333' }"
+        :cell-style="{ padding: '12px 8px' }"
+        height="68vh"
+      >
+        <el-table-column label="序号" width="70" align="center" fixed="left">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="工序" prop="process" width="140" align="center" fixed="left" />
+        <el-table-column label="步骤分解" prop="stepBreak" width="140" align="center" />
+        <el-table-column label="环境因素" prop="environmentElement" width="180" align="center" />
+
+        <el-table-column label="时态" width="240" align="center">
+          <el-table-column label="过去" prop="timeBefore" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeBefore">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="现在" prop="timeNow" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeNow">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="将来" prop="timeFuture" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.timeFuture">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="状态" width="240" align="center">
+          <el-table-column label="正常" prop="statusNormal" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.statusNormal">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="异常" prop="statusException" width="60" align="center">
+            <template #default="{ row }">
+              <el-button
+                circle
+                type="success"
+                style="border: none"
+                plain
+                v-if="row.statusException"
+              >
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="紧急" prop="statusDanger" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.statusDanger">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="环境影响类型" width="700" align="center">
+          <el-table-column label="能源/资源耗用" prop="typeEnergy" width="100" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeEnergy ? '✔' : '' }}
+              </span> -->
+
+              <el-button circle type="success" style="border: none" plain v-if="row.typeEnergy">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="水体" prop="typeWater" width="60" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeWater ? '✔' : '' }}
+              </span> -->
+              <el-button circle type="success" style="border: none" plain v-if="row.typeWater">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="大气" prop="typeGas" width="60" align="center">
+            <template #default="{ row }">
+              <!-- <span>
+                {{ row.typeGas ? '✔' : '' }}
+              </span> -->
+
+              <el-button circle type="success" style="border: none" plain v-if="row.typeGas">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="噪音" prop="typeNoise" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeNoise">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="废弃物" prop="typeWaste" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeWaste">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="土壤" prop="typeSoil" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeSoil">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+          <el-table-column label="其他" prop="typeOther" width="60" align="center">
+            <template #default="{ row }">
+              <el-button circle type="success" style="border: none" plain v-if="row.typeOther">
+                <span class="text-[#259644]">
+                  {{ '✔' }}
+                </span>
+              </el-button>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column label="控制措施" prop="controlMethod" min-width="200" align="center" />
+        <el-table-column label="创建日期" prop="createTime" width="160" align="center">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" prop="remark" width="150" align="center" />
+
+        <el-table-column label="操作" width="120" fixed="right" align="center">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="editRow(row)">编辑</el-button>
+            <el-button type="danger" link @click="deleteRow(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="mt-2 flex justify-right">
+        <el-pagination
+          v-model:current-page="pagination.pageNo"
+          v-model:page-size="pagination.pageSize"
+          :total="total"
+          layout="total, sizes, prev, pager, next"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          background
+        />
+      </div>
+    </ContentWrap>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="50%" destroy-on-close>
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-width="120px"
+        v-loading="formLoading"
+      >
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="工序" prop="process">
+              <el-input v-model="formData.process" placeholder="请输入工序" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="步骤分解" prop="stepBreak">
+              <el-input v-model="formData.stepBreak" placeholder="请输入步骤分解" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="环境因素" prop="environmentElement">
+              <el-input v-model="formData.environmentElement" placeholder="请输入环境因素" />
+            </el-form-item>
+          </el-col>
+          <!-- <el-col :span="12">
+            <el-form-item label="控制措施" prop="controlMethod">
+              <el-input
+                type="textarea"
+                v-model="formData.controlMethod"
+                placeholder="请输入控制措施"
+              />
+            </el-form-item>
+          </el-col> -->
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="时态" prop="timeValue">
+              <el-radio-group v-model="formData.timeValue">
+                <el-radio label="timeBefore">过去</el-radio>
+                <el-radio label="timeNow">现在</el-radio>
+                <el-radio label="timeFuture">将来</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" prop="statusValue">
+              <el-radio-group v-model="formData.statusValue">
+                <el-radio label="statusNormal">正常</el-radio>
+                <el-radio label="statusException">异常</el-radio>
+                <el-radio label="statusDanger">紧急</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="环境影响类型" prop="typeValue">
+              <el-radio-group v-model="formData.typeValue">
+                <el-radio label="typeEnergy">能源/资源耗用</el-radio>
+                <el-radio label="typeWater">水体污染</el-radio>
+                <el-radio label="typeGas">大气污染</el-radio>
+                <el-radio label="typeNoise">噪声污染</el-radio>
+                <el-radio label="typeWaste">废弃物</el-radio>
+                <el-radio label="typeSoil">土壤污染</el-radio>
+                <el-radio label="typeOther">其他</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="控制措施" prop="controlMethod">
+              <el-input
+                type="textarea"
+                v-model="formData.controlMethod"
+                placeholder="请输入控制措施"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="所在部门" prop="deptId">
+              <el-tree-select
+                clearable
+                v-model="formData.deptId"
+                :data="deptList2"
+                :props="defaultProps"
+                check-strictly
+                node-key="id"
+                filterable
+                placeholder="请选择所在部门"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="备注">
+              <el-input v-model="formData.remark" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="cancelForm">取 消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { IotEnvironmentApi } from '@/api/pms/qhse/index'
+import { formatDate } from '@/utils/formatTime'
+import { defaultProps } from '@/utils/tree'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+// 表格数据
+const deptList2 = ref<Tree[]>([]) // 树形结构
+const tableData = ref([])
+const total = ref(0)
+
+// 筛选参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  process: '',
+  stepBreak: '',
+  environmentElement: ''
+})
+
+const pagination = reactive({
+  pageNo: 1,
+  pageSize: 10
+})
+
+// 分页和查询
+const queryFormRef = ref()
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+const handleSizeChange = (val) => {
+  queryParams.pageSize = val
+  queryParams.pageNo = 1 // 重置为第一页
+
+  getList()
+}
+
+const handleCurrentChange = (val) => {
+  queryParams.pageNo = val
+  getList()
+}
+
+const downloadFile = (response) => {
+  // 创建 blob 对象
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  // 获取文件名
+  let fileName = '环境因素评价矩阵.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  // 创建下载链接
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  // 触发下载
+  document.body.appendChild(link)
+  link.click()
+
+  // 清理
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    // 调用导出接口
+    const response = await IotEnvironmentApi.exportEnvironment(queryParams)
+
+    // 下载文件
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+const getList = async () => {
+  const res = await IotEnvironmentApi.getEnvironmentList(queryParams)
+  tableData.value = res.list
+  total.value = res.total
+}
+
+// 弹窗相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const formLoading = ref(false)
+const submitLoading = ref(false)
+const isEdit = ref(false)
+
+// 表单数据
+const formData = ref({
+  process: '',
+  stepBreak: '',
+  environmentElement: '',
+  timeValue: '', // 单选时态值
+  statusValue: '', // 单选状态值
+  typeValue: '', // 单选影响类型值
+  controlMethod: '',
+  remark: '',
+  deptId: null as number | null
+})
+
+// 表单验证规则
+const formRules = {
+  stepBreak: [{ required: true, message: '请输入步骤分解', trigger: 'blur' }],
+  environmentElement: [{ required: true, message: '请输入环境因素', trigger: 'blur' }],
+  controlMethod: [{ required: true, message: '请输入控制措施', trigger: 'blur' }],
+  timeValue: [{ required: true, message: '请选择时态', trigger: 'change' }],
+  statusValue: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  typeValue: [{ required: true, message: '请选择环境影响类型', trigger: 'change' }]
+}
+
+// 表单引用
+const formRef = ref()
+
+// 打开新增对话框
+const openAddDialog = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增环境因素'
+  resetFormData()
+  dialogVisible.value = true
+}
+
+// 编辑行
+const editRow = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑环境因素'
+
+  // 构造表单数据
+  formData.value = {
+    ...row,
+    timeValue: '', // 初始化单选值
+    statusValue: '', // 初始化单选值
+    typeValue: '' // 初始化单选值
+  }
+
+  // 设置单选值
+  if (row.timeBefore) formData.value.timeValue = 'timeBefore'
+  if (row.timeNow) formData.value.timeValue = 'timeNow'
+  if (row.timeFuture) formData.value.timeValue = 'timeFuture'
+
+  if (row.statusNormal) formData.value.statusValue = 'statusNormal'
+  if (row.statusException) formData.value.statusValue = 'statusException'
+  if (row.statusDanger) formData.value.statusValue = 'statusDanger'
+
+  if (row.typeEnergy) formData.value.typeValue = 'typeEnergy'
+  if (row.typeWater) formData.value.typeValue = 'typeWater'
+  if (row.typeGas) formData.value.typeValue = 'typeGas'
+  if (row.typeNoise) formData.value.typeValue = 'typeNoise'
+  if (row.typeWaste) formData.value.typeValue = 'typeWaste'
+  if (row.typeSoil) formData.value.typeValue = 'typeSoil'
+  if (row.typeOther) formData.value.typeValue = 'typeOther'
+
+  dialogVisible.value = true
+}
+
+// 删除行
+const deleteRow = (row) => {
+  ElMessageBox.confirm('确定要删除这条记录吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      // 这里应该调用实际的API删除
+      await IotEnvironmentApi.deleteEnvironment(row.id)
+
+      ElMessage.success('删除成功')
+      getList() // 重新获取列表
+    })
+    .catch(() => {
+      // 取消删除
+    })
+}
+
+// 重置表单数据
+const resetFormData = () => {
+  formData.value = {
+    process: '',
+    stepBreak: '',
+    environmentElement: '',
+    timeValue: '',
+    statusValue: '',
+    typeValue: '',
+    controlMethod: '',
+    remark: '',
+    deptId: null
+  }
+  nextTick(() => {
+    formRef.value?.clearValidate()
+  })
+}
+
+// 取消表单
+const cancelForm = () => {
+  dialogVisible.value = false
+  resetFormData()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 根据单选值更新布尔值
+    const updatedData = {
+      ...formData.value,
+      timeBefore: formData.value.timeValue === 'timeBefore',
+      timeNow: formData.value.timeValue === 'timeNow',
+      timeFuture: formData.value.timeValue === 'timeFuture',
+      statusNormal: formData.value.statusValue === 'statusNormal',
+      statusException: formData.value.statusValue === 'statusException',
+      statusDanger: formData.value.statusValue === 'statusDanger',
+      typeEnergy: formData.value.typeValue === 'typeEnergy',
+      typeWater: formData.value.typeValue === 'typeWater',
+      typeGas: formData.value.typeValue === 'typeGas',
+      typeNoise: formData.value.typeValue === 'typeNoise',
+      typeWaste: formData.value.typeValue === 'typeWaste',
+      typeSoil: formData.value.typeValue === 'typeSoil',
+      typeOther: formData.value.typeValue === 'typeOther'
+    }
+
+    // 删除临时单选字段
+    delete updatedData.timeValue
+    delete updatedData.statusValue
+    delete updatedData.typeValue
+
+    if (isEdit.value) {
+      // 更新操作
+      await IotEnvironmentApi.updateEnvironment(updatedData)
+      ElMessage.success('更新成功')
+    } else {
+      // 创建操作
+      await IotEnvironmentApi.createEnvironment(updatedData)
+      ElMessage.success('创建成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error('表单验证失败:', error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+onMounted(async () => {
+  // 初始化数据
+  getList()
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped lang="scss">
+::v-deep .el-table__header th {
+  border: 0.3px solid #adb0b7;
+}
+
+.factor-matrix .toolbar {
+  margin: 12px 0;
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.el-table .row-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  margin-top: 6px;
+}
+
+.el-checkbox .el-checkbox__input {
+  margin-left: 6px;
+}
+</style>

+ 620 - 0
src/views/pms/qhse/faultReport/index.vue

@@ -0,0 +1,620 @@
+<template>
+  <div class="app-container">
+    <ContentWrap style="border: 0">
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryForm"
+        :inline="true"
+        label-width="100px"
+      >
+        <el-row :gutter="20">
+          <el-col :span="24" :xs="24" :sm="12" :md="6">
+            <el-form-item label="事件级别" prop="accidentGrade">
+              <el-input
+                v-model="queryParams.accidentGrade"
+                placeholder="请输入事件级别"
+                clearable
+                @keyup.enter="handleQuery"
+                style="width: 200px"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24" :xs="24" :sm="12" :md="6">
+            <el-form-item label="事件类型" prop="accidentType">
+              <el-input
+                v-model="queryParams.accidentType"
+                placeholder="请选择事件类型"
+                clearable
+                style="width: 200px"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item style="display: block">
+              <el-button type="primary" @click="handleQuery" :icon="Search">搜索</el-button>
+              <el-button @click="resetQuery" :icon="Refresh">重置</el-button>
+              <el-button type="primary" @click="openForm('create')" color="#626aef">
+                <Icon icon="ep:plus" class="mr-5px" /> 新增
+              </el-button>
+              <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+                <Icon icon="ep:download" class="mr-5px" /> 导出
+              </el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+
+    <ContentWrap style="border: 0">
+      <el-table
+        v-loading="loading"
+        :data="list"
+        row-key="id"
+        border
+        style="width: 100%"
+        :header-cell-style="{ background: '#f5f7fa', color: '#333', height: '50px' }"
+        :cell-style="{ padding: '12px 8px' }"
+        height="70vh"
+        :max-height="tableHeight"
+      >
+        <el-table-column prop="actualTime" label="事件时间" align="center">
+          <template #default="{ row }">
+            {{ formatDate(row.actualTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="accidentGrade" label="事件级别" align="center" />
+        <el-table-column prop="accidentType" label="事件类型" align="center" />
+        <el-table-column
+          prop="lossSituation"
+          label="事件损失情况"
+          align="center"
+          show-overflow-tooltip
+        />
+
+        <el-table-column prop="accidentAddress" label="事件地址" align="center" />
+        <el-table-column prop="deptName" label="部门名称" align="center" />
+        <el-table-column prop="dutyPerson" label="现场负责人" align="center" />
+
+        <el-table-column label="操作" align="center" width="150" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="openForm('update', row)" :icon="Edit"
+              >编辑</el-button
+            >
+            <el-button link type="danger" @click="handleDelete(row)" :icon="Delete">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="mt-2 mb-2 float-right">
+        <el-pagination
+          v-model:current-page="queryParams.pageNo"
+          v-model:page-size="queryParams.pageSize"
+          :total="total"
+          layout="total, sizes, prev, pager, next"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          background
+        />
+      </div>
+    </ContentWrap>
+
+    <!-- 表单弹窗 -->
+    <el-dialog
+      :title="title"
+      v-model="open"
+      width="800px"
+      :fullscreen="isMobile"
+      append-to-body
+      :close-on-click-modal="false"
+      destroy-on-close
+      custom-class="self-dialog"
+    >
+      <template #title>
+        <div
+          style="
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            border-bottom: 1px solid #ebeef5;
+            padding-bottom: 20px;
+            padding-left: 20px;
+            width: 108%;
+            margin-left: -15px;
+          "
+        >
+          <span>{{ title }}</span>
+        </div>
+      </template>
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
+        <el-row :gutter="20">
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="事件时间" prop="actualTime">
+              <el-date-picker
+                v-model="formData.actualTime"
+                type="datetime"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="x"
+                placeholder="选择事件发生时间"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="事件级别" prop="accidentGrade">
+              <el-input v-model="formData.accidentGrade" placeholder="请输入事件级别" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="事件类型" prop="accidentType">
+              <el-input
+                v-model="formData.accidentType"
+                placeholder="请选择事件类型"
+                clearable
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="事件地址" prop="accidentAddress">
+              <el-input v-model="formData.accidentAddress" placeholder="请输入事件地址" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="现场负责人" prop="dutyPerson">
+              <el-input
+                v-model="formData.dutyPerson"
+                placeholder="请输入现场负责人姓名"
+                clearable
+              />
+            </el-form-item>
+          </el-col>
+
+          <!-- <el-col :span="24" :xs="24" :sm="24" :md="12">
+            <el-form-item label="事件损失情况" prop="lossSituation">
+              <el-input
+                v-model="formData.lossSituation"
+                placeholder="请输入事件损失情况"
+                clearable
+              />
+            </el-form-item>
+          </el-col> -->
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="事件损失情况" prop="lossSituation">
+              <el-input
+                v-model="formData.lossSituation"
+                placeholder="请输入事件损失情况"
+                clearable
+                type="textarea"
+                :rows="2"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="现场采取措施" prop="emergencyMeasure">
+              <el-input
+                v-model="formData.emergencyMeasure"
+                type="textarea"
+                :rows="2"
+                placeholder="请输入现场采取的应急措施"
+                maxlength="500"
+                show-word-limit
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="事件简要经过" prop="description">
+              <el-input
+                v-model="formData.description"
+                type="textarea"
+                :rows="2"
+                placeholder="请输入事件详细经过"
+                maxlength="1000"
+                show-word-limit
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="附件/现场图片" prop="pic">
+              <UploadImage v-model="formData.pic" width="260px" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="备注" prop="remark">
+              <el-input
+                v-model="formData.remark"
+                type="textarea"
+                :rows="3"
+                placeholder="请输入备注信息"
+                maxlength="500"
+                show-word-limit
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel">取 消</el-button>
+          <el-button type="primary" @click="submitForm" :loading="submitLoading" color="#626aef">
+            确 定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Search, Refresh, Plus, Edit, Delete } from '@element-plus/icons-vue'
+import { defaultProps } from '@/utils/tree'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { IotFailureApi } from '@/api/pms/qhse/index'
+import UploadImage from '@/components/UploadFile/src/UploadImg.vue'
+import { formatDate } from '@/utils/formatTime'
+
+// Data
+const loading = ref(false)
+const submitLoading = ref(false)
+const list = ref([])
+const total = ref(0)
+const open = ref(false)
+const title = ref('')
+const formRef = ref()
+const mode = ref('') // 'create' or 'update'
+const deptList2 = ref([]) // 树形结构
+
+// 表单数据
+const formData = ref({
+  actualTime: null, // 使用时间戳格式
+  accidentGrade: '',
+  accidentType: '',
+  accidentAddress: '',
+  deptId: null,
+  deptName: '',
+  dutyPerson: '',
+  lossSituation: '',
+  emergencyMeasure: '',
+  description: '',
+  pic: '',
+  remark: ''
+})
+
+// 查询参数
+const queryParams = ref({
+  pageNo: 1,
+  pageSize: 10,
+  actualTime: [],
+  accidentGrade: undefined,
+  accidentType: undefined
+})
+
+// 表单验证规则
+const formRules = reactive({
+  actualTime: [{ required: true, message: '请选择事件时间', trigger: 'change' }],
+  accidentGrade: [{ required: true, message: '请输入事件级别', trigger: 'blur' }],
+  accidentType: [{ required: true, message: '请选择事件类型', trigger: 'change' }],
+  accidentAddress: [{ required: true, message: '请输入事件地址', trigger: 'blur' }],
+  deptId: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
+  deptName: [{ required: true, message: '请输入队伍名称', trigger: 'blur' }],
+  dutyPerson: [{ required: true, message: '请输入现场负责人', trigger: 'blur' }]
+  // description: [
+  //   { required: true, message: '请输入事件简要经过', trigger: 'blur' },
+  //   { min: 1, max: 1000, message: '事件简要经过长度应在1-1000之间', trigger: 'blur' }
+  // ]
+})
+
+// 计算属性
+const isMobile = computed(() => {
+  return window.innerWidth < 768
+})
+
+const tableHeight = computed(() => {
+  if (isMobile.value) {
+    return window.innerHeight - 300 // 为移动端减少高度,考虑其他元素占用空间
+  }
+  return '70vh'
+})
+
+// Methods
+const getList = async () => {
+  loading.value = true
+  try {
+    const response = await IotFailureApi.getFailureList(queryParams.value)
+    list.value = response.list
+    total.value = response.total
+    loading.value = false
+  } catch (error) {
+    loading.value = false
+    ElMessage.error('获取列表失败')
+  }
+}
+
+const handleSizeChange = (val) => {
+  queryParams.value.pageSize = val
+  queryParams.value.pageNo = 1 // 重置为第一页
+  getList()
+}
+
+const handleCurrentChange = (val) => {
+  queryParams.value.pageNo = val
+  getList()
+}
+
+const resetQuery = () => {
+  // 重置查询参数
+  queryParams.value = {
+    pageNo: 1,
+    pageSize: 10,
+    actualTime: [],
+    accidentGrade: undefined,
+    accidentType: undefined
+  }
+  handleQuery()
+}
+
+const handleQuery = () => {
+  queryParams.value.pageNo = 1
+  getList()
+}
+
+// 打开表单 - 支持新增和编辑
+const openForm = async (modeVal, row = {}) => {
+  reset()
+  mode.value = modeVal
+
+  if (modeVal === 'create') {
+    title.value = '添加事故事件上报'
+    open.value = true
+  } else if (modeVal === 'update') {
+    title.value = '修改事故事件上报'
+
+    // 直接使用列表中的数据,不需要调用详情接口
+    formData.value = { ...row }
+
+    // 如果是字符串或日期格式的时间,转换为时间戳
+    if (typeof row.actualTime === 'string' && row.actualTime) {
+      formData.value.actualTime = new Date(row.actualTime).getTime()
+    } else if (row.actualTime instanceof Date) {
+      formData.value.actualTime = row.actualTime.getTime()
+    } else if (typeof row.actualTime === 'number') {
+      // 如果已经是时间戳格式,直接使用
+      formData.value.actualTime = row.actualTime
+    }
+
+    open.value = true
+  }
+}
+
+const handleDelete = async (row) => {
+  const ids = [row.id]
+  await ElMessageBox.confirm('是否确认删除选中的故障报告数据项?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+
+  try {
+    // 删除API调用
+    await IotFailureApi.deleteFailure(ids)
+    ElMessage.success('删除成功')
+    getList()
+  } catch (error) {
+    ElMessage.error('删除失败')
+  }
+}
+
+const reset = () => {
+  formData.value = {
+    id: undefined,
+    actualTime: null,
+    accidentGrade: '',
+    accidentType: '',
+    accidentAddress: '',
+    deptId: null,
+    deptName: '',
+    dutyPerson: '',
+    lossSituation: '',
+    emergencyMeasure: '',
+    description: '',
+    pic: '',
+    remark: ''
+  }
+  if (formRef.value) {
+    formRef.value.resetFields()
+  }
+}
+
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  await formRef.value.validate(async (valid) => {
+    if (valid) {
+      submitLoading.value = true
+      try {
+        // 在提交前确保时间是时间戳格式
+        const submitData = { ...formData.value }
+
+        if (mode.value === 'update' && formData.value.id) {
+          // 更新API调用
+          submitData.id = formData.value.id
+          await IotFailureApi.updateFailure(submitData)
+          ElMessage.success('更新成功')
+        } else {
+          // 创建API调用
+          await IotFailureApi.createFailure(submitData)
+          ElMessage.success('新增成功')
+        }
+        open.value = false
+        getList()
+      } catch (error) {
+        ElMessage.error(error.message || '操作失败')
+      } finally {
+        submitLoading.value = false
+      }
+    } else {
+      ElMessage.error('请填写必填项')
+    }
+  })
+}
+
+const downloadFile = (response) => {
+  // 创建 blob 对象
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  // 获取文件名
+  let fileName = '事故事件上报.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  // 创建下载链接
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  // 触发下载
+  document.body.appendChild(link)
+  link.click()
+
+  // 清理
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+let exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    // 调用导出接口
+    const response = await IotFailureApi.exportFailure(queryParams.value)
+
+    // 下载文件
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+const cancel = () => {
+  open.value = false
+  reset()
+}
+
+// 监听窗口大小变化
+const handleResize = () => {
+  // 这里可以添加响应式逻辑
+}
+
+// Lifecycle hooks
+onMounted(async () => {
+  window.addEventListener('resize', handleResize)
+  getList()
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize)
+})
+</script>
+
+<style scoped lang="scss">
+.app-container {
+  padding: 15px 10px;
+}
+
+/* 移动端适配 */
+@media (max-width: 768px) {
+  .app-container {
+    padding: 10px 5px;
+  }
+
+  .el-form-item__label {
+    display: block;
+    text-align: left;
+    padding-bottom: 5px;
+  }
+
+  .el-form-item__content {
+    margin-left: 0 !important;
+  }
+
+  .el-button + .el-button {
+    margin-left: 5px;
+  }
+
+  /* 调整表格字体大小 */
+  :deep(.el-table .el-table__cell) {
+    padding: 8px 0;
+  }
+
+  :deep(.el-table th.el-table__cell) {
+    padding: 10px 0;
+  }
+}
+
+/* 通用样式 */
+.dialog-footer {
+  text-align: right;
+}
+
+:deep(.el-textarea__inner) {
+  min-height: 80px !important;
+}
+
+/* 移动端分页样式 */
+.mt-2.flex.justify-right {
+  display: flex;
+  justify-content: center;
+  margin-top: 10px;
+}
+
+/* 移动端按钮样式 */
+@media (max-width: 768px) {
+  .el-button {
+    margin-bottom: 5px;
+  }
+}
+
+::v-deep .el-button {
+  border-radius: 3px;
+}
+</style>

+ 568 - 0
src/views/pms/qhse/hazard/index.vue

@@ -0,0 +1,568 @@
+<template>
+  <div class="hazard-table-container">
+    <ContentWrap style="border: 0">
+      <!-- 搜索工作栏 -->
+      <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+        <el-form-item label="风险等级" prop="riskGrade">
+          <el-select
+            v-model="queryParams.riskGrade"
+            placeholder="请选择风险等级"
+            clearable
+            style="width: 200px"
+          >
+            <el-option
+              v-for="dict in getStrDictOptions(DICT_TYPE.DANGER_GRADE)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </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" @click="openForm('create')" color="#626aef">
+            <Icon icon="ep:plus" class="mr-5px" /> 新增
+          </el-button>
+          <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+            <Icon icon="ep:download" class="mr-5px" /> 导出
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <ContentWrap style="border: 0">
+      <el-table
+        :data="tableData"
+        border
+        style="width: 100%"
+        :header-cell-style="{ background: '#f5f7fa', color: '#333' }"
+        :cell-style="{ padding: '12px 8px' }"
+        height="70vh"
+      >
+        <!-- 区域/位置 列(已合并) -->
+        <el-table-column prop="region" label="区域/位置" width="150" align="center" fixed="left" />
+
+        <!-- 其他列保持不变 -->
+        <el-table-column label="序号" width="70" align="center">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="elementDescription"
+          label="危害因素描述"
+          width="200"
+          align="center"
+        />
+        <el-table-column prop="maybeResult" min-width="320" label="可导致的后果" align="center" />
+
+        <!-- 风险评价列保持不变 -->
+        <el-table-column label="风险评价" width="320" align="center">
+          <el-table-column prop="evalKn" label="可能性 (L)" width="80" align="center">
+            <template #default="{ row }">
+              {{ row.evalKn }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="evalYz" label="严重性 (S)" width="80" align="center">
+            <template #default="{ row }">
+              {{ row.evalYz }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="evalFxz" label="风险值 (R)" width="80" align="center">
+            <template #default="{ row }">
+              {{ row.evalFxz }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="riskGrade" label="风险等级" width="100" align="center">
+            <template #default="scope">
+              <div class="bg-[#ffff00] w-full rounded-md" v-if="scope.row.riskGrade === 'normal'">
+                一般风险
+              </div>
+              <div class="bg-[#ffc000] w-full rounded-md" v-else-if="scope.row.riskGrade === 'big'">
+                较大风险
+              </div>
+              <div class="bg-[#0070c0] w-full text-white rounded-md" v-else>低风险</div>
+            </template>
+          </el-table-column>
+        </el-table-column>
+
+        <el-table-column
+          prop="controlMethod"
+          label="控制措施"
+          min-width="200"
+          show-overflow-tooltip
+          align="center"
+        />
+        <el-table-column prop="charge" label="责任人 " min-width="100" align="center" />
+        <el-table-column label="操作" width="150" align="center" fixed="right">
+          <template #default="{ row }">
+            <div class="flex gap-3 justify-center">
+              <el-link
+                :underline="false"
+                size="small"
+                type="primary"
+                @click="openForm('edit', row)"
+              >
+                编辑
+              </el-link>
+              <el-link :underline="false" size="small" type="danger" @click="deleteRow(row)">
+                删除
+              </el-link>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="mt-2 flex justify-right">
+        <el-pagination
+          v-model:current-page="pagination.pageNo"
+          v-model:page-size="pagination.pageSize"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          background
+        />
+      </div>
+    </ContentWrap>
+
+    <!-- 新增/编辑弹窗 -->
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="50%" @close="resetForm">
+      <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
+        <el-row :gutter="20">
+          <!-- 第一行 -->
+          <el-col :span="12">
+            <el-form-item label="区域/位置" prop="region">
+              <el-input v-model="formData.region" placeholder="请输入区域/位置" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="危害因素描述" prop="elementDescription">
+              <el-input v-model="formData.elementDescription" placeholder="请输入危害因素描述" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <!-- 第二行 -->
+          <el-col :span="12">
+            <el-form-item label="可能导致的后果" prop="maybeResult">
+              <el-input
+                v-model="formData.maybeResult"
+                placeholder="请输入可能导致的后果"
+                type="textarea"
+                :rows="1"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="风险评价可能性" prop="evalKn">
+              <el-input-number
+                v-model="formData.evalKn"
+                controls-position="right"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <!-- 第三行 -->
+          <el-col :span="12">
+            <el-form-item label="风险评价严重性" prop="evalYz">
+              <el-input-number
+                v-model="formData.evalYz"
+                controls-position="right"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="风险评价风险值" prop="evalFxz">
+              <el-input-number v-model="formData.evalFxz" disabled style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <!-- 第四行 -->
+          <el-col :span="12">
+            <el-form-item label="风险等级" prop="riskGrade">
+              <el-select
+                v-model="formData.riskGrade"
+                placeholder="请选择风险等级"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in getStrDictOptions(DICT_TYPE.DANGER_GRADE)"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="责任人" prop="charge">
+              <el-input v-model="formData.charge" placeholder="请输入责任人" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="备注" prop="remark">
+              <el-input
+                v-model="formData.remark"
+                type="textarea"
+                placeholder="请输入备注"
+                :rows="1"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- 控制措施单独一行(占满) -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="控制措施" prop="controlMethod">
+              <el-input
+                v-model="formData.controlMethod"
+                type="textarea"
+                :rows="4"
+                placeholder="请输入控制措施"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted } from 'vue'
+import { IotDangerApi } from '@/api/pms/qhse/index'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+
+// 查询参数
+const queryParams = reactive({
+  riskGrade: ''
+})
+
+// 表格数据
+const tableData = ref([])
+
+// 弹窗控制
+const dialogVisible = ref(false)
+const dialogTitle = ref('新增')
+const formData = reactive({
+  region: '', // 区域/位置
+  charge: '',
+  elementDescription: '', // 危害因素描述
+  maybeResult: '', // 可能导致的后果
+  evalKn: 1, // 可能性
+  evalYz: 1, // 严重性
+  evalFxz: 1, // 风险值(自动计算)
+  riskGrade: '一般风险', // 风险等级
+  controlMethod: '', // 控制措施
+  remark: '' // 备注
+})
+
+// 表单校验规则
+const rules = {
+  region: [{ required: true, message: '请输入区域/位置', trigger: 'blur' }],
+  charge: [{ required: true, message: '请输入责任人', trigger: 'blur' }],
+  elementDescription: [{ required: true, message: '请输入危害因素描述', trigger: 'blur' }],
+  maybeResult: [{ required: true, message: '请输入可能导致的后果', trigger: 'blur' }],
+  evalKn: [{ required: true, message: '请输入风险评价可能性', trigger: 'change' }],
+  evalYz: [{ required: true, message: '请输入风险评价严重性', trigger: 'change' }],
+  riskGrade: [{ required: true, message: '请选择风险等级', trigger: 'change' }],
+  controlMethod: [{ required: true, message: '请输入控制措施', trigger: 'blur' }]
+}
+
+watch(
+  () => [formData.evalKn, formData.evalYz],
+  ([kn, yz]) => {
+    if (kn && yz) {
+      formData.evalFxz = kn * yz
+    }
+  },
+  { immediate: true }
+)
+// 搜索
+const handleQuery = () => {
+  pagination.pageNo = 1 // 搜索后回到第一页
+  loadTableData()
+}
+
+const downloadFile = (response) => {
+  // 创建 blob 对象
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  // 获取文件名
+  let fileName = '危险源.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  // 创建下载链接
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  // 触发下载
+  document.body.appendChild(link)
+  link.click()
+
+  // 清理
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    // 调用导出接口
+    const response = await IotDangerApi.exportDanger(queryParams)
+
+    // 下载文件
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+  }
+}
+
+// 重置查询
+const resetQuery = () => {
+  queryParams.riskGrade = '' // 清空风险等级筛选
+  pagination.pageNo = 1 // 重置为第一页
+  loadTableData()
+}
+
+// 删除确认
+const deleteRow = async (row) => {
+  try {
+    await ElMessageBox.confirm(`确认删除 ${row.elementDescription} 吗?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+
+    await IotDangerApi.deleteDanger(row.id)
+    ElMessage.success('删除成功')
+    loadTableData() // 重新加载数据
+  } catch (error) {
+    // 用户取消或删除失败
+    if (error !== 'cancel') {
+      ElMessage.error('删除失败')
+    }
+  }
+}
+
+// 每页数量变化
+const handleSizeChange = (val) => {
+  pagination.pageSize = val
+  pagination.pageNo = 1 // 重置为第一页
+  loadTableData()
+}
+
+// 预先计算合并信息
+const spanArr = ref([])
+const pos = ref(0)
+
+// 计算合并信息
+const getSpanArr = (data) => {
+  spanArr.value = []
+  pos.value = 0
+
+  data.forEach((item, index) => {
+    if (index === 0) {
+      spanArr.value.push(1)
+      pos.value = 0
+    } else {
+      if (data[index].region === data[index - 1].region) {
+        spanArr.value[pos.value] += 1
+        spanArr.value.push(0)
+      } else {
+        spanArr.value.push(1)
+        pos.value = index
+      }
+    }
+  })
+}
+
+// 行合并方法
+const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
+  if (columnIndex === 0) {
+    const _row = spanArr.value[rowIndex]
+    const _col = _row > 0 ? 1 : 0
+    return {
+      rowspan: _row,
+      colspan: _col
+    }
+  }
+  return {
+    rowspan: 1,
+    colspan: 1
+  }
+}
+
+// 当前页变化
+const handleCurrentChange = (val) => {
+  pagination.pageNo = val
+  loadTableData()
+}
+
+// 打开表单
+const openForm = (type, row = null) => {
+  dialogTitle.value = type === 'create' ? '新增' : '编辑'
+  if (type === 'edit') {
+    Object.assign(formData, row)
+    // 计算风险值 R = L × S
+    formData.riskValue = formData.possibility * formData.severity
+  } else {
+    resetForm()
+  }
+  dialogVisible.value = true
+}
+
+// 重置表单
+const resetForm = () => {
+  Object.keys(formData).forEach((key) => {
+    formData[key] = ''
+  })
+}
+
+// 提交表单
+const formRef = ref(null)
+const submitForm = async () => {
+  await formRef.value.validate()
+  try {
+    const params = {
+      ...formData,
+      evalFxz: formData.evalFxz // 使用已计算的值
+    }
+
+    if (dialogTitle.value === '新增') {
+      await IotDangerApi.createDanger(params)
+      ElMessage.success('新增成功')
+    } else {
+      params.id = formData.id
+      await IotDangerApi.updateDanger(params)
+      ElMessage.success('修改成功')
+    }
+
+    loadTableData()
+    dialogVisible.value = false
+  } catch (error) {
+    ElMessage.error('提交失败')
+  }
+}
+
+// 加载数据
+const exportLoading = ref(false)
+let total = ref(0)
+const pagination = reactive({
+  pageNo: 1,
+  pageSize: 10
+})
+const loadTableData = async () => {
+  try {
+    const params = {
+      pageNo: pagination.pageNo,
+      pageSize: pagination.pageSize,
+      riskGrade: queryParams.riskGrade // 添加搜索参数
+    }
+    const res = await IotDangerApi.getDangerList(params)
+    tableData.value = res.list || []
+    total.value = res.total || 0
+
+    // // 按 region 排序(支持中文)
+    // tableData.value.sort((a, b) => {
+    //   return a.region.localeCompare(b.region, 'zh-CN')
+    // })
+
+    // 计算合并信息
+    getSpanArr(tableData.value)
+  } catch (error) {
+    console.error('加载失败:', error)
+  }
+}
+
+// 页面挂载后加载数据
+onMounted(() => {
+  loadTableData()
+})
+</script>
+
+<style scoped lang="scss">
+::v-deep .el-button {
+  border-radius: 0;
+}
+
+::v-deep .el-select__wrapper {
+  border-radius: 0 !important;
+  height: 26px;
+}
+.hazard-table-container {
+  margin: 20px;
+  margin-top: 10px;
+}
+
+.area-header {
+  font-weight: bold;
+  text-align: center;
+  padding: 12px 0;
+  background-color: #f5f7fa;
+  border-bottom: 1px solid #ddd;
+}
+
+.sub-row {
+  padding: 12px 0;
+  text-align: left;
+}
+
+.risk-evaluation {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  align-items: center;
+}
+
+.risk-item {
+  font-size: 12px;
+  padding: 4px 8px;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  color: #333;
+}
+
+.risk-level {
+  font-weight: bold;
+  color: #333;
+  padding: 6px 12px;
+  border-radius: 4px;
+}
+</style>

+ 545 - 0
src/views/pms/qhse/index.vue

@@ -0,0 +1,545 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1" v-if="treeShow">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="contentSpan" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+          <el-form-item label="计量器具名称" prop="measureName">
+            <el-input
+              v-model="queryParams.measureName"
+              placeholder="请输入计量器具名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleAdd" type="primary"
+              ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
+            >
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+            >
+            <el-button @click="handleExport" type="success" plain :loading="exportLoading"
+              ><Icon icon="ep:download" class="mr-5px" /> 导出Excel</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="t('monitor.serial')" width="70" align="center" fixed="left">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="名称" align="center" prop="measureName" fixed="left" />
+          <el-table-column
+            label="编码"
+            align="center"
+            prop="measureCode"
+            width="150"
+            fixed="left"
+          />
+          <el-table-column label="计量单位" align="center" prop="measureUnit" />
+
+          <el-table-column label="责任人" align="center" prop="dutyPerson" />
+          <el-table-column label="品牌" align="center" prop="brand" />
+          <el-table-column label="规格型号" align="center" prop="modelName" />
+          <el-table-column label="分类" align="center" prop="classify" width="150">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.MEASURE_TYPE" :value="scope.row.classify" />
+            </template>
+          </el-table-column>
+          <el-table-column label="采购日期" align="center" prop="buyDate">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.buyDate) }}
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="有效期" align="center" prop="validity">
+            <template #default="scope">
+              {{ formatDateCorrectly(scope.row.validity) }}
+            </template>
+          </el-table-column> -->
+
+          <el-table-column label="价格" align="center" prop="measurePrice">
+            <template #default="scope">
+              {{ scope.row.measurePrice }}
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center" prop="remark" />
+
+          <el-table-column
+            :label="t('devicePerson.operation')"
+            align="center"
+            fixed="right"
+            min-width="120px"
+          >
+            <template #default="scope">
+              <el-button link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
+              <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 新增/编辑台账对话框 -->
+  <el-dialog
+    :title="dialogTitle"
+    v-model="dialogVisible"
+    width="800px"
+    destroy-on-close
+    @close="closeDialog"
+  >
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计量器具名称" prop="measureName">
+            <el-input v-model="formData.measureName" placeholder="请输入计量器具名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="责任人" prop="dutyPerson">
+            <el-input v-model="formData.dutyPerson" placeholder="请输入责任人" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="部门" prop="deptId">
+            <el-tree-select
+              clearable
+              v-model="formData.deptId"
+              :data="deptList2"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择所在部门"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="品牌" prop="brand">
+            <el-input v-model="formData.brand" placeholder="请输入品牌" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="规格型号" prop="modelName">
+            <el-input v-model="formData.modelName" placeholder="请输入规格型号" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="单位" prop="measureUnit">
+            <el-input v-model="formData.measureUnit" placeholder="请输入单位" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="分类" prop="classify">
+            <el-select
+              v-model="formData.classify"
+              placeholder="请选择平台"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.MEASURE_TYPE)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="价格" prop="measurePrice">
+            <el-input-number
+              v-model="formData.measurePrice"
+              :precision="2"
+              :step="1"
+              placeholder="请输入价格"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="采购日期" prop="buyDate">
+            <el-date-picker
+              v-model="formData.buyDate"
+              type="date"
+              value-format="x"
+              placeholder="请选择采购日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" placeholder="请输入描述" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <!-- <el-col :span="12">
+          <el-form-item label="有效期" prop="validity">
+            <el-date-picker
+              v-model="formData.validity"
+              type="date"
+              value-format="x"
+              placeholder="请选择有效期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col> -->
+        <!-- <el-col :span="12">
+          <el-form-item label="证书编码" prop="measureCertNo">
+            <el-input v-model="formData.measureCertNo" placeholder="请输入证书编码" />
+          </el-form-item>
+        </el-col> -->
+      </el-row>
+
+      <!-- <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" placeholder="请输入描述" />
+          </el-form-item>
+        </el-col>
+      </el-row> -->
+    </el-form>
+
+    <template #footer>
+      <el-button @click="closeDialog">取 消</el-button>
+      <el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { IotInstrumentApi } from '@/api/pms/qhse/index'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { handleTree } from '@/utils/tree'
+import { defaultProps } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { ElMessageBox } from 'element-plus'
+const deptList = ref<Tree[]>([]) // 树形结构
+const deptList2 = ref<Tree[]>([]) // 树形结构
+import { formatDate } from '@/utils/formatTime'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+
+defineOptions({ name: 'IotQHSEMeasure' })
+
+const loading = ref(true) // 列表的加载中
+const formLoading = ref(false) // 表单加载中
+const submitLoading = ref(false) // 提交按钮加载中
+let exportLoading = ref(false)
+
+const { t } = useI18n()
+
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  deptId: undefined
+})
+const queryFormRef = ref(null) // 搜索的表单
+
+const contentSpan = ref(20)
+const treeShow = ref(true)
+
+// 对话框相关
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const isEdit = ref(false)
+
+// 表单相关
+const formRef = ref()
+const formData = ref({
+  measureName: '',
+  measureUnit: '',
+  dutyPerson: '',
+  brand: '',
+  modelName: '',
+  classify: '',
+  buyDate: null,
+  remark: '',
+  deptId: '',
+  measureCode: '',
+  // validity: null, // 有效期
+
+  measurePrice: 0, // 价格
+  measureCertNo: ''
+})
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+// 表单验证规则
+const formRules = {
+  measureName: [{ required: true, message: '计量器具名称不能为空', trigger: 'blur' }],
+  dutyPerson: [{ required: true, message: '责任人不能为空', trigger: 'blur' }],
+  classify: [{ required: true, message: '分类不能为空', trigger: 'blur' }],
+  deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
+  measureCertNo: [{ required: true, message: '证书编码不能为空', trigger: 'blur' }],
+  validity: [{ required: true, message: '有效期不能为空', trigger: 'blur' }]
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const downloadFile = (response: any) => {
+  // 创建 blob 对象
+  const blob = new Blob([response], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
+  })
+
+  // 获取文件名
+  let fileName = '计量器具台账.xlsx'
+  const disposition = response.headers ? response.headers['content-disposition'] : ''
+  if (disposition) {
+    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
+    const matches = filenameRegex.exec(disposition)
+    if (matches != null && matches[1]) {
+      fileName = matches[1].replace(/['"]/g, '')
+    }
+  }
+
+  // 创建下载链接
+  const url = window.URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.setAttribute('download', fileName)
+
+  // 触发下载
+  document.body.appendChild(link)
+  link.click()
+
+  // 清理
+  document.body.removeChild(link)
+  window.URL.revokeObjectURL(url)
+}
+
+const handleExport = async () => {
+  try {
+    exportLoading.value = true
+    // 调用导出接口
+    const response = await IotInstrumentApi.exportInstrument(queryParams)
+
+    // 下载文件
+    downloadFile(response)
+    exportLoading.value = false
+  } catch (error) {
+    ElMessage.error('导出失败,请重试')
+    console.error('导出错误:', error)
+  } finally {
+  }
+}
+
+/** 首页处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+// 显示新增对话框
+const handleAdd = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增台账'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 显示编辑对话框
+const handleEdit = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑台账'
+
+  formData.value = {
+    ...row,
+    // 确保日期字段正确处理
+    buyDate: row.buyDate ? ensureMillisecondTimestamp(row.buyDate) : null,
+    validity: row.validity ? ensureMillisecondTimestamp(row.validity) : null
+  }
+
+  dialogVisible.value = true
+}
+
+// 确保时间戳是毫秒级的
+const ensureMillisecondTimestamp = (timestamp) => {
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 秒级时间戳转为毫秒级
+    return time * 1000
+  }
+  return time
+}
+
+//删除成套
+const handleDelete = async (id: number) => {
+  ElMessageBox.confirm('确定要删除该台账吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await IotInstrumentApi.deleteInstrument(id)
+        ElMessage.success('删除成功')
+        getList()
+      } catch (error) {
+        console.error(error)
+      }
+    })
+    .catch(() => {
+      // 取消操作
+    })
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.value = {
+    measureName: '',
+    measureUnit: '',
+    dutyPerson: '',
+    brand: '',
+    modelName: '',
+    classify: '',
+    buyDate: null,
+    remark: '',
+    deptId: '',
+    measureCode: '',
+    // validity: null, // 有效期
+
+    measurePrice: 0, // 价格
+    measureCertNo: ''
+  }
+  formRef.value?.clearValidate()
+}
+
+// 关闭对话框
+const closeDialog = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+// 提交表单
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    submitLoading.value = true
+
+    // 准备提交数据
+    const submitData = {
+      ...formData.value,
+      // 确保日期字段以正确的格式提交
+      buyDate: formData.value.buyDate,
+      validity: formData.value.validity
+    }
+
+    if (isEdit.value) {
+      // 编辑
+      await IotInstrumentApi.updateInstrument(submitData)
+      ElMessage.success('编辑成功')
+    } else {
+      // 新增
+      await IotInstrumentApi.createInstrument(submitData)
+      ElMessage.success('新增成功')
+    }
+
+    dialogVisible.value = false
+    getList()
+  } catch (error) {
+    console.error(error)
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+onMounted(async () => {
+  getList()
+
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+::deep(.el-tree--highlight-current) {
+  height: 200px !important;
+}
+::deep(.el-transfer-panel__body) {
+  height: 700px !important;
+}
+</style>

+ 392 - 0
src/views/pms/qhse/iotmeasuredetect/IotMeasureDetectForm.vue

@@ -0,0 +1,392 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计量器具" prop="measureId">
+            <el-input
+              v-model="formData.measureName"
+              disabled
+              placeholder="计量器具"
+              style="width: 300px"
+            >
+              <template #append>
+                <el-link @click="selectMeasure" :underline="false">选择</el-link>
+              </template>
+            </el-input>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="证书编码" prop="measureCertNo">
+            <el-input v-model="formData.measureCertNo" placeholder="请输入证书编码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="检测/校准日期" prop="detectDate">
+            <el-date-picker
+              v-model="formData.detectDate"
+              type="date"
+              value-format="x"
+              placeholder="选择检测/校准日期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="检测/校准机构" prop="detectOrg">
+            <el-input v-model="formData.detectOrg" placeholder="请输入检测/校准机构" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="检测/校准标准" prop="detectStandard">
+            <el-input v-model="formData.detectStandard" placeholder="请输入检测/校准标准" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="检测/校准有效期" prop="validityPeriod">
+            <el-date-picker
+              v-model="formData.validityPeriod"
+              type="date"
+              value-format="x"
+              placeholder="选择检测/校准有效期"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="校准金额" prop="detectAmount">
+            <el-input v-model="formData.detectAmount" placeholder="请输入校准金额" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="部门" prop="deptId">
+            <el-tree-select
+              style="width: 100%"
+              clearable
+              v-model="formData.deptId"
+              :data="deptList2"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              filterable
+              placeholder="请选择所在部门"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="检测/校准内容" prop="detectContent">
+            <Editor v-model="formData.detectContent" height="150px" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+
+  <Dialog title="选择仪器" v-model="measureDialogVisible" width="60%">
+    <ContentWrap>
+      <!-- 搜索工作栏 -->
+      <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+        <el-form-item label="计量器具名称" prop="measureName">
+          <el-input
+            v-model="queryParams.measureName"
+            placeholder="请输入计量器具名称"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery"
+            ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+          >
+          <el-button @click="resetQuery"
+            ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+    <div class="pb-10">
+      <el-table
+        v-loading="loading"
+        :data="measureList"
+        :stripe="true"
+        :show-overflow-tooltip="true"
+        ref="measureTableRef"
+      >
+        <el-table-column width="50" align="center">
+          <template #default="scope">
+            <el-radio
+              :model-value="selectedMeasureId"
+              :label="scope.row.id"
+              @change="handleRadioChange(scope.row)"
+            >
+              &nbsp;
+            </el-radio>
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('monitor.serial')" width="70" align="center">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" prop="measureUnit" />
+        <el-table-column label="名称" align="center" prop="measureName" />
+        <el-table-column label="责任人" align="center" prop="dutyPerson" />
+        <el-table-column label="品牌" align="center" prop="brand" />
+        <el-table-column label="规格型号" align="center" prop="modelName" />
+        <el-table-column label="分类" align="center" prop="classify" />
+        <el-table-column label="采购日期" align="center" prop="buyDate">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.buyDate) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="有效期" align="center" prop="validity">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.validity) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="上次检验日期" align="center" prop="lastTime" min-width="150">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.lastTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="价格" align="center" prop="measurePrice">
+          <template #default="scope">
+            {{ scope.row.measurePrice }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+
+    <template #footer>
+      <el-button @click="confirmSelectMeasure" type="primary">确 定</el-button>
+      <el-button @click="measureDialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { IotMeasureDetectApi, IotMeasureDetectVO, IotInstrumentApi } from '@/api/pms/qhse/index'
+import { formatDate } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import { onMounted, ref } from 'vue'
+import * as DeptApi from '@/api/system/dept'
+
+/** 计量器具-检测校准明细 表单 */
+defineOptions({ name: 'IotMeasureDetectForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const deptList2 = ref<Tree[]>([]) // 树形结构
+import { defaultProps } from '@/utils/tree'
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  detectDate: undefined,
+  detectOrg: undefined,
+  detectContent: undefined,
+  detectStandard: undefined,
+  validityPeriod: undefined,
+  detectAmount: undefined,
+  deptId: undefined,
+  measureName: '',
+  measureId: '',
+  measureCertNo: ''
+})
+const formRules = reactive({
+  detectDate: [{ required: true, message: '检测/校准日期不能为空', trigger: 'blur' }],
+  detectOrg: [{ required: true, message: '检测/校准机构不能为空', trigger: 'blur' }],
+  detectContent: [{ required: true, message: '检测/校准内容不能为空', trigger: 'blur' }],
+  validityPeriod: [{ required: true, message: '检测/校准有效期不能为空', trigger: 'blur' }],
+  measureCertNo: [{ required: true, message: '证书编码不能为空', trigger: 'blur' }],
+  detectStandard: [{ required: true, message: '检测/校准标准不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const measureList = 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 IotMeasureDetectApi.getIotMeasureDetect(id)
+      formData.value.detectDate = Number(formData.value.detectDate)
+    } 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 IotMeasureDetectVO
+    if (formType.value === 'create') {
+      await IotMeasureDetectApi.createIotMeasureDetect(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotMeasureDetectApi.updateIotMeasureDetect(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 确保处理各种可能的时间戳格式
+  let time = Number(timestamp)
+
+  // 处理不同时间戳格式
+  if (time < 10000000000) {
+    time = time * 1000
+  }
+
+  // 检查是否为有效日期
+  const date = new Date(time)
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+
+  // 验证日期合理性(例如:不能是过于久远的日期)
+  const minValidYear = 1900
+  if (date.getFullYear() < minValidYear) {
+    console.warn('Invalid date detected:', timestamp)
+    return ''
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    detectDate: undefined,
+    detectOrg: undefined,
+    detectContent: undefined,
+    validityPeriod: undefined,
+    detectAmount: undefined,
+    deptId: undefined,
+    measureName: '',
+    measureId: '',
+    measureCertNo: '',
+    detectStandard: undefined
+  }
+  formRef.value?.resetFields()
+}
+
+/** 获取计量器具列表 */
+let loading = ref(false)
+let total = ref(0)
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  deptId: undefined
+})
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+const resetQuery = () => {
+  queryParams.measureName = undefined
+  queryParams.deptId = undefined
+  handleQuery()
+}
+// 仪器选择相关
+const measureDialogVisible = ref(false)
+const selectedMeasureId = ref<number | undefined>(undefined) // 当前选中的仪器ID
+const selectedMeasure = ref<any>(null) // 当前选中的仪器对象
+// 处理单选框变化
+const handleRadioChange = (row: any) => {
+  selectedMeasureId.value = row.id
+  selectedMeasure.value = row
+}
+const selectMeasure = () => {
+  measureDialogVisible.value = true
+  getList()
+}
+
+// 确认选择仪器
+const confirmSelectMeasure = () => {
+  if (!selectedMeasure.value) {
+    message.warning('请先选择一个计量器具')
+    return
+  }
+
+  // 将选中的仪器信息填入表单
+  formData.value.measureId = selectedMeasure.value.id
+  formData.value.measureName = selectedMeasure.value.measureName
+
+  // 关闭选择仪器对话框
+  measureDialogVisible.value = false
+}
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(queryParams)
+    measureList.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(async () => {
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>

+ 222 - 0
src/views/pms/qhse/iotmeasuredetect/index.vue

@@ -0,0 +1,222 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="检测/校准日期" prop="detectDate">
+        <el-date-picker
+          v-model="queryParams.detectDate"
+          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-200px"
+        />
+      </el-form-item>
+      <el-form-item label="检测/校准机构" prop="detectOrg">
+        <el-input
+          v-model="queryParams.detectOrg"
+          placeholder="请输入检测/校准机构"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="检测/校准有效期" prop="validityPeriod">
+        <el-date-picker
+          v-model="queryParams.validityPeriod"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择检测/校准有效期"
+          clearable
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="校准金额" prop="detectAmount">
+        <el-input
+          v-model="queryParams.detectAmount"
+          placeholder="请输入校准金额"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </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')">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+          <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="计量器具名称" align="center" prop="measureName" />
+      <el-table-column label="证书编码" align="center" prop="measureCertNo" />
+      <el-table-column label="检测/校准日期" align="center" prop="detectDate">
+        <template #default="scope">
+          <span>{{ formatDateCorrectly(scope.row.detectDate) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="检测/校准机构" align="center" prop="detectOrg" />
+      <el-table-column label="检测/校准标准" align="center" prop="detectStandard" />
+      <el-table-column label="检测/校准内容" align="center" prop="detectContent">
+        <template #default="scope">
+          <div v-html="scope.row.detectContent"></div>
+        </template>
+      </el-table-column>
+      <el-table-column label="检测/校准有效期" align="center" prop="validityPeriod" width="180px">
+        <template #default="scope">
+          <span>{{ formatDateCorrectly(scope.row.validityPeriod) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="校准金额" align="center" prop="detectAmount" />
+
+      <el-table-column label="部门名称" align="center" prop="deptName" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button link type="primary" @click="openForm('update', scope.row.id)">
+            编辑
+          </el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotMeasureDetectForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { IotMeasureDetectApi, IotMeasureDetectVO } from '@/api/pms/qhse/index'
+import IotMeasureDetectForm from './IotMeasureDetectForm.vue'
+import { formatDate } from '@/utils/formatTime'
+/** 计量器具-检测校准明细 列表 */
+defineOptions({ name: 'IotMeasureDetect' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotMeasureDetectVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureId: undefined,
+  detectDate: [],
+  detectOrg: undefined,
+  detectContent: undefined,
+  validityPeriod: undefined,
+  detectAmount: undefined,
+  createTime: [],
+  deptId: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureDetectApi.getIotMeasureDetectPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 确保处理各种可能的时间戳格式
+  let time = Number(timestamp)
+
+  // 处理不同时间戳格式
+  if (time < 10000000000) {
+    time = time * 1000
+  }
+
+  // 检查是否为有效日期
+  const date = new Date(time)
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+
+  // 验证日期合理性(例如:不能是过于久远的日期)
+  const minValidYear = 1900
+  if (date.getFullYear() < minValidYear) {
+    console.warn('Invalid date detected:', timestamp)
+    return ''
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+/** 重置按钮操作 */
+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 IotMeasureDetectApi.deleteIotMeasureDetect(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotMeasureDetectApi.exportIotMeasureDetect(queryParams)
+    download.excel(data, '计量器具-检测校准明细.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 349 - 0
src/views/pms/qhse/iotmeasurerecord/IotMeasureRecordForm.vue

@@ -0,0 +1,349 @@
+<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="计量器具" prop="measureId">
+        <el-input v-model="formData.measureName" disabled placeholder="选择计量器具">
+          <template #append>
+            <el-link @click="selectMeasure" :underline="false">选择计量器具</el-link>
+          </template>
+        </el-input>
+      </el-form-item>
+      <el-form-item label="使用日期" prop="useDate">
+        <el-date-picker
+          v-model="formData.useDate"
+          type="date"
+          value-format="x"
+          placeholder="选择使用日期"
+        />
+      </el-form-item>
+      <el-form-item label="使用原因" prop="useReason">
+        <el-input v-model="formData.useReason" placeholder="请输入使用原因" />
+      </el-form-item>
+      <el-form-item label="计量项目" prop="measureProject">
+        <el-input v-model="formData.measureProject" placeholder="请输入计量项目" />
+      </el-form-item>
+      <el-form-item label="使用人" prop="usePerson">
+        <el-input v-model="formData.usePerson" placeholder="请输入使用人" />
+      </el-form-item>
+
+      <el-form-item label="部门" prop="deptId">
+        <el-tree-select
+          clearable
+          v-model="formData.deptId"
+          :data="deptList2"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          filterable
+          placeholder="请选择所在部门"
+        />
+      </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>
+
+  <Dialog title="选择仪器" v-model="measureDialogVisible" width="60%">
+    <ContentWrap>
+      <!-- 搜索工作栏 -->
+      <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+        <el-form-item label="计量器具名称" prop="measureName">
+          <el-input
+            v-model="queryParams.measureName"
+            placeholder="请输入计量器具名称"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-200px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery"
+            ><Icon icon="ep:search" class="mr-5px" /> {{ t('devicePerson.search') }}</el-button
+          >
+          <el-button @click="resetQuery"
+            ><Icon icon="ep:refresh" class="mr-5px" /> {{ t('devicePerson.reset') }}</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+    <div class="pb-10">
+      <el-table
+        v-loading="loading"
+        :data="measureList"
+        :stripe="true"
+        :show-overflow-tooltip="true"
+        ref="measureTableRef"
+      >
+        <el-table-column width="50" align="center">
+          <template #default="scope">
+            <el-radio
+              :model-value="selectedMeasureId"
+              :label="scope.row.id"
+              @change="handleRadioChange(scope.row)"
+            >
+              &nbsp;
+            </el-radio>
+          </template>
+        </el-table-column>
+        <el-table-column :label="t('monitor.serial')" width="70" align="center">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" align="center" prop="measureUnit" />
+        <el-table-column label="名称" align="center" prop="measureName" />
+        <el-table-column label="责任人" align="center" prop="dutyPerson" />
+        <el-table-column label="品牌" align="center" prop="brand" />
+        <el-table-column label="规格型号" align="center" prop="modelName" />
+        <el-table-column label="分类" align="center" prop="classify" />
+        <el-table-column label="采购日期" align="center" prop="buyDate">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.buyDate) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="有效期" align="center" prop="validity">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.validity) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="上次检验日期" align="center" prop="lastTime" min-width="150">
+          <template #default="scope">
+            {{ formatDateCorrectly(scope.row.lastTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="价格" align="center" prop="measurePrice">
+          <template #default="scope">
+            {{ scope.row.measurePrice }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+
+    <template #footer>
+      <el-button @click="confirmSelectMeasure" type="primary">确 定</el-button>
+      <el-button @click="measureDialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { IotMeasureRecordApi, IotMeasureRecordVO, IotInstrumentApi } from '@/api/pms/qhse/index'
+import { onMounted, ref } from 'vue'
+import { formatDate } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import { defaultProps } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+
+/** 计量器具-使用记录 表单 */
+defineOptions({ name: 'IotMeasureRecordForm' })
+
+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({
+  measureId: undefined,
+  measureName: '', // 添加计量器具名称字段
+  useDate: undefined,
+  useReason: undefined,
+  measureProject: undefined,
+  usePerson: undefined,
+  userId: undefined,
+  deptId: undefined
+})
+const formRules = reactive({
+  measureId: [{ required: true, message: '计量器具不能为空', trigger: 'blur' }],
+  useDate: [{ required: true, message: '使用日期不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+// 仪器选择相关
+const measureDialogVisible = ref(false)
+
+const selectedMeasureId = ref<number | undefined>(undefined) // 当前选中的仪器ID
+const selectedMeasure = ref<any>(null) // 当前选中的仪器对象
+const deptList2 = ref<Tree[]>([]) // 树形结构
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 打开弹窗 */
+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 {
+      const recordData = await IotMeasureRecordApi.getIotMeasureRecord(id)
+      formData.value = recordData
+
+      // 特别处理 useDate 字段
+      if (recordData.useDate) {
+        // 确保 useDate 是正确的毫秒时间戳
+        const date = new Date(recordData.useDate)
+        if (!isNaN(date.getTime()) && date.getFullYear() >= 1900) {
+          formData.value.useDate = recordData.useDate
+        } else {
+          formData.value.useDate = undefined
+        }
+      }
+
+      // 设置选中的仪器ID
+      selectedMeasureId.value = formData.value.measureId
+    } 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 IotMeasureRecordVO
+    if (formType.value === 'create') {
+      await IotMeasureRecordApi.createIotMeasureRecord(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await IotMeasureRecordApi.updateIotMeasureRecord(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    measureId: undefined,
+    measureName: '',
+    useDate: undefined,
+    useReason: undefined,
+    measureProject: undefined,
+    usePerson: undefined,
+    userId: undefined,
+    deptId: undefined
+  }
+  formRef.value?.resetFields()
+  selectedMeasureId.value = undefined
+  selectedMeasure.value = null
+}
+
+const resetQuery = () => {
+  queryParams.measureName = undefined
+  queryParams.deptId = undefined
+  handleQuery()
+}
+
+const measureList = ref([])
+let total = ref(0)
+const loading = ref(true) // 列表的加载中
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  deptId: undefined
+})
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotInstrumentApi.getInstrumentList(queryParams)
+    measureList.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const selectMeasure = () => {
+  measureDialogVisible.value = true
+  getList()
+}
+
+// 处理单选框变化
+const handleRadioChange = (row: any) => {
+  selectedMeasureId.value = row.id
+  selectedMeasure.value = row
+}
+
+// 确认选择仪器
+const confirmSelectMeasure = () => {
+  if (!selectedMeasure.value) {
+    message.warning('请先选择一个计量器具')
+    return
+  }
+
+  // 将选中的仪器信息填入表单
+  formData.value.measureId = selectedMeasure.value.id
+  formData.value.measureName = selectedMeasure.value.measureName
+
+  // 关闭选择仪器对话框
+  measureDialogVisible.value = false
+}
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 确保处理各种可能的时间戳格式
+  let time = Number(timestamp)
+
+  // 处理不同时间戳格式
+  if (time < 10000000000) {
+    time = time * 1000
+  }
+
+  // 检查是否为有效日期
+  const date = new Date(time)
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+
+  // 验证日期合理性(例如:不能是过于久远的日期)
+  const minValidYear = 1900
+  if (date.getFullYear() < minValidYear) {
+    console.warn('Invalid date detected:', timestamp)
+    return ''
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+onMounted(async () => {
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>

+ 219 - 0
src/views/pms/qhse/iotmeasurerecord/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="计量器具名称" prop="measureName">
+        <el-input
+          v-model="queryParams.measureName"
+          placeholder="请输入计量器具名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="使用日期" prop="useDate">
+        <el-date-picker
+          v-model="queryParams.useDate"
+          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 label="计量项目" prop="measureProject">
+        <el-input
+          v-model="queryParams.measureProject"
+          placeholder="请输入计量项目"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </el-form-item>
+      <el-form-item label="使用人" prop="usePerson">
+        <el-input
+          v-model="queryParams.usePerson"
+          placeholder="请输入使用人"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-150px"
+        />
+      </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 label="部门名称" prop="deptId">
+        <el-input
+          v-model="queryParams.deptId"
+          placeholder="请输入部门名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </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')">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button type="success" plain @click="handleExport" :loading="exportLoading">
+          <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="计量器具名称" align="center" prop="measureName" />
+      <el-table-column label="使用日期" align="center" prop="useDate">
+        <template #default="scope">
+          {{ formatDateCorrectly(scope.row.useDate) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="使用原因" align="center" prop="useReason" />
+      <el-table-column label="计量项目" align="center" prop="measureProject" />
+      <el-table-column label="使用人" align="center" prop="usePerson" />
+
+      <el-table-column label="部门名称" align="center" prop="deptName" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button link type="primary" @click="openForm('update', scope.row.id)">
+            编辑
+          </el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotMeasureRecordForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import download from '@/utils/download'
+import { IotMeasureRecordApi, IotMeasureRecordVO } from '@/api/pms/qhse/index'
+import IotMeasureRecordForm from './IotMeasureRecordForm.vue'
+import { formatDate } from '@/utils/formatTime'
+
+/** 计量器具-使用记录 列表 */
+defineOptions({ name: 'IotMeasureRecord' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<IotMeasureRecordVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  measureName: undefined,
+  useDate: [],
+  useReason: undefined,
+  measureProject: undefined,
+  usePerson: undefined,
+  createTime: [],
+  deptId: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+// 正确格式化日期的函数
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  // 如果是秒级时间戳,转换为毫秒级
+  let time = Number(timestamp)
+  if (time < 10000000000) {
+    // 小于这个数通常表示秒级时间戳
+    time = time * 1000
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotMeasureRecordApi.getIotMeasureRecordPage(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 IotMeasureRecordApi.deleteIotMeasureRecord(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await IotMeasureRecordApi.exportIotMeasureRecord(queryParams)
+    download.excel(data, '计量器具-使用记录.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  getList()
+})
+</script>

+ 47 - 0
src/views/pms/stat/rdkbfr.vue

@@ -0,0 +1,47 @@
+<template>
+  <div class="full-screen-container" v-loading="loading">
+    <iframe :src="url" frameborder="0" class="full-screen-iframe" allowfullscreen></iframe>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue'
+import { IotStatApi } from '@/api/pms/stat'
+
+let url = ref('')
+const loading = ref(true) // 加载中
+
+onMounted(async () => {
+  const res = await IotStatApi.getSsoToken()
+
+  url.value =
+    'https://report.deepoil.cc/webroot/decision/v10/entry/access/a12df128-c84f-44be-a55d-bababbf4a132?preview=true&page_number=1&ssoToken=' +
+    res
+
+  loading.value = false
+})
+</script>
+
+<style scoped>
+.full-screen-container {
+  width: calc(100% - 0px); /* 减去侧边栏宽度 */
+  height: calc(100vh - 90px); /* 减去顶部导航高度 */
+  overflow: hidden;
+  position: absolute;
+  top: 0px; /* 顶部导航的高度 */
+  left: 0px; /* 侧边栏的宽度 */
+  right: 0;
+  bottom: 0;
+  z-index: 1;
+}
+
+.full-screen-iframe {
+  width: 100%;
+  height: 100%;
+  border: none;
+  margin: 0;
+  padding: 0;
+  overflow: auto;
+  display: block;
+}
+</style>

+ 5 - 5
src/views/report-statistics/rd-daily-report.vue

@@ -71,11 +71,11 @@ const columns = ref<Column[]>([
     fixed: 'left',
     'min-width': '120px'
   },
-  {
-    label: '时间节点',
-    prop: 'timeRange',
-    'min-width': '120px'
-  },
+  // {
+  //   label: '时间节点',
+  //   prop: 'timeRange',
+  //   'min-width': '120px'
+  // },
   {
     label: '施工状态',
     prop: 'rdStatus',

Некоторые файлы не были показаны из-за большого количества измененных файлов