Forráskód Böngészése

Merge remote-tracking branch 'origin/master'

lipenghui 2 napja
szülő
commit
1699e1927f
37 módosított fájl, 10271 hozzáadás és 2630 törlés
  1. 1 1
      .env.local
  2. 1 4
      index.html
  3. 6 0
      public/js/base64.min.js
  4. 34 0
      src/api/pms/device/index.ts
  5. 4 0
      src/api/pms/iotrhdailyreport/index.ts
  6. 4 0
      src/api/pms/iotrydailyreport/index.ts
  7. 59 0
      src/components/DailyTableColumn/index.vue
  8. 161 0
      src/components/DeptTreeSelect/index.vue
  9. 38 7
      src/layout/components/Message/src/Message.vue
  10. 575 575
      src/locales/en.ts
  11. 478 478
      src/locales/ru.ts
  12. 577 577
      src/locales/zh-CN.ts
  13. 49 3
      src/views/Home/Index.vue
  14. 47 11
      src/views/pms/device/DeviceInfo.vue
  15. 114 0
      src/views/pms/device/completeSet/AssociationDevices.vue
  16. 528 0
      src/views/pms/device/completeSet/DeviceCompleteSet.vue
  17. 66 7
      src/views/pms/device/monitor/TdDeviceInfo.vue
  18. 45 1
      src/views/pms/dingding.vue
  19. 230 142
      src/views/pms/iotmainworkorder/index.vue
  20. 319 260
      src/views/pms/iotprojectinfo/index.vue
  21. 379 355
      src/views/pms/iotprojecttask/IotProjectTaskForm.vue
  22. 2 2
      src/views/pms/iotrddailyreport/fillDailyReport.vue
  23. 2 2
      src/views/pms/iotrddailyreport/index.vue
  24. 917 0
      src/views/pms/iotrhdailyreport/approval.vue
  25. 892 0
      src/views/pms/iotrhdailyreport/fill.vue
  26. 46 29
      src/views/pms/iotrhdailyreport/index.vue
  27. 44 15
      src/views/pms/iotrhdailyreport/summary.vue
  28. 1258 0
      src/views/pms/iotrydailyreport/approval.vue
  29. 1194 0
      src/views/pms/iotrydailyreport/fill.vue
  30. 48 30
      src/views/pms/iotrydailyreport/index.vue
  31. 38 83
      src/views/pms/iotrydailyreport/summary.vue
  32. 1018 0
      src/views/pms/iotrydailyreport/xapproval.vue
  33. 994 0
      src/views/pms/iotrydailyreport/xfill.vue
  34. 36 18
      src/views/pms/iotrydailyreport/xjindex.vue
  35. 40 12
      src/views/pms/iotrydailyreport/xsummary.vue
  36. 26 18
      src/views/pms/maintenance/index.vue
  37. 1 0
      types/global.d.ts

+ 1 - 1
.env.local

@@ -3,7 +3,7 @@ NODE_ENV=development
 
 VITE_DEV=true
 
-# 请求路径
+# 请求路径  http://192.168.188.149:48080  https://iot.deepoil.cc
 VITE_BASE_URL='https://iot.deepoil.cc'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务

+ 1 - 4
index.html

@@ -3,10 +3,7 @@
   <head>
     <script src="https://g.alicdn.com/code/npm/@ali/dingtalk-h5-remote-debug/0.1.3/index.js"></script>
     <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
-    <script
-      type="text/javascript"
-      src="https://cdn.jsdelivr.net/npm/js-base64@3.6.0/base64.min.js"
-    ></script>
+    <script async type="text/javascript" src="/js/base64.min.js"></script>
     <script src="https://api.map.baidu.com/api?v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0"></script>
     <meta charset="UTF-8" />
     <link rel="icon" href="/favicon.ico" />

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 6 - 0
public/js/base64.min.js


+ 34 - 0
src/api/pms/device/index.ts

@@ -155,5 +155,39 @@ export const IotDeviceApi = {
   },
   getIotDeviceTdPage: async (params: any) => {
     return await request.get({ url: `/rq/iot-device/td/page`, params })
+  },
+
+  // 设备成套接口
+
+  // 查看详情时获取成套设备列表
+  getIotDeviceSets: async (params: any) => {
+    return await request.get({ url: `/rq/iot-device-group-detail/page`, params })
+  },
+
+  // 新增时根据部门id获取设备列表
+  getIotDeviceSetOptions: async (id: any) => {
+    return await request.get({ url: `/rq/iot-device/dept/${id}` })
+  },
+
+  // 获取成套列表
+  getIotDeviceSetList: async (params: any) => {
+    return await request.get({ url: `/rq/iot-device-group/page`, params })
+  },
+  // 添加成套
+  createIotDeviceSet: async (data: any) => {
+    return await request.post({ url: `/rq/iot-device-group/create`, data })
+  },
+  // 编辑成套
+  updateIotDeviceSet: async (data: any) => {
+    return await request.put({ url: `/rq/iot-device-group/update`, data })
+  },
+  // 删除成套
+  deleteIotDeviceSet: async (id: number) => {
+    return await request.delete({ url: `/rq/iot-device-group/delete?id=` + id })
+  },
+
+  // 获取关联设备
+  getIotDeviceSetRelation: async (params) => {
+    return await request.get({ url: `/rq/iot-device-group/device/group`, params })
   }
 }

+ 4 - 0
src/api/pms/iotrhdailyreport/index.ts

@@ -93,6 +93,10 @@ export const IotRhDailyReportApi = {
     return await request.put({ url: `/pms/iot-rh-daily-report/update`, data })
   },
 
+  approvalIotRhDailyReport: async (data: { id: number; auditStatus: 20 | 30; opinion: string }) => {
+    return await request.put({ url: `/pms/iot-rh-daily-report/approval`, data })
+  },
+
   // 删除瑞恒日报
   deleteIotRhDailyReport: async (id: number) => {
     return await request.delete({ url: `/pms/iot-rh-daily-report/delete?id=` + id })

+ 4 - 0
src/api/pms/iotrydailyreport/index.ts

@@ -111,5 +111,9 @@ export const IotRyDailyReportApi = {
   // 导出瑞鹰日报 Excel
   exportIotRyDailyReport: async (params) => {
     return await request.download({ url: `/pms/iot-ry-daily-report/export-excel`, params })
+  },
+
+  approvalIotRyDailyReport: async (data: { id: number; auditStatus: 20 | 30; opinion: string }) => {
+    return await request.put({ url: `/pms/iot-ry-daily-report/approval`, data })
   }
 }

+ 59 - 0
src/components/DailyTableColumn/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <template v-for="(col, index) in columns" :key="index">
+    <el-table-column v-if="col.children" :label="col.label" align="center">
+      <template v-for="(child, childIndex) in col.children" :key="childIndex">
+        <el-table-column v-if="child.isTag" v-bind="child" align="center" resizable>
+          <template #default="{ row }">
+            <dict-tag v-if="child.dictType" :type="child.dictType!" :value="row[child.prop!]" />
+            <el-tag :type="tagType(row[child.prop!])" v-else>{{
+              child.formatter?.(row) || ''
+            }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column v-else v-bind="child" align="center" resizable />
+      </template>
+    </el-table-column>
+    <el-table-column v-else-if="col.isTag" v-bind="col" align="center" resizable>
+      <template #default="{ row }">
+        <dict-tag v-if="col.dictType" :type="col.dictType!" :value="row[col.prop!]" />
+        <el-tag :type="tagType(row[col.prop!])" v-else>{{ col.formatter?.(row) || '' }}</el-tag>
+      </template>
+    </el-table-column>
+    <el-table-column v-else v-bind="col" align="center" resizable />
+  </template>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'DailyTableColumn'
+})
+
+interface ColumnProps {
+  label: string
+  prop?: string
+  children?: ColumnProps[]
+  isTag?: boolean
+  dictType?: string
+  formatter?: (row: any) => any
+  [key: string]: any
+}
+
+const props = defineProps<{
+  columns: ColumnProps[]
+}>()
+
+const { columns } = toRefs(props)
+
+const tagType = (status: number) => {
+  switch (status) {
+    case 0:
+      return 'info'
+    case 10:
+      return 'primary'
+    case 20:
+      return 'success'
+    case 30:
+      return 'danger'
+  }
+}
+</script>

+ 161 - 0
src/components/DeptTreeSelect/index.vue

@@ -0,0 +1,161 @@
+<script lang="ts" setup>
+import { defaultProps, handleTree } from '@/utils/tree'
+import { ElTree } from 'element-plus'
+import * as DeptApi from '@/api/system/dept'
+import { Search } from '@element-plus/icons-vue'
+
+const props = defineProps({
+  deptId: {
+    type: Number,
+    required: true
+  },
+  modelValue: {
+    type: Number,
+    default: undefined
+  },
+  topId: {
+    type: Number,
+    required: true
+  }
+})
+
+const emits = defineEmits(['update:modelValue', 'node-click'])
+
+const deptName = ref('')
+
+const deptList = ref<Tree[]>([])
+
+const treeRef = ref<InstanceType<typeof ElTree>>()
+
+const expandedKeys = ref<number[]>([])
+
+const sortTreeBySort = (treeNodes: Tree[]) => {
+  if (!treeNodes || !Array.isArray(treeNodes)) return treeNodes
+  const sortedNodes = [...treeNodes].sort((a, b) => {
+    const sortA = a.sort != null ? a.sort : 999999
+    const sortB = b.sort != null ? b.sort : 999999
+    return sortA - sortB
+  })
+
+  sortedNodes.forEach((node) => {
+    if (node.children && Array.isArray(node.children)) {
+      node.children = sortTreeBySort(node.children)
+    }
+  })
+  return sortedNodes
+}
+
+const loadTree = async () => {
+  try {
+    let id = props.deptId
+
+    if (id !== props.topId) {
+      const depts = await DeptApi.specifiedSimpleDepts(props.topId)
+
+      const self = depts.find((item) => item.id === props.deptId)
+
+      if (depts.length && !self) {
+        id = props.topId
+      }
+    }
+
+    emits('update:modelValue', id)
+
+    const res = await DeptApi.specifiedSimpleDepts(id)
+    deptList.value = sortTreeBySort(handleTree(res))
+
+    // 加载完成后,如果有选中值,尝试高亮并展开
+    nextTick(() => {
+      if (props.modelValue && treeRef.value) {
+        treeRef.value.setCurrentKey(props.modelValue)
+        expandedKeys.value = [props.modelValue] // 默认展开选中的节点
+      } else if (deptList.value.length > 0) {
+        // 默认展开第一级
+        expandedKeys.value = deptList.value.map((item) => item.id)
+      }
+    })
+  } catch (error) {
+    console.error('加载部门树失败:', error)
+  }
+}
+
+const handleNodeClick = (data: Tree) => {
+  // 1. 更新 v-model
+  emits('update:modelValue', data.id)
+  // 2. 抛出点击事件供父组件其他用途
+  emits('node-click', data)
+}
+
+/** 筛选节点逻辑 */
+const filterNode = (value: string, data: Tree) => {
+  if (!value) return true
+  return data.name.includes(value)
+}
+
+/** 监听输入框进行过滤 */
+watch(deptName, (val) => {
+  treeRef.value?.filter(val)
+})
+
+watch(
+  () => props.deptId,
+  (newVal, oldVal) => {
+    if (newVal !== oldVal) {
+      loadTree()
+    }
+  }
+)
+
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (newVal && treeRef.value) {
+      // 设置高亮
+      treeRef.value.setCurrentKey(newVal)
+      // 自动展开该节点 (将新ID加入展开数组)
+      if (!expandedKeys.value.includes(newVal)) {
+        expandedKeys.value.push(newVal)
+      }
+    }
+  },
+  { immediate: true }
+)
+
+/** 初始化 */
+onMounted(() => {
+  console.log('props :>> ', props)
+  loadTree()
+})
+</script>
+
+<template>
+  <div class="gap-4 flex flex-col h-full">
+    <h1 class="text-lg font-medium">部门</h1>
+    <el-input
+      v-model="deptName"
+      size="default"
+      placeholder="请输入部门名称"
+      clearable
+      :prefix-icon="Search"
+    />
+    <div class="flex-1 relative">
+      <el-auto-resizer class="absolute">
+        <template #default="{ height }">
+          <el-scrollbar :style="{ height: `${height}px` }">
+            <el-tree
+              ref="treeRef"
+              :data="deptList"
+              :props="defaultProps"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              node-key="id"
+              highlight-current
+              :default-expanded-keys="expandedKeys"
+              @node-click="handleNodeClick"
+            />
+          </el-scrollbar>
+        </template>
+      </el-auto-resizer>
+    </div>
+  </div>
+</template>

+ 38 - 7
src/layout/components/Message/src/Message.vue

@@ -31,11 +31,12 @@ const goMyList = () => {
     name: 'MyNotifyMessage'
   })
 }
-const routerDetail = (item) =>{
+const routerDetail = (item) => {
+  console.log('item :>> ', item)
   let id = item.businessId
   if (item.businessType === 'generateInspect') {
-    push({ name:'InspectOrderWrite', params:{id} })
-  }else if(item.businessType === 'failureReport') {
+    push({ name: 'InspectOrderWrite', params: { id } })
+  } else if (item.businessType === 'failureReport') {
     push({
       name: 'BpmProcessInstanceDetail',
       query: {
@@ -43,7 +44,7 @@ const routerDetail = (item) =>{
       }
     })
   } else if (item.businessType === 'generateMaintain') {
-    push({ name: 'MaintainEdit', params: {id } })
+    push({ name: 'MaintainEdit', params: { id } })
   } else if (item.businessType === 'maintainOut') {
     push({
       name: 'BpmProcessInstanceDetail',
@@ -73,8 +74,38 @@ const routerDetail = (item) =>{
     })
   } else if (item.businessType === 'generateOperation') {
     const param = item.templateParams
-    id = param.deptId+','+param.userId+','+param.createTime+','+param.businessId+','+param.orderStatus;
-    push({ name: 'FillOrderInfo',params:{id}})
+    id =
+      param.deptId +
+      ',' +
+      param.userId +
+      ',' +
+      param.createTime +
+      ',' +
+      param.businessId +
+      ',' +
+      param.orderStatus
+    push({ name: 'FillOrderInfo', params: { id } })
+  } else if (item.businessType === 'rhDailyReport') {
+    push({ path: '/iotdayilyreport/IotRhDailyReport/fill', query: { id: id } })
+  } else if (item.businessType === 'rhReportApproval') {
+    push({
+      path: '/iotdayilyreport/IotRhDailyReport/approval',
+      query: { id: id }
+    })
+  } else if (item.businessType === 'ryDailyReport') {
+    push({ path: '/iotdayilyreport/IotRyDailyReport/fill', query: { id: id } })
+  } else if (item.businessType === 'ryReportApproval') {
+    push({
+      path: '/iotdayilyreport/IotRyDailyReport/approval',
+      query: { id: id }
+    })
+  } else if (item.businessType === 'ryXjDailyReport') {
+    push({ path: '/iotdayilyreport/IotRyXjDailyReport/fill', query: { id: id } })
+  } else if (item.businessType === 'ryXjReportApproval') {
+    push({
+      path: '/iotdayilyreport/IotRyXjDailyReport/approval',
+      query: { id: id }
+    })
   }
 }
 // ========== 初始化 =========
@@ -177,7 +208,7 @@ onMounted(() => {
   .message-item:hover {
     transform: scale(0.95);
     background-color: #dcf8e4;
-    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
   }
 }
 </style>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 575 - 575
src/locales/en.ts


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 478 - 478
src/locales/ru.ts


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 577 - 577
src/locales/zh-CN.ts


+ 49 - 3
src/views/Home/Index.vue

@@ -367,7 +367,31 @@ const formatDate = (date): string => {
 const initMyoption = async () => {
   // 只在初始化时设置默认值,不覆盖已有值
   if (!completeRateParams.createTime || completeRateParams.createTime.length === 0) {
-    completeRateParams.createTime = [formatDate(start), formatDate(end)]
+    const start = new Date()
+    start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) // 近一周
+
+    const end = new Date()
+
+    // 将开始时间设置为当天的 00:00:00
+    const startTime = new Date(start)
+    startTime.setHours(0, 0, 0, 0)
+
+    // 将结束时间设置为当天的 23:59:59
+    const endTime = new Date(end)
+    endTime.setHours(23, 59, 59, 999)
+
+    // 格式化为 YYYY-MM-DD HH:mm:ss
+    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}`
+    }
+
+    completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
   }
 
   const res = await IotStatApi.getCompleteRate(completeRateParams)
@@ -386,8 +410,30 @@ const initMyoption = async () => {
 }
 
 const handleDateChange = (val) => {
-  console.log('handleDateChange', val)
-  completeRateParams.createTime = [val[0], val[1]]
+  if (!val || val.length !== 2) return
+
+  const [start, end] = val
+
+  // 将开始时间设置为当天的 00:00:00
+  const startTime = new Date(start)
+  startTime.setHours(0, 0, 0, 0)
+
+  // 将结束时间设置为当天的 23:59:59
+  const endTime = new Date(end)
+  endTime.setHours(23, 59, 59, 999)
+
+  // 格式化为 YYYY-MM-DD HH:mm:ss
+  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}`
+  }
+
+  completeRateParams.createTime = [formatDate(startTime), formatDate(endTime)]
   initMyoption()
 }
 

+ 47 - 11
src/views/pms/device/DeviceInfo.vue

@@ -1,7 +1,6 @@
 <template>
   <ContentWrap>
     <div style="display: flex; flex-direction: row; height: 12em; margin-top: 2px">
-
       <div style="flex: 1; height: 12em; margin-left: 20px">
         <el-image
           :key="index"
@@ -147,20 +146,46 @@
         </el-form>
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.fileLibrary')" name="file">
-<!--        <DeviceUpload ref="fileRef" v-if="loadedTabs.includes('1')" />-->
-        <DeviceFile ref="fileRef" :deviceId="id" :deviceName="formData.deviceName"  v-if="loadedTabs.includes('1')" />
+        <!--        <DeviceUpload ref="fileRef" v-if="loadedTabs.includes('1')" />-->
+        <DeviceFile
+          ref="fileRef"
+          :deviceId="id"
+          :deviceName="formData.deviceName"
+          v-if="loadedTabs.includes('1')"
+        />
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.deviceBOM')" name="bom">
-        <BomList ref="bomRef" v-model:activeName="activeName" :deviceId="id" :deviceCategoryName="formData.assetClassName" v-if="loadedTabs.includes('2')" />
+        <BomList
+          ref="bomRef"
+          v-model:activeName="activeName"
+          :deviceId="id"
+          :deviceCategoryName="formData.assetClassName"
+          v-if="loadedTabs.includes('2')"
+        />
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.operationRecords')" name="record">
-        <RecordList ref="recordRef" v-model:activeName="activeName" :deviceId="id" v-if="loadedTabs.includes('3')" />
+        <RecordList
+          ref="recordRef"
+          v-model:activeName="activeName"
+          :deviceId="id"
+          v-if="loadedTabs.includes('3')"
+        />
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.faultRecords')" name="failure">
-        <FailureList ref="failureRef" v-model:activeName="activeName" :deviceId="id" v-if="loadedTabs.includes('4')" />
+        <FailureList
+          ref="failureRef"
+          v-model:activeName="activeName"
+          :deviceId="id"
+          v-if="loadedTabs.includes('4')"
+        />
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.repairRecords')" name="maintain">
-        <MaintainList ref="maintainRef" v-model:activeName="activeName" :deviceId="id" v-if="loadedTabs.includes('5')" />
+        <MaintainList
+          ref="maintainRef"
+          v-model:activeName="activeName"
+          :deviceId="id"
+          v-if="loadedTabs.includes('5')"
+        />
       </el-tab-pane>
       <el-tab-pane :label="t('deviceInfo.maintenanceRecords')" name="maintenance">
         <MaintenanceList
@@ -202,6 +227,16 @@
           v-if="loadedTabs.includes('10')"
         />
       </el-tab-pane>
+
+      <!-- 关联设备 -->
+      <el-tab-pane :label="t('deviceInfo.associationDevice')" name="association">
+        <AssociationDevices
+          ref="personRef"
+          v-model:activeName="activeName"
+          :deviceId="id"
+          v-if="loadedTabs.includes('11')"
+        />
+      </el-tab-pane>
     </el-tabs>
   </ContentWrap>
 </template>
@@ -218,11 +253,12 @@ import MaintainList from '@/views/pms/device/MaintainList.vue'
 import InspectList from '@/views/pms/device/InspectList.vue'
 import MaintenanceList from '@/views/pms/device/maintenance/MaintenanceList.vue'
 import AllotLogList from '@/views/pms/device/allotlog/AllotLogList.vue'
-import DeviceStatusLogList from "@/views/pms/device/statuslog/DeviceStatusLogList.vue";
+import DeviceStatusLogList from '@/views/pms/device/statuslog/DeviceStatusLogList.vue'
 import PersonList from '@/views/pms/device/personlog/PersonList.vue'
 import RecordList from '@/views/pms/device/record/RecordList.vue'
+import AssociationDevices from '@/views/pms/device/completeSet/AssociationDevices.vue'
 import { createImageViewer } from '@/components/ImageViewer'
-import { ref, onMounted } from 'vue';
+import { ref, onMounted } from 'vue'
 
 const defaultPicUrl = ref(
   import.meta.env.VITE_BASE_URL + '/admin-api/infra/file/29/get/IntegratedSolution.png'
@@ -261,7 +297,7 @@ const formData = ref({
 })
 const pics = ref([])
 const imgSrc = ref('')
-const loadedTabs = ref(['info']); // 记录已加载的标签
+const loadedTabs = ref(['info']) // 记录已加载的标签
 
 /** 获得详情 */
 const getDetail = async () => {
@@ -294,7 +330,7 @@ const imagePreview = (imgUrl: string) => {
 const handleTabClick = (tab) => {
   if (!loadedTabs.value.includes(tab.index)) {
     // 这里可以添加每个标签对应的加载逻辑,如果有的话
-    loadedTabs.value.push(tab.index);
+    loadedTabs.value.push(tab.index)
   }
 }
 

+ 114 - 0
src/views/pms/device/completeSet/AssociationDevices.vue

@@ -0,0 +1,114 @@
+<template>
+  <div v-loading="loading" style="height: 100%">
+    <ContentWrap>
+      <!-- 搜索工作栏 -->
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item label="设备编码" prop="deviceCode">
+          <el-input
+            v-model="queryParams.deviceCode"
+            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>
+    <el-table :data="deviceList" style="width: 100%">
+      <!-- 序号 -->
+      <el-table-column :label="t('monitor.serial')" width="70" align="center">
+        <template #default="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="groupName" label="成套名称" align="center" />
+      <el-table-column prop="deviceName" :label="t('iotDevice.name')" align="center" />
+      <el-table-column prop="deviceCode" label="设备编码" align="center" />
+      <el-table-column prop="ifMaster" label="是否主设备" align="center">
+        <template #default="scope">
+          <el-tag v-if="scope.row.ifMaster" class="text-[#62a66a]">是</el-tag>
+
+          <el-tag v-else class="text-[#9093a6]" type="info">否</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="ifMaster" label="创建时间" align="center">
+        <template #default="scope">
+          {{ formatToDate(scope.row.createTime) }}
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getDeviceList(props.deviceId)"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { formatToDate } from '@/utils/dateUtil'
+import { IotDeviceApi } from '@/api/pms/device'
+const { t } = useI18n() // 国际化
+defineOptions({
+  name: 'AssociationDevices'
+})
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deviceId: '',
+  deviceCode: ''
+})
+const loading = ref(false)
+const total = ref(0) // 列表的总页数
+const deviceList = ref([])
+
+const props = defineProps({
+  deviceId: Number
+})
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getDeviceList(props.deviceId)
+}
+
+const resetQuery = () => {
+  queryParams.deviceCode = ''
+  handleQuery()
+}
+
+const getDeviceList = async (id) => {
+  loading.value = true
+  try {
+    queryParams.deviceId = id
+    const res = await IotDeviceApi.getIotDeviceSetRelation(queryParams)
+    deviceList.value = res.list
+    total.value = res.total
+  } catch (error) {
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getDeviceList(props.deviceId)
+})
+</script>
+
+<style lang="scss" scoped></style>

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

@@ -0,0 +1,528 @@
+<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"
+          label-width="68px"
+        >
+          <!-- <el-form-item label="部门名称" prop="deptName" style="margin-left: 20px">
+            <el-input
+              v-model="queryParams.deptName"
+              placeholder="请输入部门名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-200px"
+            />
+          </el-form-item> -->
+          <el-form-item label="成套名称" prop="name">
+            <el-input
+              v-model="queryParams.name"
+              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-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="deptName" />
+          <el-table-column label="成套名称" align="center" prop="name" />
+
+          <el-table-column label="描述" align="center" prop="remark" />
+          <el-table-column label="设备数量" align="center" prop="deviceCount">
+            <template #default="scope">
+              {{ (scope.row.details && scope.row.details.length) || 0 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="主设备" align="center" prop="mainDeviceName">
+            <template #default="scope">
+              {{
+                scope.row.details.filter((item) => item.ifMaster)[0]?.deviceName +
+                  ' ' +
+                  scope.row.details.filter((item) => item.ifMaster)[0]?.deviceCode || '无'
+              }}
+            </template>
+          </el-table-column>
+
+          <el-table-column :label="t('devicePerson.operation')" align="center" 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" @close="cancel">
+    <template #header>
+      <div class="my-header" style="padding-bottom: 20px">
+        <span>{{ dialogTitle }}</span>
+      </div>
+    </template>
+    <el-form ref="formRef" :model="formData" :inline="true" :rules="formRules" label-width="80px">
+      <el-form-item label="成套名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入成套名称" />
+      </el-form-item>
+
+      <el-form-item :label="t('iotDevice.dept')" prop="deptId">
+        <el-tree-select
+          v-model="formData.deptId"
+          :data="deptList"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          filterable
+          placeholder="请选择所在部门"
+          @change="handleDeptChange"
+          style="width: 200px"
+        />
+      </el-form-item>
+
+      <el-form-item label="描述" prop="remark">
+        <el-input
+          v-model="formData.remark"
+          style="width: 470px"
+          type="textarea"
+          placeholder="请输入描述"
+          :rows="2"
+        />
+      </el-form-item>
+
+      <el-form-item label="选择设备" prop="devices">
+        <div class="transfer-container">
+          <el-transfer
+            v-model="selectedDeviceIds"
+            :data="deviceOptions"
+            :titles="['设备列表', '已选择设备']"
+            :button-texts="['移除', '添加']"
+            filterable
+            :filter-method="filterDeviceMethod"
+            filter-placeholder="请输入设备名称"
+            @change="rightDeviceChange"
+          >
+            <template #left-empty>
+              <el-empty :image-size="60" :description="isEdit ? '加载中...' : '请选择设备'" />
+            </template>
+            <template #right-empty>
+              <el-empty :image-size="60" :description="isEdit ? '加载中...' : '请选择设备'" />
+            </template>
+          </el-transfer>
+        </div>
+      </el-form-item>
+
+      <div v-if="selectedDevices.length > 0" class="mt-4">
+        <el-form-item label="设置主设备" prop="mainDevice" label-width="100">
+          <el-select
+            v-model="mainDeviceId"
+            placeholder="请选择主设备"
+            clearable
+            filterable
+            style="width: 200px"
+            @change="setMainDevice"
+          >
+            <el-option
+              v-for="device in selectedDevices"
+              :key="device.id"
+              :label="device.label"
+              :value="device.id"
+            />
+          </el-select>
+        </el-form-item>
+      </div>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="cancel">取 消</el-button>
+      <el-button type="primary" @click="submit">确 定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { IotDeviceApi } from '@/api/pms/device'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import { ElMessageBox } from 'element-plus'
+const deptList = ref<Tree[]>([]) // 树形结构
+
+defineOptions({ name: 'IotDeviceComplete' })
+
+const loading = ref(true) // 列表的加载中
+
+const { t } = useI18n()
+
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  deptName: undefined,
+  name: 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({
+  name: '',
+  details: [],
+  deptId: '',
+  remark: ''
+})
+
+// 表单验证规则
+const formRules = {
+  name: [{ required: true, message: '成套名称不能为空', trigger: 'blur' }],
+  code: [{ required: true, message: '成套编码不能为空', trigger: 'blur' }],
+  deptId: [{ required: true, message: '请选择部门', trigger: 'change' }],
+  devices: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (selectedDeviceIds.value.length === 0) {
+          callback(new Error('请至少选择一个设备'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ],
+  mainDevice: [
+    {
+      required: true,
+      validator: (rule: any, value: any, callback: any) => {
+        if (!mainDeviceId.value) {
+          callback(new Error('请选择主设备'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ]
+}
+
+// 部门树数据
+const selectedDeptId = ref<number | string>('')
+
+// 新增时部门改变时获取设备列表
+const handleDeptChange = async (deptId) => {
+  if (deptId) {
+    selectedDeptId.value = deptId
+    getDeviceList(deptId)
+  }
+
+  // 清空主设备she 设备列表
+  selectedDevices.value = []
+  mainDeviceId.value = ''
+  selectedDeviceIds.value = []
+}
+// 获取设备列表
+const getDeviceList = async (deptId) => {
+  try {
+    const res = await IotDeviceApi.getIotDeviceSetOptions(deptId)
+    deviceOptions.value = res.map((item) => ({
+      key: item.id, // 始终使用id作为key
+      label: `${item.deviceName} (${item.deviceCode})`,
+      ...item
+    }))
+  } catch (err) {
+    console.error(err)
+  }
+}
+
+// 设备选择相关
+const deviceOptions = ref<any[]>([])
+const selectedDeviceIds = ref([])
+const selectedDevices = ref([])
+const mainDeviceId = ref('')
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await IotDeviceApi.getIotDeviceSetList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 首页处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+// 设备过滤方法
+const filterDeviceMethod = (query, item) => {
+  return item.label.toLowerCase().includes(query.toLowerCase())
+}
+
+// 更新已选择的设备列表
+const updateSelectedDevices = () => {
+  // 根据 selectedDeviceIds 从 deviceOptions 中找到对应的设备
+  selectedDevices.value = deviceOptions.value.filter((item) =>
+    selectedDeviceIds.value.includes(item.key)
+  )
+
+  console.log('selectedDevices>>>>>>>>>>>>>>>>', selectedDevices.value)
+
+  // 构建 details 数据
+  formData.value.details = selectedDevices.value.map((item) => ({
+    deviceId: item.id,
+    deviceName: item.deviceName,
+    deviceCode: item.deviceCode,
+    deptId: item.deptId,
+    ifMaster: item.id === mainDeviceId.value ? true : false
+  }))
+
+  // 如果主设备不在当前选择中,则清空主设备
+  if (
+    mainDeviceId.value &&
+    !selectedDevices.value.some((device) => device.id === mainDeviceId.value)
+  ) {
+    mainDeviceId.value = ''
+  }
+}
+
+const rightDeviceChange = (val) => {
+  selectedDeviceIds.value = val
+  updateSelectedDevices()
+
+  // 手动触发验证
+  if (formRef.value) {
+    formRef.value.validateField('devices')
+    if (val.length > 0) {
+      formRef.value.validateField('mainDevice')
+    }
+  }
+}
+
+// 设置主设备
+const setMainDevice = (val) => {
+  mainDeviceId.value = val
+  // 更新 details 中的 ifMaster 字段
+  console.log('selectedDevices.value>>>>>>>>>>>>>>>>', selectedDevices.value)
+
+  formData.value.details = selectedDevices.value.map((item) => ({
+    deviceId: item.id,
+    deviceName: item.deviceName,
+    deviceCode: item.deviceCode,
+    deptId: item.deptId,
+    ifMaster: item.id === mainDeviceId.value ? true : false
+  }))
+
+  console.log('formData.value.details>>>>>>>>>>>>>>>>', formData.value.details)
+
+  // 手动触发验证
+  if (formRef.value) {
+    formRef.value.validateField('mainDevice')
+  }
+}
+
+// 显示新增对话框
+const handleAdd = () => {
+  isEdit.value = false
+  dialogTitle.value = '新增成套设备'
+  resetForm()
+  dialogVisible.value = true
+}
+
+// 显示编辑对话框
+const handleEdit = (row) => {
+  isEdit.value = true
+  dialogTitle.value = '编辑成套设备'
+
+  formData.value = {
+    ...row
+  }
+
+  // 先清空之前的选项
+  selectedDeviceIds.value = []
+  selectedDevices.value = []
+  deviceOptions.value = []
+
+  // 获取设备列表后再设置已选择的设备
+  getDeviceList(row.deptId).then(() => {
+    // 设置已选择的设备IDs(使用deviceOptions中的key值)
+    selectedDeviceIds.value = row.details.map((item) => {
+      // 在编辑模式下,使用deviceOptions中对应项的key作为标识
+      const option = deviceOptions.value.find((opt) => opt.deviceId === item.deviceId)
+      return option ? option.key : item.deviceId
+    })
+
+    mainDeviceId.value = row.details.find((item) => item.ifMaster)?.deviceId || ''
+
+    // 更新selectedDevices数组
+    nextTick(() => {
+      updateSelectedDevices()
+    })
+  })
+
+  dialogVisible.value = true
+}
+//删除成套
+const handleDelete = async (id: number) => {
+  ElMessageBox.confirm('确定要删除该成套设备吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        await IotDeviceApi.deleteIotDeviceSet(id)
+        ElMessage.success('删除成功')
+        getList()
+      } catch (error) {
+        console.error(error)
+      }
+    })
+    .catch(() => {
+      // 取消操作
+    })
+}
+
+// 重置表单
+const resetForm = () => {
+  formData.value = {
+    name: '',
+    details: [],
+    deptId: '',
+    remark: ''
+  }
+  selectedDeviceIds.value = []
+  selectedDevices.value = []
+  deviceOptions.value = []
+  mainDeviceId.value = ''
+}
+
+// 取消操作
+const cancel = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+// 提交表单
+const submit = async () => {
+  if (!formRef.value) return
+
+  await formRef.value.validate(async (valid) => {
+    if (!valid) return
+    try {
+      const data = {
+        ...formData.value
+      }
+
+      console.log('提交数据:', data)
+
+      if (isEdit.value) {
+        await IotDeviceApi.updateIotDeviceSet(data)
+        ElMessage.success('编辑成功')
+      } else {
+        await IotDeviceApi.createIotDeviceSet(data)
+        ElMessage.success('新增成功')
+      }
+
+      dialogVisible.value = false
+      getList()
+    } catch (error) {
+      console.error(error)
+    }
+  })
+}
+
+onMounted(async () => {
+  getList()
+
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>
+
+<style scoped>
+.transfer-container {
+  display: flex;
+  flex-direction: column;
+}
+
+.transfer-footer {
+  margin-top: 8px;
+  text-align: right;
+}
+
+::deep(.el-transfer-panel) {
+  width: 300px;
+}
+
+::deep(.el-tree--highlight-current) {
+  height: 200px !important;
+}
+::deep(.el-transfer-panel__body) {
+  height: 700px !important;
+}
+</style>

+ 66 - 7
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -85,6 +85,61 @@ async function loadDimensions() {
   dimensionLoading.value = false
 }
 
+async function updateDimensionValues() {
+  if (!query.id) return
+
+  try {
+    // 1. 并行获取最新数据
+    const [gatewayRes, carRes] = await Promise.all([
+      IotDeviceApi.getIotDeviceTds(Number(query.id)),
+      IotDeviceApi.getIotDeviceZHBDTds(Number(query.id))
+    ])
+
+    // 2. 创建一个 Map 用于快速查找 (Identifier -> Value)
+    // 这样可以将复杂度从 O(N*M) 降低到 O(N)
+    const newValueMap = new Map<string, any>()
+
+    const addToMap = (data: any[]) => {
+      if (!data) return
+      data.forEach((item) => {
+        if (item.identifier) {
+          newValueMap.set(item.identifier, item.value)
+        }
+      })
+    }
+
+    addToMap(gatewayRes as any[])
+    addToMap(carRes as any[])
+
+    // 3. 更新 dimensions.value (保留了之前的 color 和其他属性)
+    dimensions.value.forEach((item) => {
+      if (newValueMap.has(item.identifier)) {
+        item.value = newValueMap.get(item.identifier)
+      }
+    })
+
+    // 4. 如果还需要同步更新 gatewayDimensions 和 carDimensions
+    // (假设这些是引用类型,如果它们引用的是同一个对象,上面更新 dimensions 时可能已经同步了。
+    // 如果它们是独立的对象数组,则需要显式更新)
+
+    // 更新 Gateway 原始列表
+    gatewayDimensions.value.forEach((item) => {
+      if (newValueMap.has(item.identifier)) {
+        item.value = newValueMap.get(item.identifier)
+      }
+    })
+
+    // 更新 Car 原始列表
+    carDimensions.value.forEach((item) => {
+      if (newValueMap.has(item.identifier)) {
+        item.value = newValueMap.get(item.identifier)
+      }
+    })
+  } catch (error) {
+    console.error('Failed to update dimension values:', error)
+  }
+}
+
 const selectedDate = ref<string[]>([
   ...rangeShortcuts[2].value().map((v) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'))
 ])
@@ -186,7 +241,7 @@ function render() {
         let item = params.map(
           (el) => `<div class="flex items-center justify-between mt-1">
             <span>${el.marker} ${el.seriesName}</span>
-            <span>${el.value[2].toFixed(2)}</span>
+            <span>${el.value[2]?.toFixed(2)}</span>
           </div>`
         )
 
@@ -207,7 +262,7 @@ function render() {
     ],
     yAxis: {
       type: 'value',
-      min: minInterval.value,
+      min: -minInterval.value,
       max: maxInterval.value,
       interval: 1,
       axisLabel: {
@@ -236,7 +291,7 @@ function render() {
 }
 
 function mapData({ value, ts }) {
-  if (value === 0) return [ts, 0, 0]
+  if (!value) return [ts, 0, 0]
 
   const isPositive = value > 0
   const absItem = Math.abs(value)
@@ -285,8 +340,9 @@ async function fetchIncrementData() {
       .then((res) => {
         if (!res.length) return
 
-        const sorted = res.sort((a, b) => a.ts - b.ts)
-
+        const sorted = res
+          .sort((a, b) => a.ts - b.ts)
+          .map((item) => ({ ts: item.ts, value: item.value }))
         // push 到本地
         chartData.value[name].push(...sorted)
         // 更新 lastTs
@@ -304,7 +360,10 @@ async function fetchIncrementData() {
 const timer = ref<NodeJS.Timeout | null>(null)
 
 function startAutoFetch() {
-  timer.value = setInterval(fetchIncrementData, 10000)
+  timer.value = setInterval(() => {
+    updateDimensionValues()
+    fetchIncrementData()
+  }, 10000)
 }
 
 function stopAutoFetch() {
@@ -398,7 +457,7 @@ function handleClickSpec(modelName: string) {
     }
   })
   chart?.resize()
-  // genderIntervalArrDebounce()
+  genderIntervalArr()
 }
 
 const exportChart = () => {

+ 45 - 1
src/views/pms/dingding.vue

@@ -50,7 +50,13 @@ const businessRoutes: Record<string, string> = {
   generateOperation: '',
   generateMaintenance: '',
   generateMaintain: '',
-  rdReportApproval: 'rdReportApproval'
+  rdReportApproval: 'rdReportApproval',
+  rhDailyReport: 'rhDailyReport',
+  rhReportApproval: 'rhReportApproval',
+  ryDailyReport: 'ryDailyReport',
+  ryReportApproval: 'ryReportApproval',
+  ryXjDailyReport: 'ryXjDailyReport',
+  ryXjReportApproval: 'ryXjReportApproval'
 }
 
 // const href = ref('')
@@ -97,6 +103,23 @@ onMounted(async () => {
     } else if (type === 'rdReportApproval') {
       window.location.href =
         import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiDu/approval?id=' + id
+    } else if (type === 'rhDailyReport') {
+      window.location.href = import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruihen/edit?id=' + id
+    } else if (type === 'rhReportApproval') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruihen/approval?id=' + id
+    } else if (type === 'ryDailyReport') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiying/edit?id=' + id
+    } else if (type === 'ryReportApproval') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiying/approval?id=' + id
+    } else if (type === 'ryXjDailyReport') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiyingx/edit?id=' + id
+    } else if (type === 'ryXjReportApproval') {
+      window.location.href =
+        import.meta.env.VITE_BASE_URL + '/deepoil/#/pages/ruiyingx/approval?id=' + id
     } else {
       window.location.href =
         import.meta.env.VITE_BASE_URL +
@@ -168,6 +191,27 @@ onMounted(async () => {
         name: 'DailyReportApprovalForm',
         params: { id }
       })
+    } else if (type === 'rhDailyReport') {
+      push({ path: '/iotdayilyreport/IotRhDailyReport/fill', query: { id: id } })
+    } else if (type === 'rhReportApproval') {
+      push({
+        path: '/iotdayilyreport/IotRhDailyReport/approval',
+        query: { id: id }
+      })
+    } else if (type === 'ryDailyReport') {
+      push({ path: '/iotdayilyreport/IotRyDailyReport/fill', query: { id: id } })
+    } else if (type === 'ryReportApproval') {
+      push({
+        path: '/iotdayilyreport/IotRyDailyReport/approval',
+        query: { id: id }
+      })
+    } else if (type === 'ryXjDailyReport') {
+      push({ path: '/iotdayilyreport/IotRyXjDailyReport/fill', query: { id: id } })
+    } else if (type === 'ryXjReportApproval') {
+      push({
+        path: '/iotdayilyreport/IotRyXjDailyReport/approval',
+        query: { id: id }
+      })
     }
   } else {
     // 默认跳转

+ 230 - 142
src/views/pms/iotmainworkorder/index.vue

@@ -1,11 +1,20 @@
 <template>
   <el-row :gutter="20">
-    <el-col :class="{'leftcontent': true, 'collapsed': isLeftContentCollapsed}" :span="isLeftContentCollapsed ? 0 : 4" :xs="24">
+    <el-col
+      :class="{ leftcontent: true, collapsed: isLeftContentCollapsed }"
+      :span="isLeftContentCollapsed ? 0 : 4"
+      :xs="24"
+    >
       <ContentWrap class="h-1/1">
         <DeptTree @node-click="handleDeptNodeClick" />
       </ContentWrap>
     </el-col>
-    <el-col class="rightcontent" :span="isLeftContentCollapsed ? 24 : 20" :xs="24" style="position: relative;height: 100vh;">
+    <el-col
+      class="rightcontent"
+      :span="isLeftContentCollapsed ? 24 : 20"
+      :xs="24"
+      style="position: relative; height: 100vh"
+    >
       <div
         class="toggle-button"
         :style="{ left: isLeftContentCollapsed ? '0px' : '-13px' }"
@@ -14,7 +23,10 @@
         @mouseout="handleMouseOut"
         :title="hoverText"
       >
-        <span style="font-size: 5px;" :class="{'triangle': true, 'rotated': isLeftContentCollapsed}"></span>
+        <span
+          style="font-size: 5px"
+          :class="{ triangle: true, rotated: isLeftContentCollapsed }"
+        ></span>
       </div>
       <ContentWrap>
         <!-- 搜索工作栏 -->
@@ -61,16 +73,19 @@
             />
           </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 @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="['pms:iot-main-work-order:create']"
             >
-              <Icon icon="ep:plus" class="mr-5px" />  {{ t('operationFill.add') }}
+              <Icon icon="ep:plus" class="mr-5px" /> {{ t('operationFill.add') }}
             </el-button>
             <el-button
               type="success"
@@ -87,38 +102,76 @@
 
       <!-- 列表 -->
       <ContentWrap ref="tableContainerRef" class="table-wrap">
-        <el-table v-loading="loading" :data="list" :stripe="true" style="width: 100%" ref="tableRef">
-          <el-table-column :label="t('iotDevice.serial')" align="center" :width="columnWidths.serial">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          style="width: 100%"
+          ref="tableRef"
+        >
+          <el-table-column
+            :label="t('iotDevice.serial')"
+            align="center"
+            :width="columnWidths.serial"
+          >
             <template #default="scope">
               {{ scope.$index + 1 }}
             </template>
           </el-table-column>
-          <el-table-column :label="t('bomList.name')" align="center" prop="name" :width="columnWidths.name"/>
-          <el-table-column :label="t('iotDevice.dept')" align="center" prop="deptName" :width="columnWidths.deptName"/>
-          <el-table-column :label="t('bomList.status')" align="center" prop="result" :width="columnWidths.result">
+          <el-table-column
+            :label="t('bomList.name')"
+            align="center"
+            prop="name"
+            :width="columnWidths.name"
+          />
+          <el-table-column
+            :label="t('iotDevice.dept')"
+            align="center"
+            prop="deptName"
+            :width="columnWidths.deptName"
+          />
+          <el-table-column
+            :label="t('bomList.status')"
+            align="center"
+            prop="result"
+            :width="columnWidths.result"
+          >
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_MAIN_WORK_ORDER_RESULT" :value="scope.row.result" />
             </template>
           </el-table-column>
-          <el-table-column :label="t('bomList.serviceDue')" align="center" :width="columnWidths.serviceDue">
+          <el-table-column
+            :label="t('bomList.serviceDue')"
+            align="center"
+            :width="columnWidths.serviceDue"
+          >
             <template #default="scope">
-          <span :class="getDistanceClass(scope.row.mainDistance)">
-            {{ scope.row.mainDistance }}
-          </span>
+              <span :class="getDistanceClass(scope.row.mainDistance)">
+                {{ scope.row.mainDistance }}
+              </span>
             </template>
           </el-table-column>
-          <el-table-column :label="t('bomList.type')" align="center" prop="type" :width="columnWidths.type">
+          <el-table-column
+            :label="t('bomList.type')"
+            align="center"
+            prop="type"
+            :width="columnWidths.type"
+          >
             <template #default="scope">
               <dict-tag :type="DICT_TYPE.PMS_MAIN_WORK_ORDER_TYPE" :value="scope.row.type" />
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotMaintain.PersonInCharge')" align="center"
-                           prop="responsiblePersonName" :width="columnWidths.responsiblePersonName"/>
+          <el-table-column
+            :label="t('iotMaintain.PersonInCharge')"
+            align="center"
+            prop="responsiblePersonName"
+            :width="columnWidths.responsiblePersonName"
+          />
           <el-table-column
             :label="t('dict.createTime')"
             align="center"
             prop="createTime"
-            :formatter="dateFormatter"
+            :formatter="dateFormatter2"
             :width="columnWidths.createTime"
           />
           <el-table-column
@@ -128,13 +181,18 @@
             :width="columnWidths.updateTime"
           >
             <template #default="scope">
-          <span v-if="scope.row.result == 2">
-            {{ formatCellDate(scope.row.updateTime) }}
-          </span>
+              <span v-if="scope.row.result == 2">
+                {{ formatCellDate(scope.row.updateTime) }}
+              </span>
               <span v-else></span>
             </template>
           </el-table-column>
-          <el-table-column :label="t('iotMaintain.operation')" align="center" :width="columnWidths.operation" fixed="right">
+          <el-table-column
+            :label="t('iotMaintain.operation')"
+            align="center"
+            :width="columnWidths.operation"
+            fixed="right"
+          >
             <template #default="scope">
               <el-button
                 v-if="isNegativeMainDistance(scope.row.mainDistance)"
@@ -160,7 +218,7 @@
                 @click="detail(scope.row.id)"
                 v-hasPermi="['pms:iot-main-work-order:query']"
               >
-                {{ t('operationFill.view')  }}
+                {{ t('operationFill.view') }}
               </el-button>
               <el-button
                 link
@@ -169,7 +227,7 @@
                 v-hasPermi="['pms:iot-main-work-order:back']"
                 v-if="scope.row.result === 2 && scope.row.status === 0"
               >
-                {{ t('workOrderMaterial.back')  }}
+                {{ t('workOrderMaterial.back') }}
               </el-button>
               <el-button
                 link
@@ -177,9 +235,13 @@
                 class="warning-btn"
                 @click="openModifyForm('modify', scope.row.id)"
                 v-hasPermi="['pms:iot-main-work-order:update']"
-                v-if="scope.row.result === 2 && scope.row.status === 1 && currentUserId?.toString() === scope.row.responsiblePerson"
+                v-if="
+                  scope.row.result === 2 &&
+                  scope.row.status === 1 &&
+                  currentUserId?.toString() === scope.row.responsiblePerson
+                "
               >
-                {{ t('modelTemplate.update')  }}
+                {{ t('modelTemplate.update') }}
               </el-button>
             </template>
           </el-table-column>
@@ -204,8 +266,12 @@
     width="500px"
     :close-on-click-modal="false"
   >
-    <el-form :model="delayReasonForm" label-width="0px" :rules="delayReasonRules"
-             ref="delayReasonFormRef">
+    <el-form
+      :model="delayReasonForm"
+      label-width="0px"
+      :rules="delayReasonRules"
+      ref="delayReasonFormRef"
+    >
       <el-form-item label=" " prop="delayReason" class="required-item" label-width="16px">
         <el-input
           v-model="delayReasonForm.delayReason"
@@ -226,17 +292,16 @@
       </el-button>
     </template>
   </el-dialog>
-
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
+import { dateFormatter2 } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotMainWorkOrderApi, IotMainWorkOrderVO } from '@/api/pms/iotmainworkorder'
 import IotMainWorkOrderForm from './IotMainWorkOrderForm.vue'
-import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
-import DeptTree from "@/views/system/user/DeptTree.vue";
-import { useUserStore } from "@/store/modules/user";
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { useUserStore } from '@/store/modules/user'
 const { push } = useRouter() // 路由跳转
 
 /** 保养工单 列表 */
@@ -246,12 +311,12 @@ defineOptions({ name: 'IotMainWorkOrder' })
 const delayReasonFormRef = ref<InstanceType<typeof ElForm>>()
 
 // 定义响应式变量存储当前登录人ID
-const currentUserId = ref<number | undefined>(undefined);
+const currentUserId = ref<number | undefined>(undefined)
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const tableRef = ref() // 表格引用
-const isLeftContentCollapsed = ref(false);
+const isLeftContentCollapsed = ref(false)
 // 表格容器引用 用于获取容器宽度
 const tableContainerRef = ref()
 const loading = ref(true) // 列表的加载中
@@ -279,16 +344,20 @@ const queryParams = reactive({
   status: undefined,
   processInstanceId: undefined,
   auditStatus: undefined,
-  createTime: [],
+  createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
-const hoverText = ref('');
+const hoverText = ref('')
 
 // 定义表单验证规则
 const delayReasonRules = {
   delayReason: [
-    { required: true, message: t('workOrderMaterial.inputDelayReason') || '请输入延时原因', trigger: 'blur' }
+    {
+      required: true,
+      message: t('workOrderMaterial.inputDelayReason') || '请输入延时原因',
+      trigger: 'blur'
+    }
   ]
 }
 
@@ -308,20 +377,20 @@ const columnWidths = ref({
 
 // 计算文本宽度
 const getTextWidth = (text: string, fontSize = 14) => {
-  const span = document.createElement('span');
-  span.style.visibility = 'hidden';
-  span.style.position = 'absolute';
-  span.style.whiteSpace = 'nowrap';
-  span.style.fontSize = `${fontSize}px`;
-  span.style.fontFamily = 'inherit';
-  span.innerText = text;
+  const span = document.createElement('span')
+  span.style.visibility = 'hidden'
+  span.style.position = 'absolute'
+  span.style.whiteSpace = 'nowrap'
+  span.style.fontSize = `${fontSize}px`
+  span.style.fontFamily = 'inherit'
+  span.innerText = text
 
-  document.body.appendChild(span);
-  const width = span.offsetWidth;
-  document.body.removeChild(span);
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
 
-  return width;
-};
+  return width
+}
 
 /** 延保原因相关状态 */
 const delayReasonDialogVisible = ref(false)
@@ -400,87 +469,106 @@ const handleDeptNodeClick = async (row) => {
 
 // 计算列宽度
 const calculateColumnWidths = () => {
-  const MIN_WIDTH = 80; // 最小列宽
-  const PADDING = 25; // 列内边距
-  const FLEXIBLE_COLUMN = 'name'; // 可伸缩列
+  const MIN_WIDTH = 80 // 最小列宽
+  const PADDING = 25 // 列内边距
+  const FLEXIBLE_COLUMN = 'name' // 可伸缩列
 
   // 确保表格容器存在
-  if (!tableContainerRef.value?.$el) return;
+  if (!tableContainerRef.value?.$el) return
 
-  const container = tableContainerRef.value.$el;
-  const containerWidth = container.clientWidth;
+  const container = tableContainerRef.value.$el
+  const containerWidth = container.clientWidth
 
   // 1. 计算所有列的最小宽度
-  const minWidths: Record<string, number> = {};
-  let totalMinWidth = 0;
+  const minWidths: Record<string, number> = {}
+  let totalMinWidth = 0
 
   // 计算列最小宽度的函数
   const calculateColumnMinWidth = (key: string, label: string, getValue: Function) => {
-    const headerWidth = getTextWidth(label) * 1.2;
-    let contentMaxWidth = 0;
+    const headerWidth = getTextWidth(label) * 1.2
+    let contentMaxWidth = 0
 
     // 计算内容最大宽度
     list.value.forEach((row, index) => {
-      const text = String(getValue ? getValue(row, index) : (row[key] || ''));
-      const textWidth = getTextWidth(text);
-      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth;
-    });
+      const text = String(getValue ? getValue(row, index) : row[key] || '')
+      const textWidth = getTextWidth(text)
+      if (textWidth > contentMaxWidth) contentMaxWidth = textWidth
+    })
 
-    const minWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING;
-    minWidths[key] = minWidth;
-    totalMinWidth += minWidth;
-    return minWidth;
-  };
+    const minWidth = Math.max(headerWidth, contentMaxWidth, MIN_WIDTH) + PADDING
+    minWidths[key] = minWidth
+    totalMinWidth += minWidth
+    return minWidth
+  }
 
   // 计算各列最小宽度
-  calculateColumnMinWidth('serial', t('iotDevice.serial'), (row: any, index: number) => `${index + 1}`);
-  const nameMinWidth = calculateColumnMinWidth('name', t('bomList.name'), (row: any) => row.name);
-  calculateColumnMinWidth('deptName', t('iotDevice.dept'), (row: any) => row.deptName);
+  calculateColumnMinWidth(
+    'serial',
+    t('iotDevice.serial'),
+    (row: any, index: number) => `${index + 1}`
+  )
+  const nameMinWidth = calculateColumnMinWidth('name', t('bomList.name'), (row: any) => row.name)
+  calculateColumnMinWidth('deptName', t('iotDevice.dept'), (row: any) => row.deptName)
   calculateColumnMinWidth('result', t('bomList.status'), (row: any) => {
-    const dict = getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_RESULT)
-      .find(d => d.value === row.result);
-    return dict ? dict.label : '';
-  });
-  calculateColumnMinWidth('serviceDue', t('bomList.serviceDue'), (row: any) => row.mainDistance || '');
+    const dict = getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_RESULT).find(
+      (d) => d.value === row.result
+    )
+    return dict ? dict.label : ''
+  })
+  calculateColumnMinWidth(
+    'serviceDue',
+    t('bomList.serviceDue'),
+    (row: any) => row.mainDistance || ''
+  )
   calculateColumnMinWidth('type', t('bomList.type'), (row: any) => {
-    const dict = getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_TYPE)
-      .find(d => d.value === row.type);
-    return dict ? dict.label : '';
-  });
-  calculateColumnMinWidth('responsiblePersonName', t('iotMaintain.PersonInCharge'), (row: any) => row.responsiblePersonName);
-  calculateColumnMinWidth('createTime', t('dict.createTime'), (row: any) => dateFormatter(null, null, row.createTime));
-  calculateColumnMinWidth('updateTime', t('dict.fillTime'), (row: any) => row.result == 2 ? formatCellDate(row.updateTime) : '');
+    const dict = getStrDictOptions(DICT_TYPE.PMS_MAIN_WORK_ORDER_TYPE).find(
+      (d) => d.value === row.type
+    )
+    return dict ? dict.label : ''
+  })
+  calculateColumnMinWidth(
+    'responsiblePersonName',
+    t('iotMaintain.PersonInCharge'),
+    (row: any) => row.responsiblePersonName
+  )
+  calculateColumnMinWidth('createTime', t('dict.createTime'), (row: any) =>
+    dateFormatter(null, null, row.createTime)
+  )
+  calculateColumnMinWidth('updateTime', t('dict.fillTime'), (row: any) =>
+    row.result == 2 ? formatCellDate(row.updateTime) : ''
+  )
 
   // 操作列固定宽度
-  minWidths.operation = 160;
-  totalMinWidth += 160;
+  minWidths.operation = 160
+  totalMinWidth += 160
 
   // 2. 计算可伸缩列最终宽度
-  const newWidths: Record<string, string> = {};
-  const availableWidth = containerWidth - 17; // 减去滚动条宽度
+  const newWidths: Record<string, string> = {}
+  const availableWidth = containerWidth - 17 // 减去滚动条宽度
 
   // 应用最小宽度到所有列
-  Object.keys(minWidths).forEach(key => {
-    newWidths[key] = `${minWidths[key]}px`;
-  });
+  Object.keys(minWidths).forEach((key) => {
+    newWidths[key] = `${minWidths[key]}px`
+  })
 
   // 计算可伸缩列需要的宽度
   if (totalMinWidth < availableWidth) {
     // 有剩余空间:分配给可伸缩列
-    newWidths[FLEXIBLE_COLUMN] = `${minWidths[FLEXIBLE_COLUMN] + (availableWidth - totalMinWidth)}px`;
+    newWidths[FLEXIBLE_COLUMN] =
+      `${minWidths[FLEXIBLE_COLUMN] + (availableWidth - totalMinWidth)}px`
   } else {
     // 空间不足:确保可伸缩列至少显示内容
-    newWidths[FLEXIBLE_COLUMN] = `${nameMinWidth}px`;
+    newWidths[FLEXIBLE_COLUMN] = `${nameMinWidth}px`
   }
 
   // 3. 更新列宽配置
-  columnWidths.value = newWidths;
+  columnWidths.value = newWidths
 
   // 4. 触发表格重新布局
   nextTick(() => {
-    tableRef.value?.doLayout();
-  });
-};
+    tableRef.value?.doLayout()
+  })
+}
 
 /** 查询列表 */
 const getList = async () => {
@@ -502,8 +590,8 @@ const getList = async () => {
 
 // 日期格式化辅助函数
 const formatCellDate = (dateString: string | null) => {
-  if (!dateString) return '';
-  return dateFormatter(null, null, dateString);
+  if (!dateString) return ''
+  return dateFormatter(null, null, dateString)
 }
 
 /** 搜索按钮操作 */
@@ -531,7 +619,7 @@ const handleBack = async (id: number) => {
     // 调用接口提交退回请求
     await IotMainWorkOrderApi.updateIotMainWorkOrder({
       id: id,
-      backFlag: '1'  // 退回标识参数
+      backFlag: '1' // 退回标识参数
     })
 
     message.success(t('common.success') || '退回成功')
@@ -562,58 +650,56 @@ const resultOptions = computed(() => [
 ])
 
 const getDistanceClass = (distance: number | string | null) => {
-  if (distance === null || distance === undefined) return '';
+  if (distance === null || distance === undefined) return ''
 
   // 如果是数字类型,直接处理
   if (typeof distance === 'number') {
-    return distance < 0 ? 'negative-distance' :
-      distance > 0 ? 'positive-distance' : '';
+    return distance < 0 ? 'negative-distance' : distance > 0 ? 'positive-distance' : ''
   }
 
   // 如果是字符串,提取数字部分
   if (typeof distance === 'string') {
     // 使用正则提取数字部分(包括负号、小数点和科学计数法)
-    const numericPart = distance.match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/)?.[0];
+    const numericPart = distance.match(/[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/)?.[0]
 
     // 如果提取到数字部分,转换为数值
     if (numericPart) {
-      const num = parseFloat(numericPart);
-      return num < 0 ? 'negative-distance' :
-        num > 0 ? 'positive-distance' : '';
+      const num = parseFloat(numericPart)
+      return num < 0 ? 'negative-distance' : num > 0 ? 'positive-distance' : ''
     }
   }
 
-  return '';
-};
+  return ''
+}
 
 const toggleLeftContent = () => {
-  isLeftContentCollapsed.value = !isLeftContentCollapsed.value;
-};
+  isLeftContentCollapsed.value = !isLeftContentCollapsed.value
+}
 
 const handleMouseOver = () => {
-  hoverText.value = isLeftContentCollapsed.value ? '展开' : '收起';
-};
+  hoverText.value = isLeftContentCollapsed.value ? '展开' : '收起'
+}
 
 const handleMouseOut = () => {
-  hoverText.value = '';
-};
+  hoverText.value = ''
+}
 
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
   // 修改
   if (typeof id === 'number') {
-    push({ name: 'IotMainWorkOrderOptimize', params: {id } })
+    push({ name: 'IotMainWorkOrderOptimize', params: { id } })
     return
   } else {
-    push({ name: 'IotMainWorkOrderAdd', params:{} })
+    push({ name: 'IotMainWorkOrderAdd', params: {} })
   }
 }
 
 const openModifyForm = (type: string, id?: number) => {
   // 修改
   if (typeof id === 'number') {
-    push({ name: 'IotMainWorkOrderModify', params: {id } })
+    push({ name: 'IotMainWorkOrderModify', params: { id } })
     return
   }
 }
@@ -632,7 +718,7 @@ const handleDelete = async (id: number) => {
 }
 
 const detail = (id?: number) => {
-  push({ name: 'IotMainWorkOrderDetail', params:{id} })
+  push({ name: 'IotMainWorkOrderDetail', params: { id } })
 }
 
 /** 导出按钮操作 */
@@ -651,58 +737,60 @@ const handleExport = async () => {
 }
 
 // 声明 ResizeObserver 实例
-let resizeObserver: ResizeObserver | null = null;
+let resizeObserver: ResizeObserver | null = null
 
 /** 初始化 **/
 onMounted(() => {
   // 获取当前登录人的id 与 保养工单创建人id 匹配修改退回工单 的权限
   const userId = useUserStore().getUser.id
-  currentUserId.value = userId;
+  currentUserId.value = userId
 
   getList()
-  window.addEventListener('resize', calculateColumnWidths);
+  window.addEventListener('resize', calculateColumnWidths)
   // 创建 ResizeObserver 监听表格容器尺寸变化
   if (tableContainerRef.value?.$el) {
     resizeObserver = new ResizeObserver(() => {
       // 使用防抖避免频繁触发
-      clearTimeout(window.resizeTimer);
+      clearTimeout(window.resizeTimer)
       window.resizeTimer = setTimeout(() => {
-        calculateColumnWidths();
-      }, 100);
-    });
-    resizeObserver.observe(tableContainerRef.value.$el);
+        calculateColumnWidths()
+      }, 100)
+    })
+    resizeObserver.observe(tableContainerRef.value.$el)
   }
 })
 
 onUnmounted(() => {
-  window.removeEventListener('resize', calculateColumnWidths);
+  window.removeEventListener('resize', calculateColumnWidths)
 
   // 清除 ResizeObserver
   if (resizeObserver && tableContainerRef.value?.$el) {
-    resizeObserver.unobserve(tableContainerRef.value.$el);
-    resizeObserver = null;
+    resizeObserver.unobserve(tableContainerRef.value.$el)
+    resizeObserver = null
   }
 
   // 清除定时器
   if (window.resizeTimer) {
-    clearTimeout(window.resizeTimer);
+    clearTimeout(window.resizeTimer)
   }
 })
 
 // 监听列表数据变化重新计算列宽
-watch(list, () => {
-  nextTick(calculateColumnWidths)
-}, { deep: true })
+watch(
+  list,
+  () => {
+    nextTick(calculateColumnWidths)
+  },
+  { deep: true }
+)
 
 // 监听左侧菜单状态变化(展开/收起)
 watch(isLeftContentCollapsed, () => {
   // 添加延迟以确保 DOM 更新完成
-  setTimeout(calculateColumnWidths, 50);
+  setTimeout(calculateColumnWidths, 50)
 })
-
 </script>
 <style scoped>
-
 .leftcontent {
   transition: width 0.3s ease;
   position: relative;
@@ -715,7 +803,7 @@ watch(isLeftContentCollapsed, () => {
 
 /* 正数样式 - 淡绿色 */
 .positive-distance {
-  color: #67c23a;  /* element-plus 成功色 */
+  color: #67c23a; /* element-plus 成功色 */
   background-color: rgba(103, 194, 58, 0.1); /* 10% 透明度的淡绿色背景 */
   padding: 2px 8px;
   border-radius: 4px;
@@ -724,7 +812,7 @@ watch(isLeftContentCollapsed, () => {
 
 /* 负数样式 - 淡红色 */
 .negative-distance {
-  color: #f56c6c;  /* element-plus 危险色 */
+  color: #f56c6c; /* element-plus 危险色 */
   background-color: rgba(245, 108, 108, 0.1); /* 10% 透明度的淡红色背景 */
   padding: 2px 8px;
   border-radius: 4px;
@@ -776,7 +864,7 @@ watch(isLeftContentCollapsed, () => {
 .toggle-button {
   position: absolute;
   top: 44%;
-  transform: translate(-65%,-50%);
+  transform: translate(-65%, -50%);
   width: 12px;
   height: 40px;
   background-color: #f0f0f0;

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 319 - 260
src/views/pms/iotprojectinfo/index.vue


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 379 - 355
src/views/pms/iotprojecttask/IotProjectTaskForm.vue


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

@@ -109,7 +109,7 @@
             label="创建时间"
             align="center"
             prop="createTime"
-            :formatter="dateFormatter"
+            :formatter="dateFormatter2"
             width="180px"
           />
           <el-table-column label="日报状态" align="center" prop="status">
@@ -157,7 +157,7 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
+import { dateFormatter2 } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
 import { DICT_TYPE } from '@/utils/dict'

+ 2 - 2
src/views/pms/iotrddailyreport/index.vue

@@ -89,7 +89,7 @@
             label="创建时间"
             align="center"
             prop="createTime"
-            :formatter="dateFormatter"
+            :formatter="dateFormatter2"
             :min-width="columnWidths.createTime.width"
             resizable
           />
@@ -294,7 +294,7 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
+import { dateFormatter2 } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotRdDailyReportApi, IotRdDailyReportVO } from '@/api/pms/iotrddailyreport'
 import IotRdDailyReportForm from './IotRdDailyReportForm.vue'

+ 917 - 0
src/views/pms/iotrhdailyreport/approval.vue

@@ -0,0 +1,917 @@
+<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, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+
+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 // 日耗油量(升)
+  nonProductionTime: number // 非生产时间(小时)
+  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 // 审核意见
+}
+
+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) => (row.transitTime * 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: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    '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') {
+    const originalValue = row.transitTime ?? 0
+
+    if (originalValue > 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 FORM_KEYS = [
+  'id',
+  'deptName',
+  'contractName',
+  'taskName',
+  'dailyGasInjection',
+  'dailyWaterInjection',
+  'dailyInjectGasTime',
+  'dailyInjectWaterTime',
+  'nonProductionTime',
+  'nptReason',
+  'productionStatus',
+  'remark',
+  'relocationDays',
+  'capacity',
+  'createTime',
+  'deptId',
+  'projectId',
+  'taskId',
+  'auditStatus',
+  'opinion'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({
+  dailyGasInjection: 0,
+  dailyWaterInjection: 0,
+  dailyInjectGasTime: 0,
+  dailyInjectWaterTime: 0,
+  nonProductionTime: 0,
+  relocationDays: 0,
+  capacity: 0
+})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRhDailyReportApi.getIotRhDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.auditStatus !== 10) {
+      formType.value = 'readonly'
+    }
+
+    // if (!form.value.capacity) {
+    //   message.error('请维护增压机产能')
+    // }
+  } finally {
+  }
+}
+
+const formType = ref<'approval' | 'readonly'>('approval')
+
+function handleOpenForm(id: number, type: 'approval' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'approval')
+  }
+})
+
+const transitTime = computed(() => {
+  const cap = form.value.capacity
+  const gas = form.value.dailyGasInjection ?? 0
+
+  if (!cap) return { original: 0, value: '0%' }
+
+  const original = gas / cap
+  return { original, value: (original * 100).toFixed(2) + '%' }
+})
+
+const sumTimes = () => {
+  const { dailyInjectGasTime = 0, dailyInjectWaterTime = 0, nonProductionTime = 0 } = form.value
+  return parseFloat((dailyInjectGasTime + dailyInjectWaterTime + nonProductionTime).toFixed(2))
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== 24) {
+    callback(new Error(`当前合计 ${total} 小时,三项时间之和必须等于 24`))
+  } else {
+    callback()
+  }
+}
+
+const validateNptReason = (_rule: any, value: any, callback: any) => {
+  if ((form.value.nonProductionTime || 0) > 0 && !value) {
+    callback(new Error('非生产时间大于 0 时,必须选择原因'))
+  } else {
+    callback()
+  }
+}
+
+const timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  dailyGasInjection: [{ required: true, message: '请输入当日注气量', trigger: 'blur' }],
+  dailyWaterInjection: [{ required: true, message: '请输入当日注水量', trigger: 'blur' }],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: 'blur' }],
+
+  // 复用规则
+  dailyInjectGasTime: timeRuleItem,
+  dailyInjectWaterTime: timeRuleItem,
+  nonProductionTime: timeRuleItem,
+
+  nptReason: [{ validator: validateNptReason, trigger: ['change', 'blur'] }]
+})
+
+watch(
+  [
+    () => form.value.dailyInjectGasTime,
+    () => form.value.dailyInjectWaterTime,
+    () => form.value.nonProductionTime
+  ],
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField('nptReason')
+      if (sumTimes() === 24) {
+        formRef.value?.clearValidate([
+          'dailyInjectGasTime',
+          'dailyInjectWaterTime',
+          'nonProductionTime'
+        ])
+      }
+    })
+  }
+)
+
+const submitForm = async (auditStatus: 20 | 30) => {
+  if (!formRef.value) return
+
+  try {
+    // await formRef.value.validate()
+    formLoading.value = true
+    const { opinion, id } = form.value
+
+    const data = { id: id, auditStatus, opinion } as any
+
+    await IotRhDailyReportApi.approvalIotRhDailyReport(data)
+    message.success(auditStatus === 20 ? '通过成功' : '拒绝成功')
+    dialogVisible.value = false
+
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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, 'approval')"
+                        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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">运行时效:</span>
+                  当日注气量 / 产能
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >120% 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  注气 + 注水 + 非生产 = 24H
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠24H 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <!-- <div
+          v-if="form.opinion"
+          class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+        >
+          <Icon
+            icon="ep:warning-filled"
+            class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+          />
+          <div class="flex flex-col">
+            <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+            <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+              {{ form.opinion }}
+            </p>
+          </div>
+        </div> -->
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="搬迁安装天数(D)" prop="relocationDays">
+            <el-input v-model="form.relocationDays" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="运行时效" prop="transitTime">
+            <el-input
+              :model-value="transitTime.value"
+              placeholder="运行时效"
+              disabled
+              :class="{ 'warning-input': transitTime.original > 1.2 }"
+              id="transitTimeInput"
+            />
+          </el-form-item>
+          <el-form-item label="当日注气量(方)" prop="dailyGasInjection">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyGasInjection"
+              placeholder="请输入当日注气量(方)"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="当日注水量(方)" prop="dailyWaterInjection">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyWaterInjection"
+              placeholder="请输入当日注水量(方)"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="当日注气时间(H)" prop="dailyInjectGasTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyInjectGasTime"
+              placeholder="请输入当日注气时间(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="当日注水时间(H)" prop="dailyInjectWaterTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyInjectWaterTime"
+              placeholder="当日注水时间(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.nonProductionTime"
+              placeholder="非生产时间(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间原因" prop="nptReason">
+            <el-select v-model="form.nptReason" placeholder="请选择" disabled clearable>
+              <el-option
+                v-for="(dict, index) of getStrDictOptions(DICT_TYPE.PMS_PROJECT_NPT_REASON)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <div class="grid grid-cols-1 gap-4 mt-5">
+            <el-form-item label="生产动态" prop="productionStatus">
+              <el-input
+                v-model="form.productionStatus"
+                placeholder="请输入生产动态"
+                type="textarea"
+                autosize
+                :max-length="1000"
+                disabled
+              />
+            </el-form-item>
+            <el-form-item label="备注" prop="remark">
+              <el-input
+                v-model="form.remark"
+                placeholder="请输入备注"
+                :max-length="1000"
+                type="textarea"
+                autosize
+                disabled
+              />
+            </el-form-item>
+          </div>
+        </div>
+        <el-form-item class="mt-4" label="审批意见" prop="opinion">
+          <el-input
+            v-model="form.opinion"
+            placeholder="请输入审批意见"
+            :max-length="1000"
+            type="textarea"
+            autosize
+            :disabled="formType === 'readonly'"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button
+          size="default"
+          @click="submitForm(20)"
+          type="primary"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批通过
+        </el-button>
+        <el-button
+          size="default"
+          @click="submitForm(30)"
+          type="danger"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批拒绝
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog>
+  </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(.orange-input) {
+  .el-input__inner {
+    color: orange !important;
+    -webkit-text-fill-color: orange !important;
+  }
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 892 - 0
src/views/pms/iotrhdailyreport/fill.vue

@@ -0,0 +1,892 @@
+<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, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+
+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 // 日耗油量(升)
+  nonProductionTime: number // 非生产时间(小时)
+  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 // 审核意见
+}
+
+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) => (row.transitTime * 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: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    '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') {
+    const originalValue = row.transitTime ?? 0
+
+    if (originalValue > 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 FORM_KEYS = [
+  'id',
+  'deptId',
+  'projectId',
+  'taskId',
+  'deptName',
+  'contractName',
+  'taskName',
+  'dailyGasInjection',
+  'dailyWaterInjection',
+  'dailyInjectGasTime',
+  'dailyInjectWaterTime',
+  'nonProductionTime',
+  'nptReason',
+  'productionStatus',
+  'remark',
+  'relocationDays',
+  'capacity',
+  'createTime',
+  'opinion'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({
+  dailyGasInjection: 0,
+  dailyWaterInjection: 0,
+  dailyInjectGasTime: 0,
+  dailyInjectWaterTime: 0,
+  nonProductionTime: 0,
+  relocationDays: 0,
+  capacity: 0
+})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRhDailyReportApi.getIotRhDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.status !== 0) {
+      formType.value = 'readonly'
+    }
+
+    if (!form.value.capacity) {
+      message.error('请维护增压机产能')
+    }
+  } finally {
+  }
+}
+
+const formType = ref<'edit' | 'readonly'>('edit')
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+
+const transitTime = computed(() => {
+  const cap = form.value.capacity
+  const gas = form.value.dailyGasInjection ?? 0
+
+  if (!cap) return { original: 0, value: '0%' }
+
+  const original = gas / cap
+  return { original, value: (original * 100).toFixed(2) + '%' }
+})
+
+const sumTimes = () => {
+  const { dailyInjectGasTime = 0, dailyInjectWaterTime = 0, nonProductionTime = 0 } = form.value
+  return parseFloat((dailyInjectGasTime + dailyInjectWaterTime + nonProductionTime).toFixed(2))
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== 24) {
+    callback(new Error(`当前合计 ${total} 小时,三项时间之和必须等于 24`))
+  } else {
+    callback()
+  }
+}
+
+const validateNptReason = (_rule: any, value: any, callback: any) => {
+  if ((form.value.nonProductionTime || 0) > 0 && !value) {
+    callback(new Error('非生产时间大于 0 时,必须选择原因'))
+  } else {
+    callback()
+  }
+}
+
+const timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  dailyGasInjection: [{ required: true, message: '请输入当日注气量', trigger: ['change', 'blur'] }],
+  dailyWaterInjection: [
+    { required: true, message: '请输入当日注水量', trigger: ['change', 'blur'] }
+  ],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
+
+  // 复用规则
+  dailyInjectGasTime: timeRuleItem,
+  dailyInjectWaterTime: timeRuleItem,
+  nonProductionTime: timeRuleItem,
+
+  nptReason: [{ validator: validateNptReason, trigger: ['change', 'blur'] }]
+})
+
+watch(
+  [
+    () => form.value.dailyInjectGasTime,
+    () => form.value.dailyInjectWaterTime,
+    () => form.value.nonProductionTime
+  ],
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField('nptReason')
+      if (sumTimes() === 24) {
+        formRef.value?.clearValidate([
+          'dailyInjectGasTime',
+          'dailyInjectWaterTime',
+          'nonProductionTime'
+        ])
+      }
+    })
+  }
+)
+
+const { t } = useI18n()
+
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    formLoading.value = true
+    const { createTime, ...other } = form.value
+    const data = { ...other, fillOrderCreateTime: createTime } as any
+    await IotRhDailyReportApi.createIotRhDailyReport(data)
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+        :disabled="formType === 'readonly'"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">运行时效:</span>
+                  当日注气量 / 产能
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >120% 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  注气 + 注水 + 非生产 = 24H
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠24H 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <div
+            v-if="form.opinion"
+            class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+          >
+            <Icon
+              icon="ep:warning-filled"
+              class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+            />
+            <div class="flex flex-col">
+              <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+              <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+                {{ form.opinion }}
+              </p>
+            </div>
+          </div>
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="搬迁安装天数(D)" prop="relocationDays">
+            <el-input v-model="form.relocationDays" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="运行时效" prop="transitTime">
+            <el-input
+              :model-value="transitTime.value"
+              placeholder="运行时效"
+              disabled
+              :class="{ 'warning-input': transitTime.original > 1.2 }"
+              id="transitTimeInput"
+            />
+          </el-form-item>
+          <el-form-item label="当日注气量(方)" prop="dailyGasInjection">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyGasInjection"
+              placeholder="请输入当日注气量(方)"
+            />
+          </el-form-item>
+          <el-form-item label="当日注水量(方)" prop="dailyWaterInjection">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyWaterInjection"
+              placeholder="请输入当日注水量(方)"
+            />
+          </el-form-item>
+          <el-form-item label="当日注气时间(H)" prop="dailyInjectGasTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyInjectGasTime"
+              placeholder="请输入当日注气时间(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="当日注水时间(H)" prop="dailyInjectWaterTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyInjectWaterTime"
+              placeholder="当日注水时间(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.nonProductionTime"
+              placeholder="非生产时间(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间原因" prop="nptReason">
+            <el-select v-model="form.nptReason" placeholder="请选择" clearable>
+              <el-option
+                v-for="(dict, index) of getStrDictOptions(DICT_TYPE.PMS_PROJECT_NPT_REASON)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+        <div class="grid grid-cols-1 gap-4 mt-5">
+          <el-form-item label="生产动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              placeholder="请输入生产动态"
+              type="textarea"
+              autosize
+              :max-length="1000"
+            />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input
+              v-model="form.remark"
+              placeholder="请输入备注"
+              :max-length="1000"
+              type="textarea"
+              autosize
+            />
+          </el-form-item>
+        </div>
+      </el-form>
+      <template #footer>
+        <el-button size="default" @click="submitForm" type="primary" :disabled="formLoading">
+          确 定
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog>
+  </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(.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>

+ 46 - 29
src/views/pms/iotrhdailyreport/index.vue

@@ -1,9 +1,18 @@
 <template>
-  <el-row :gutter="20">
+  <el-row :gutter="20" class="h-full">
     <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="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>
@@ -136,7 +145,12 @@
             show-overflow-tooltip
             border
           >
-            <el-table-column :label="t('iotDevice.serial')" width="56px" align="center">
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
               <template #default="scope">
                 {{ scope.$index + 1 }}
               </template>
@@ -145,9 +159,10 @@
               label="日期"
               align="center"
               prop="createTime"
-              :formatter="dateFormatter"
+              :formatter="dateFormatter2"
               :min-width="columnWidths.createTime.width"
               resizable
+              fixed="left"
             />
             <el-table-column
               label="施工队伍"
@@ -155,14 +170,7 @@
               prop="deptName"
               :min-width="columnWidths.deptName.width"
               resizable
-            />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              class-name="contract-name-column"
-              :min-width="columnWidths.contractName.width"
-              resizable
+              fixed="left"
             />
             <el-table-column
               label="任务"
@@ -170,6 +178,7 @@
               prop="taskName"
               :min-width="columnWidths.taskName.width"
               resizable
+              fixed="left"
             />
             <!-- <el-table-column label="施工状态" align="center" prop="constructionStatus" /> -->
             <el-table-column
@@ -178,6 +187,7 @@
               prop="constructionStatus"
               :min-width="columnWidths.constructionStatus.width"
               resizable
+              fixed="left"
             >
               <template #default="scope">
                 <dict-tag
@@ -272,7 +282,7 @@
                 <dict-tag :type="DICT_TYPE.PMS_PROJECT_NPT_REASON" :value="scope.row.nptReason" />
               </template>
             </el-table-column>
-            <el-table-column
+            <!-- <el-table-column
               label="施工开始日期"
               align="center"
               prop="constructionStartDate"
@@ -287,7 +297,7 @@
               :formatter="dateFormatter"
               :min-width="columnWidths.constructionEndDate.width"
               resizable
-            />
+            /> -->
             <el-table-column
               label="生产动态"
               align="center"
@@ -295,6 +305,14 @@
               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="注气量(万方)"
@@ -327,7 +345,7 @@
               :min-width="columnWidths.capacity.width"
               resizable
             />
-            <el-table-column label="操作" align="center" fixed="right">
+            <!-- <el-table-column label="操作" align="center" fixed="right">
               <template #default="scope">
                 <el-button
                   link
@@ -346,7 +364,7 @@
                   删除
                 </el-button>
               </template>
-            </el-table-column>
+            </el-table-column> -->
           </el-table>
         </div>
         <!-- 分页 -->
@@ -371,18 +389,19 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter, rangeShortcuts } from '@/utils/formatTime'
+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 DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.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)
 
 /** 瑞恒日报 列表 */
@@ -400,7 +419,7 @@ const total = ref(0) // 列表的总页数
 let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -439,7 +458,7 @@ const exportLoading = ref(false) // 导出的加载中
 // 添加弹窗引用
 const unfilledDialogRef = ref()
 
-const rootDeptId = ref(157)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 // 新增统计相关变量
 const statistics = ref({
@@ -476,7 +495,7 @@ const columnWidths = ref<
     label: '日期',
     prop: 'createTime',
     width: '120px',
-    fn: (row: IotRhDailyReportVO) => dateFormatter(null, null, row.createTime)
+    fn: (row: IotRhDailyReportVO) => dateFormatter2(null, null, row.createTime)
   },
   deptName: {
     label: '施工队伍',
@@ -839,6 +858,7 @@ const route = useRoute()
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   // 重置后需要重新获取统计数据
   getStatistics()
   // 重新获取工作量统计数据
@@ -883,7 +903,7 @@ const selectedDept = ref<{ id: number; name: string }>()
 const handleDeptNodeClick = async (row) => {
   // 记录选中的部门信息
   selectedDept.value = { id: row.id, name: row.name }
-  queryParams.deptId = row.id
+  // queryParams.deptId = row.id
   await getList()
 }
 
@@ -908,11 +928,8 @@ let resizeObserver: ResizeObserver | null = null
 /** 初始化 **/
 onMounted(() => {
   if (Object.keys(route.query).length > 0) {
-    queryParams = {
-      ...queryParams,
-      ...route.query,
-      deptId: Number(route.query.deptId) as any
-    }
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
     handleQuery()
   } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化

+ 44 - 15
src/views/pms/iotrhdailyreport/summary.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import dayjs from 'dayjs'
 import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
 import { useDebounceFn } from '@vueuse/core'
@@ -12,6 +11,10 @@ import { Motion, AnimatePresence } from 'motion-v'
 import { rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 
+import { useUserStore } from '@/store/modules/user'
+
+const deptId = useUserStore().getUser.deptId
+
 interface Query {
   pageNo: number
   pageSize: number
@@ -21,12 +24,12 @@ interface Query {
   createTime: string[]
 }
 
-const id = 157
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 157,
+  deptId: deptId,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ]
@@ -85,7 +88,7 @@ const getTotal = useDebounceFn(async () => {
 
   try {
     let res1: any[]
-    if (query.value.createTime.length !== 0) {
+    if (query.value.createTime && query.value.createTime.length === 2) {
       res1 = await IotRhDailyReportApi.rhDailyReportStatistics({
         createTime: query.value.createTime,
         deptId: query.value.deptId
@@ -100,13 +103,16 @@ const getTotal = useDebounceFn(async () => {
 
     totalWork.value = {
       ...totalWork.value,
+      totalFuelConsumption: 0,
+      totalPowerConsumption: 0,
+      totalWaterInjection: 0,
       ...res2,
       totalGasInjection: (res2.totalGasInjection || 0) / 10000
     }
   } finally {
     totalLoading.value = false
   }
-}, 1000)
+}, 500)
 
 interface List {
   id: number | null
@@ -144,6 +150,10 @@ const columns = (type: string) => {
     {
       label: '累计油耗(L)',
       prop: 'cumulativeFuelConsumption'
+    },
+    {
+      label: '平均时效(%)',
+      prop: 'transitTime'
     }
   ]
 }
@@ -151,7 +161,9 @@ const columns = (type: string) => {
 const listLoading = ref(false)
 
 const formatter = (row: List, column: any) => {
-  return row[column.property] ?? 0
+  if (column.property === 'transitTime') {
+    return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
+  } else return row[column.property] ?? 0
 }
 
 const getList = useDebounceFn(async () => {
@@ -176,7 +188,7 @@ const getList = useDebounceFn(async () => {
   } finally {
     listLoading.value = false
   }
-}, 1000)
+}, 500)
 
 const tab = ref<'表格' | '看板'>('表格')
 
@@ -238,7 +250,7 @@ const getChart = useDebounceFn(async () => {
   } finally {
     chartLoading.value = false
   }
-}, 1000)
+}, 500)
 
 const resizer = () => {
   chart?.resize()
@@ -262,10 +274,10 @@ const render = () => {
   }
 
   const maxVal = values.length === 0 ? 10000 : Math.max(...values)
-  const minVal = values.length === 0 ? 0 : Math.min(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values) > 0 ? 0 : Math.min(...values)
 
   const maxDigits = (Math.floor(maxVal) + '').length
-  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const minDigits = minVal === 0 ? 0 : (Math.floor(Math.abs(minVal)) + '').length
   const interval = Math.max(maxDigits, minDigits)
 
   const maxInterval = interval
@@ -304,7 +316,7 @@ const render = () => {
     },
     yAxis: {
       type: 'value',
-      min: minInterval,
+      min: -minInterval,
       max: maxInterval,
       interval: 1,
       axisLabel: {
@@ -341,7 +353,6 @@ const render = () => {
 
 const handleDeptNodeClick = (node: any) => {
   deptName.value = node.name
-  query.value.deptId = node.id
   handleQuery()
 }
 
@@ -360,7 +371,7 @@ const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     contractName: '',
     taskName: '',
     createTime: []
@@ -370,9 +381,20 @@ const resetQuery = () => {
 
 watch(
   () => query.value.createTime,
-  () => handleQuery(false)
+  (val) => {
+    if (!val) {
+      totalWork.value.totalCount = 0
+      totalWork.value.notReported = 0
+      totalWork.value.alreadyReported = 0
+    }
+    handleQuery(false)
+  }
 )
 
+watch([() => query.value.contractName, () => query.value.taskName], () => {
+  handleQuery(false)
+})
+
 onMounted(() => {
   handleQuery()
 })
@@ -448,7 +470,13 @@ const tolist = (id: number) => {
 <template>
   <div class="grid grid-cols-[16%_1fr] gap-5">
     <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4">
-      <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" />
+      <!-- <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" /> -->
+      <DeptTreeSelect
+        :deptId="id"
+        :top-id="157"
+        v-model="query.deptId"
+        @node-click="handleDeptNodeClick"
+      />
     </div>
     <div class="grid grid-rows-[62px_164px_1fr] h-full gap-4">
       <el-form
@@ -482,6 +510,7 @@ const tolist = (id: number) => {
               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>

+ 1258 - 0
src/views/pms/iotrydailyreport/approval.vue

@@ -0,0 +1,1258 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+
+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
+  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
+}
+
+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: '非生产时间',
+    children: [
+      {
+        label: '事故(H)',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '修理(H)',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '自停(H)',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '复杂(H)',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '搬迁(H)',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '整改(H)',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '等停(H)',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '冬休(H)',
+        prop: 'winterBreakTime',
+        '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
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const gasTime = row.drillingWorkingTime || 0
+  const waterTime = row.otherProductionTime || 0
+  const nonProdTime =
+    row.accidentTime ||
+    0 + row.repairTime ||
+    0 + row.selfStopTime ||
+    0 + row.complexityTime ||
+    0 + row.relocationTime ||
+    0 + row.rectificationTime ||
+    0 + row.waitingStopTime ||
+    0 + row.winterBreakTime ||
+    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 === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 15)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = [
+    'drillingWorkingTime',
+    'otherProductionTime',
+    'accidentTime',
+    'repairTime',
+    'selfStopTime',
+    'complexityTime',
+    'relocationTime',
+    'rectificationTime',
+    'waitingStopTime',
+    'winterBreakTime'
+  ]
+  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 FORM_KEYS = [
+  'id',
+  'deptName',
+  'contractName',
+  'taskName',
+  'rigStatus',
+  'designWellDepth',
+  'currentDepth',
+  'dailyPowerUsage',
+  'dailyFuel',
+  'mudDensity',
+  'mudViscosity',
+  'lateralLength',
+  'wellInclination',
+  'azimuth',
+  'designWellStruct',
+  'productionStatus',
+  'remark',
+  'createTime',
+  'deptId',
+  'projectId',
+  'taskId',
+  'opinion',
+  'personnel',
+  'accidentTime',
+  'repairTime',
+  'selfStopTime',
+  'complexityTime',
+  'relocationTime',
+  'rectificationTime',
+  'waitingStopTime',
+  'winterBreakTime',
+  'drillingWorkingTime',
+  'otherProductionTime',
+  'lastCurrentDepth'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRyDailyReportApi.getIotRyDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.auditStatus !== 10) {
+      formType.value = 'readonly'
+    }
+  } finally {
+  }
+}
+
+const formType = ref<'approval' | 'readonly'>('approval')
+
+function handleOpenForm(id: number, type: 'approval' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'approval')
+  }
+})
+
+// const transitTime = computed(() => {
+//   const cap = form.value.capacity
+//   const gas = form.value.dailyGasInjection ?? 0
+
+//   if (!cap) return { original: 0, value: '0%' }
+
+//   const original = gas / cap
+//   return { original, value: (original * 100).toFixed(2) + '%' }
+// })
+
+const sumTimes = () => {
+  const {
+    drillingWorkingTime = 0,
+    otherProductionTime = 0,
+    accidentTime = 0,
+    repairTime = 0,
+    selfStopTime = 0,
+    complexityTime = 0,
+    relocationTime = 0,
+    rectificationTime = 0,
+    waitingStopTime = 0,
+    winterBreakTime = 0
+  } = form.value
+  return parseFloat(
+    (
+      drillingWorkingTime +
+      otherProductionTime +
+      accidentTime +
+      repairTime +
+      selfStopTime +
+      complexityTime +
+      relocationTime +
+      rectificationTime +
+      waitingStopTime +
+      winterBreakTime
+    ).toFixed(2)
+  )
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== 24) {
+    callback(new Error(`当前合计 ${total} 小时,时间之和必须等于 24`))
+  } else {
+    callback()
+  }
+}
+
+// const validateNptReason = (_rule: any, value: any, callback: any) => {
+//   if ((form.value.nonProductionTime || 0) > 0 && !value) {
+//     callback(new Error('非生产时间大于 0 时,必须选择原因'))
+//   } else {
+//     callback()
+//   }
+// }
+
+const validateLastCurrentDepth = (_rule: any, value: any, callback: any) => {
+  if (value && value < (form.value.lastCurrentDepth ?? 0)) {
+    callback(new Error('当前深度需大于等于上一次填报深度'))
+  } else {
+    callback()
+  }
+}
+
+const timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  currentDepth: [
+    { required: true, message: '请输入当前深度', trigger: ['change', 'blur'] },
+    { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
+  ],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
+
+  // 复用规则
+  drillingWorkingTime: timeRuleItem,
+  otherProductionTime: timeRuleItem,
+  accidentTime: timeRuleItem,
+  repairTime: timeRuleItem,
+  selfStopTime: timeRuleItem,
+  complexityTime: timeRuleItem,
+  relocationTime: timeRuleItem,
+  rectificationTime: timeRuleItem,
+  waitingStopTime: timeRuleItem,
+  winterBreakTime: timeRuleItem
+})
+
+watch(
+  [
+    () => form.value.drillingWorkingTime,
+    () => form.value.otherProductionTime,
+    () => form.value.accidentTime,
+    () => form.value.repairTime,
+    () => form.value.selfStopTime,
+    () => form.value.complexityTime,
+    () => form.value.relocationTime,
+    () => form.value.rectificationTime,
+    () => form.value.waitingStopTime,
+    () => form.value.winterBreakTime
+  ],
+  () => {
+    nextTick(() => {
+      if (sumTimes() === 24) {
+        formRef.value?.clearValidate([
+          'drillingWorkingTime',
+          'otherProductionTime',
+          'accidentTime',
+          'repairTime',
+          'selfStopTime',
+          'complexityTime',
+          'relocationTime',
+          'rectificationTime',
+          'waitingStopTime',
+          'winterBreakTime'
+        ])
+      }
+    })
+  }
+)
+
+const submitForm = async (auditStatus: 20 | 30) => {
+  if (!formRef.value) return
+
+  try {
+    // await formRef.value.validate()
+    formLoading.value = true
+    const { opinion, id } = form.value
+
+    const data = { id: id, auditStatus, opinion } as any
+
+    await IotRyDailyReportApi.approvalIotRyDailyReport(data)
+    message.success(auditStatus === 20 ? '通过成功' : '拒绝成功')
+    dialogVisible.value = false
+
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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, 'approval')"
+                        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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200"> 油量消耗:</span>
+                  当日油耗
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >15吨 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  进尺 + 其它生产 + 非生产 = 24H
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠24H 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <!-- <div
+          v-if="form.opinion"
+          class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+        >
+          <Icon
+            icon="ep:warning-filled"
+            class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+          />
+          <div class="flex flex-col">
+            <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+            <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+              {{ form.opinion }}
+            </p>
+          </div>
+        </div> -->
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="施工状态" prop="rigStatus">
+            <el-select v-model="form.rigStatus" placeholder="请选择施工状态" disabled>
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设计井深(m)" prop="designWellDepth">
+            <el-input v-model="form.designWellDepth" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="当前井深(m)" prop="currentDepth">
+            <el-input-number
+              class="placeholder-"
+              :min="0"
+              v-model="form.currentDepth"
+              placeholder="请输入当前井深(m)"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="当日用电量(kWh)" prop="dailyPowerUsage">
+            <el-input-number
+              :min="0"
+              v-model="form.dailyPowerUsage"
+              placeholder="请输入当日用电量(kWh)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="当日油耗(吨)" prop="dailyFuel">
+            <el-input-number
+              :min="0"
+              v-model="form.dailyFuel"
+              placeholder="请输入当日油耗(吨)"
+              clearable
+              :class="{ 'warning-input': (form.dailyFuel ?? 0) > 15 }"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="泥浆密度(g/cm³)" prop="mudDensity">
+            <el-input-number
+              :min="0"
+              v-model="form.mudDensity"
+              placeholder="请输入泥浆性能-密度(g/cm³)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="泥浆粘度(S)" prop="mudViscosity">
+            <el-input-number
+              :min="0"
+              v-model="form.mudViscosity"
+              placeholder="请输入泥浆性能-粘度(S)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="水平段长度(m)" prop="lateralLength">
+            <el-input-number
+              :min="0"
+              v-model="form.lateralLength"
+              placeholder="请输入水平段长度(m)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="井斜(°)" prop="wellInclination">
+            <el-input-number
+              :min="0"
+              v-model="form.wellInclination"
+              placeholder="请输入井斜(°)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="方位(°)" prop="azimuth">
+            <el-input-number
+              :min="0"
+              v-model="form.azimuth"
+              placeholder="请输入方位(°)"
+              clearable
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="设计井身结构" prop="designWellStruct">
+            <el-input
+              v-model="form.designWellStruct"
+              placeholder=""
+              type="textarea"
+              disabled
+              autosize
+            />
+          </el-form-item>
+          <!-- <el-form-item label="生产动态" prop="productionStatus">
+          <el-input
+            v-model="form.productionStatus"
+            placeholder="请输入生产动态"
+            type="textarea"
+            autosize
+            :max-length="1000"
+            disabled
+          />
+        </el-form-item> -->
+          <el-form-item label="人员情况" prop="personnel">
+            <el-input
+              v-model="form.personnel"
+              placeholder="请输入人员情况"
+              type="textarea"
+              :max-length="1000"
+              autosize
+              disabled
+            />
+          </el-form-item>
+          <!-- <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            placeholder="请输入备注"
+            :max-length="1000"
+            type="textarea"
+            autosize
+            disabled
+          />
+        </el-form-item> -->
+        </div>
+        <el-divider content-position="left">生产时间</el-divider>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="进尺工作时间(H)" prop="drillingWorkingTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.drillingWorkingTime"
+              placeholder="进尺工作时间(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="其它生产时间(H)" prop="otherProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.otherProductionTime"
+              placeholder="其它生产时间(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+        </div>
+        <el-divider content-position="left">非生产时间</el-divider>
+        <div class="grid grid-cols-4 gap-4 mt-5">
+          <el-form-item label="事故(H)" prop="accidentTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.accidentTime"
+              placeholder="请输入事故(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="修理(H)" prop="repairTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.repairTime"
+              placeholder="请输入修理(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="自停(H)" prop="selfStopTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.selfStopTime"
+              placeholder="请输入自停(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="复杂(H)" prop="complexityTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.complexityTime"
+              placeholder="请输入复杂(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="搬迁(H)" prop="relocationTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.relocationTime"
+              placeholder="请输入搬迁(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="整改(H)" prop="rectificationTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.rectificationTime"
+              placeholder="请输入整改(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="等停(H)" prop="waitingStopTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.waitingStopTime"
+              placeholder="请输入等停(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="冬休(H)" prop="winterBreakTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.winterBreakTime"
+              placeholder="请输入冬休(H)"
+              disabled
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+        </div>
+        <div class="grid grid-cols-1 gap-4 mt-5">
+          <el-form-item label="生产动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              placeholder="请输入生产动态"
+              type="textarea"
+              autosize
+              :max-length="1000"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input
+              v-model="form.remark"
+              placeholder="请输入备注"
+              :max-length="1000"
+              type="textarea"
+              autosize
+              disabled
+            />
+          </el-form-item>
+        </div>
+        <el-form-item class="mt-4" label="审批意见" prop="opinion">
+          <el-input
+            v-model="form.opinion"
+            placeholder="请输入审批意见"
+            :max-length="1000"
+            type="textarea"
+            autosize
+            :disabled="formType === 'readonly'"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button
+          size="default"
+          @click="submitForm(20)"
+          type="primary"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批通过
+        </el-button>
+        <el-button
+          size="default"
+          @click="submitForm(30)"
+          type="danger"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批拒绝
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog>
+  </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(.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>

+ 1194 - 0
src/views/pms/iotrydailyreport/fill.vue

@@ -0,0 +1,1194 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+
+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
+  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
+}
+
+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: '非生产时间',
+    children: [
+      {
+        label: '事故(H)',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '修理(H)',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '自停(H)',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '复杂(H)',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '搬迁(H)',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '整改(H)',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '等停(H)',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '冬休(H)',
+        prop: 'winterBreakTime',
+        '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
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const gasTime = row.drillingWorkingTime || 0
+  const waterTime = row.otherProductionTime || 0
+  const nonProdTime =
+    row.accidentTime ||
+    0 + row.repairTime ||
+    0 + row.selfStopTime ||
+    0 + row.complexityTime ||
+    0 + row.relocationTime ||
+    0 + row.rectificationTime ||
+    0 + row.waitingStopTime ||
+    0 + row.winterBreakTime ||
+    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 === 'dailyFuel') {
+    const originalValue = row.dailyFuel ?? 0
+
+    if (originalValue > 15)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = [
+    'drillingWorkingTime',
+    'otherProductionTime',
+    'accidentTime',
+    'repairTime',
+    'selfStopTime',
+    'complexityTime',
+    'relocationTime',
+    'rectificationTime',
+    'waitingStopTime',
+    'winterBreakTime'
+  ]
+  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 FORM_KEYS = [
+  'id',
+  'deptName',
+  'contractName',
+  'taskName',
+  'rigStatus',
+  'designWellDepth',
+  'currentDepth',
+  'dailyPowerUsage',
+  'dailyFuel',
+  'mudDensity',
+  'mudViscosity',
+  'lateralLength',
+  'wellInclination',
+  'azimuth',
+  'designWellStruct',
+  'productionStatus',
+  'remark',
+  'createTime',
+  'deptId',
+  'projectId',
+  'taskId',
+  'opinion',
+  'personnel',
+  'accidentTime',
+  'repairTime',
+  'selfStopTime',
+  'complexityTime',
+  'relocationTime',
+  'rectificationTime',
+  'waitingStopTime',
+  'winterBreakTime',
+  'drillingWorkingTime',
+  'otherProductionTime',
+  'lastCurrentDepth'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRyDailyReportApi.getIotRyDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.status !== 0) {
+      formType.value = 'readonly'
+    }
+  } finally {
+  }
+}
+
+const formType = ref<'edit' | 'readonly'>('edit')
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+
+// const transitTime = computed(() => {
+//   const cap = form.value.capacity
+//   const gas = form.value.dailyGasInjection ?? 0
+
+//   if (!cap) return { original: 0, value: '0%' }
+
+//   const original = gas / cap
+//   return { original, value: (original * 100).toFixed(2) + '%' }
+// })
+
+const sumTimes = () => {
+  const {
+    drillingWorkingTime = 0,
+    otherProductionTime = 0,
+    accidentTime = 0,
+    repairTime = 0,
+    selfStopTime = 0,
+    complexityTime = 0,
+    relocationTime = 0,
+    rectificationTime = 0,
+    waitingStopTime = 0,
+    winterBreakTime = 0
+  } = form.value
+  return parseFloat(
+    (
+      drillingWorkingTime +
+      otherProductionTime +
+      accidentTime +
+      repairTime +
+      selfStopTime +
+      complexityTime +
+      relocationTime +
+      rectificationTime +
+      waitingStopTime +
+      winterBreakTime
+    ).toFixed(2)
+  )
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== 24) {
+    callback(new Error(`当前合计 ${total} 小时,时间之和必须等于 24`))
+  } else {
+    callback()
+  }
+}
+
+// const validateNptReason = (_rule: any, value: any, callback: any) => {
+//   if ((form.value.nonProductionTime || 0) > 0 && !value) {
+//     callback(new Error('非生产时间大于 0 时,必须选择原因'))
+//   } else {
+//     callback()
+//   }
+// }
+
+const validateLastCurrentDepth = (_rule: any, value: any, callback: any) => {
+  if (value && value < (form.value.lastCurrentDepth ?? 0)) {
+    callback(new Error(`当前深度需大于等于上一次填报深度 ${form.value.lastCurrentDepth}`))
+  } else {
+    callback()
+  }
+}
+
+const timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  currentDepth: [
+    { required: true, message: '请输入当前深度', trigger: ['change', 'blur'] },
+    { validator: validateLastCurrentDepth, trigger: ['change', 'blur'] }
+  ],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
+
+  // 复用规则
+  drillingWorkingTime: timeRuleItem,
+  otherProductionTime: timeRuleItem,
+  accidentTime: timeRuleItem,
+  repairTime: timeRuleItem,
+  selfStopTime: timeRuleItem,
+  complexityTime: timeRuleItem,
+  relocationTime: timeRuleItem,
+  rectificationTime: timeRuleItem,
+  waitingStopTime: timeRuleItem,
+  winterBreakTime: timeRuleItem
+})
+
+watch(
+  [
+    () => form.value.drillingWorkingTime,
+    () => form.value.otherProductionTime,
+    () => form.value.accidentTime,
+    () => form.value.repairTime,
+    () => form.value.selfStopTime,
+    () => form.value.complexityTime,
+    () => form.value.relocationTime,
+    () => form.value.rectificationTime,
+    () => form.value.waitingStopTime,
+    () => form.value.winterBreakTime
+  ],
+  () => {
+    nextTick(() => {
+      if (sumTimes() === 24) {
+        formRef.value?.clearValidate([
+          'drillingWorkingTime',
+          'otherProductionTime',
+          'accidentTime',
+          'repairTime',
+          'selfStopTime',
+          'complexityTime',
+          'relocationTime',
+          'rectificationTime',
+          'waitingStopTime',
+          'winterBreakTime'
+        ])
+      }
+    })
+  }
+)
+
+const { t } = useI18n()
+
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    formLoading.value = true
+    const { createTime, ...other } = form.value
+    const data = { ...other, fillOrderCreateTime: createTime, projectClassification: '1' } as any
+    await IotRyDailyReportApi.createIotRyDailyReport(data)
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+        :disabled="formType === 'readonly'"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200"> 油量消耗:</span>
+                  当日油耗
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >15吨 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  进尺 + 其它生产 + 非生产 = 24H
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠24H 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <div
+            v-if="form.opinion"
+            class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+          >
+            <Icon
+              icon="ep:warning-filled"
+              class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+            />
+            <div class="flex flex-col">
+              <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+              <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+                {{ form.opinion }}
+              </p>
+            </div>
+          </div>
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="施工状态" prop="rigStatus">
+            <el-select v-model="form.rigStatus" placeholder="请选择施工状态">
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设计井深(m)" prop="designWellDepth">
+            <el-input v-model="form.designWellDepth" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="当前井深(m)" prop="currentDepth">
+            <el-input-number
+              class="placeholder-"
+              :min="0"
+              v-model="form.currentDepth"
+              placeholder="请输入当前井深(m)"
+            />
+          </el-form-item>
+          <el-form-item label="当日用电量(kWh)" prop="dailyPowerUsage">
+            <el-input-number
+              :min="0"
+              v-model="form.dailyPowerUsage"
+              placeholder="请输入当日用电量(kWh)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="当日油耗(吨)" prop="dailyFuel">
+            <el-input-number
+              :min="0"
+              v-model="form.dailyFuel"
+              placeholder="请输入当日油耗(吨)"
+              clearable
+              :class="{ 'warning-input': (form.dailyFuel ?? 0) > 15 }"
+            />
+          </el-form-item>
+          <el-form-item label="泥浆密度(g/cm³)" prop="mudDensity">
+            <el-input-number
+              :min="0"
+              v-model="form.mudDensity"
+              placeholder="请输入泥浆性能-密度(g/cm³)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="泥浆粘度(S)" prop="mudViscosity">
+            <el-input-number
+              :min="0"
+              v-model="form.mudViscosity"
+              placeholder="请输入泥浆性能-粘度(S)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="水平段长度(m)" prop="lateralLength">
+            <el-input-number
+              :min="0"
+              v-model="form.lateralLength"
+              placeholder="请输入水平段长度(m)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="井斜(°)" prop="wellInclination">
+            <el-input-number
+              :min="0"
+              v-model="form.wellInclination"
+              placeholder="请输入井斜(°)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="方位(°)" prop="azimuth">
+            <el-input-number
+              :min="0"
+              v-model="form.azimuth"
+              placeholder="请输入方位(°)"
+              clearable
+            />
+          </el-form-item>
+          <el-form-item label="设计井身结构" prop="designWellStruct">
+            <el-input
+              v-model="form.designWellStruct"
+              placeholder=""
+              type="textarea"
+              disabled
+              autosize
+            />
+          </el-form-item>
+          <el-form-item label="人员情况" prop="personnel">
+            <el-input
+              v-model="form.personnel"
+              placeholder="请输入人员情况"
+              type="textarea"
+              :max-length="1000"
+              autosize
+            />
+          </el-form-item>
+        </div>
+        <el-divider content-position="left">生产时间</el-divider>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="进尺工作时间(H)" prop="drillingWorkingTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.drillingWorkingTime"
+              placeholder="进尺工作时间(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="其它生产时间(H)" prop="otherProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.otherProductionTime"
+              placeholder="其它生产时间(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+        </div>
+        <el-divider content-position="left">非生产时间</el-divider>
+        <div class="grid grid-cols-4 gap-4 mt-5">
+          <el-form-item label="事故(H)" prop="accidentTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.accidentTime"
+              placeholder="请输入事故(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="修理(H)" prop="repairTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.repairTime"
+              placeholder="请输入修理(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="自停(H)" prop="selfStopTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.selfStopTime"
+              placeholder="请输入自停(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="复杂(H)" prop="complexityTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.complexityTime"
+              placeholder="请输入复杂(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="搬迁(H)" prop="relocationTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.relocationTime"
+              placeholder="请输入搬迁(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="整改(H)" prop="rectificationTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.rectificationTime"
+              placeholder="请输入整改(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="等停(H)" prop="waitingStopTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.waitingStopTime"
+              placeholder="请输入等停(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+          <el-form-item label="冬休(H)" prop="winterBreakTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.winterBreakTime"
+              placeholder="请输入冬休(H)"
+              :class="{ 'orange-input': sumTimes() !== 24 }"
+            />
+          </el-form-item>
+        </div>
+        <div class="grid grid-cols-1 gap-4 mt-5">
+          <el-form-item label="生产动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              placeholder="请输入生产动态"
+              type="textarea"
+              autosize
+              :max-length="1000"
+            />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input
+              v-model="form.remark"
+              placeholder="请输入备注"
+              :max-length="1000"
+              type="textarea"
+              autosize
+            />
+          </el-form-item>
+        </div>
+      </el-form>
+      <template #footer>
+        <el-button size="default" @click="submitForm" type="primary" :disabled="formLoading">
+          确 定
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog>
+  </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(.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>

+ 48 - 30
src/views/pms/iotrydailyreport/index.vue

@@ -1,9 +1,17 @@
 <template>
   <el-row :gutter="20">
     <el-col :span="4" :xs="24">
-      <ContentWrap class="h-1/1">
+      <!-- <ContentWrap class="h-1/1">
         <DeptTree2 :deptId="rootDeptId" @node-click="handleDeptNodeClick" />
-      </ContentWrap>
+      </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>
@@ -102,7 +110,12 @@
             show-overflow-tooltip
             border
           >
-            <el-table-column :label="t('iotDevice.serial')" width="56px" align="center">
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
               <template #default="scope">
                 {{ scope.$index + 1 }}
               </template>
@@ -114,6 +127,7 @@
               :formatter="dateFormatter2"
               :min-width="columnWidths.createTime.width"
               resizable
+              fixed="left"
             />
             <el-table-column
               label="施工队伍"
@@ -121,34 +135,25 @@
               prop="deptName"
               :min-width="columnWidths.deptName.width"
               resizable
+              fixed="left"
             />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              :min-width="columnWidths.contractName.width"
-              resizable
-            />
+
             <el-table-column
               label="任务"
               align="center"
               prop="taskName"
               :min-width="columnWidths.taskName.width"
               resizable
+              fixed="left"
             />
-            <el-table-column
-              label="设备型号"
-              align="center"
-              prop="equipmentType"
-              :min-width="columnWidths.equipmentType.width"
-              resizable
-            />
+
             <el-table-column
               :label="t('project.status')"
               align="center"
               prop="rigStatus"
               :min-width="columnWidths.rigStatus.width"
               resizable
+              fixed="left"
             >
               <template #default="scope">
                 <dict-tag
@@ -157,6 +162,13 @@
                 />
               </template>
             </el-table-column>
+            <el-table-column
+              label="设备型号"
+              align="center"
+              prop="equipmentType"
+              :min-width="columnWidths.equipmentType.width"
+              resizable
+            />
             <el-table-column
               label="上井次完井时间"
               align="center"
@@ -258,7 +270,7 @@
               </el-table-column>
             </el-table-column>
 
-            <el-table-column
+            <!-- <el-table-column
               label="施工开始日期"
               align="center"
               prop="constructionStartDate"
@@ -273,7 +285,7 @@
               :formatter="dateFormatter"
               :min-width="columnWidths.constructionEndDate.width"
               resizable
-            />
+            /> -->
             <el-table-column
               label="水平段长度(m)"
               align="center"
@@ -308,6 +320,13 @@
               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"
@@ -424,12 +443,13 @@ import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyrep
 import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import { useDebounceFn } from '@vueuse/core'
 
+import { useUserStore } from '@/store/modules/user'
+
 dayjs.extend(quarterOfYear)
 
 /** 瑞鹰日报 列表 */
@@ -441,7 +461,7 @@ const { t } = useI18n() // 国际化
 // 添加 selectedRowData 响应式变量
 const selectedRowData = ref<Record<string, any> | null>(null)
 
-const rootDeptId = ref(158)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 const loading = ref(true) // 列表的加载中
 const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
@@ -449,7 +469,7 @@ const total = ref(0) // 列表的总页数
 let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -652,13 +672,13 @@ const columnWidths = ref<
     width: '120px'
   },
   selfStopTime: {
-    label: '修理(H)',
+    label: '自停(H)',
     prop: 'selfStopTime',
     width: '120px'
   },
   complexityTime: {
     label: '复杂(H)',
-    prop: 'selfStopTime',
+    prop: 'complexityTime',
     width: '120px'
   },
   relocationTime: {
@@ -837,6 +857,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -880,7 +901,7 @@ const selectedDept = ref<{ id: number; name: string }>()
 const handleDeptNodeClick = async (row) => {
   // 记录选中的部门信息
   selectedDept.value = { id: row.id, name: row.name }
-  queryParams.deptId = row.id
+  // queryParams.deptId = row.id
   await getList()
 }
 
@@ -907,11 +928,8 @@ const route = useRoute()
 /** 初始化 **/
 onMounted(() => {
   if (Object.keys(route.query).length > 0) {
-    queryParams = {
-      ...queryParams,
-      ...route.query,
-      deptId: Number(route.query.deptId) as any
-    }
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
     handleQuery()
   } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化

+ 38 - 83
src/views/pms/iotrydailyreport/summary.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import dayjs from 'dayjs'
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useDebounceFn } from '@vueuse/core'
@@ -13,7 +12,8 @@ import { rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 
 import { useUserStore } from '@/store/modules/user'
-import * as DeptApi from "@/api/system/dept";
+
+const deptId = useUserStore().getUser.deptId
 
 interface Query {
   pageNo: number
@@ -25,27 +25,18 @@ interface Query {
   projectClassification: 1 | 2
 }
 
-// 添加 DeptTree2 的 ref
-const deptTreeRef = ref<InstanceType<typeof DeptTree2>>()
-
-// 将 id 改为 ref,并根据条件动态赋值
-const id = ref(158)
-
-// 跟踪是否选择了部门树节点
-const hasSelectedDeptNode = ref(false)
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 158,  // 初始值,会在onMounted中根据条件调整
+  deptId: deptId,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
   projectClassification: 1
 })
 
-const dept = ref() // 当前登录人所属部门对象
-
 const totalWorkKeys: [string, string, string, string, number][] = [
   ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
   [
@@ -91,7 +82,7 @@ const getTotal = useDebounceFn(async () => {
 
   try {
     let res1: any[]
-    if (query.value.createTime && query.value.createTime.length !== 0) {
+    if (query.value.createTime && query.value.createTime.length === 2) {
       res1 = await IotRyDailyReportApi.ryDailyReportStatistics({
         createTime: query.value.createTime,
         projectClassification: query.value.projectClassification
@@ -106,6 +97,9 @@ const getTotal = useDebounceFn(async () => {
 
     totalWork.value = {
       ...totalWork.value,
+      totalFuelConsumption: 0,
+      totalPowerConsumption: 0,
+      totalFootage: 0,
       ...res2,
       totalGasInjection: (res2.totalGasInjection || 0) / 10000
     }
@@ -146,6 +140,10 @@ const columns = (type: string) => {
     {
       label: '累计油耗(吨)',
       prop: 'cumulativeFuelConsumption'
+    },
+    {
+      label: '平均时效(%)',
+      prop: 'transitTime'
     }
   ]
 }
@@ -153,34 +151,15 @@ const columns = (type: string) => {
 const listLoading = ref(false)
 
 const formatter = (row: List, column: any) => {
-  return row[column.property] ?? 0
+  if (column.property === 'transitTime') {
+    return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
+  } else return row[column.property] ?? 0
 }
 
 const getList = useDebounceFn(async () => {
   listLoading.value = true
   try {
-    // 创建查询参数,如果 createTime 为空则不传
-    const params: any = {
-      pageNo: query.value.pageNo,
-      pageSize: query.value.pageSize,
-      deptId: query.value.deptId,
-      projectClassification: query.value.projectClassification
-    }
-
-    // 只有 createTime 有值时才添加
-    if (query.value.createTime && query.value.createTime.length === 2) {
-      params.createTime = query.value.createTime
-    }
-
-    // 可选条件
-    if (query.value.contractName) {
-      params.contractName = query.value.contractName
-    }
-    if (query.value.taskName) {
-      params.taskName = query.value.taskName
-    }
-
-    const res = await IotRyDailyReportApi.getIotRyDailyReportSummary(params)
+    const res = await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)
 
     const { list: reslist } = res
 
@@ -290,10 +269,11 @@ const render = () => {
   }
 
   const maxVal = values.length === 0 ? 10000 : Math.max(...values)
-  const minVal = values.length === 0 ? 0 : Math.min(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values) > 0 ? 0 : Math.min(...values)
 
   const maxDigits = (Math.floor(maxVal) + '').length
-  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const minDigits = minVal === 0 ? 0 : (Math.floor(Math.abs(minVal)) + '').length
+
   const interval = Math.max(maxDigits, minDigits)
 
   const maxInterval = interval
@@ -332,7 +312,7 @@ const render = () => {
     },
     yAxis: {
       type: 'value',
-      min: minInterval,
+      min: -minInterval,
       max: maxInterval,
       interval: 1,
       axisLabel: {
@@ -368,9 +348,7 @@ const render = () => {
 }
 
 const handleDeptNodeClick = (node: any) => {
-  hasSelectedDeptNode.value = true // 标记用户已经选择了部门节点
-
-  query.value.deptId = node.id
+  deptName.value = node.name
   handleQuery()
 }
 
@@ -386,13 +364,10 @@ const handleQuery = (setPage = true) => {
 }
 
 const resetQuery = () => {
-  // 重置时,重置选择状态
-  hasSelectedDeptNode.value = false
-
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: id.value, // 使用动态计算的id值
+    deptId: deptId,
     createTime: [],
     projectClassification: 1
   }
@@ -401,44 +376,17 @@ const resetQuery = () => {
 
 watch(
   () => query.value.createTime,
-  () => handleQuery(false)
-)
-
-onMounted(async () => {
-  const deptId = useUserStore().getUser.deptId
-  dept.value = await DeptApi.getDept(deptId)
-  dept.value.parentId
-
-  // 根据条件动态设置id值
-  if (deptId.value === 158) {
-    // 情况1: 当前登录人部门id等于158,使用原逻辑
-    id.value = 158
-    query.value.deptId = 158
-  } else if (dept.value?.parentId === 158) {
-    // 情况2: 当前登录人上级部门id等于158
-    // 需要获取组织部门树的顶级节点id
-    await nextTick()
-    // 获取组织部门树的顶级节点id
-    if (deptTreeRef.value) {
-      try {
-        const topDeptId = await deptTreeRef.value.getTopDeptId()
-        id.value = topDeptId || 158
-      } catch (error) {
-        console.error('获取顶级部门失败:', error)
-        id.value = 158
-      }
-    } else {
-      id.value = 158
+  (val) => {
+    if (!val) {
+      totalWork.value.totalCount = 0
+      totalWork.value.notReported = 0
+      totalWork.value.alreadyReported = 0
     }
-
-    // 设置查询条件的部门id为当前登录人部门id
-    query.value.deptId = id.value
-  } else {
-    // 其他情况,使用默认值
-    id.value = 158
-    query.value.deptId = 158
+    handleQuery(false)
   }
+)
 
+onMounted(() => {
   handleQuery()
 })
 
@@ -513,7 +461,13 @@ const tolist = (id: number) => {
 <template>
   <div class="grid grid-cols-[16%_1fr] gap-5">
     <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4">
-      <DeptTree2 ref="deptTreeRef" :deptId="id" @node-click="handleDeptNodeClick" />
+      <!-- <DeptTree2 ref="deptTreeRef" :deptId="id" @node-click="handleDeptNodeClick" /> -->
+      <DeptTreeSelect
+        :deptId="id"
+        :top-id="158"
+        v-model="query.deptId"
+        @node-click="handleDeptNodeClick"
+      />
     </div>
     <div class="grid grid-rows-[62px_164px_1fr] h-full gap-5">
       <el-form
@@ -548,6 +502,7 @@ const tolist = (id: number) => {
               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>

+ 1018 - 0
src/views/pms/iotrydailyreport/xapproval.vue

@@ -0,0 +1,1018 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+
+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
+  nonProductionTime: number
+  ryNptReason: string
+  productionStatus: string
+  totalStaffNum: number
+  onDutyStaffNum: number
+  leaveStaffNum: number
+  status: number
+  auditStatus: number
+  opinion: string
+  offDutyStaffNum: number
+  remark: string
+}
+
+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: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    '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
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const productionTime = row.productionTime || 0
+  const nonProductionTime = row.nonProductionTime || 0
+  const ratedProductionTime = row.ratedProductionTime || 0
+
+  // 计算总和
+  const sum = productionTime + nonProductionTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - ratedProductionTime) < 0.01 // 使用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 > 15)
+      return {
+        color: 'blue',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['ratedProductionTime', 'productionTime', '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[]
+  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 FORM_KEYS = [
+  'id',
+  'deptId',
+  'projectId',
+  'taskId',
+  'deptName',
+  'contractName',
+  'taskName',
+  'repairStatus',
+  'technique',
+  'wellCategory',
+  'designWellDepth',
+  'casingPipeSize',
+  'wellControlLevel',
+  'currentOperation',
+  'nextPlan',
+  'transitTime',
+  'ratedProductionTime',
+  'productionTime',
+  'nonProductionTime',
+  'ryNptReason',
+  'productionStatus',
+  'totalStaffNum',
+  'onDutyStaffNum',
+  'leaveStaffNum',
+  'remark',
+  'opinion',
+  'createTime',
+  'dailyFuel'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRyDailyReportApi.getIotRyDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.auditStatus !== 10) {
+      formType.value = 'readonly'
+    }
+  } finally {
+  }
+}
+
+const formType = ref<'approval' | 'readonly'>('approval')
+
+function handleOpenForm(id: number, type: 'approval' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'approval')
+  }
+})
+
+const transitTime = computed(() => {
+  const cap = form.value.productionTime ?? 0
+  const gas = form.value.ratedProductionTime ?? 0
+
+  if (!gas) return { original: 0, value: '0%' }
+
+  const original = cap / gas
+  return { original, value: (original * 100).toFixed(2) + '%' }
+})
+
+const onDutyStaffNum = computed(() => {
+  return (form.value.totalStaffNum ?? 0) - (form.value.leaveStaffNum ?? 0)
+})
+
+const sumTimes = () => {
+  const { productionTime = 0, nonProductionTime = 0 } = form.value
+  return productionTime + nonProductionTime
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== form.value.ratedProductionTime) {
+    callback(new Error(`生产时间和非生产时间之和必须等于额定生产时间`))
+  } else {
+    callback()
+  }
+}
+
+const validateNptReason = (_rule: any, value: any, callback: any) => {
+  if ((form.value.nonProductionTime || 0) > 0 && !value) {
+    callback(new Error('非生产时间大于 0 时,必须选择原因'))
+  } else {
+    callback()
+  }
+}
+
+const timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  repairStatus: [{ required: true, message: '请输入施工状态', trigger: ['change', 'blur'] }],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
+
+  // 复用规则
+  productionTime: timeRuleItem,
+  nonProductionTime: timeRuleItem,
+  ratedProductionTime: timeRuleItem,
+
+  ryNptReason: [{ validator: validateNptReason, trigger: ['change', 'blur'] }]
+})
+
+watch(
+  [
+    () => form.value.productionTime,
+    () => form.value.nonProductionTime,
+    () => form.value.ratedProductionTime
+  ],
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField('nptReason')
+      if (sumTimes() === form.value.ratedProductionTime) {
+        formRef.value?.clearValidate(['productionTime', 'nonProductionTime', 'ratedProductionTime'])
+      }
+    })
+  }
+)
+
+const { t } = useI18n()
+
+const submitForm = async (auditStatus: 20 | 30) => {
+  if (!formRef.value) return
+
+  try {
+    // await formRef.value.validate()
+    formLoading.value = true
+    const { opinion, id } = form.value
+
+    const data = { id: id, auditStatus, opinion } as any
+
+    await IotRyDailyReportApi.approvalIotRyDailyReport(data)
+    message.success(auditStatus === 20 ? '通过成功' : '拒绝成功')
+    dialogVisible.value = false
+
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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, 'approval')"
+                        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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+        :disabled="formType === 'readonly'"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">运行时效:</span>
+                  生产时间/额定生产时间
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >100% 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  生产 + 非生产 = 额定生产
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠额定生产 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <!-- <div
+          v-if="form.opinion"
+          class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+        >
+          <Icon
+            icon="ep:warning-filled"
+            class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+          />
+          <div class="flex flex-col">
+            <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+            <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+              {{ form.opinion }}
+            </p>
+          </div>
+        </div> -->
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item :label="t('project.status')" prop="repairStatus">
+            <el-select v-model="form.repairStatus" placeholder="请选择" clearable disabled>
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(
+                  DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE
+                )"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item :label="t('project.technology')" prop="technique">
+            <el-select v-model="form.technique" placeholder="请选择" disabled>
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="井别" prop="wellCategory">
+            <el-input v-model="form.wellCategory" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="设计井深(m)" prop="designWellDepth">
+            <el-input v-model="form.designWellDepth" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="井控级别" prop="wellControlLevel">
+            <el-input v-model="form.wellControlLevel" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="套生段产管尺寸(mm)" prop="casingPipeSize">
+            <el-input v-model="form.casingPipeSize" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="当日油耗(L)" prop="dailyFuel">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyFuel"
+              placeholder="请输入当日油耗(L)"
+            />
+          </el-form-item>
+          <el-form-item :label="t('project.currentOperation')" prop="currentOperation">
+            <el-input
+              v-model="form.currentOperation"
+              placeholder="请输入目前工序"
+              type="textarea"
+              autosize
+              :maxlength="1000"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item :label="t('project.nextPlan')" prop="nextPlan">
+            <el-input
+              v-model="form.nextPlan"
+              placeholder="请输入下步工序"
+              type="textarea"
+              autosize
+              :maxlength="1000"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="运行时效" prop="transitTime">
+            <el-input
+              :model-value="transitTime.value"
+              placeholder="运行时效"
+              disabled
+              :class="{ 'warning-input': transitTime.original >= 1.0 }"
+              id="transitTimeInput"
+            />
+          </el-form-item>
+          <el-form-item label="额定生产时间(H)" prop="ratedProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.ratedProductionTime"
+              placeholder="请输入额定生产时间(H)"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="生产时间(H)" prop="productionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.productionTime"
+              placeholder="请输入生产时间(H)"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.nonProductionTime"
+              placeholder="非生产时间(H)"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间原因" prop="nptReason">
+            <el-select v-model="form.ryNptReason" placeholder="请选择" clearable disabled>
+              <el-option
+                v-for="(dict, index) of getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_NPT_REASON)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="全员数量" prop="totalStaffNum">
+            <el-input-number
+              :min="0"
+              v-model="form.totalStaffNum"
+              placeholder="请输入全员数量"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="在岗人数" prop="onDutyStaffNum">
+            <el-input-number :min="0" v-model="onDutyStaffNum" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="休假人员数量" prop="leaveStaffNum">
+            <el-input-number
+              :min="0"
+              v-model="form.leaveStaffNum"
+              placeholder="请输入休假人员数量"
+              disabled
+            />
+          </el-form-item>
+        </div>
+        <div class="grid grid-cols-1 gap-4 mt-5">
+          <el-form-item label="生产动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              placeholder="请输入生产动态"
+              type="textarea"
+              autosize
+              :max-length="1000"
+              disabled
+            />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input
+              v-model="form.remark"
+              placeholder="请输入备注"
+              :max-length="1000"
+              type="textarea"
+              autosize
+              disabled
+            />
+          </el-form-item>
+        </div>
+        <el-form-item class="mt-4" label="审批意见" prop="opinion">
+          <el-input
+            v-model="form.opinion"
+            placeholder="请输入审批意见"
+            :max-length="1000"
+            type="textarea"
+            autosize
+            :disabled="formType === 'readonly'"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button
+          size="default"
+          @click="submitForm(20)"
+          type="primary"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批通过
+        </el-button>
+        <el-button
+          size="default"
+          @click="submitForm(30)"
+          type="danger"
+          :disabled="formLoading || formType === 'readonly'"
+        >
+          审批拒绝
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog></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(.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>

+ 994 - 0
src/views/pms/iotrydailyreport/xfill.vue

@@ -0,0 +1,994 @@
+<script lang="ts" setup>
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { FormInstance, FormRules } from 'element-plus'
+import Form from '@/components/Form/src/Form.vue'
+import { useUserStore } from '@/store/modules/user'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+
+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
+  nonProductionTime: number
+  ryNptReason: string
+  productionStatus: string
+  totalStaffNum: number
+  onDutyStaffNum: number
+  leaveStaffNum: number
+  status: number
+  auditStatus: number
+  opinion: string
+  offDutyStaffNum: number
+  remark: string
+}
+
+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: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    '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
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const productionTime = row.productionTime || 0
+  const nonProductionTime = row.nonProductionTime || 0
+  const ratedProductionTime = row.ratedProductionTime || 0
+
+  // 计算总和
+  const sum = productionTime + nonProductionTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - ratedProductionTime) < 0.01 // 使用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 > 15)
+      return {
+        color: 'blue',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['ratedProductionTime', 'productionTime', '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[]
+  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 FORM_KEYS = [
+  'id',
+  'deptId',
+  'projectId',
+  'taskId',
+  'deptName',
+  'contractName',
+  'taskName',
+  'repairStatus',
+  'technique',
+  'wellCategory',
+  'designWellDepth',
+  'casingPipeSize',
+  'wellControlLevel',
+  'currentOperation',
+  'nextPlan',
+  'transitTime',
+  'ratedProductionTime',
+  'productionTime',
+  'nonProductionTime',
+  'ryNptReason',
+  'productionStatus',
+  'totalStaffNum',
+  'onDutyStaffNum',
+  'leaveStaffNum',
+  'remark',
+  'opinion',
+  'createTime',
+  'dailyFuel'
+] as const
+
+type FormKey = (typeof FORM_KEYS)[number]
+type Form = Partial<Pick<List, FormKey>>
+
+const dialogVisible = ref(false)
+const formRef = ref<FormInstance>()
+const formLoading = ref(false)
+const message = useMessage()
+
+const initFormData = (): Form => ({})
+
+const form = ref<Form>(initFormData())
+
+async function loadDetail(id: number) {
+  try {
+    const res = await IotRyDailyReportApi.getIotRyDailyReport(id)
+    FORM_KEYS.forEach((key) => {
+      form.value[key] = res[key] ?? form.value[key]
+    })
+    form.value.id = id
+
+    if (res.status !== 0) {
+      formType.value = 'readonly'
+    }
+  } finally {
+  }
+}
+
+const formType = ref<'edit' | 'readonly'>('edit')
+
+function handleOpenForm(id: number, type: 'edit' | 'readonly') {
+  form.value = initFormData()
+  formRef.value?.resetFields()
+
+  formType.value = type
+
+  dialogVisible.value = true
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
+}
+
+const route = useRoute()
+
+onMounted(() => {
+  if (Object.keys(route.query).length > 0) {
+    handleOpenForm(Number(route.query.id), 'edit')
+  }
+})
+
+const transitTime = computed(() => {
+  const cap = form.value.productionTime ?? 0
+  const gas = form.value.ratedProductionTime ?? 0
+
+  if (!gas) return { original: 0, value: '0%' }
+
+  const original = cap / gas
+  return { original, value: (original * 100).toFixed(2) + '%' }
+})
+
+const onDutyStaffNum = computed(() => {
+  return (form.value.totalStaffNum ?? 0) - (form.value.leaveStaffNum ?? 0)
+})
+
+const sumTimes = () => {
+  const { productionTime = 0, nonProductionTime = 0 } = form.value
+  return productionTime + nonProductionTime
+}
+
+const validateTotalTime = (_rule: any, _value: any, callback: any) => {
+  const total = sumTimes()
+  if (total !== form.value.ratedProductionTime) {
+    callback(new Error(`生产时间和非生产时间之和必须等于额定生产时间`))
+  } else {
+    callback()
+  }
+}
+
+const validateNptReason = (_rule: any, value: any, callback: any) => {
+  if ((form.value.nonProductionTime || 0) > 0 && !value) {
+    callback(new Error('非生产时间大于 0 时,必须选择原因'))
+  } else {
+    callback()
+  }
+}
+
+const _timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  repairStatus: [{ required: true, message: '请输入施工状态', trigger: ['change', 'blur'] }],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
+
+  // 复用规则
+  // productionTime: timeRuleItem,
+  // nonProductionTime: timeRuleItem,
+  // ratedProductionTime: timeRuleItem,
+
+  ryNptReason: [{ validator: validateNptReason, trigger: ['change', 'blur'] }]
+})
+
+watch(
+  [
+    () => form.value.productionTime,
+    () => form.value.nonProductionTime,
+    () => form.value.ratedProductionTime
+  ],
+  () => {
+    nextTick(() => {
+      formRef.value?.validateField('nptReason')
+      if (sumTimes() === form.value.ratedProductionTime) {
+        formRef.value?.clearValidate(['productionTime', 'nonProductionTime', 'ratedProductionTime'])
+      }
+    })
+  }
+)
+
+const { t } = useI18n()
+
+const submitForm = async () => {
+  if (!formRef.value) return
+
+  try {
+    await formRef.value.validate()
+    formLoading.value = true
+
+    const total = sumTimes()
+    if (total !== form.value.ratedProductionTime) {
+      message.error(`生产时间和非生产时间之和必须等于额定生产时间`)
+    }
+
+    const { createTime, ...other } = form.value
+
+    const data = { ...other, fillOrderCreateTime: createTime, projectClassification: '2' } as any
+    await IotRyDailyReportApi.createIotRyDailyReport(data)
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    loadList()
+  } catch (error) {
+    console.warn('表单校验未通过或提交出错')
+  } finally {
+    formLoading.value = false
+  }
+}
+</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>
+    <Dialog title="编辑" v-model="dialogVisible">
+      <el-form
+        ref="formRef"
+        label-position="top"
+        size="default"
+        :rules="rules"
+        :model="form"
+        v-loading="formLoading"
+        require-asterisk-position="right"
+        :disabled="formType === 'readonly'"
+      >
+        <div class="flex flex-col gap-3 text-sm">
+          <div
+            class="rounded-md border border-blue-100 bg-blue-50/80 p-3 dark:border-blue-900/30 dark:bg-blue-900/10"
+          >
+            <div class="flex flex-col gap-2.5">
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">运行时效:</span>
+                  生产时间/额定生产时间
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-red-200 bg-red-100 px-2 py-0.5 text-xs font-medium text-red-600 dark:bg-red-900/20 dark:border-red-800 dark:text-red-400"
+                >
+                  >100% 红色预警
+                </span>
+              </div>
+              <div class="flex items-center justify-between">
+                <div class="text-gray-600 dark:text-gray-400">
+                  <span class="font-bold text-gray-800 dark:text-gray-200">时间平衡:</span>
+                  生产 + 非生产 = 额定生产
+                </div>
+                <span
+                  class="inline-flex items-center rounded border border-orange-200 bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-600 dark:bg-orange-900/20 dark:border-orange-800 dark:text-orange-400"
+                >
+                  ≠额定生产 橙色预警
+                </span>
+              </div>
+            </div>
+          </div>
+          <div
+            v-if="form.opinion"
+            class="flex gap-3 rounded-md border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/10"
+          >
+            <Icon
+              icon="ep:warning-filled"
+              class="mt-0.5 shrink-0 text-base text-yellow-600 dark:text-yellow-500"
+            />
+            <div class="flex flex-col">
+              <h4 class="mb-1 font-bold text-yellow-800 dark:text-yellow-500"> 审核意见 </h4>
+              <p class="leading-relaxed text-gray-600 dark:text-gray-400">
+                {{ form.opinion }}
+              </p>
+            </div>
+          </div>
+        </div>
+        <div class="grid grid-cols-2 gap-4 mt-5">
+          <el-form-item label="施工队伍" prop="deptName">
+            <el-input v-model="form.deptName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="项目" prop="contractName">
+            <el-input v-model="form.contractName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="任务" prop="taskName">
+            <el-input v-model="form.taskName" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item :label="t('project.status')" prop="repairStatus">
+            <el-select v-model="form.repairStatus" placeholder="请选择" clearable>
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(
+                  DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_SCHEDULE
+                )"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item :label="t('project.technology')" prop="technique">
+            <el-select v-model="form.technique" placeholder="请选择" disabled>
+              <el-option
+                v-for="(dict, index) in getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <!-- <el-form-item label="运行时效" prop="transitTime">
+          <el-input
+            :model-value="transitTime.value"
+            placeholder="运行时效"
+            disabled
+            :class="{ 'warning-input': transitTime.original > 1.0 }"
+            id="transitTimeInput"
+          />
+        </el-form-item> -->
+          <el-form-item label="井别" prop="wellCategory">
+            <el-input v-model="form.wellCategory" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="设计井深(m)" prop="designWellDepth">
+            <el-input v-model="form.designWellDepth" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="井控级别" prop="wellControlLevel">
+            <el-input v-model="form.wellControlLevel" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="套生段产管尺寸(mm)" prop="casingPipeSize">
+            <el-input v-model="form.casingPipeSize" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="当日油耗(L)" prop="dailyFuel">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.dailyFuel"
+              placeholder="请输入当日油耗(L)"
+            />
+          </el-form-item>
+          <el-form-item :label="t('project.currentOperation')" prop="currentOperation">
+            <el-input
+              v-model="form.currentOperation"
+              placeholder="请输入目前工序"
+              type="textarea"
+              autosize
+              :maxlength="1000"
+            />
+          </el-form-item>
+          <el-form-item :label="t('project.nextPlan')" prop="nextPlan">
+            <el-input
+              v-model="form.nextPlan"
+              placeholder="请输入下步工序"
+              type="textarea"
+              autosize
+              :maxlength="1000"
+            />
+          </el-form-item>
+          <el-form-item label="运行时效" prop="transitTime">
+            <el-input
+              :model-value="transitTime.value"
+              placeholder="运行时效"
+              disabled
+              :class="{ 'warning-input': transitTime.original >= 1.0 }"
+              id="transitTimeInput"
+            />
+          </el-form-item>
+          <el-form-item label="额定生产时间(H)" prop="ratedProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.ratedProductionTime"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              placeholder="请输入额定生产时间(H)"
+            />
+          </el-form-item>
+          <el-form-item label="生产时间(H)" prop="productionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.productionTime"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              placeholder="请输入生产时间(H)"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+            <el-input-number
+              class="w-full!"
+              :min="0"
+              v-model="form.nonProductionTime"
+              :class="{ 'orange-input': sumTimes() !== Number(form.ratedProductionTime ?? 0) }"
+              placeholder="非生产时间(H)"
+            />
+          </el-form-item>
+          <el-form-item label="非生产时间原因" prop="nptReason">
+            <el-select v-model="form.ryNptReason" placeholder="请选择" clearable>
+              <el-option
+                v-for="(dict, index) of getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_NPT_REASON)"
+                :key="index"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="全员数量" prop="totalStaffNum">
+            <el-input-number :min="0" v-model="form.totalStaffNum" placeholder="请输入全员数量" />
+          </el-form-item>
+          <el-form-item label="在岗人数" prop="onDutyStaffNum">
+            <el-input-number :min="0" v-model="onDutyStaffNum" placeholder="" disabled />
+          </el-form-item>
+          <el-form-item label="休假人员数量" prop="leaveStaffNum">
+            <el-input-number
+              :min="0"
+              v-model="form.leaveStaffNum"
+              placeholder="请输入休假人员数量"
+            />
+          </el-form-item>
+        </div>
+        <div class="grid grid-cols-1 gap-4 mt-5">
+          <el-form-item label="生产动态" prop="productionStatus">
+            <el-input
+              v-model="form.productionStatus"
+              placeholder="请输入生产动态"
+              type="textarea"
+              autosize
+              :max-length="1000"
+            />
+          </el-form-item>
+          <el-form-item label="备注" prop="remark">
+            <el-input
+              v-model="form.remark"
+              placeholder="请输入备注"
+              :max-length="1000"
+              type="textarea"
+              autosize
+            /> </el-form-item
+        ></div>
+      </el-form>
+      <template #footer>
+        <el-button size="default" @click="submitForm" type="primary" :disabled="formLoading">
+          确 定
+        </el-button>
+        <el-button size="default" @click="dialogVisible = false">取 消</el-button>
+      </template>
+    </Dialog></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(.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>

+ 36 - 18
src/views/pms/iotrydailyreport/xjindex.vue

@@ -1,9 +1,17 @@
 <template>
   <el-row :gutter="20">
     <el-col :span="4" :xs="24">
-      <ContentWrap class="h-1/1">
+      <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>
+      </ContentWrap> -->
     </el-col>
     <el-col :span="20" :xs="24">
       <ContentWrap>
@@ -108,7 +116,12 @@
             show-overflow-tooltip
             border
           >
-            <el-table-column :label="t('iotDevice.serial')" width="56px" align="center">
+            <el-table-column
+              :label="t('iotDevice.serial')"
+              width="56px"
+              align="center"
+              fixed="left"
+            >
               <template #default="scope">
                 {{ scope.$index + 1 }}
               </template>
@@ -120,6 +133,7 @@
               :formatter="dateFormatter2"
               :min-width="columnWidths.createTime.width"
               resizable
+              fixed="left"
             />
             <el-table-column
               label="施工队伍"
@@ -127,20 +141,16 @@
               prop="deptName"
               :min-width="columnWidths.deptName.width"
               resizable
+              fixed="left"
             />
-            <el-table-column
-              label="项目"
-              align="center"
-              prop="contractName"
-              :min-width="columnWidths.contractName.width"
-              resizable
-            />
+
             <el-table-column
               label="任务"
               align="center"
               prop="taskName"
               :min-width="columnWidths.taskName.width"
               resizable
+              fixed="left"
             />
             <el-table-column
               :label="t('project.status')"
@@ -148,6 +158,7 @@
               prop="repairStatus"
               :min-width="columnWidths.repairStatus.width"
               resizable
+              fixed="left"
             >
               <template #default="scope">
                 <dict-tag
@@ -310,6 +321,13 @@
               :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"
@@ -384,9 +402,10 @@ import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyrep
 import IotRyXjDailyReportForm from './IotRyXjDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import { useDebounceFn } from '@vueuse/core'
 
+import { useUserStore } from '@/store/modules/user'
+
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 
@@ -407,7 +426,7 @@ const total = ref(0) // 列表的总页数
 let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -449,7 +468,7 @@ let queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
-const rootDeptId = ref(158)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 // 表格引用
 const tableRef = ref()
@@ -834,6 +853,7 @@ const getList = async () => {
 // 百分比格式化函数
 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 '-'
   // 将小数转换为百分比,保留两位小数
@@ -849,6 +869,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -920,11 +941,8 @@ const route = useRoute()
 /** 初始化 **/
 onMounted(() => {
   if (Object.keys(route.query).length > 0) {
-    queryParams = {
-      ...queryParams,
-      ...route.query,
-      deptId: Number(route.query.deptId) as any
-    }
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
     handleQuery()
   } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化

+ 40 - 12
src/views/pms/iotrydailyreport/xsummary.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import dayjs from 'dayjs'
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useDebounceFn } from '@vueuse/core'
@@ -12,6 +11,10 @@ import { Motion, AnimatePresence } from 'motion-v'
 import { rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 
+import { useUserStore } from '@/store/modules/user'
+
+const deptId = useUserStore().getUser.deptId
+
 interface Query {
   pageNo: number
   pageSize: number
@@ -22,12 +25,12 @@ interface Query {
   projectClassification: 1 | 2
 }
 
-const id = 158
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 158,
+  deptId: deptId,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
@@ -81,7 +84,7 @@ const getTotal = useDebounceFn(async () => {
 
   try {
     let res1: any[]
-    if (query.value.createTime.length !== 0) {
+    if (query.value.createTime && query.value.createTime.length === 2) {
       res1 = await IotRyDailyReportApi.ryDailyReportStatistics({
         createTime: query.value.createTime,
         projectClassification: query.value.projectClassification
@@ -96,6 +99,10 @@ const getTotal = useDebounceFn(async () => {
 
     totalWork.value = {
       ...totalWork.value,
+      totalFuelConsumption: 0,
+      totalPowerConsumption: 0,
+      constructionWells: 0,
+      completedWells: 0,
       ...res2
     }
   } finally {
@@ -139,6 +146,10 @@ const columns = (type: string) => {
     {
       label: '累计油耗(吨)',
       prop: 'cumulativeFuelConsumption'
+    },
+    {
+      label: '平均时效(%)',
+      prop: 'transitTime'
     }
   ]
 }
@@ -146,7 +157,9 @@ const columns = (type: string) => {
 const listLoading = ref(false)
 
 const formatter = (row: List, column: any) => {
-  return row[column.property] ?? 0
+  if (column.property === 'transitTime') {
+    return (Number(row.transitTime ?? 0) * 100).toFixed(2) + '%'
+  } else return row[column.property] ?? 0
 }
 
 const getList = useDebounceFn(async () => {
@@ -254,10 +267,10 @@ const render = () => {
   }
 
   const maxVal = values.length === 0 ? 10000 : Math.max(...values)
-  const minVal = values.length === 0 ? 0 : Math.min(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values) > 0 ? 0 : Math.min(...values)
 
   const maxDigits = (Math.floor(maxVal) + '').length
-  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const minDigits = minVal === 0 ? 0 : (Math.floor(Math.abs(minVal)) + '').length
   const interval = Math.max(maxDigits, minDigits)
 
   const maxInterval = interval
@@ -296,7 +309,7 @@ const render = () => {
     },
     yAxis: {
       type: 'value',
-      min: minInterval,
+      min: -minInterval,
       max: maxInterval,
       interval: 1,
       axisLabel: {
@@ -332,7 +345,8 @@ const render = () => {
 }
 
 const handleDeptNodeClick = (node: any) => {
-  query.value.deptId = node.id
+  deptName.value = node.name
+  // query.value.deptId = node.id
   handleQuery()
 }
 
@@ -351,7 +365,7 @@ const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     createTime: [],
     projectClassification: 1
   }
@@ -360,7 +374,14 @@ const resetQuery = () => {
 
 watch(
   () => query.value.createTime,
-  () => handleQuery(false)
+  (val) => {
+    if (!val) {
+      totalWork.value.totalCount = 0
+      totalWork.value.notReported = 0
+      totalWork.value.alreadyReported = 0
+    }
+    handleQuery(false)
+  }
 )
 
 onMounted(() => {
@@ -438,7 +459,13 @@ const tolist = (id: number) => {
 <template>
   <div class="grid grid-cols-[16%_1fr] gap-5">
     <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4">
-      <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" />
+      <!-- <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" /> -->
+      <DeptTreeSelect
+        :deptId="id"
+        :top-id="158"
+        v-model="query.deptId"
+        @node-click="handleDeptNodeClick"
+      />
     </div>
     <div class="grid grid-rows-[62px_164px_1fr] h-full gap-5">
       <el-form
@@ -473,6 +500,7 @@ const tolist = (id: number) => {
               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>

+ 26 - 18
src/views/pms/maintenance/index.vue

@@ -45,9 +45,12 @@
             />
           </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 @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
@@ -79,8 +82,12 @@
           </el-table-column>
           <el-table-column :label="t('main.planCode')" align="center" prop="serialNumber" />
           <el-table-column :label="t('main.planName')" align="center" prop="name" />
-          <el-table-column :label="t('iotMaintain.PersonInCharge')" align="center" prop="responsiblePersonName" />
-          <el-table-column :label="t('maintain.status')" align="center" prop="status" >
+          <el-table-column
+            :label="t('iotMaintain.PersonInCharge')"
+            align="center"
+            prop="responsiblePersonName"
+          />
+          <el-table-column :label="t('maintain.status')" align="center" prop="status">
             <template #default="scope">
               <el-switch
                 v-model="scope.row.status"
@@ -94,7 +101,7 @@
             :label="t('operationFill.createTime')"
             align="center"
             prop="createTime"
-            :formatter="dateFormatter"
+            :formatter="dateFormatter2"
             width="180px"
           />
           <el-table-column :label="t('maintain.operation')" align="center" min-width="120px">
@@ -131,11 +138,11 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
+import { dateFormatter2 } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotMaintenancePlanApi, IotMaintenancePlanVO } from '@/api/pms/maintenance'
-import DeptTree from "@/views/system/user/DeptTree.vue";
-import {CommonStatusEnum} from "@/utils/constants";
+import DeptTree from '@/views/system/user/DeptTree.vue'
+import { CommonStatusEnum } from '@/utils/constants'
 const { push } = useRouter() // 路由跳转
 
 /** 保养计划 列表 */
@@ -157,7 +164,7 @@ const queryParams = reactive({
   responsiblePersonName: undefined,
   remark: undefined,
   status: undefined,
-  createTime: [],
+  createTime: []
 })
 
 // 响应式变量存储选中的部门
@@ -197,14 +204,14 @@ const resetQuery = () => {
 }
 
 const detail = (id?: number) => {
-  push({ name: 'IotMaintenancePlanDetail', params:{id} })
+  push({ name: 'IotMaintenancePlanDetail', params: { id } })
 }
 
 /** 添加/修改操作 */
 const openForm = (type: string, id?: number) => {
   //修改
   if (typeof id === 'number') {
-    push({ name: 'IotMainPlanEdit', params: {id } })
+    push({ name: 'IotMainPlanEdit', params: { id } })
     return
   }
   // 新增 保养计划
@@ -219,14 +226,15 @@ const openForm = (type: string, id?: number) => {
 
 const handleStatusChange = async (row: IotMaintenancePlanVO) => {
   try {
-    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用';
-    await message.confirm('确认要"' + text + '""' + row.name + '" 保养计划吗?');
-    await IotMaintenancePlanApi.updatePlanStatus(row.id, row.status);
-    await getList();
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.name + '" 保养计划吗?')
+    await IotMaintenancePlanApi.updatePlanStatus(row.id, row.status)
+    await getList()
   } catch {
-    row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
   }
-};
+}
 
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {

+ 1 - 0
types/global.d.ts

@@ -48,6 +48,7 @@ declare global {
   interface Tree {
     id: number
     name: string
+    sort?: number
     children?: Tree[] | any[]
   }
   // 分页数据公共返回

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott