Răsfoiți Sursa

设备成套合并更改

yanghao 1 zi în urmă
părinte
comite
424822b6e0
35 a modificat fișierele cu 9314 adăugiri și 994 ștergeri
  1. 1 0
      package.json
  2. 58 0
      pnpm-lock.yaml
  3. 15 0
      src/api/pms/iotrhdailyreport/index.ts
  4. 19 0
      src/api/pms/iotrydailyreport/index.ts
  5. 59 0
      src/components/DailyTableColumn/index.vue
  6. 161 0
      src/components/DeptTreeSelect/index.vue
  7. 31 16
      src/config/axios/service.ts
  8. 1 1
      src/layout/components/AppView.vue
  9. 38 7
      src/layout/components/Message/src/Message.vue
  10. 1 1
      src/router/modules/remaining.ts
  11. 20 6
      src/utils/formatTime.ts
  12. 80 41
      src/views/pms/device/monitor/TdDeviceInfo.vue
  13. 239 50
      src/views/pms/device/monitor/color.ts
  14. 28 1
      src/views/pms/dingding.vue
  15. 5 0
      src/views/pms/iotprojectinfo/index.vue
  16. 74 3
      src/views/pms/iotprojecttask/IotProjectTaskForm.vue
  17. 780 22
      src/views/pms/iotrddailyreport/FillDailyReportForm.vue
  18. 9 12
      src/views/pms/iotrddailyreport/index.vue
  19. 8 0
      src/views/pms/iotrddailyreport/statistics.vue
  20. 62 2
      src/views/pms/iotrhdailyreport/DeptTree2.vue
  21. 898 0
      src/views/pms/iotrhdailyreport/approval.vue
  22. 878 0
      src/views/pms/iotrhdailyreport/fill.vue
  23. 56 120
      src/views/pms/iotrhdailyreport/index.vue
  24. 386 162
      src/views/pms/iotrhdailyreport/summary.vue
  25. 4 1
      src/views/pms/iotrydailyreport/IotRyXjDailyReportForm.vue
  26. 259 0
      src/views/pms/iotrydailyreport/UnfilledReportDialog.vue
  27. 1163 0
      src/views/pms/iotrydailyreport/approval.vue
  28. 1168 0
      src/views/pms/iotrydailyreport/fill.vue
  29. 67 132
      src/views/pms/iotrydailyreport/index.vue
  30. 375 151
      src/views/pms/iotrydailyreport/summary.vue
  31. 989 0
      src/views/pms/iotrydailyreport/xapproval.vue
  32. 962 0
      src/views/pms/iotrydailyreport/xfill.vue
  33. 50 114
      src/views/pms/iotrydailyreport/xjindex.vue
  34. 369 152
      src/views/pms/iotrydailyreport/xsummary.vue
  35. 1 0
      types/global.d.ts

+ 1 - 0
package.json

@@ -69,6 +69,7 @@
     "min-dash": "^4.1.1",
     "mitt": "^3.0.1",
     "moment": "^2.30.1",
+    "motion-v": "^1.7.4",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",

+ 58 - 0
pnpm-lock.yaml

@@ -140,6 +140,9 @@ importers:
       moment:
         specifier: ^2.30.1
         version: 2.30.1
+      motion-v:
+        specifier: ^1.7.4
+        version: 1.7.4(@vueuse/core@10.11.1(vue@3.5.12(typescript@5.3.3)))(vue@3.5.12(typescript@5.3.3))
       nprogress:
         specifier: ^0.2.0
         version: 0.2.0
@@ -3390,6 +3393,20 @@ packages:
   fraction.js@4.3.7:
     resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
 
+  framer-motion@12.23.12:
+    resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==}
+    peerDependencies:
+      '@emotion/is-prop-valid': '*'
+      react: ^18.0.0 || ^19.0.0
+      react-dom: ^18.0.0 || ^19.0.0
+    peerDependenciesMeta:
+      '@emotion/is-prop-valid':
+        optional: true
+      react:
+        optional: true
+      react-dom:
+        optional: true
+
   fs-extra@10.1.0:
     resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
     engines: {node: '>=12'}
@@ -3524,6 +3541,9 @@ packages:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
 
+  hey-listen@1.0.8:
+    resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
+
   highlight.js@11.10.0:
     resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
     engines: {node: '>=12.0.0'}
@@ -4055,6 +4075,18 @@ packages:
   moment@2.30.1:
     resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
 
+  motion-dom@12.23.12:
+    resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==}
+
+  motion-utils@12.23.6:
+    resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+
+  motion-v@1.7.4:
+    resolution: {integrity: sha512-YNDUAsany04wfI7YtHxQK3kxzNvh+OdFUk9GpA3+hMt7j6P+5WrVAAgr8kmPPoVza9EsJiAVhqoN3YYFN0Twrw==}
+    peerDependencies:
+      '@vueuse/core': '>=10.0.0'
+      vue: '>=3.0.0'
+
   mpd-parser@0.22.1:
     resolution: {integrity: sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==}
     hasBin: true
@@ -8890,6 +8922,12 @@ snapshots:
 
   fraction.js@4.3.7: {}
 
+  framer-motion@12.23.12:
+    dependencies:
+      motion-dom: 12.23.12
+      motion-utils: 12.23.6
+      tslib: 2.8.1
+
   fs-extra@10.1.0:
     dependencies:
       graceful-fs: 4.2.11
@@ -9027,6 +9065,8 @@ snapshots:
 
   he@1.2.0: {}
 
+  hey-listen@1.0.8: {}
+
   highlight.js@11.10.0: {}
 
   htm@3.1.1: {}
@@ -9521,6 +9561,24 @@ snapshots:
 
   moment@2.30.1: {}
 
+  motion-dom@12.23.12:
+    dependencies:
+      motion-utils: 12.23.6
+
+  motion-utils@12.23.6: {}
+
+  motion-v@1.7.4(@vueuse/core@10.11.1(vue@3.5.12(typescript@5.3.3)))(vue@3.5.12(typescript@5.3.3)):
+    dependencies:
+      '@vueuse/core': 10.11.1(vue@3.5.12(typescript@5.3.3))
+      framer-motion: 12.23.12
+      hey-listen: 1.0.8
+      motion-dom: 12.23.12
+      vue: 3.5.12(typescript@5.3.3)
+    transitivePeerDependencies:
+      - '@emotion/is-prop-valid'
+      - react
+      - react-dom
+
   mpd-parser@0.22.1:
     dependencies:
       '@babel/runtime': 7.26.0

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

@@ -47,6 +47,10 @@ export const IotRhDailyReportApi = {
     return await request.get({ url: `/pms/iot-rh-daily-report/statistics`, params })
   },
 
+  getIotRhDailyReportSummaryPolyline: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rh-daily-report/polylineStatistics`, params })
+  },
+
   // 累计工作量统计
   totalWorkload: async (params: any) => {
     return await request.get({ url: `/pms/iot-rh-daily-report/totalWorkload`, params })
@@ -57,6 +61,13 @@ export const IotRhDailyReportApi = {
     return await request.get({ url: `/pms/iot-rh-daily-report/rhDailyReportStatistics`, params })
   },
 
+  exportRhDailyReportStatistics: async (params: any) => {
+    return await request.download({
+      url: `/pms/iot-rh-daily-report/exportStatistics`,
+      params
+    })
+  },
+
   // 按照日期查询瑞恒日报统计数据 未填报队伍明细
   rhUnReportDetails: async (params: any) => {
     return await request.get({ url: `/pms/iot-rh-daily-report/rhUnReportDetails`, params })
@@ -82,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 })

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

@@ -54,6 +54,21 @@ export interface IotRyDailyReportVO {
 
 // 瑞鹰日报 API
 export const IotRyDailyReportApi = {
+  exportRyDailyReportStatistics: async (params: any) => {
+    return await request.download({
+      url: `/pms/iot-ry-daily-report/exportStatistics`,
+      params
+    })
+  },
+
+  ryUnReportDetails: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/ryUnReportDetails`, params })
+  },
+
+  getIotRyDailyReportSummaryPolyline: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/polylineStatistics`, params })
+  },
+
   // 查询瑞鹰日报分页
   getIotRyDailyReportPage: async (params: any) => {
     return await request.get({ url: `/pms/iot-ry-daily-report/page`, params })
@@ -96,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>

+ 31 - 16
src/config/axios/service.ts

@@ -194,34 +194,46 @@ service.interceptors.response.use(
 
       const requestUrl = response.config.url || ''
       // 判断是否包含rq/iot路径
-      if (requestUrl.includes('rq/')||requestUrl.includes('system/dict')||requestUrl.includes('system/auth/get-permission-info')||requestUrl.includes('system/dept/list')
-        ||requestUrl.includes('system/menu/simple-list')||requestUrl.includes('system/menu/list')||requestUrl.includes('system/dept/simple-list')
-        ||requestUrl.includes('pms/')||requestUrl.includes('system/user/page')||requestUrl.includes('supplier/base/page')||requestUrl.includes('system/dept/get')
-        ||requestUrl.includes('system/user/simpleUserList')||requestUrl.includes('system/dept/companyLevelDepts')||requestUrl.includes('system/dept/companyLevelChildrenDepts')
-        ||requestUrl.includes('system/user/companyDeptsEmployee')||requestUrl.includes('system/dept/specifiedSimpleDepts')) {
+      if (
+        requestUrl.includes('rq/') ||
+        requestUrl.includes('system/dict') ||
+        requestUrl.includes('system/auth/get-permission-info') ||
+        requestUrl.includes('system/dept/list') ||
+        requestUrl.includes('system/menu/simple-list') ||
+        requestUrl.includes('system/menu/list') ||
+        requestUrl.includes('system/dept/simple-list') ||
+        requestUrl.includes('pms/') ||
+        requestUrl.includes('system/user/page') ||
+        requestUrl.includes('supplier/base/page') ||
+        requestUrl.includes('system/dept/get') ||
+        requestUrl.includes('system/user/simpleUserList') ||
+        requestUrl.includes('system/dept/companyLevelDepts') ||
+        requestUrl.includes('system/dept/companyLevelChildrenDepts') ||
+        requestUrl.includes('system/user/companyDeptsEmployee') ||
+        requestUrl.includes('system/dept/specifiedSimpleDepts')
+      ) {
         const localeStore = useLocaleStore()
         const lang = localeStore.getCurrentLocale.lang
-        if (data&& data.data) {
+        if (data && data.data) {
           if (data.data.list) {
             if (Array.isArray(data.data.list)) {
               const list = langHelper.transformArray(data.data.list, lang)
-              data.data.list = list;
-              return data;
+              data.data.list = list
+              return data
             }
-          }else if (data &&Array.isArray(data.data)) {
+          } else if (data && Array.isArray(data.data)) {
             const list = langHelper.transformArray(data.data, lang)
-            data.data = list;
-            return data;
-          }else if (data && typeof data.data === 'object') {
-            const object =  langHelper.transformObject(data, lang)
+            data.data = list
+            return data
+          } else if (data && typeof data.data === 'object') {
+            const object = langHelper.transformObject(data, lang)
             data = object
             return data
           } else {
             return data
           }
         }
-
-      }else {
+      } else {
         return data
       }
       // return data
@@ -230,6 +242,7 @@ service.interceptors.response.use(
   (error: AxiosError) => {
     console.log('err' + error) // for debug
     let { message } = error
+    console.log('message :>> ', message)
     const { t } = useI18n()
     if (message === 'Network Error') {
       message = t('sys.api.errorMessage')
@@ -237,13 +250,15 @@ service.interceptors.response.use(
       message = t('sys.api.apiTimeoutMessage')
     } else if (message.includes('Request failed with status code')) {
       message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3)
+    } else if (message.includes('canceled')) {
+      return Promise.reject(error)
     }
     ElMessage.error(message)
     return Promise.reject(error)
   }
 )
 
-const isSystemPagePath = (path: string): boolean=> {
+const isSystemPagePath = (path: string): boolean => {
   // 正则说明:
   // ^.*system/ 匹配开头任意字符直到system/
   // (?:[^/]+/)* 匹配零个或多个非斜杠字符组成的路径段

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

@@ -38,7 +38,7 @@ provide('reload', reload)
     :class="[
       'p-[var(--app-content-padding)] w-full bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
       {
-        '!min-h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] pb-0':
+        'h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] !min-h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] pb-0':
           footer
       }
     ]"

+ 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>

+ 1 - 1
src/router/modules/remaining.ts

@@ -536,7 +536,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
         component: () => import('@/views/pms/device/monitor/TdDeviceInfo.vue'),
         name: 'TdDeviceDetail',
         meta: {
-          noCache: false,
+          noCache: true,
           hidden: true,
           canTo: true,
           icon: 'ep:info',

+ 20 - 6
src/utils/formatTime.ts

@@ -1,11 +1,15 @@
 import dayjs from 'dayjs'
+import quarter from 'dayjs/plugin/quarterOfYear'
+
+dayjs.extend(quarter)
+
 import type { TableColumnCtx } from 'element-plus'
 
 /**
  * 日期快捷选项适用于 el-date-picker
  */
 
-export  const rangeShortcuts = [
+export const rangeShortcuts = [
   {
     text: '今天',
     value: () => {
@@ -23,14 +27,16 @@ export  const rangeShortcuts = [
   {
     text: '本周',
     value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
+      return [dayjs().subtract(6, 'day').startOf('day').toDate(), dayjs().endOf('day').toDate()]
     }
   },
   {
     text: '上周',
     value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
+      return [
+        dayjs().subtract(13, 'day').startOf('day').toDate(),
+        dayjs().subtract(7, 'day').endOf('day').toDate()
+      ]
     }
   },
   {
@@ -291,7 +297,11 @@ export function formatPast2(ms: number): string {
  * @param column 字段
  * @param cellValue 字段值
  */
-export function dateFormatter(_row: any, _column: TableColumnCtx<any> | null, cellValue: any): string {
+export function dateFormatter(
+  _row: any,
+  _column: TableColumnCtx<any> | null,
+  cellValue: any
+): string {
   return cellValue ? formatDate(cellValue) : ''
 }
 
@@ -302,7 +312,11 @@ export function dateFormatter(_row: any, _column: TableColumnCtx<any> | null, ce
  * @param column 字段
  * @param cellValue 字段值
  */
-export function dateFormatter2(_row: any, _column: TableColumnCtx<any> |null, cellValue: any): string {
+export function dateFormatter2(
+  _row: any,
+  _column: TableColumnCtx<any> | null,
+  cellValue: any
+): string {
   return cellValue ? formatDate(cellValue, 'YYYY-MM-DD') : ''
 }
 

+ 80 - 41
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -25,6 +25,7 @@ interface Dimensions {
   name: string
   value: string
   color?: string
+  response?: boolean
 }
 
 const dimensions = ref<Dimensions[]>([])
@@ -41,6 +42,12 @@ const selectedDimension = ref<SelectedDimension>({})
 
 const dimensionLoading = ref(false)
 
+const disabledDimension = computed(() => (identifier: string) => {
+  const response = dimensions.value.find((item) => item.identifier === identifier)?.response
+
+  return { disabled: disabledDimensions.value.includes(identifier) || response, loading: response }
+})
+
 async function loadDimensions() {
   if (!query.id) return
 
@@ -79,7 +86,7 @@ async function loadDimensions() {
 }
 
 const selectedDate = ref<string[]>([
-  ...rangeShortcuts[3].value().map((v) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'))
+  ...rangeShortcuts[2].value().map((v) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'))
 ])
 
 interface ChartData {
@@ -110,10 +117,11 @@ function genderIntervalArr(init: boolean = false) {
   }
 
   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)
 
   maxInterval.value = interval
@@ -157,9 +165,9 @@ function render() {
 
   chart.setOption({
     grid: {
-      left: '8%',
-      top: '0%',
-      right: '8%',
+      left: '6%',
+      top: '5%',
+      right: '6%',
       bottom: '12%'
     },
     tooltip: {
@@ -260,28 +268,36 @@ function updateSingleSeries(name: string) {
 const lastTsMap = ref<Record<Dimensions['name'], number>>({})
 
 async function fetchIncrementData() {
-  for (const { identifier, name } of dimensions.value) {
+  for (const item of dimensions.value) {
+    const { identifier, name } = item
+
     const lastTs = lastTsMap.value[name]
     if (!lastTs) continue
 
+    item.response = true
+
     IotStatApi.getDeviceInfoChart(
       data.value.deviceCode,
       identifier,
       dayjs(lastTs).format('YYYY-MM-DD HH:mm:ss'),
       dayjs().format('YYYY-MM-DD HH:mm:ss')
-    ).then((res) => {
-      if (!res.length) return
-
-      const sorted = res.sort((a, b) => a.ts - b.ts)
-
-      // push 到本地
-      chartData.value[name].push(...sorted)
-      // 更新 lastTs
-      lastTsMap.value[identifier] = sorted.at(-1).ts
-
-      // 更新图表
-      updateSingleSeries(name)
-    })
+    )
+      .then((res) => {
+        if (!res.length) return
+
+        const sorted = res.sort((a, b) => a.ts - b.ts)
+
+        // push 到本地
+        chartData.value[name].push(...sorted)
+        // 更新 lastTs
+        lastTsMap.value[identifier] = sorted.at(-1).ts
+
+        // 更新图表
+        updateSingleSeries(name)
+      })
+      .finally(() => {
+        item.response = false
+      })
   }
 }
 
@@ -292,6 +308,7 @@ function startAutoFetch() {
 }
 
 function stopAutoFetch() {
+  cancelAllRequests()
   if (timer.value) clearInterval(timer.value)
   timer.value = null
 }
@@ -305,28 +322,38 @@ async function initLoadChartData(real_time: boolean = true) {
 
   chartLoading.value = true
 
-  for (const { identifier, name } of dimensions.value) {
-    const res = await IotStatApi.getDeviceInfoChart(
-      data.value.deviceCode,
-      identifier,
-      selectedDate.value[0],
-      selectedDate.value[1]
-    )
+  dimensions.value = dimensions.value.map((item) => {
+    item.response = true
+    return item
+  })
+
+  for (const item of dimensions.value) {
+    const { identifier, name } = item
+    try {
+      const res = await IotStatApi.getDeviceInfoChart(
+        data.value.deviceCode,
+        identifier,
+        selectedDate.value[0],
+        selectedDate.value[1]
+      )
 
-    const sorted = res
-      .sort((a, b) => a.ts - b.ts)
-      .map((item) => ({ ts: item.ts, value: item.value }))
+      const sorted = res
+        .sort((a, b) => a.ts - b.ts)
+        .map((item) => ({ ts: item.ts, value: item.value }))
 
-    chartData.value[name] = sorted
+      chartData.value[name] = sorted
 
-    lastTsMap.value[name] = sorted.at(-1)?.ts ?? 0
+      lastTsMap.value[name] = sorted.at(-1)?.ts ?? 0
 
-    updateSingleSeries(name)
+      updateSingleSeries(name)
 
-    chartLoading.value = false
+      chartLoading.value = false
 
-    if (selectedDimension.value[name]) {
-      genderIntervalArr()
+      if (selectedDimension.value[name]) {
+        genderIntervalArr()
+      }
+    } finally {
+      item.response = false
     }
   }
 
@@ -486,12 +513,18 @@ onUnmounted(() => {
       <button
         v-for="item in gatewayDimensions"
         :key="item.identifier"
-        class="border-none h-12 bg-white dark:bg-[#1d1e1f] rounded-lg px-6 shadow flex items-center hover:scale-103 transition-all cursor-pointer"
+        class="border-none h-12 bg-white dark:bg-[#1d1e1f] rounded-lg px-8 shadow flex items-center hover:scale-103 transition-all cursor-pointer"
         :class="{ 'bg-blue-200': selectedDimension[item.name] }"
-        :disabled="disabledDimensions.includes(item.identifier)"
+        :disabled="disabledDimension(item.identifier).disabled"
         @click="handleClickSpec(item.name)"
       >
-        <span class="text-sm text-[var(--el-text-color-regular)]">{{ item.name }}</span>
+        <span class="text-sm text-[var(--el-text-color-regular)] flex items-center gap-2 relative">
+          <i
+            v-show="disabledDimension(item.identifier).loading"
+            class="i-line-md:loading-loop size-5 absolute -left-6"
+          ></i>
+          {{ item.name }}
+        </span>
         <span class="text-lg font-medium ms-a">{{ item.value }}</span>
       </button>
     </div>
@@ -507,10 +540,16 @@ onUnmounted(() => {
         :key="item.identifier"
         class="border-none h-12 bg-white dark:bg-[#1d1e1f] rounded-lg px-6 shadow flex items-center hover:scale-103 transition-all cursor-pointer"
         :class="{ 'bg-blue-200': selectedDimension[item.name] }"
-        :disabled="disabledDimensions.includes(item.identifier)"
+        :disabled="disabledDimension(item.identifier).disabled"
         @click="handleClickSpec(item.name)"
       >
-        <span class="text-sm text-[var(--el-text-color-regular)]">{{ item.name }}</span>
+        <span class="text-sm text-[var(--el-text-color-regular)] flex items-center gap-2">
+          <i
+            v-show="disabledDimension(item.identifier).loading"
+            class="i-line-md:loading-loop size-5"
+          ></i>
+          {{ item.name }}
+        </span>
         <span class="text-lg font-medium ms-a">{{ item.value }}</span>
       </button>
     </div>

+ 239 - 50
src/views/pms/device/monitor/color.ts

@@ -1,52 +1,241 @@
 export const colors = [
-  '#5470C6',
-  '#91CC75',
-  '#FAC858',
-  '#EE6666',
-  '#73C0DE',
-  '#3BA272',
-  '#FC8452',
-  '#9A60B4',
-  '#EA7CCC',
-  '#2E91E5',
-  '#1CA71C',
-  '#FB0D0D',
-  '#DA16FF',
-  '#222A2A',
-  '#B68100',
-  '#750D86',
-  '#EB663B',
-  '#0D2A63',
-  '#87BC45',
-  '#F58518',
-  '#8C564B',
-  '#7F7F7F',
-  '#BCBD22',
-  '#17BECF',
-  '#4C72B0',
-  '#55A868',
-  '#C44E52',
-  '#8172B2',
-  '#CCB974',
-  '#64B5CD',
-  '#4E79A7',
-  '#F28E2B',
-  '#E15759',
-  '#76B7B2',
-  '#59A14F',
-  '#EDC948',
-  '#B07AA1',
-  '#FF9DA7',
-  '#9C755F',
-  '#BAB0AC',
-  '#1F77B4',
-  '#AEC7E8',
-  '#FF7F0E',
-  '#FFBB78',
-  '#2CA02C',
-  '#98DF8A',
-  '#D62728',
-  '#FF9896',
-  '#9467BD',
-  '#C5B0D5'
+  // --- 第 1 组 (高对比度起手) ---
+  '#5470C6', // 蓝
+  '#FAC858', // 黄
+  '#EE6666', // 红
+  '#3BA272', // 绿
+  '#9A60B4', // 紫
+  '#FC8452', // 橙
+  '#73C0DE', // 浅蓝
+  '#91CC75', // 浅绿
+  '#222A2A', // 深灰
+  '#EA7CCC', // 粉
+
+  // --- 第 2 组 ---
+  '#0D2A63', // 深蓝
+  '#F58518', // 鲜橙
+  '#1CA71C', // 纯绿
+  '#DA16FF', // 亮紫
+  '#B68100', // 暗金
+  '#C44E52', // 砖红
+  '#17BECF', // 青
+  '#8C564B', // 棕
+  '#2E91E5', // 亮蓝
+  '#EDC948', // 芥末黄
+
+  // --- 第 3 组 ---
+  '#750D86', // 深紫
+  '#87BC45', // 草绿
+  '#FB0D0D', // 鲜红
+  '#76B7B2', // 蓝绿
+  '#EB663B', // 珊瑚橙
+  '#8172B2', // 灰紫
+  '#CCB974', // 沙黄
+  '#59A14F', // 森林绿
+  '#F28E2B', // 橘黄
+  '#4E79A7', // 钢蓝
+
+  // --- 第 4 组 ---
+  '#E15759', // 绯红
+  '#55A868', // 叶绿
+  '#FF9DA7', // 嫩粉
+  '#4C72B0', // 靛蓝
+  '#BCBD22', // 橄榄
+  '#64B5CD', // 天蓝
+  '#B07AA1', // 兰花紫
+  '#9C755F', // 咖啡
+  '#FF7F0E', // 胡萝卜橙
+  '#1F77B4', // 经典蓝
+
+  // --- 第 5 组 (加入 Material Design 鲜艳色) ---
+  '#00FFCC', // 荧光青
+  '#D62728', // 宝石红
+  '#98DF8A', // 薄荷绿
+  '#9467BD', // 薰衣草
+  '#AEC7E8', // 淡蓝
+  '#FFBB78', // 杏黄
+  '#2CA02C', // 熟绿
+  '#C5B0D5', // 浅紫
+  '#FF9896', // 鲑鱼粉
+  '#7F7F7F', // 中灰
+
+  // --- 第 6 组 ---
+  '#240046', // 午夜紫
+  '#FFD166', // 奶酪黄
+  '#06D6A0', // 碧绿
+  '#EF476F', // 玫瑰红
+  '#118AB2', // 太平洋蓝
+  '#F72585', // 霓虹粉
+  '#43AA8B', // 丛林绿
+  '#F8961E', // 琥珀
+  '#577590', // 蓝灰
+  '#F94144', // 珊瑚红
+
+  // --- 第 7 组 ---
+  '#2B2D42', // 炭黑蓝
+  '#A7C957', // 鳄梨绿
+  '#6A4C93', // 皇家紫
+  '#FF595E', // 糖果红
+  '#8AC926', // 酸橙绿
+  '#1982C4', // 蔚蓝
+  '#FFCA3A', // 香蕉黄
+  '#003566', // 海军蓝
+  '#FF5400', // 国际橙
+  '#8338EC', // 电光紫
+
+  // --- 第 8 组 ---
+  '#001219', // 墨绿黑
+  '#E9D8A6', // 香槟
+  '#BB3E03', // 铁锈红
+  '#0A9396', // 孔雀蓝
+  '#EE9B00', // 姜黄
+  '#9B2226', // 血红
+  '#94D2BD', // 蒂芙尼蓝
+  '#CA6702', // 焦糖
+  '#005F73', // Petrol蓝
+  '#AE2012', // 砖红
+
+  // --- 第 9 组 ---
+  '#355070', // 深丹宁
+  '#E56B6F', // 柔粉红
+  '#6D597A', // 葡萄紫
+  '#B56576', // 豆沙红
+  '#EAAC8B', // 桃皮绒
+  '#E07A5F', // 陶土
+  '#3D405B', // 墨水蓝
+  '#81B29A', // 鼠尾草绿
+  '#F2CC8F', // 奶油黄
+  '#F4F1DE', // 羊皮纸色
+
+  // --- 第 10 组 (高饱和度 赛博朋克风) ---
+  '#FF006E', // 亮玫红
+  '#3A86FF', // 亮蓝
+  '#8338EC', // 蓝紫
+  '#FFBE0B', // 金盏花
+  '#FB5607', // 熔岩橙
+  '#70D6FF', // 冰蓝
+  '#FF70A6', // 泡泡糖粉
+  '#FF9770', // 浅橙
+  '#E9FF70', // 柠檬黄
+  '#001524', // 极夜黑
+
+  // --- 第 11 组 ---
+  '#5F0F40', // 酒红
+  '#9A031E', // 深绯
+  '#FB8B24', // 橘红
+  '#E36414', // 柿子色
+  '#0F4C5C', // 深青
+  '#590D22', // 波尔多红
+  '#FFCCD5', // 淡粉
+  '#A8DADC', // 浅湖蓝
+  '#457B9D', // 钢青
+  '#1D3557', // 普鲁士蓝
+
+  // --- 第 12 组 ---
+  '#D9ED92', // 嫩芽绿
+  '#B5E48C', // 苹果绿
+  '#99D98C', // 抹茶
+  '#76C893', // 草地
+  '#52B69A', // 翡翠
+  '#34A0A4', // 蓝玉
+  '#168AAD', // 蔚蓝
+  '#1A759F', // 深湖蓝
+  '#1E6091', // 靛青
+  '#184E77', // 深海蓝
+
+  // --- 第 13 组 ---
+  '#003049', // 普鲁士深蓝
+  '#D62828', // 消防红
+  '#F77F00', // 橙皮
+  '#FCBF49', // 日光黄
+  '#EAE2B7', // 米白
+  '#264653', // 炭青
+  '#2A9D8F', // 波斯绿
+  '#E9C46A', // 沙金
+  '#F4A261', // 浅褐
+  '#E76F51', // 焦赭
+
+  // --- 第 14 组 ---
+  '#606C38', // 橄榄褐
+  '#283618', // 森林黑绿
+  '#FEFAE0', // 奶油白
+  '#DDA15E', // 鹿皮
+  '#BC6C25', // 树皮褐
+  '#CCD5AE', // 浅橄榄
+  '#E9EDC9', // 浅豆绿
+  '#FAEDCD', // 杏仁
+  '#D4A373', // 拿铁
+  '#8D99AE', // 冷灰
+
+  // --- 第 15 组 ---
+  '#7400B8', // 电紫
+  '#6930C3', // 葡萄
+  '#5E60CE', // 蓝鸢尾
+  '#5390D9', // 矢车菊
+  '#4EA8DE', // 天空
+  '#48BFE3', // 浅蓝
+  '#56CFE1', // 青空
+  '#64DFDF', // 绿松石
+  '#72EFDD', // 薄荷
+  '#80FFDB', // 冰绿
+
+  // --- 第 16 组 ---
+  '#FF595E', // 珊瑚粉
+  '#FFCA3A', // 芥末
+  '#8AC926', // 梨绿
+  '#1982C4', // 纯蓝
+  '#6A4C93', // 茄紫
+  '#F9C74F', // 蛋黄
+  '#90BE6D', // 开心果
+  '#43AA8B', // 丛林
+  '#577590', // 军舰灰
+  '#F3722C', // 胡萝卜
+
+  // --- 第 17 组 ---
+  '#F94144', // 烈焰红
+  '#F3722C', // 柿子
+  '#F8961E', // 橙黄
+  '#F9844A', // 橙粉
+  '#F9C74F', // 玉米黄
+  '#90BE6D', // 抹茶
+  '#43AA8B', // 绿松石
+  '#4D908E', // 藻绿
+  '#577590', // 蓝灰
+  '#277DA1', // 远洋蓝
+
+  // --- 第 18 组 ---
+  '#006466', // 深墨绿
+  '#065A60', // 深青
+  '#0B525B', // 油蓝
+  '#144552', // 墨蓝
+  '#1B3A4B', // 深夜蓝
+  '#212F45', // 极深蓝
+  '#272640', // 极深紫
+  '#312244', // 墨紫
+  '#3E1F47', // 李子黑
+  '#4D194D', // 葡萄黑
+
+  // --- 第 19 组 ---
+  '#F08080', // 淡珊瑚
+  '#20B2AA', // 浅海绿
+  '#778899', // 娄石蓝
+  '#9370DB', // 适中紫
+  '#3CB371', // 春绿
+  '#FF6347', // 番茄
+  '#4682B4', // 钢蓝
+  '#DAA520', // 金麒麟
+  '#CD5C5C', // 栗色
+  '#008B8B', // 深青
+
+  // --- 第 20 组 ---
+  '#800000', // 栗红
+  '#808000', // 橄榄
+  '#008000', // 纯绿
+  '#008080', // 鸭翅青
+  '#000080', // 海军蓝
+  '#4B0082', // 靛蓝
+  '#DC143C', // 猩红
+  '#00CED1', // 暗青
+  '#FFD700', // 金色
+  '#FF00FF' // 洋红
 ]

+ 28 - 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('')
@@ -168,6 +174,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 {
     // 默认跳转

+ 5 - 0
src/views/pms/iotprojectinfo/index.vue

@@ -218,6 +218,11 @@
           </template>
         </el-table-column>
         <el-table-column label="设计工作量" align="center" prop="workloadDesign" />
+        <el-table-column label="工作量单位" align="center" prop="workloadUnit" :width="columnWidths.payment">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.PMS_PROJECT_WORKLOAD_UNIT" :value="scope.row.workloadUnit" />
+          </template>
+        </el-table-column>
         <el-table-column label="施工队伍" align="center">
           <template #default="{ row }">
             <el-tooltip

+ 74 - 3
src/views/pms/iotprojecttask/IotProjectTaskForm.vue

@@ -844,6 +844,10 @@ const workloadDialogVisible = ref(false);
 const workloadList = ref<any[]>([]);
 const hasInitialWorkload = ref(false);
 
+// 跟踪设备列表是否为空
+const hasDevicesAvailable = ref(false); // 默认假设有设备,等接口返回后更新
+
+
 // 设计工作量错误信息
 const workloadDesignError = ref('');
 
@@ -1075,7 +1079,7 @@ const taskFormRules = computed(() => {
     ],
     workloadUnit: [{ required: currentTask.value.platformWell !== '1', message: '工作量单位不能为空', trigger: 'change' }],
     deptIds: [{ required: true, message: '施工队伍不能为空', trigger: 'change' }],
-    deviceIds: [{ required: true, message: '施工设备不能为空', trigger: 'change' }],
+    deviceIds: [{ required: hasDevicesAvailable.value, message: '施工设备不能为空', trigger: 'change' }],
     responsiblePerson: [
       {
         required: true,
@@ -1232,6 +1236,9 @@ const openDeviceDialogForForm = async () => {
     };
     const data = await IotDeviceApi.getDevicesByDepts(params);
 
+    // 更新 hasDevicesAvailable 状态
+    hasDevicesAvailable.value = data && data.length > 0;
+
     // 更新设备映射表
     const newDeviceMap = {...deviceMap.value};
     data.forEach(device => {
@@ -1242,9 +1249,16 @@ const openDeviceDialogForForm = async () => {
     filteredDeviceList.value = data;
     deviceDialogVisible.value = true;
 
+    // 如果没有设备可用,显示提示信息
+    if (!hasDevicesAvailable.value) {
+      ElMessage.info('当前施工队伍下没有可用设备,设备字段为非必填项');
+    }
+
   } catch (error) {
     ElMessage.error('获取设备列表失败');
     console.error('获取设备列表失败:', error);
+    // 如果获取失败,也设为非必填,避免用户无法提交
+    hasDevicesAvailable.value = false;
   }
 };
 
@@ -1847,6 +1861,34 @@ const open = async () => {
         currentTask.value = JSON.parse(JSON.stringify(tableData.value[0]));
         isNewTask.value = false;
 
+        // 如果当前任务有施工队伍,立即查询设备并更新 hasDevicesAvailable
+        if (currentTask.value.deptIds && currentTask.value.deptIds.length > 0) {
+          try {
+            const params = {
+              deptIds: currentTask.value.deptIds
+            };
+            const deviceData = await IotDeviceApi.getDevicesByDepts(params);
+
+            // 更新 hasDevicesAvailable 状态
+            hasDevicesAvailable.value = deviceData && deviceData.length > 0;
+
+            // 更新设备列表和映射表(如果有设备)
+            if (hasDevicesAvailable.value) {
+              deviceList.value = deviceData;
+              deviceMap.value = {};
+              deviceData.forEach(device => {
+                deviceMap.value[device.id] = device;
+              });
+            }
+          } catch (error) {
+            console.error('初始化查询设备失败:', error);
+            hasDevicesAvailable.value = false;
+          }
+        } else {
+          // 如果没有施工队伍,默认没有设备可用
+          hasDevicesAvailable.value = false;
+        }
+
         // 处理平台井数据
         // 将数字类型的 platformWell 转换为字符串类型
         currentTask.value.platformWell = currentTask.value.platformWell?.toString() || '0';
@@ -1982,7 +2024,8 @@ const validateAllRows = (): boolean => {
       allValid = false;
     }
 
-    if (!row.deviceIds || row.deviceIds.length === 0) {
+    // 修改:只有当有设备可用时才验证设备字段
+    if (hasDevicesAvailable.value && (!row.deviceIds || row.deviceIds.length === 0)) {
       allValid = false;
     }
 
@@ -2700,22 +2743,50 @@ watch(() => currentTask.value.workloadDesign, () => {
 });
 
 // 监听当前任务的变化,设置默认展开的keys
-watch(() => currentTask.value.deptIds, (newVal) => {
+watch(() => currentTask.value.deptIds, (newVal, oldVal) => {
   if (newVal && newVal.length > 0) {
     defaultExpandedKeys.value = [...newVal];
   }
+
+  // 【新增】施工队伍变化时,重新查询设备并更新状态
+  const params = {
+    deptIds: newVal
+  };
+  /* IotDeviceApi.getDevicesByDepts(params).then(data => {
+    // 更新 hasDevicesAvailable 状态
+    hasDevicesAvailable.value = data && data.length > 0;
+
+    // 更新设备列表和映射表
+    if (hasDevicesAvailable.value) {
+      const newDeviceMap = {...deviceMap.value};
+      data.forEach(device => {
+        newDeviceMap[device.id] = device;
+      });
+      deviceMap.value = newDeviceMap;
+    }
+  }).catch(error => {
+    console.error('施工队伍变化时查询设备失败:', error);
+    hasDevicesAvailable.value = false;
+  }); */
+
   if (!newVal || newVal.length === 0) {
     // 清空施工队伍时,同时清空相关人员和设备
     currentTask.value.responsiblePerson = [];
     currentTask.value.submitter = [];
     currentTask.value.deviceIds = [];
 
+    // 重置设备可用状态
+    hasDevicesAvailable.value = true;
+
     // 清除相关字段的验证状态
     nextTick(() => {
       taskFormRef.value?.validateField('responsiblePerson');
       taskFormRef.value?.validateField('submitter');
       taskFormRef.value?.validateField('deviceIds');
     });
+  } else if (newVal !== oldVal) {
+    // 施工队伍发生变化时,重置设备可用状态(因为不同队伍可能有不同的设备)
+    // hasDevicesAvailable.value = true;
   }
 }, { immediate: true, deep: true });
 

+ 780 - 22
src/views/pms/iotrddailyreport/FillDailyReportForm.vue

@@ -64,20 +64,20 @@
         <div class="table-row">
           <div class="table-cell">
             <div class="cell-content">
-              <span class="cell-label">搬迁日期:</span>
-              <span class="cell-value">{{ formatDate(dailyReportData.dpDate) || '-' }}</span>
+              <span class="cell-label">设计工作量:</span>
+              <span class="cell-value">{{ dailyReportData.workloadDesign || '-' }}</span>
             </div>
           </div>
           <div class="table-cell">
             <div class="cell-content">
               <span class="cell-label">开工日期:</span>
-              <span class="cell-value">{{ formatDate(dailyReportData.sgDate) || '-' }}</span>
+              <span class="cell-value">{{ dailyReportData.commencementDate || '-' }}</span>
             </div>
           </div>
           <div class="table-cell">
             <div class="cell-content">
               <span class="cell-label">完工日期:</span>
-              <span class="cell-value">{{ formatDate(dailyReportData.wgDate) || '-' }}</span>
+              <span class="cell-value">{{ dailyReportData.completionDate || '-' }}</span>
             </div>
           </div>
         </div>
@@ -86,13 +86,13 @@
           <div class="table-cell">
             <div class="cell-content">
               <span class="cell-label">施工周期D:</span>
-              <span class="cell-value">{{ constructionPeriod || 0 }}</span>
+              <span class="cell-value">{{ dailyReportData.constructionPeriod || '' }}</span>
             </div>
           </div>
           <div class="table-cell">
             <div class="cell-content">
               <span class="cell-label">停待时间D:</span>
-              <span class="cell-value">{{ dailyReportData.faultDowntime || 0 }}</span>
+              <span class="cell-value">{{ dailyReportData.idleTime || '' }}</span>
             </div>
           </div>
           <div class="table-cell">
@@ -115,6 +115,27 @@
       </div>
     </ContentWrap>
 
+    <!-- 实际进度显示区域(新增) -->
+    <ContentWrap class="section-padding" v-if="showActualProgress">
+      <h3 class="progress-title">任务进度</h3>
+      <div class="actual-progress-container">
+        <div v-if="actualProgressData.length > 0">
+          <el-steps direction="horizontal" :active="actualProgressData.length - 1" finish-status="success">
+            <el-step
+              v-for="(step, index) in actualProgressData"
+              :key="index"
+              :title="step.title"
+              :description="step.description"
+              :status="step.status"
+            />
+          </el-steps>
+        </div>
+        <div v-else class="no-progress-data">
+          暂无实际进度数据
+        </div>
+      </div>
+    </ContentWrap>
+
     <!-- 第三部分:日报填报表单 -->
     <ContentWrap class="section-padding">
       <el-form
@@ -320,6 +341,21 @@
           </el-col>
         </el-row>
 
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="当日油耗(L)" prop="dailyFuel">
+              <el-input
+                v-model="formData.dailyFuel"
+                type="text"
+                :min="0"
+                placeholder="自动计算当日油耗"
+                :readonly="isReadonlyMode"
+                @blur="formatDailyFuel"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
         <!-- 第三行:当日生产动态 -->
         <el-row>
           <el-col :span="24">
@@ -444,6 +480,91 @@
       </el-form>
     </ContentWrap>
 
+    <!-- 油耗信息区域 - 当有油耗数据时显示 -->
+    <ContentWrap class="fuel-consumption-section" v-if="showFuelConsumption">
+      <h2 class="text-lg font-semibold mb-4">油耗信息</h2>
+
+      <div class="fuel-consumption-table">
+        <el-table
+          :data="fuelConsumptionData"
+          border
+          style="width: 100%"
+          class="fuel-consumption-el-table"
+          table-layout="fixed"
+          :key="fuelTableKey"
+          row-key="deviceId"
+        >
+          <!-- 车辆编码 -->
+          <el-table-column
+            prop="deviceCode"
+            label="车辆编码"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="120"
+          />
+
+          <!-- 车辆名称 -->
+          <el-table-column
+            prop="deviceName"
+            label="车辆名称"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="150"
+          />
+
+          <!-- 发生日期 -->
+          <el-table-column
+            label="发生日期"
+            align="center"
+            :show-overflow-tooltip="false"
+            width="120"
+          >
+            <template #default="scope">
+              {{ formatDate(scope.row.queryDate) }}
+            </template>
+          </el-table-column>
+
+          <!-- 中航北斗油耗 -->
+          <el-table-column
+            label="中航北斗油耗(L)"
+            align="center"
+            width="140"
+          >
+            <template #default="scope">
+              {{ formatNumber(scope.row.zhbdFuel, 2) }}
+            </template>
+          </el-table-column>
+
+          <!-- 实际油耗 -->
+          <el-table-column
+            label="实际油耗(L)"
+            align="center"
+            width="140"
+          >
+            <template #default="scope">
+              <!-- 编辑模式下显示输入框 -->
+              <el-input
+                v-if="!isReadonlyMode"
+                v-model="scope.row.customFuel"
+                type="text"
+                :min="0"
+                placeholder="请输入实际油耗"
+                @blur="handleCustomFuelChange(scope.row)"
+                style="width: 100%"
+                size="small"
+                @focus="handleFuelInputFocus(scope.row)"
+                :key="scope.row.deviceId"
+              />
+              <!-- 只读模式下显示数值 -->
+              <span v-else>
+            {{ formatNumber(scope.row.customFuel, 2) }}
+          </span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </ContentWrap>
+
     <!-- 平台井工作量区域 - 只在平台井的详情或审批模式下显示 -->
     <ContentWrap class="platform-workload-section" v-if="(isDetailMode || isApprovalMode) && dailyReportData?.platformWell === 1">
       <h2 class="text-lg font-semibold mb-4">平台井工作量</h2>
@@ -618,9 +739,17 @@ const formLoading = ref(false)
 const formRef = ref()
 const id = params.id // 瑞都日报id
 
+const fuelTableKey = ref(0) // 用于强制重新渲染表格
+
+// 添加一个新的响应式变量用于输入
+const dailyFuelInput = ref('')
+
 // 日报数据
 const dailyReportData = ref<any>({})
 
+// 修改 reportFuels 的 watch,添加标志位避免自动覆盖
+const dailyFuelManuallyModified = ref(false)
+
 // 添加模式判断计算属性
 const isApprovalMode = computed(() => params.mode === 'approval')
 const isDetailMode = computed(() => params.mode === 'detail')
@@ -680,6 +809,15 @@ const pageTitle = computed(() => {
   }
 })
 
+// 处理输入框获取焦点事件
+const handleFuelInputFocus = (fuelItem: any) => {
+  // 如果 customFuel 是空的,确保它显示默认值
+  if (!fuelItem.customFuel || fuelItem.customFuel === '') {
+    const zhbdValue = parseFloat(fuelItem.zhbdFuel);
+    fuelItem.customFuel = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00';
+  }
+}
+
 // 模式提示信息
 const modeNotice = computed(() => {
   if (isApprovalMode.value) {
@@ -742,6 +880,7 @@ const initPlatformData = (reportData: any) => {
 
         return existingData || {
           taskId: platform.id,
+          dailyFuel: platform.dailyFuel || '',
           reportId: platform.reportId, // 使用接口返回的 reportId
           wellName: platform.wellName,
           rdStatus: platform.rdStatus || '', // 初始为空
@@ -840,6 +979,7 @@ const formData = ref({
   rdStatus: '', // 施工状态
   deviceIds: [] as number[], // 设备ID数组
   techniqueIds: [], // 施工工艺
+  dailyFuel: '', // 当日油耗
   productionStatus: '', // 当日生产动态
   nextPlan: '', // 下步工作计划
   externalRental: '', // 外租设备
@@ -848,7 +988,8 @@ const formData = ref({
   // 添加动态字段对象
   dynamicFields: {} as Record<string, any>,
   // 附件列表
-  attachments: [] as any[]
+  attachments: [] as any[],
+  reportFuels: [] as any[] // 油耗信息数组
 })
 
 // 添加上传成功处理函数
@@ -1061,6 +1202,34 @@ const openDeviceDialog = async () => {
   }
 }
 
+// 修改格式化函数,改为失去焦点时触发
+const formatDailyFuel = () => {
+  if (!dailyFuelInput.value || dailyFuelInput.value.trim() === '') {
+    formData.value.dailyFuel = ''
+    dailyFuelInput.value = ''
+    return
+  }
+
+  // 移除非数字字符(除了小数点)
+  const cleaned = dailyFuelInput.value.replace(/[^\d.]/g, '')
+
+  // 确保只有一个小数点
+  const parts = cleaned.split('.')
+  if (parts.length > 2) {
+    dailyFuelInput.value = parts[0] + '.' + parts.slice(1).join('')
+  }
+
+  const numValue = parseFloat(dailyFuelInput.value)
+  if (!isNaN(numValue)) {
+    // 限制到两位小数
+    formData.value.dailyFuel = formatNumber(numValue, 2)
+    dailyFuelInput.value = formData.value.dailyFuel
+  } else {
+    formData.value.dailyFuel = ''
+    dailyFuelInput.value = ''
+  }
+}
+
 // 处理穿梭框变化
 const handleTransferChange = (value: number[], direction: string, movedKeys: number[]) => {
   // 可以添加额外的处理逻辑
@@ -1099,14 +1268,6 @@ const initDeviceData = (reportData: any) => {
   }
 }
 
-// 表单验证规则
-/* const formRules = reactive({
-  timeRange: [{ required: true, message: '时间节点不能为空', trigger: 'change' }],
-  rdStatus: [{ required: true, message: '施工状态不能为空', trigger: 'change' }],
-  techniqueIds: [{ required: true, message: '施工工艺不能为空', trigger: 'change' }],
-  productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }]
-}) */
-
 const formRules = computed(() => {
   // 判断是否为虚拟项目
   const isVirtualProject = dailyReportData.value.virtualProject === 'Y'
@@ -1114,7 +1275,33 @@ const formRules = computed(() => {
   // 基础校验规则(时间节点、当日生产动态始终必填)
   const rules = {
     timeRange: [{ required: true, message: '时间节点不能为空', trigger: 'change' }],
-    productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }]
+    productionStatus: [{ required: true, message: '当日生产动态不能为空', trigger: 'blur' }],
+    nextPlan: [{ required: true, message: '下步工作计划不能为空', trigger: 'blur' }],
+    dailyFuel: [
+      {
+        required: true,
+        message: '当日油耗不能为空',
+        trigger: 'blur'
+      },
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          if (value === '' || value === null || value === undefined) {
+            callback()
+            return
+          }
+
+          const numValue = Number(value)
+          if (isNaN(numValue)) {
+            callback(new Error('当日油耗必须是数字'))
+          } else if (numValue < 0) {
+            callback(new Error('当日油耗不能小于0'))
+          } else {
+            callback()
+          }
+        },
+        trigger: 'blur'
+      }
+    ]
   }
 
   // 非虚拟项目时,添加施工状态、施工工艺的必填校验
@@ -1228,7 +1415,15 @@ const submitForm = async () => {
     extProperty: extProperties,
     deviceIds: formData.value.deviceIds, // 设备ID集合
     // 在填报模式下也提交审批意见字段
-    opinion: isEditMode.value ? approvalForm.opinion : undefined
+    opinion: isEditMode.value ? approvalForm.opinion : undefined,
+    // 将油耗数据格式化为后端需要的格式
+    /* reportFuels: formData.value.reportFuels.map(fuel => ({
+      ...fuel,
+      // 确保 customFuel 是数字格式
+      customFuel: fuel.customFuel ? parseFloat(fuel.customFuel) : null
+    })), */
+    // 确保当日油耗是数字格式
+    dailyFuel: formData.value.dailyFuel ? parseFloat(formData.value.dailyFuel) : 0
   }
 
   // 删除不需要复制的字段
@@ -1255,6 +1450,12 @@ const submitForm = async () => {
         bizId: pair.reportId // 替换 bizId 为当前平台井的 reportId
       }))
 
+      // 车辆油耗:复制并修改 reportId 为当前 pair 的 reportId
+      platformData.reportFuels = (baseData.reportFuels || []).map(reportFuel => ({
+        ...reportFuel, // 深拷贝单个油耗
+        reportId: pair.reportId // 替换 reportId 为当前平台井的 reportId
+      }))
+
       // 重新构建 dynamicFields(如果需要)
       const dynamicFields = {}
       if (platformData.extProperty && platformData.extProperty.length > 0) {
@@ -1482,6 +1683,25 @@ watch(() => formData.value.techniqueIds, async (newTechniqueIds, oldTechniqueIds
   }
 }, { deep: true })
 
+// 监听 formData.dailyFuel 变化,同步到输入变量
+watch(() => formData.value.dailyFuel, (newVal) => {
+  if (newVal !== null && newVal !== undefined && newVal !== '') {
+    // 将数字转换为字符串显示,但不干扰输入
+    dailyFuelInput.value = String(newVal)
+  } else {
+    dailyFuelInput.value = ''
+  }
+  dailyFuelManuallyModified.value = true
+}, { immediate: true })
+
+// 监听reportFuels的变化,自动更新当日油耗
+watch(() => formData.value.reportFuels, (newFuels) => {
+  // 只有在编辑模式且用户没有手动修改过当日油耗时才自动计算
+  if (!isReadonlyMode.value) {
+    calculateAndUpdateDailyFuel();
+  }
+}, { deep: true })
+
 // 更新当前平台井的 extProperty
 const updateCurrentPlatformExtProperty = () => {
   if (!currentPlatformId.value) return
@@ -1555,7 +1775,7 @@ const loadPlatformData = (platformId: number) => {
   if (platformData) {
     // 更新表单字段
     formData.value.rdStatus = platformData.rdStatus || ''
-    // formData.value.techniqueIds = platformData.techniqueIds ? [...platformData.techniqueIds] : []
+    // formData.value.dailyFuel = platformData.dailyFuel ? [...platformData.dailyFuel] : []
     // 将施工工艺数值转换为对应的标签
     if (platformData.techniqueIds && Array.isArray(platformData.techniqueIds)) {
       // 如果是数字数组,转换为字符串数组(与数据字典格式匹配)
@@ -1564,6 +1784,15 @@ const loadPlatformData = (platformId: number) => {
       formData.value.techniqueIds = platformData.techniqueIds ? [...platformData.techniqueIds] : []
     }
 
+    // 在详情或审批模式下,更新 dailyFuel 为当前平台井的值
+    if (isDetailMode.value || isApprovalMode.value) {
+      // 使用平台井的 dailyFuel 值
+      const platformDailyFuel = platformData.dailyFuel || ''
+      formData.value.dailyFuel = platformDailyFuel ? formatNumber(platformDailyFuel, 2) : ''
+      // 同步更新输入框
+      dailyFuelInput.value = formData.value.dailyFuel
+    }
+
     // 更新动态属性
     if (platformData.extProperty && platformData.extProperty.length > 0) {
       const dynamicFields: Record<string, any> = {}
@@ -1581,6 +1810,12 @@ const loadPlatformData = (platformId: number) => {
     formData.value.rdStatus = ''
     formData.value.techniqueIds = []
     formData.value.dynamicFields = {}
+
+    // 在详情或审批模式下,清空 dailyFuel
+    if (isDetailMode.value || isApprovalMode.value) {
+      formData.value.dailyFuel = ''
+      dailyFuelInput.value = ''
+    }
   }
 }
 
@@ -1603,6 +1838,48 @@ const getCurrentExtProperties = () => {
   })
 }
 
+// 是否显示实际进度
+const showActualProgress = computed(() => {
+  // 在所有模式下都显示,如果有数据就显示
+  return dailyReportData.value?.taskProgresses && dailyReportData.value.taskProgresses.length > 0
+})
+
+// 实际进度数据
+const actualProgressData = computed(() => {
+  if (!dailyReportData.value?.taskProgresses || !Array.isArray(dailyReportData.value.taskProgresses)) {
+    return []
+  }
+
+  // 将 taskProgresses 转换为 el-steps 需要的格式
+  return dailyReportData.value.taskProgresses.map((progress: any, index: number) => {
+    // 格式化日期:如果只有日期部分,使用日期格式;如果有时间,使用日期时间格式
+    let formattedDate = ''
+    if (progress.createTime) {
+      // 判断日期格式,如果包含时间则显示完整时间,否则只显示日期
+      if (progress.createTime.includes(' ')) {
+        // 已经是完整的日期时间格式
+        formattedDate = progress.createTime
+      } else {
+        // 只有日期部分
+        formattedDate = progress.createTime
+      }
+    }
+
+    // 构建标题:日期 + 状态
+    const title = formattedDate && progress.rdStatusLabel
+      ? `${formattedDate} ${progress.rdStatusLabel}`
+      : progress.rdStatusLabel || '未知状态'
+
+    return {
+      title: title,
+      description: '', // 可以根据需要添加描述信息
+      status: undefined, // el-steps 会自动计算状态
+      // 保留原始数据,便于调试
+      rawData: progress
+    }
+  })
+})
+
 // 初始化表单数据
 const initFormData = (reportData: any) => {
   // 处理附件数据格式转换
@@ -1632,6 +1909,7 @@ const initFormData = (reportData: any) => {
     platformWell: reportData.platformWell,
     rdStatus: reportData.rdStatus || '',
     techniqueIds: techniqueIds,
+    dailyFuel: reportData.dailyFuel, // 当日油耗默认值
     productionStatus: reportData.productionStatus || '',
     nextPlan: reportData.nextPlan || '',
     externalRental: reportData.externalRental || '',
@@ -1670,10 +1948,119 @@ const initFormData = (reportData: any) => {
   // 初始化设备数据
   initDeviceData(reportData)
 
-  // 如果是平台井模式且有数据,初始化 platformWellPairs 中的第一个平台井数据
-  /* if (reportData.platformWell === 1 && formData.value.platformId) {
-    loadPlatformData(formData.value.platformId)
-  } */
+  // 初始化油耗数据 - 根据模式选择数据源
+  if (isDetailMode.value || isApprovalMode.value) {
+    // 详情或审批模式:优先使用 reportedFuels
+    let fuelSource = reportData.reportedFuels;
+
+    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
+      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
+      const processedFuels = fuelSource.map((fuel: any) => {
+        // 创建全新的对象,避免引用共享
+        const newFuel = {
+          ...fuel, // 使用展开运算符创建浅拷贝
+          // 确保每个字段都有独立的值
+          createTime: fuel.createTime,
+          updateTime: fuel.updateTime,
+          creator: fuel.creator,
+          updater: fuel.updater,
+          deleted: fuel.deleted,
+          id: fuel.id,
+          type: fuel.type,
+          reportId: fuel.reportId,
+          deviceId: fuel.deviceId,
+          deviceCode: fuel.deviceCode,
+          yfDeviceCode: fuel.yfDeviceCode,
+          deviceName: fuel.deviceName,
+          carId: fuel.carId,
+          zhbdFuel: fuel.zhbdFuel,
+          customFuel: null, // 初始化为null
+          queryDate: fuel.queryDate,
+          remark: fuel.remark
+        };
+
+        let customFuelValue;
+
+        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
+        if (fuel.customFuel !== null && fuel.customFuel !== undefined) {
+          const numValue = parseFloat(fuel.customFuel);
+          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00';
+        } else {
+          // 如果 customFuel 为空,则使用 zhbdFuel 的值
+          const zhbdValue = parseFloat(fuel.zhbdFuel);
+          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00';
+        }
+
+        return {
+          ...newFuel,
+          customFuel: customFuelValue
+        };
+      });
+
+      formData.value.reportFuels = processedFuels;
+
+      // 计算初始的当日油耗
+      calculateTotalDailyFuel();
+    } else {
+      // 如果 reportedFuels 不存在或为空,设置空数组
+      formData.value.reportFuels = [];
+    }
+  } else {
+    // 编辑模式:优先使用 reportedFuels,不存在则使用 reportFuels
+    let fuelSource = reportData.reportedFuels || reportData.reportFuels || [];
+
+    if (fuelSource && Array.isArray(fuelSource) && fuelSource.length > 0) {
+      // 处理每个油耗数据项,设置 customFuel 的默认值并确保格式正确
+      const processedFuels = fuelSource.map((fuel: any) => {
+        // 创建全新的对象,避免引用共享
+        const newFuel = {
+          ...fuel, // 使用展开运算符创建浅拷贝
+          // 确保每个字段都有独立的值
+          createTime: fuel.createTime,
+          updateTime: fuel.updateTime,
+          creator: fuel.creator,
+          updater: fuel.updater,
+          deleted: fuel.deleted,
+          id: fuel.id,
+          type: fuel.type,
+          reportId: fuel.reportId,
+          deviceId: fuel.deviceId,
+          deviceCode: fuel.deviceCode,
+          yfDeviceCode: fuel.yfDeviceCode,
+          deviceName: fuel.deviceName,
+          carId: fuel.carId,
+          zhbdFuel: fuel.zhbdFuel,
+          customFuel: null, // 初始化为null
+          queryDate: fuel.queryDate,
+          remark: fuel.remark
+        };
+
+        let customFuelValue;
+
+        // 如果 customFuel 不为空,确保它是字符串格式并已正确格式化
+        if (fuel.customFuel !== null && fuel.customFuel !== undefined && fuel.customFuel !== '') {
+          const numValue = parseFloat(fuel.customFuel);
+          customFuelValue = !isNaN(numValue) ? formatNumber(numValue, 2) : '0.00';
+        } else {
+          // 如果 customFuel 为空,则使用 zhbdFuel 的值
+          const zhbdValue = parseFloat(fuel.zhbdFuel);
+          customFuelValue = !isNaN(zhbdValue) ? formatNumber(zhbdValue, 2) : '0.00';
+        }
+
+        return {
+          ...newFuel,
+          customFuel: customFuelValue
+        };
+      });
+
+      formData.value.reportFuels = processedFuels;
+
+      // 计算初始的当日油耗
+      calculateTotalDailyFuel();
+    } else {
+      formData.value.reportFuels = [];
+    }
+  }
 
 }
 
@@ -1689,6 +2076,13 @@ onMounted(async () => {
       const response = await IotRdDailyReportApi.getIotRdDailyReport(id)
       dailyReportData.value = response || {}
       initFormData(dailyReportData.value)
+
+      // 确保油耗数据在初始化后立即渲染
+      await nextTick()
+      // 强制更新油耗数据
+      if (formData.value.reportFuels && formData.value.reportFuels.length > 0) {
+        formData.value.reportFuels = [...formData.value.reportFuels]
+      }
     }
   } catch (error) {
     console.error('初始化数据失败:', error)
@@ -1724,6 +2118,207 @@ const getWorkloadColumns = () => {
   return columns;
 };
 
+// 添加一个深拷贝油耗数据的辅助函数
+const deepCopyFuelData = (fuelData: any) => {
+  if (!fuelData) return null;
+
+  return {
+    createTime: fuelData.createTime,
+    updateTime: fuelData.updateTime,
+    creator: fuelData.creator,
+    updater: fuelData.updater,
+    deleted: fuelData.deleted,
+    id: fuelData.id,
+    type: fuelData.type,
+    reportId: fuelData.reportId,
+    deviceId: fuelData.deviceId,
+    deviceCode: fuelData.deviceCode,
+    yfDeviceCode: fuelData.yfDeviceCode,
+    deviceName: fuelData.deviceName,
+    carId: fuelData.carId,
+    zhbdFuel: fuelData.zhbdFuel,
+    customFuel: fuelData.customFuel,
+    queryDate: fuelData.queryDate,
+    remark: fuelData.remark
+  };
+};
+
+// 强制刷新表格
+const refreshFuelTable = () => {
+  fuelTableKey.value += 1;
+}
+
+// 计算当日油耗的默认值
+const calculateDailyFuel = (reportData: any) => {
+  let dailyFuelValue = 0
+
+  // 如果有接口返回的dailyFuel,优先使用
+  if (reportData.dailyFuel !== null && reportData.dailyFuel !== undefined) {
+    dailyFuelValue = Number(reportData.dailyFuel)
+  }
+
+  // 如果reportFuels有数据,累加zhbdFuel的值
+  if (reportData.reportFuels && Array.isArray(reportData.reportFuels) && reportData.reportFuels.length > 0) {
+    const totalZhbdFuel = reportData.reportFuels.reduce((sum: number, item: any) => {
+      const zhbdFuelValue = Number(item.zhbdFuel) || 0
+      return sum + zhbdFuelValue
+    }, 0)
+
+    // 只有当累计值大于0时才覆盖原有的dailyFuel值
+    if (totalZhbdFuel > 0) {
+      dailyFuelValue = totalZhbdFuel
+    }
+  }
+
+  return formatNumber(dailyFuelValue, 2)
+}
+
+// 处理当日油耗输入
+const handleDailyFuelInput = () => {
+  // 确保保留两位小数
+  if (formData.value.dailyFuel !== '') {
+    const numValue = parseFloat(formData.value.dailyFuel)
+    if (!isNaN(numValue)) {
+      formData.value.dailyFuel = numValue.toFixed(2)
+    }
+  }
+}
+
+// 添加数字格式化函数
+const formatNumber = (value: any, decimalPlaces: number = 2) => {
+  if (value === null || value === undefined || value === '' || value === 'NaN') {
+    return '0.00';
+  }
+
+  // 如果已经是字符串,尝试转换为数字
+  if (typeof value === 'string') {
+    // 移除可能的非数字字符
+    const cleaned = value.replace(/[^\d.-]/g, '');
+    const num = Number(cleaned);
+    if (isNaN(num)) {
+      return '0.00';
+    }
+    return num.toFixed(decimalPlaces);
+  }
+
+  const num = Number(value);
+  if (isNaN(num)) {
+    return '0.00';
+  }
+
+  return num.toFixed(decimalPlaces)
+}
+
+// 新增:计算并更新当日油耗的方法
+const calculateAndUpdateDailyFuel = () => {
+  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
+    return;
+  }
+
+  // 计算所有车辆的实际油耗总和
+  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
+    const customFuelValue = Number(item.customFuel) || 0;
+    return sum + customFuelValue;
+  }, 0);
+
+  // 只有当累计的实际油耗大于0时,才更新当日油耗
+  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
+    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2);
+    // 更新输入框显示
+    if (dailyFuelInput.value) {
+      dailyFuelInput.value = formData.value.dailyFuel;
+    }
+  }
+}
+
+// 添加计算属性:获取油耗数据显示数据源
+const fuelConsumptionData = computed(() => {
+  // 所有模式都统一使用 formData.value.reportFuels 作为数据源
+  // 因为 formData.value.reportFuels 在 initFormData 中已经正确处理了所有情况
+  return formData.value.reportFuels || [];
+});
+
+// 判断是否显示油耗信息区域
+const showFuelConsumption = computed(() => {
+  const data = fuelConsumptionData.value;
+  return data && Array.isArray(data) && data.length > 0;
+});
+
+// 重新计算当日油耗const formatNumber
+const calculateTotalDailyFuel = () => {
+  if (!formData.value.reportFuels || !Array.isArray(formData.value.reportFuels)) {
+    return;
+  }
+
+  // 计算所有车辆的实际油耗总和
+  const totalCustomFuel = formData.value.reportFuels.reduce((sum: number, item: any) => {
+    const customFuelValue = Number(item.customFuel) || 0;
+    return sum + customFuelValue;
+  }, 0);
+
+  // 只有当累计的实际油耗大于0时,才自动更新当日油耗
+  if (totalCustomFuel > 0 && !isReadonlyMode.value) {
+    formData.value.dailyFuel = formatNumber(totalCustomFuel, 2);
+  }
+};
+
+// 处理实际油耗变化
+const handleCustomFuelChange = (fuelItem: any) => {
+  // 获取当前输入的值
+  let value = fuelItem.customFuel;
+
+  // 如果输入为空,则重置为zhbdFuel的值
+  if (value === '' || value === null || value === undefined) {
+    fuelItem.customFuel = formatNumber(fuelItem.zhbdFuel, 2);
+    return;
+  }
+
+  // 移除非数字字符(除了小数点)
+  const cleaned = value.toString().replace(/[^\d.]/g, '');
+
+  // 确保只有一个小数点
+  const parts = cleaned.split('.');
+  let formattedValue = cleaned;
+  if (parts.length > 2) {
+    formattedValue = parts[0] + '.' + parts.slice(1).join('');
+  }
+
+  // 转换为数字并格式化为两位小数
+  const numValue = parseFloat(formattedValue);
+  if (!isNaN(numValue)) {
+    // 限制到两位小数
+    fuelItem.customFuel = formatNumber(numValue, 2);
+  } else {
+    // 如果转换失败,设置为0.00
+    fuelItem.customFuel = '0.00';
+  }
+
+  // 同步更新 formData.reportFuels 中的数据
+  // 确保通过 deviceId 正确找到并更新对应的记录
+  if (formData.value.reportFuels && fuelItem.deviceId) {
+    const index = formData.value.reportFuels.findIndex(
+      item => item.deviceId === fuelItem.deviceId
+    );
+
+    if (index !== -1) {
+      // 创建新对象,避免引用问题
+      const updatedFuel = {
+        ...formData.value.reportFuels[index],
+        customFuel: fuelItem.customFuel
+      };
+
+      // 使用 Vue.set 或直接赋值确保响应性
+      formData.value.reportFuels[index] = updatedFuel;
+
+      // 强制刷新表格
+      fuelTableKey.value += 1;
+    }
+  }
+
+  // 手动触发当日油耗的重新计算
+  calculateAndUpdateDailyFuel();
+}
+
 // 详情 审批 平台井 获取工作量值
 const getWorkloadValue = (platform, identifier) => {
   if (!platform.extProperty) return '';
@@ -2195,4 +2790,167 @@ const handleApprove = async (action: 'pass' | 'reject') => {
   width: 100%;
 }
 
+/* 油耗信息区域样式 */
+.fuel-consumption-section {
+  padding-left: 0px;
+  padding-right: 0px;
+  margin-top: 20px;
+}
+
+/* 强制表格宽度为100% */
+:deep(.fuel-consumption-el-table) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 确保表格内部元素也充满宽度 */
+:deep(.fuel-consumption-el-table .el-table) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 强制设置表头宽度为100% */
+:deep(.fuel-consumption-el-table .el-table__header) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 表头不换行 */
+:deep(.fuel-consumption-el-table .el-table__header-wrapper th) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: bold;
+}
+
+/* 强制设置表格主体宽度为100% */
+:deep(.fuel-consumption-el-table .el-table__body) {
+  width: 100% !important;
+  min-width: 100% !important;
+}
+
+/* 表头和表体都设置为100%宽度 */
+:deep(.fuel-consumption-el-table .el-table__header-wrapper),
+:deep(.fuel-consumption-el-table .el-table__body-wrapper) {
+  width: 100% !important;
+}
+
+/* 单元格内容居中 */
+:deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
+  text-align: center;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
+/* 实际油耗输入框样式 */
+:deep(.fuel-consumption-el-table .el-input__inner) {
+  text-align: center;
+  padding: 0 5px;
+  height: 28px;
+  line-height: 28px;
+}
+
+/* 只读模式下的数值显示 */
+.fuel-consumption-el-table .readonly-value {
+  color: #606266;
+  font-weight: normal;
+}
+
+/* 强制设置表格宽度为100% */
+:deep(.fuel-consumption-el-table .el-table) {
+  width: 100% !important;
+}
+
+/* 表格容器填满父容器 */
+.fuel-consumption-table {
+  width: 100%;
+  overflow-x: auto;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .fuel-consumption-section {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+
+  :deep(.fuel-consumption-el-table .el-table__header-wrapper th),
+  :deep(.fuel-consumption-el-table .el-table__body-wrapper td) {
+    padding: 8px 5px;
+    font-size: 12px;
+  }
+}
+
+/* 当日油耗输入框样式优化 */
+:deep(.el-form-item .el-input-number) {
+  width: 100%;
+}
+
+/* 当日油耗字段在只读模式下的样式 */
+:deep(.is-disabled .el-input__inner[type="number"]) {
+  background-color: #f5f7fa;
+  border-color: #e4e7ed;
+  color: #606266;
+  cursor: not-allowed;
+}
+
+/* 实际进度区域样式 */
+.actual-progress-container {
+  margin-top: 10px;
+  padding: 20px;
+  border: 1px solid #e6e6e6;
+  border-radius: 8px;
+  background-color: #fafafa;
+}
+
+.progress-title {
+  margin: 0 0 16px 0;
+  font-size: 16px;
+  font-weight: bold;
+  color: #67c23a; /* 实际进度使用绿色标题 */
+}
+
+.no-progress-data {
+  text-align: center;
+  padding: 20px 0;
+  color: #909399;
+  font-style: italic;
+}
+
+/* 调整步骤组件样式以适应水平布局 */
+:deep(.actual-progress-container .el-steps--horizontal) {
+  flex-wrap: nowrap;
+  overflow-x: auto;
+  padding-bottom: 10px;
+}
+
+:deep(.actual-progress-container .el-step__title) {
+  font-size: 12px;
+  line-height: 1.4;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 120px;
+}
+
+/* 确保步骤容器有足够空间 */
+:deep(.actual-progress-container .el-step) {
+  flex-basis: auto;
+  flex-shrink: 0;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  :deep(.actual-progress-container .el-step__title) {
+    font-size: 11px;
+    max-width: 100px;
+  }
+
+  .actual-progress-container {
+    padding: 15px;
+  }
+}
 </style>

+ 9 - 12
src/views/pms/iotrddailyreport/index.vue

@@ -213,21 +213,13 @@
             :min-width="columnWidths.hourCount.width"
             resizable
           />
-          <!--
           <el-table-column
-            label="施工开始日期"
+            label="油耗(L)"
             align="center"
-            prop="constructionStartDate"
-            :formatter="dateFormatter"
-            :min-width="columnWidths.constructionStartDate"
+            prop="dailyFuel"
+            :min-width="columnWidths.dailyFuel.width"
+            resizable
           />
-          <el-table-column
-            label="施工结束日期"
-            align="center"
-            prop="constructionEndDate"
-            :formatter="dateFormatter"
-            :min-width="columnWidths.constructionEndDate"
-          /> -->
           <el-table-column
             label="当日生产动态"
             align="center"
@@ -553,6 +545,11 @@ const columnWidths = ref<
     prop: 'hourCount',
     width: '120px'
   },
+  dailyFuel: {
+    label: '油耗(L)',
+    prop: 'dailyFuel',
+    width: '120px'
+  },
   productionStatus: {
     label: '当日生产动态',
     prop: 'productionStatus',

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

@@ -142,6 +142,12 @@
             prop="workloadDesign"
             :width="columnWidths.workloadDesign"
           />
+          <el-table-column
+            label="油耗(L)"
+            align="center"
+            prop="totalDailyFuel"
+            :width="columnWidths.totalDailyFuel"
+          />
           <!-- 已完成工作量分组列 -->
           <el-table-column label="已完成工作量" align="center">
             <!-- 动态生成列 -->
@@ -407,6 +413,7 @@ const columnWidths = ref({
   wellName: '120px',
   techniques: '120px',
   workloadDesign: '120px',
+  totalDailyFuel: '120px',
   operation: '120px'
 })
 
@@ -477,6 +484,7 @@ const calculateColumnWidths = () => {
   calculateColumnWidth('wellName', '井号', (row: any) => row.wellName)
   calculateColumnWidth('techniques', '工艺', (row: any) => row.techniques)
   calculateColumnWidth('workloadDesign', '总工作量', (row: any) => row.workloadDesign)
+  calculateColumnWidth('totalDailyFuel', '油耗(L)', (row: any) => row.totalDailyFuel)
 
   // 操作列固定宽度
   newWidths.operation = '120px'

+ 62 - 2
src/views/pms/iotrhdailyreport/DeptTree2.vue

@@ -94,10 +94,69 @@ const handleMenuClick = (action) => {
 const getTree = async () => {
   const res = await DeptApi.specifiedSimpleDepts(props.deptId)
   deptList.value = []
-  deptList.value.push(...handleTree(res))
+  const treeData = handleTree(res)
+
+  // 对树进行排序
+  const sortedTree = sortTreeBySort(treeData)
+
+  deptList.value.push(...sortedTree)
   firstLevelKeys.value = deptList.value.map((node) => node.id)
 }
 
+/** 递归对树节点按sort排序 */
+const sortTreeBySort = (treeNodes: Tree[]) => {
+  if (!treeNodes || !Array.isArray(treeNodes)) return treeNodes
+
+  // 对当前层级节点按sort升序排序
+  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 getTopDeptId = async (): Promise<number> => {
+  try {
+    // 如果已经加载了部门树,直接获取第一级节点的第一个节点
+    if (deptList.value && deptList.value.length > 0) {
+      // 假设第一级节点就是顶级节点
+      return deptList.value[0].id
+    }
+
+    // 如果没有加载部门树,则根据传入的deptId重新获取
+    const res = await DeptApi.specifiedSimpleDepts(props.deptId)
+    const treeData = handleTree(res)
+
+    // 对树进行排序
+    const sortedTree = sortTreeBySort(treeData)
+
+    if (sortedTree && sortedTree.length > 0) {
+      return sortedTree[0].id
+    }
+
+    // 如果没有找到顶级部门,返回默认值
+    return 158
+  } catch (error) {
+    console.error('获取顶级部门ID失败:', error)
+    return 158 // 默认值
+  }
+}
+
+// 修改3: 将方法暴露给父组件
+defineExpose({
+  getTopDeptId,
+})
+
 /** 基于名字过滤 */
 const filterNode = (name: string, data: Tree) => {
   if (!name) return true
@@ -123,7 +182,8 @@ watch(
     if (newVal !== oldVal) {
       getTree()
     }
-  }
+  },
+  { immediate: true } // 添加立即执行,确保组件初始化时加载树
 )
 
 /** 初始化 */

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

@@ -0,0 +1,898 @@
+<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
+  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')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    '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: '累计',
+  //   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="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
+          />
+        </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
+          />
+        </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
+          />
+        </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>
+</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(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

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

@@ -0,0 +1,878 @@
+<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="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)"
+          />
+        </el-form-item>
+        <el-form-item label="当日注水时间(H)" prop="dailyInjectWaterTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.dailyInjectWaterTime"
+            placeholder="当日注水时间(H)"
+          />
+        </el-form-item>
+        <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.nonProductionTime"
+            placeholder="非生产时间(H)"
+          />
+        </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>
+</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(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 56 - 120
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,112 +389,20 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } 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'
 
-dayjs.extend(quarterOfYear)
+import { useUserStore } from '@/store/modules/user'
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
+dayjs.extend(quarterOfYear)
 
 /** 瑞恒日报 列表 */
 defineOptions({ name: 'IotRhDailyReport' })
@@ -490,10 +416,10 @@ const selectedRowData = ref<Record<string, any> | null>(null)
 const loading = ref(true) // 列表的加载中
 const list = ref<IotRhDailyReportVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
-const queryParams = reactive({
+let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -523,14 +449,16 @@ const queryParams = reactive({
   status: undefined,
   processInstanceId: undefined,
   auditStatus: undefined,
-  createTime: []
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 // 添加弹窗引用
 const unfilledDialogRef = ref()
 
-const rootDeptId = ref(157)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 // 新增统计相关变量
 const statistics = ref({
@@ -567,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: '施工队伍',
@@ -799,6 +727,7 @@ const getStatistics = async () => {
 const getList = async () => {
   loading.value = true
   try {
+    console.log('22 :>> ', 11)
     const data = await IotRhDailyReportApi.getIotRhDailyReportPage(queryParams)
     list.value = data.list
     total.value = data.total
@@ -924,9 +853,12 @@ const handleQuery = () => {
   getList()
 }
 
+const route = useRoute()
+
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   // 重置后需要重新获取统计数据
   getStatistics()
   // 重新获取工作量统计数据
@@ -971,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()
 }
 
@@ -995,7 +927,11 @@ let resizeObserver: ResizeObserver | null = null
 
 /** 初始化 **/
 onMounted(() => {
-  getList()
+  if (Object.keys(route.query).length > 0) {
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
+    handleQuery()
+  } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化
   if (tableContainerRef.value?.$el) {
     resizeObserver = new ResizeObserver(() => {

+ 386 - 162
src/views/pms/iotrhdailyreport/summary.vue

@@ -1,105 +1,19 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import dayjs from 'dayjs'
 import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
 import { useDebounceFn } from '@vueuse/core'
 import CountTo from '@/components/count-to1.vue'
+import * as echarts from 'echarts'
+import UnfilledReportDialog from './UnfilledReportDialog.vue'
 
-dayjs.extend(quarterOfYear)
+import { Motion, AnimatePresence } from 'motion-v'
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
+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
@@ -110,43 +24,49 @@ interface Query {
   createTime: string[]
 }
 
-const id = 157
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 157,
-  createTime: []
+  deptId: deptId,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
 })
 
-const totalWorkKeys = [
-  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky'],
+const totalWorkKeys: [string, string, string, string, number][] = [
+  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
   [
     'alreadyReported',
     '个',
     '已填报',
-    'i-material-symbols:check-circle-outline-rounded text-emerald'
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
   ],
-  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose'],
+  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose', 0],
   [
     'totalFuelConsumption',
     'L',
     '累计油耗',
-    'i-material-symbols:directions-car-outline-rounded text-sky'
+    'i-material-symbols:directions-car-outline-rounded text-sky',
+    2
   ],
   [
     'totalPowerConsumption',
     'KWH',
     '累计用电量',
-    'i-material-symbols:electric-bolt-outline-rounded text-sky'
+    'i-material-symbols:electric-bolt-outline-rounded text-sky',
+    2
   ],
   [
     'totalWaterInjection',
     '方',
     '累计注水量',
-    'i-material-symbols:water-drop-outline-rounded text-sky'
+    'i-material-symbols:water-drop-outline-rounded text-sky',
+    2
   ],
-  ['totalGasInjection', '万方', '累计注气量', 'i-material-symbols:cloud-outline text-sky']
+  ['totalGasInjection', '万方', '累计注气量', 'i-material-symbols:cloud-outline text-sky', 4]
 ]
 
 const totalWork = ref({
@@ -168,9 +88,10 @@ 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
+        createTime: query.value.createTime,
+        deptId: query.value.deptId
       })
 
       totalWork.value.totalCount = res1[0].count
@@ -188,7 +109,7 @@ const getTotal = useDebounceFn(async () => {
   } finally {
     totalLoading.value = false
   }
-}, 1000)
+}, 500)
 
 interface List {
   id: number | null
@@ -201,8 +122,6 @@ interface List {
   transitTime: number | null
 }
 
-const total = ref<number>(1000)
-
 const list = ref<List[]>([])
 
 const type = ref('2')
@@ -241,32 +160,190 @@ const formatter = (row: List, column: any) => {
 const getList = useDebounceFn(async () => {
   listLoading.value = true
   try {
-    const res = (await IotRhDailyReportApi.getIotRhDailyReportSummary(query.value)) as {
-      total: number
-      list: any[]
-    }
+    const res = await IotRhDailyReportApi.getIotRhDailyReportSummary(query.value)
 
-    const { total: resTotal, list: resList } = res
+    const { list: reslist } = res
 
-    total.value = resTotal
+    type.value = reslist[0]?.type || '2'
 
-    type.value = resList[0]?.type || '2'
-
-    list.value = resList.map(
-      ({ id, projectDeptIa, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
-        id: type === '2' ? projectDeptIa : teamId,
-        name: type === '2' ? projectDeptName : teamName,
-        ...other,
-        cumulativeGasInjection: (other.cumulativeGasInjection || 0) / 10000
-      })
+    list.value = reslist.map(
+      ({ id, projectDeptId, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => {
+        return {
+          id: type === '2' ? projectDeptId : teamId,
+          name: type === '2' ? projectDeptName : teamName,
+          ...other,
+          cumulativeGasInjection: (other.cumulativeGasInjection || 0) / 10000
+        }
+      }
     )
   } finally {
     listLoading.value = false
   }
-}, 1000)
+}, 500)
+
+const tab = ref<'表格' | '看板'>('表格')
+
+const currentTab = ref<'表格' | '看板'>('表格')
+
+const deptName = ref('瑞恒兴域')
+
+const direction = ref<'left' | 'right'>('right')
+
+const handleSelectTab = (val: '表格' | '看板') => {
+  tab.value = val
+  direction.value = val === '看板' ? 'right' : 'left'
+  nextTick(() => {
+    currentTab.value = val
+    setTimeout(() => {
+      render()
+    })
+  })
+}
+
+const chartRef = ref<HTMLDivElement | null>(null)
+let chart: echarts.ECharts | null = null
+
+const xAxisData = ref<string[]>([])
+
+const legend = ref<string[][]>([
+  ['累计油耗 (吨)', 'cumulativeFuelConsumption'],
+  ['累计注气量 (万方)', 'cumulativeGasInjection'],
+  ['累计用电量 (KWH)', 'cumulativePowerConsumption'],
+  ['累计注水量 (方)', 'cumulativeWaterInjection'],
+  ['平均时效 (%)', 'transitTime']
+])
+
+const chartData = ref<Record<string, number[]>>({
+  cumulativeFuelConsumption: [],
+  cumulativeGasInjection: [],
+  cumulativePowerConsumption: [],
+  cumulativeWaterInjection: [],
+  transitTime: []
+})
+
+let chartLoading = ref(false)
+
+const getChart = useDebounceFn(async () => {
+  chartLoading.value = true
+
+  try {
+    const res = await IotRhDailyReportApi.getIotRhDailyReportSummaryPolyline(query.value)
+
+    chartData.value = {
+      cumulativeFuelConsumption: res.map((item) => item.cumulativeFuelConsumption || 0),
+      cumulativeGasInjection: res.map((item) => (item.cumulativeGasInjection || 0) / 10000),
+      cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
+      cumulativeWaterInjection: res.map((item) => item.cumulativeWaterInjection || 0),
+      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+    }
+
+    xAxisData.value = res.map((item) => item.reportDate || '')
+  } finally {
+    chartLoading.value = false
+  }
+}, 500)
+
+const resizer = () => {
+  chart?.resize()
+}
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizer)
+})
+
+const render = () => {
+  if (!chartRef.value) return
+
+  chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+
+  window.addEventListener('resize', resizer)
+
+  const values: number[] = []
+
+  for (const [_name, key] of legend.value) {
+    values.push(...(chartData.value[key] || []))
+  }
+
+  const maxVal = values.length === 0 ? 10000 : Math.max(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values)
+
+  const maxDigits = (Math.floor(maxVal) + '').length
+  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const interval = Math.max(maxDigits, minDigits)
+
+  const maxInterval = interval
+  const minInterval = minDigits
+
+  const intervalArr = [0]
+  for (let i = 1; i <= interval; i++) {
+    intervalArr.push(Math.pow(10, i))
+  }
+
+  chart.setOption({
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      },
+      formatter: (params) => {
+        let d = `${params[0].axisValueLabel}<br>`
+        let item = params.map((el) => {
+          return `<div class="flex items-center justify-between mt-1 gap-1">
+            <span>${el.marker} ${el.seriesName}</span>
+            <span>${chartData.value[legend.value[el.componentIndex][1]][el.dataIndex].toFixed(2)} ${el.seriesName.split(' ')[1]}</span>
+          </div>`
+        })
+
+        return d + item.join('')
+      }
+    },
+    legend: {
+      data: legend.value.map(([name]) => name),
+      show: true
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData.value
+    },
+    yAxis: {
+      type: 'value',
+      min: minInterval,
+      max: maxInterval,
+      interval: 1,
+      axisLabel: {
+        formatter: (v) => {
+          const num = v === 0 ? 0 : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v)
+
+          return num.toLocaleString()
+        }
+      }
+    },
+    series: legend.value.map(([name, key]) => ({
+      name,
+      type: 'line',
+      smooth: true,
+      showSymbol: true,
+      data: chartData.value[key].map((value) => {
+        // return value
+        if (value === 0) return 0
+
+        const isPositive = value > 0
+        const absItem = Math.abs(value)
+
+        const min_value = Math.max(...intervalArr.filter((v) => v <= absItem))
+        const min_index = intervalArr.findIndex((v) => v === min_value)
+
+        const new_value =
+          (absItem - min_value) / (intervalArr[min_index + 1] - intervalArr[min_index]) + min_index
+
+        return isPositive ? new_value : -new_value
+      })
+    }))
+  })
+}
 
 const handleDeptNodeClick = (node: any) => {
-  query.value.deptId = node.id
+  deptName.value = node.name
   handleQuery()
 }
 
@@ -274,15 +351,18 @@ const handleQuery = (setPage = true) => {
   if (setPage) {
     query.value.pageNo = 1
   }
-  getTotal()
+  getChart().then(() => {
+    render()
+  })
   getList()
+  getTotal()
 }
 
 const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     contractName: '',
     taskName: '',
     createTime: []
@@ -292,20 +372,104 @@ 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()
 })
+
+const exportChart = () => {
+  if (!chart) return
+  let img = new Image()
+  img.src = chart.getDataURL({
+    type: 'png',
+    pixelRatio: 1,
+    backgroundColor: '#fff'
+  })
+
+  img.onload = function () {
+    let canvas = document.createElement('canvas')
+    canvas.width = img.width
+    canvas.height = img.height
+    let ctx = canvas.getContext('2d')
+    ctx?.drawImage(img, 0, 0)
+    let dataURL = canvas.toDataURL('image/png')
+
+    let a = document.createElement('a')
+
+    let event = new MouseEvent('click')
+
+    a.href = dataURL
+    a.download = `瑞恒日报统计数据.png`
+    a.dispatchEvent(event)
+  }
+}
+
+const exportData = async () => {
+  const res = await IotRhDailyReportApi.exportRhDailyReportStatistics(query.value)
+
+  download.excel(res, '瑞恒日报统计数据.xlsx')
+}
+
+const exportAll = async () => {
+  if (tab.value === '看板') exportChart()
+  else exportData()
+}
+
+const message = useMessage()
+
+const unfilledDialogRef = ref()
+
+const openUnfilledDialog = () => {
+  // 检查是否选择了创建时间
+  if (!query.value.createTime || query.value.createTime.length === 0) {
+    message.warning('请先选择创建时间范围')
+    return
+  }
+
+  // 打开弹窗
+  unfilledDialogRef.value?.open()
+}
+
+const router = useRouter()
+
+const tolist = (id: number) => {
+  const { pageNo, pageSize, ...rest } = query.value
+
+  router.push({
+    path: '/iotdayilyreport/IotRhDailyReport',
+    query: {
+      ...rest,
+      deptId: id
+    }
+  })
+}
 </script>
 
 <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-5">
+    <div class="grid grid-rows-[62px_164px_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"
@@ -337,6 +501,7 @@ onMounted(() => {
               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>
@@ -352,46 +517,105 @@ onMounted(() => {
         <div
           v-for="info in totalWorkKeys"
           :key="info[0]"
-          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 flex flex-col items-center justify-center gap-2"
+          class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-1 flex flex-col items-center justify-center gap-1"
         >
           <div class="size-7.5" :class="info[3]"></div>
-          <count-to class="text-2xl font-medium" :start-val="0" :end-val="totalWork[info[0]]">
+          <count-to
+            class="text-2xl font-medium"
+            :start-val="0"
+            :end-val="totalWork[info[0]]"
+            :decimals="info[4]"
+            @click="info[2] === '未填报' ? openUnfilledDialog() : ''"
+          >
             <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
           </count-to>
           <div class="text-xs font-medium text-[var(--el-text-color-regular)]">{{ info[1] }}</div>
           <div class="text-sm font-medium text-[var(--el-text-color-regular)]">{{ info[2] }}</div>
         </div>
       </div>
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow pt-4 px-8">
-        <el-table
-          v-loading="listLoading"
-          :data="list"
-          :stripe="true"
-          :style="{ width: '100%' }"
-          max-height="600"
-          class="min-h-143"
-          show-overflow-tooltip
+      <div
+        class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-4 grid grid-rows-[48px_1fr] gap-2"
+      >
+        <div class="flex items-center justify-between">
+          <el-button-group>
+            <el-button
+              size="default"
+              :type="tab === '表格' ? 'primary' : 'default'"
+              @click="handleSelectTab('表格')"
+              >表格
+            </el-button>
+            <el-button
+              size="default"
+              :type="tab === '看板' ? 'primary' : 'default'"
+              @click="handleSelectTab('看板')"
+              >看板
+            </el-button>
+          </el-button-group>
+          <h3 class="text-xl font-medium">{{ `${deptName}-${tab}` }}</h3>
+          <el-button size="default" type="primary" @click="exportAll">导出</el-button>
+        </div>
+        <!-- <el-auto-resizer>
+          <template #default="{ height, width }"> -->
+        <Motion
+          as="div"
+          :style="{ position: 'relative', overflow: 'hidden' }"
+          :animate="{ height: `${500}px`, width: `100%` }"
+          :transition="{ type: 'spring', stiffness: 200, damping: 25, duration: 0.3 }"
         >
-          <el-table-column
-            v-for="item in columns(type)"
-            :key="item.prop"
-            :label="item.label"
-            :prop="item.prop"
-            align="center"
-            :formatter="formatter"
-          />
-        </el-table>
-
-        <Pagination
-          class="mt-8"
-          :total="total"
-          v-model:page="query.pageNo"
-          v-model:limit="query.pageSize"
-          @pagination="getList"
-        />
+          <AnimatePresence :initial="false" mode="sync">
+            <Motion
+              :key="currentTab"
+              as="div"
+              :initial="{ x: direction === 'left' ? '-100%' : '100%', opacity: 0 }"
+              :animate="{ x: '0%', opacity: 1 }"
+              :exit="{ x: direction === 'left' ? '50%' : '-50%', opacity: 0 }"
+              :transition="{ type: 'tween', stiffness: 300, damping: 30, duration: 0.3 }"
+              :style="{ position: 'absolute', left: 0, right: 0, top: 0 }"
+            >
+              <div :style="{ width: `100%`, height: `${500}px` }">
+                <el-table
+                  v-if="currentTab === '表格'"
+                  v-loading="listLoading"
+                  :data="list"
+                  :stripe="true"
+                  :max-height="500"
+                  show-overflow-tooltip
+                >
+                  <template v-for="item in columns(type)" :key="item.prop">
+                    <el-table-column
+                      v-if="item.prop !== 'name'"
+                      :label="item.label"
+                      :prop="item.prop"
+                      align="center"
+                      :formatter="formatter"
+                    />
+                    <el-table-column v-else :label="item.label" :prop="item.prop" align="center">
+                      <template #default="{ row }">
+                        <el-button text type="primary" @click.prevent="tolist(row.id)">{{
+                          row.name
+                        }}</el-button>
+                      </template>
+                    </el-table-column>
+                  </template>
+                </el-table>
+                <div
+                  ref="chartRef"
+                  v-loading="chartLoading"
+                  :key="dayjs().valueOf()"
+                  v-else
+                  :style="{ width: `100%`, height: `${500}px` }"
+                >
+                </div>
+              </div>
+            </Motion>
+          </AnimatePresence>
+        </Motion>
+        <!-- </template>
+        </el-auto-resizer> -->
       </div>
     </div>
   </div>
+  <UnfilledReportDialog ref="unfilledDialogRef" :query-params="query" />
 </template>
 
 <style scoped>
@@ -404,7 +628,7 @@ onMounted(() => {
   border-top-left-radius: 8px;
 
   .el-table__cell {
-    height: 52px;
+    height: 40px;
   }
 
   .el-table__header-wrapper {

+ 4 - 1
src/views/pms/iotrydailyreport/IotRyXjDailyReportForm.vue

@@ -27,7 +27,7 @@
         </el-select>
       </el-form-item>
       <el-form-item :label="t('project.technology')" prop="technique">
-        <el-select v-model="displayData.technique" placeholder="请选择" disabled>
+        <el-select v-model="displayData.technique" placeholder="请选择" >
           <el-option
             v-for="dict in getStrDictOptions(DICT_TYPE.PMS_PROJECT_RY_TECHNOLOGY)"
             :key="dict.id"
@@ -98,6 +98,9 @@
       <el-form-item :label="t('project.nextPlan')" prop="nextPlan">
         <el-input v-model="formData.nextPlan" placeholder="请输入下步工序" type="textarea"/>
       </el-form-item>
+      <el-form-item label="生产时间(H)" prop="productionTime">
+        <el-input v-model="formData.productionTime" placeholder="请输入生产时间(H)" />
+      </el-form-item>
       <el-form-item :label="t('project.transitTime')" prop="transitTime">
         <el-input v-model="displayTransitTime" placeholder="" disabled
                   :class="{'red-text': isTransitTimeOver}"

+ 259 - 0
src/views/pms/iotrydailyreport/UnfilledReportDialog.vue

@@ -0,0 +1,259 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="未填报详情"
+    width="80%"
+    top="5vh"
+    :close-on-click-modal="false"
+    @closed="handleClosed"
+  >
+    <!-- 搜索条件区域 -->
+    <ContentWrap class="mb-15px">
+      <el-form :model="searchParams" ref="searchFormRef" :inline="true" label-width="100px">
+        <el-form-item label="创建时间" prop="createTime">
+          <el-date-picker
+            v-model="searchParams.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-320px"
+            @change="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleSearch">
+            <Icon icon="ep:search" class="mr-5px" /> 搜索
+          </el-button>
+          <el-button @click="resetSearch">
+            <Icon icon="ep:refresh" class="mr-5px" /> 重置
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 列表区域 -->
+    <ContentWrap>
+      <div class="table-container">
+        <el-table
+          v-loading="loading"
+          :data="list"
+          :stripe="true"
+          style="width: 100%"
+          :cell-style="cellStyle"
+          empty-text="暂无未填报数据"
+          table-layout="fixed"
+        >
+          <el-table-column label="日期" align="center" prop="reportDate" width="120">
+            <template #default="scope">
+              <span class="date-content">{{ scope.row.reportDate }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="部门名称" prop="deptNames" min-width="300">
+            <template #default="scope">
+              <div class="dept-names-content">
+                {{ scope.row.deptNames || '-' }}
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 分页
+      <Pagination
+        :total="total"
+        v-model:page="searchParams.pageNo"
+        v-model:limit="searchParams.pageSize"
+        @pagination="getList"
+      /> -->
+    </ContentWrap>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch, nextTick } from 'vue'
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+
+const { t } = useI18n()
+const message = useMessage()
+
+// 弹窗显示控制
+const dialogVisible = ref(false)
+
+// 搜索参数
+const searchParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  createTime: []
+})
+
+// 列表数据
+const list = ref<any[]>([])
+const total = ref(0)
+const loading = ref(false)
+
+// 接收父组件传递的查询参数
+const props = defineProps<{
+  queryParams: any
+}>()
+
+// 搜索表单引用
+const searchFormRef = ref()
+
+// 打开弹窗
+const open = () => {
+  // 复制父组件的查询参数
+  if (props.queryParams.createTime && props.queryParams.createTime.length > 0) {
+    searchParams.createTime = [...props.queryParams.createTime]
+  }
+
+  dialogVisible.value = true
+  // 获取数据
+  nextTick(() => {
+    getList()
+  })
+}
+
+// 获取列表数据
+const getList = async () => {
+  // 检查时间范围
+  if (!searchParams.createTime || searchParams.createTime.length === 0) {
+    list.value = []
+    total.value = 0
+    return
+  }
+
+  loading.value = true
+  try {
+    const res = await IotRyDailyReportApi.ryUnReportDetails({
+      createTime: searchParams.createTime,
+      projectClassification: props.queryParams.projectClassification
+    })
+
+    // 处理返回数据
+    if (res && Array.isArray(res)) {
+      list.value = res
+      total.value = res.length
+    } else {
+      list.value = []
+      total.value = 0
+    }
+  } catch (error) {
+    console.error('获取未填报数据失败', error)
+    message.error('获取未填报数据失败')
+    list.value = []
+    total.value = 0
+  } finally {
+    loading.value = false
+  }
+}
+
+// 搜索
+const handleSearch = () => {
+  searchParams.pageNo = 1
+  getList()
+}
+
+// 重置搜索
+const resetSearch = () => {
+  searchFormRef.value?.resetFields()
+  handleSearch()
+}
+
+// 单元格样式
+const cellStyle = ({ row, column, rowIndex, columnIndex }: any) => {
+  // 为所有列设置基本样式
+  const baseStyle = {
+    padding: '8px 4px'
+  }
+
+  if (column.property === 'deptNames') {
+    return {
+      ...baseStyle,
+      whiteSpace: 'normal',
+      wordBreak: 'break-all',
+      lineHeight: '1.5'
+    }
+  }
+
+  return baseStyle
+}
+
+// 弹窗关闭处理
+const handleClosed = () => {
+  list.value = []
+  total.value = 0
+  searchParams.pageNo = 1
+}
+
+// 暴露方法给父组件
+defineExpose({
+  open
+})
+
+// 监听父组件查询参数变化
+watch(
+  () => props.queryParams.createTime,
+  (newVal) => {
+    if (newVal && newVal.length > 0) {
+      searchParams.createTime = [...newVal]
+    }
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped>
+/* 表格容器确保正确布局 */
+.table-container {
+  width: 100%;
+  overflow-x: auto;
+}
+
+.date-content {
+  white-space: nowrap;
+}
+
+.dept-names-content {
+  white-space: normal;
+  word-break: break-all;
+  line-height: 1.5;
+  padding: 8px 4px;
+}
+
+/* 深度样式修改确保表格正确显示 */
+:deep(.el-table) {
+  table-layout: fixed;
+}
+
+:deep(.el-table .el-table__cell) {
+  box-sizing: border-box;
+}
+
+:deep(.el-table .cell) {
+  white-space: normal;
+  word-break: break-all;
+  line-height: 1.5;
+  padding: 8px 4px;
+}
+
+:deep(.el-table td.el-table__cell) {
+  padding: 8px 4px;
+  border-bottom: 1px solid var(--el-table-border-color);
+}
+
+:deep(.el-table th.el-table__cell) {
+  padding: 8px 4px;
+  background-color: var(--el-table-header-bg-color);
+}
+
+/* 确保列宽正确分配 */
+:deep(.el-table__body colgroup col:nth-child(1)) {
+  width: 120px;
+}
+
+:deep(.el-table__body colgroup col:nth-child(2)) {
+  width: auto;
+}
+</style>

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

@@ -0,0 +1,1163 @@
+<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
+  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')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'rigStatus',
+    '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: '累计',
+  //   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.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[3].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="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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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
+          />
+        </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>
+</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(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

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

@@ -0,0 +1,1168 @@
+<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('当前深度需大于上一次填报深度'))
+  } 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="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)"
+          />
+        </el-form-item>
+        <el-form-item label="其它生产时间(H)" prop="otherProductionTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.otherProductionTime"
+            placeholder="其它生产时间(H)"
+          />
+        </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)"
+          />
+        </el-form-item>
+        <el-form-item label="修理(H)" prop="repairTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.repairTime"
+            placeholder="请输入修理(H)"
+          />
+        </el-form-item>
+        <el-form-item label="自停(H)" prop="selfStopTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.selfStopTime"
+            placeholder="请输入自停(H)"
+          />
+        </el-form-item>
+        <el-form-item label="复杂(H)" prop="complexityTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.complexityTime"
+            placeholder="请输入复杂(H)"
+          />
+        </el-form-item>
+        <el-form-item label="搬迁(H)" prop="relocationTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.relocationTime"
+            placeholder="请输入搬迁(H)"
+          />
+        </el-form-item>
+        <el-form-item label="整改(H)" prop="rectificationTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.rectificationTime"
+            placeholder="请输入整改(H)"
+          />
+        </el-form-item>
+        <el-form-item label="等停(H)" prop="waitingStopTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.waitingStopTime"
+            placeholder="请输入等停(H)"
+          />
+        </el-form-item>
+        <el-form-item label="冬休(H)" prop="winterBreakTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.winterBreakTime"
+            placeholder="请输入冬休(H)"
+          />
+        </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>
+</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(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 67 - 132
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"
@@ -251,14 +263,14 @@
                 resizable
               >
                 <template #default="scope">
-                <span :class="{'fuel-warning': shouldShowFuelWarning(scope.row)}">
-                  {{ scope.row.dailyFuel }}
-                </span>
+                  <span :class="{ 'fuel-warning': shouldShowFuelWarning(scope.row) }">
+                    {{ scope.row.dailyFuel }}
+                  </span>
                 </template>
               </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"
@@ -380,7 +399,7 @@
                 resizable
               />
             </el-table-column>
-            <el-table-column label="操作" align="center" fixed="right">
+            <!-- <el-table-column label="操作" align="center" fixed="right">
               <template #default="scope">
                 <el-button
                   link
@@ -399,7 +418,7 @@
                   删除
                 </el-button>
               </template>
-            </el-table-column>
+            </el-table-column> -->
           </el-table>
         </div>
         <!-- 分页 -->
@@ -418,112 +437,20 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
+import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
 import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import { useDebounceFn } from '@vueuse/core'
 
-dayjs.extend(quarterOfYear)
+import { useUserStore } from '@/store/modules/user'
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
+dayjs.extend(quarterOfYear)
 
 /** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyDailyReport' })
@@ -534,15 +461,15 @@ 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[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
-const queryParams = reactive({
+let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -577,7 +504,9 @@ const queryParams = reactive({
   status: undefined,
   processInstanceId: undefined,
   auditStatus: undefined,
-  createTime: []
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
@@ -743,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: {
@@ -856,7 +785,6 @@ const cellStyle = ({
   rowIndex: number
   columnIndex: number
 }) => {
-
   // 处理当日油耗预警
   if (column.property === 'dailyFuel') {
     if (shouldShowFuelWarning(row)) {
@@ -864,7 +792,7 @@ const cellStyle = ({
         color: 'red',
         fontWeight: 'bold',
         backgroundColor: '#fff5f5' // 可选:添加背景色突出显示
-      };
+      }
     }
   }
 
@@ -914,9 +842,9 @@ const getList = async () => {
 
 // 在 cellStyle 函数附近添加油耗预警判断函数
 const shouldShowFuelWarning = (row: any): boolean => {
-  const dailyFuel = parseFloat(row.dailyFuel);
-  return !isNaN(dailyFuel) && dailyFuel > 15;
-};
+  const dailyFuel = parseFloat(row.dailyFuel)
+  return !isNaN(dailyFuel) && dailyFuel > 15
+}
 
 // 计算列宽度
 
@@ -929,6 +857,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -972,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()
 }
 
@@ -994,9 +923,15 @@ const handleExport = async () => {
 // 声明 ResizeObserver 实例
 let resizeObserver: ResizeObserver | null = null
 
+const route = useRoute()
+
 /** 初始化 **/
 onMounted(() => {
-  getList()
+  if (Object.keys(route.query).length > 0) {
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
+    handleQuery()
+  } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化
   if (tableContainerRef.value?.$el) {
     resizeObserver = new ResizeObserver(() => {

+ 375 - 151
src/views/pms/iotrydailyreport/summary.vue

@@ -1,105 +1,19 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import dayjs from 'dayjs'
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useDebounceFn } from '@vueuse/core'
 import CountTo from '@/components/count-to1.vue'
+import * as echarts from 'echarts'
+import UnfilledReportDialog from './UnfilledReportDialog.vue'
 
-dayjs.extend(quarterOfYear)
+import { Motion, AnimatePresence } from 'motion-v'
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
+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
@@ -111,38 +25,43 @@ interface Query {
   projectClassification: 1 | 2
 }
 
-const id = 158
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 158,
-  createTime: [],
+  deptId: deptId,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
   projectClassification: 1
 })
 
-const totalWorkKeys = [
-  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky'],
+const totalWorkKeys: [string, string, string, string, number][] = [
+  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
   [
     'alreadyReported',
     '个',
     '已填报',
-    'i-material-symbols:check-circle-outline-rounded text-emerald'
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
   ],
-  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose'],
+  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose', 0],
   [
     'totalFuelConsumption',
     '吨',
     '累计油耗',
-    'i-material-symbols:directions-car-outline-rounded text-sky'
+    'i-material-symbols:directions-car-outline-rounded text-sky',
+    2
   ],
   [
     'totalPowerConsumption',
     'KWH',
     '累计用电量',
-    'i-material-symbols:electric-bolt-outline-rounded text-sky'
+    'i-material-symbols:electric-bolt-outline-rounded text-sky',
+    2
   ],
-  ['totalFootage', 'M', '累计进尺', 'i-solar:ruler-bold text-sky']
+  ['totalFootage', 'M', '累计进尺', 'i-solar:ruler-bold text-sky', 2]
 ]
 
 const totalWork = ref({
@@ -163,7 +82,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
@@ -197,8 +116,6 @@ interface List {
   transitTime: number | null
 }
 
-const total = ref<number>(1000)
-
 const list = ref<List[]>([])
 
 const type = ref('2')
@@ -233,20 +150,15 @@ const formatter = (row: List, column: any) => {
 const getList = useDebounceFn(async () => {
   listLoading.value = true
   try {
-    const res = (await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)) as {
-      total: number
-      list: any[]
-    }
-
-    const { total: resTotal, list: resList } = res
+    const res = await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)
 
-    total.value = resTotal
+    const { list: reslist } = res
 
-    type.value = resList[0]?.type || '2'
+    type.value = reslist[0]?.type || '2'
 
-    list.value = resList.map(
-      ({ id, projectDeptIa, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
-        id: type === '2' ? projectDeptIa : teamId,
+    list.value = reslist.map(
+      ({ id, projectDeptId, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
+        id: type === '2' ? projectDeptId : teamId,
         name: type === '2' ? projectDeptName : teamName,
         ...other
       })
@@ -256,8 +168,177 @@ const getList = useDebounceFn(async () => {
   }
 }, 1000)
 
+const tab = ref<'表格' | '看板'>('表格')
+
+const currentTab = ref<'表格' | '看板'>('表格')
+
+const deptName = ref('瑞鹰国际钻井')
+
+const direction = ref<'left' | 'right'>('right')
+
+const handleSelectTab = (val: '表格' | '看板') => {
+  tab.value = val
+  direction.value = val === '看板' ? 'right' : 'left'
+  nextTick(() => {
+    currentTab.value = val
+    setTimeout(() => {
+      render()
+    })
+  })
+}
+
+const chartRef = ref<HTMLDivElement | null>(null)
+let chart: echarts.ECharts | null = null
+
+const xAxisData = ref<string[]>([])
+
+const legend = ref<string[][]>([
+  ['累计油耗 (吨)', 'cumulativeFuelConsumption'],
+  ['累计进尺 (M)', 'cumulativeFootage'],
+  ['累计用电量 (KWH)', 'cumulativePowerConsumption'],
+  ['平均时效 (%)', 'transitTime']
+])
+
+const chartData = ref<Record<string, number[]>>({
+  cumulativeFuelConsumption: [],
+  cumulativeFootage: [],
+  cumulativePowerConsumption: [],
+  transitTime: []
+})
+
+let chartLoading = ref(false)
+
+const getChart = useDebounceFn(async () => {
+  chartLoading.value = true
+
+  try {
+    // 创建查询参数,如果 createTime 为空则不传
+    const params: any = {
+      deptId: query.value.deptId,
+      projectClassification: query.value.projectClassification
+    }
+
+    // 只有 createTime 有值时才添加
+    if (query.value.createTime && query.value.createTime.length === 2) {
+      params.createTime = query.value.createTime
+    }
+
+    const res = await IotRyDailyReportApi.getIotRyDailyReportSummaryPolyline(query.value)
+
+    chartData.value = {
+      cumulativeFuelConsumption: res.map((item) => item.cumulativeFuelConsumption || 0),
+      cumulativeFootage: res.map((item) => item.cumulativeFootage || 0),
+      cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
+      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+    }
+
+    xAxisData.value = res.map((item) => item.reportDate || '')
+  } finally {
+    chartLoading.value = false
+  }
+}, 1000)
+
+const resizer = () => {
+  chart?.resize()
+}
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizer)
+})
+
+const render = () => {
+  if (!chartRef.value) return
+
+  chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+
+  window.addEventListener('resize', resizer)
+
+  const values: number[] = []
+
+  for (const [_name, key] of legend.value) {
+    values.push(...(chartData.value[key] || []))
+  }
+
+  const maxVal = values.length === 0 ? 10000 : Math.max(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values)
+
+  const maxDigits = (Math.floor(maxVal) + '').length
+  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const interval = Math.max(maxDigits, minDigits)
+
+  const maxInterval = interval
+  const minInterval = minDigits
+
+  const intervalArr = [0]
+  for (let i = 1; i <= interval; i++) {
+    intervalArr.push(Math.pow(10, i))
+  }
+
+  chart.setOption({
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      },
+      formatter: (params) => {
+        let d = `${params[0].axisValueLabel}<br>`
+        let item = params.map((el) => {
+          return `<div class="flex items-center justify-between mt-1 gap-1">
+            <span>${el.marker} ${el.seriesName}</span>
+            <span>${chartData.value[legend.value[el.componentIndex][1]][el.dataIndex].toFixed(2)} ${el.seriesName.split(' ')[1]}</span>
+          </div>`
+        })
+
+        return d + item.join('')
+      }
+    },
+    legend: {
+      data: legend.value.map(([name]) => name),
+      show: true
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData.value
+    },
+    yAxis: {
+      type: 'value',
+      min: minInterval,
+      max: maxInterval,
+      interval: 1,
+      axisLabel: {
+        formatter: (v) => {
+          const num = v === 0 ? 0 : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v)
+
+          return num.toLocaleString()
+        }
+      }
+    },
+    series: legend.value.map(([name, key]) => ({
+      name,
+      type: 'line',
+      smooth: true,
+      showSymbol: true,
+      data: chartData.value[key].map((value) => {
+        // return value
+        if (value === 0) return 0
+
+        const isPositive = value > 0
+        const absItem = Math.abs(value)
+
+        const min_value = Math.max(...intervalArr.filter((v) => v <= absItem))
+        const min_index = intervalArr.findIndex((v) => v === min_value)
+
+        const new_value =
+          (absItem - min_value) / (intervalArr[min_index + 1] - intervalArr[min_index]) + min_index
+
+        return isPositive ? new_value : -new_value
+      })
+    }))
+  })
+}
+
 const handleDeptNodeClick = (node: any) => {
-  query.value.deptId = node.id
+  deptName.value = node.name
   handleQuery()
 }
 
@@ -265,15 +346,18 @@ const handleQuery = (setPage = true) => {
   if (setPage) {
     query.value.pageNo = 1
   }
-  getTotal()
+  getChart().then(() => {
+    render()
+  })
   getList()
+  getTotal()
 }
 
 const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     createTime: [],
     projectClassification: 1
   }
@@ -282,18 +366,98 @@ 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(() => {
   handleQuery()
 })
+
+const exportChart = () => {
+  if (!chart) return
+  let img = new Image()
+  img.src = chart.getDataURL({
+    type: 'png',
+    pixelRatio: 1,
+    backgroundColor: '#fff'
+  })
+
+  img.onload = function () {
+    let canvas = document.createElement('canvas')
+    canvas.width = img.width
+    canvas.height = img.height
+    let ctx = canvas.getContext('2d')
+    ctx?.drawImage(img, 0, 0)
+    let dataURL = canvas.toDataURL('image/png')
+
+    let a = document.createElement('a')
+
+    let event = new MouseEvent('click')
+
+    a.href = dataURL
+    a.download = `瑞鹰钻井日报统计数据.png`
+    a.dispatchEvent(event)
+  }
+}
+
+const exportData = async () => {
+  const res = await IotRyDailyReportApi.exportRyDailyReportStatistics(query.value)
+
+  download.excel(res, '瑞鹰钻井日报统计数据.xlsx')
+}
+
+const exportAll = async () => {
+  if (tab.value === '看板') exportChart()
+  else exportData()
+}
+
+const message = useMessage()
+
+const unfilledDialogRef = ref()
+
+const openUnfilledDialog = () => {
+  // 检查是否选择了创建时间
+  if (!query.value.createTime || query.value.createTime.length === 0) {
+    message.warning('请先选择创建时间范围')
+    return
+  }
+
+  // 打开弹窗
+  unfilledDialogRef.value?.open()
+}
+
+const router = useRouter()
+
+const tolist = (id: number) => {
+  const { pageNo, pageSize, ...rest } = query.value
+
+  router.push({
+    path: '/iotdayilyreport/IotRyDailyReport',
+    query: {
+      ...rest,
+      deptId: id
+    }
+  })
+}
 </script>
 
 <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 ref="deptTreeRef" :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-5">
       <el-form
@@ -328,6 +492,7 @@ onMounted(() => {
               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>
@@ -345,43 +510,102 @@ onMounted(() => {
           class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 flex flex-col items-center justify-center gap-2"
         >
           <div class="size-7.5" :class="info[3]"></div>
-          <count-to class="text-2xl font-medium" :start-val="0" :end-val="totalWork[info[0]]">
+          <count-to
+            class="text-2xl font-medium"
+            :start-val="0"
+            :end-val="totalWork[info[0]]"
+            :decimals="info[4]"
+            @click="info[2] === '未填报' ? openUnfilledDialog() : ''"
+          >
             <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
           </count-to>
           <div class="text-xs font-medium text-[var(--el-text-color-regular)]">{{ info[1] }}</div>
           <div class="text-sm font-medium text-[var(--el-text-color-regular)]">{{ info[2] }}</div>
         </div>
       </div>
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow pt-4 px-8">
-        <el-table
-          v-loading="listLoading"
-          :data="list"
-          :stripe="true"
-          :style="{ width: '100%' }"
-          max-height="600"
-          class="min-h-143"
-          show-overflow-tooltip
+      <div
+        class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-4 grid grid-rows-[48px_1fr] gap-2"
+      >
+        <div class="flex items-center justify-between">
+          <el-button-group>
+            <el-button
+              size="default"
+              :type="tab === '表格' ? 'primary' : 'default'"
+              @click="handleSelectTab('表格')"
+              >表格
+            </el-button>
+            <el-button
+              size="default"
+              :type="tab === '看板' ? 'primary' : 'default'"
+              @click="handleSelectTab('看板')"
+              >看板
+            </el-button>
+          </el-button-group>
+          <h3 class="text-xl font-medium">{{ `${deptName}-${tab}` }}</h3>
+          <el-button size="default" type="primary" @click="exportAll">导出</el-button>
+        </div>
+        <!-- <el-auto-resizer>
+          <template #default="{ height, width }"> -->
+        <Motion
+          as="div"
+          :style="{ position: 'relative', overflow: 'hidden' }"
+          :animate="{ height: `${500}px`, width: `100%` }"
+          :transition="{ type: 'spring', stiffness: 200, damping: 25, duration: 0.3 }"
         >
-          <el-table-column
-            v-for="item in columns(type)"
-            :key="item.prop"
-            :label="item.label"
-            :prop="item.prop"
-            align="center"
-            :formatter="formatter"
-          />
-        </el-table>
-
-        <Pagination
-          class="mt-8"
-          :total="total"
-          v-model:page="query.pageNo"
-          v-model:limit="query.pageSize"
-          @pagination="getList"
-        />
+          <AnimatePresence :initial="false" mode="sync">
+            <Motion
+              :key="currentTab"
+              as="div"
+              :initial="{ x: direction === 'left' ? '-100%' : '100%', opacity: 0 }"
+              :animate="{ x: '0%', opacity: 1 }"
+              :exit="{ x: direction === 'left' ? '50%' : '-50%', opacity: 0 }"
+              :transition="{ type: 'tween', stiffness: 300, damping: 30, duration: 0.3 }"
+              :style="{ position: 'absolute', left: 0, right: 0, top: 0 }"
+            >
+              <div :style="{ width: `100%`, height: `${500}px` }">
+                <el-table
+                  v-if="currentTab === '表格'"
+                  v-loading="listLoading"
+                  :data="list"
+                  :stripe="true"
+                  :max-height="500"
+                  show-overflow-tooltip
+                >
+                  <template v-for="item in columns(type)" :key="item.prop">
+                    <el-table-column
+                      v-if="item.prop !== 'name'"
+                      :label="item.label"
+                      :prop="item.prop"
+                      align="center"
+                      :formatter="formatter"
+                    />
+                    <el-table-column v-else :label="item.label" :prop="item.prop" align="center">
+                      <template #default="{ row }">
+                        <el-button text type="primary" @click.prevent="tolist(row.id)">{{
+                          row.name
+                        }}</el-button>
+                      </template>
+                    </el-table-column>
+                  </template>
+                </el-table>
+                <div
+                  ref="chartRef"
+                  v-loading="chartLoading"
+                  :key="dayjs().valueOf()"
+                  v-else
+                  :style="{ width: `100%`, height: `${500}px` }"
+                >
+                </div>
+              </div>
+            </Motion>
+          </AnimatePresence>
+        </Motion>
+        <!-- </template>
+        </el-auto-resizer> -->
       </div>
     </div>
   </div>
+  <UnfilledReportDialog ref="unfilledDialogRef" :query-params="query" />
 </template>
 
 <style scoped>

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

@@ -0,0 +1,989 @@
+<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
+  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')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'repairStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_REPAIR_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: '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: '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[3].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'
+] 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="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="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)"
+            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)"
+            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)"
+            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 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>
+</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(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

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

@@ -0,0 +1,962 @@
+<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[3].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'
+] 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()
+  console.log('total :>> ', total)
+  console.log('form.value.ratedProductionTime :>> ', form.value.ratedProductionTime)
+  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 { 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="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.2 }"
+            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="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"
+            placeholder="请输入额定生产时间(H)"
+          />
+        </el-form-item>
+        <el-form-item label="生产时间(H)" prop="productionTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.productionTime"
+            placeholder="请输入生产时间(H)"
+          />
+        </el-form-item>
+        <el-form-item label="非生产时间(H)" prop="nonProductionTime">
+          <el-input-number
+            class="w-full!"
+            :min="0"
+            v-model="form.nonProductionTime"
+            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 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>
+      </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>
+</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(.el-input-number) {
+  width: 100%;
+}
+
+:deep(.el-input-number__decrease) {
+  display: none !important;
+}
+
+:deep(.el-input-number__increase) {
+  display: none !important;
+}
+</style>

+ 50 - 114
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"
@@ -340,7 +358,7 @@
               :min-width="columnWidths.leaveStaffNum.width"
               resizable
             />
-            <el-table-column label="操作" align="center" fixed="right">
+            <!-- <el-table-column label="操作" align="center" fixed="right">
               <template #default="scope">
                 <el-button
                   link
@@ -359,7 +377,7 @@
                   删除
                 </el-button>
               </template>
-            </el-table-column>
+            </el-table-column> -->
           </el-table>
         </div>
         <!-- 分页 -->
@@ -378,113 +396,21 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
+import { dateFormatter, dateFormatter2, rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyreport'
 import IotRyXjDailyReportForm from './IotRyXjDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import 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'
 
 dayjs.extend(quarterOfYear)
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
-
 /** 瑞鹰日报 列表 */
 defineOptions({ name: 'IotRyXjDailyReport' })
 
@@ -497,10 +423,10 @@ const selectedRowData = ref<Record<string, any> | null>(null)
 const loading = ref(true) // 列表的加载中
 const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
-const queryParams = reactive({
+let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -535,12 +461,14 @@ const queryParams = reactive({
   status: undefined,
   processInstanceId: undefined,
   auditStatus: undefined,
-  createTime: []
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
-const rootDeptId = ref(158)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 // 表格引用
 const tableRef = ref()
@@ -802,14 +730,14 @@ const cellStyle = ({
 }) => {
   // 当日油耗预警逻辑
   if (column.property === 'dailyFuel') {
-    const dailyFuel = parseFloat(row.dailyFuel) || 0;
+    const dailyFuel = parseFloat(row.dailyFuel) || 0
     if (dailyFuel > 5) {
       return {
         backgroundColor: '#e6f8ff', // 浅黄色背景
         color: '#0a35c4', // 橙色文字
         fontWeight: 'bold',
         border: '1px solid #ffd591' // 可选:添加边框突出显示
-      };
+      }
     }
   }
 
@@ -925,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 '-'
   // 将小数转换为百分比,保留两位小数
@@ -940,6 +869,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -1006,9 +936,15 @@ const handleExport = async () => {
 // 声明 ResizeObserver 实例
 let resizeObserver: ResizeObserver | null = null
 
+const route = useRoute()
+
 /** 初始化 **/
 onMounted(() => {
-  getList()
+  if (Object.keys(route.query).length > 0) {
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
+    handleQuery()
+  } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化
   if (tableContainerRef.value?.$el) {
     resizeObserver = new ResizeObserver(() => {

+ 369 - 152
src/views/pms/iotrydailyreport/xsummary.vue

@@ -1,105 +1,19 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
-import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import dayjs from 'dayjs'
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useDebounceFn } from '@vueuse/core'
 import CountTo from '@/components/count-to1.vue'
+import * as echarts from 'echarts'
+import UnfilledReportDialog from './UnfilledReportDialog.vue'
 
-dayjs.extend(quarterOfYear)
+import { Motion, AnimatePresence } from 'motion-v'
 
-const rangeShortcuts = [
-  {
-    text: '今天',
-    value: () => {
-      const today = dayjs()
-      return [today.startOf('day').toDate(), today.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '昨天',
-    value: () => {
-      const yesterday = dayjs().subtract(1, 'day')
-      return [yesterday.startOf('day').toDate(), yesterday.endOf('day').toDate()]
-    }
-  },
-  {
-    text: '本周',
-    value: () => {
-      return [dayjs().startOf('week').toDate(), dayjs().endOf('week').toDate()]
-    }
-  },
-  {
-    text: '上周',
-    value: () => {
-      const lastWeek = dayjs().subtract(1, 'week')
-      return [lastWeek.startOf('week').toDate(), lastWeek.endOf('week').toDate()]
-    }
-  },
-  {
-    text: '本月',
-    value: () => {
-      return [dayjs().startOf('month').toDate(), dayjs().endOf('month').toDate()]
-    }
-  },
-  {
-    text: '上月',
-    value: () => {
-      const lastMonth = dayjs().subtract(1, 'month')
-      return [lastMonth.startOf('month').toDate(), lastMonth.endOf('month').toDate()]
-    }
-  },
-  {
-    text: '本季度',
-    value: () => {
-      return [dayjs().startOf('quarter').toDate(), dayjs().endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '上季度',
-    value: () => {
-      const lastQuarter = dayjs().subtract(1, 'quarter')
-      return [lastQuarter.startOf('quarter').toDate(), lastQuarter.endOf('quarter').toDate()]
-    }
-  },
-  {
-    text: '今年',
-    value: () => {
-      return [dayjs().startOf('year').toDate(), dayjs().endOf('year').toDate()]
-    }
-  },
-  {
-    text: '去年',
-    value: () => {
-      const lastYear = dayjs().subtract(1, 'year')
-      return [lastYear.startOf('year').toDate(), lastYear.endOf('year').toDate()]
-    }
-  },
-  {
-    text: '最近7天',
-    value: () => {
-      return [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近30天',
-    value: () => {
-      return [dayjs().subtract(29, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近90天',
-    value: () => {
-      return [dayjs().subtract(89, 'day').toDate(), dayjs().toDate()]
-    }
-  },
-  {
-    text: '最近一年',
-    value: () => {
-      return [dayjs().subtract(1, 'year').toDate(), dayjs().toDate()]
-    }
-  }
-]
+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
@@ -111,39 +25,44 @@ interface Query {
   projectClassification: 1 | 2
 }
 
-const id = 158
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 158,
-  createTime: [],
+  deptId: deptId,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ],
   projectClassification: 2
 })
 
-const totalWorkKeys = [
-  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky'],
+const totalWorkKeys: [string, string, string, string, number][] = [
+  ['totalCount', '个', '总数', 'i-tabler:report-analytics text-sky', 0],
   [
     'alreadyReported',
     '个',
     '已填报',
-    'i-material-symbols:check-circle-outline-rounded text-emerald'
+    'i-material-symbols:check-circle-outline-rounded text-emerald',
+    0
   ],
-  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose'],
+  ['notReported', '个', '未填报', 'i-material-symbols:cancel-outline-rounded text-rose', 0],
   [
     'totalFuelConsumption',
     '吨',
     '累计油耗',
-    'i-material-symbols:directions-car-outline-rounded text-sky'
+    'i-material-symbols:directions-car-outline-rounded text-sky',
+    2
   ],
   [
     'totalPowerConsumption',
     'KWH',
     '累计用电量',
-    'i-material-symbols:electric-bolt-outline-rounded text-sky'
+    'i-material-symbols:electric-bolt-outline-rounded text-sky',
+    2
   ],
-  ['constructionWells', '个', '累计施工井数', 'i-mdi:progress-wrench text-sky'],
-  ['completedWells', '个', '累计完工井数', 'i-mdi:wrench-check-outline text-emerald']
+  ['constructionWells', '个', '累计施工井数', 'i-mdi:progress-wrench text-sky', 0],
+  ['completedWells', '个', '累计完工井数', 'i-mdi:wrench-check-outline text-emerald', 0]
 ]
 
 const totalWork = ref({
@@ -165,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
@@ -198,8 +117,6 @@ interface List {
   transitTime: number | null
 }
 
-const total = ref<number>(1000)
-
 const list = ref<List[]>([])
 
 const type = ref('2')
@@ -238,20 +155,15 @@ const formatter = (row: List, column: any) => {
 const getList = useDebounceFn(async () => {
   listLoading.value = true
   try {
-    const res = (await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)) as {
-      total: number
-      list: any[]
-    }
+    const res = await IotRyDailyReportApi.getIotRyDailyReportSummary(query.value)
 
-    const { total: resTotal, list: resList } = res
+    const { list: reslist } = res
 
-    total.value = resTotal
+    type.value = reslist[0]?.type || '2'
 
-    type.value = resList[0]?.type || '2'
-
-    list.value = resList.map(
-      ({ id, projectDeptIa, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
-        id: type === '2' ? projectDeptIa : teamId,
+    list.value = reslist.map(
+      ({ id, projectDeptId, projectDeptName, teamId, teamName, sort, taskId, type, ...other }) => ({
+        id: type === '2' ? projectDeptId : teamId,
         name: type === '2' ? projectDeptName : teamName,
         ...other
       })
@@ -261,8 +173,170 @@ const getList = useDebounceFn(async () => {
   }
 }, 1000)
 
+const tab = ref<'表格' | '看板'>('表格')
+
+const currentTab = ref<'表格' | '看板'>('表格')
+
+const deptName = ref('瑞鹰国际修井')
+
+const direction = ref<'left' | 'right'>('right')
+
+const handleSelectTab = (val: '表格' | '看板') => {
+  tab.value = val
+  direction.value = val === '看板' ? 'right' : 'left'
+  nextTick(() => {
+    currentTab.value = val
+    setTimeout(() => {
+      render()
+    })
+  })
+}
+
+const chartRef = ref<HTMLDivElement | null>(null)
+let chart: echarts.ECharts | null = null
+
+const xAxisData = ref<string[]>([])
+
+const legend = ref<string[][]>([
+  ['累计施工井数 (个)', 'cumulativeConstructWells'],
+  ['累计完工井数 (个)', 'cumulativeCompletedWells'],
+  ['累计油耗 (吨)', 'cumulativeFuelConsumption'],
+  ['累计用电量 (KWH)', 'cumulativePowerConsumption'],
+  ['平均时效 (%)', 'transitTime']
+])
+
+const chartData = ref<Record<string, number[]>>({
+  cumulativeFuelConsumption: [],
+  cumulativeConstructWells: [],
+  cumulativeCompletedWells: [],
+  cumulativePowerConsumption: [],
+  transitTime: []
+})
+
+let chartLoading = ref(false)
+
+const getChart = useDebounceFn(async () => {
+  chartLoading.value = true
+
+  try {
+    const res = await IotRyDailyReportApi.getIotRyDailyReportSummaryPolyline(query.value)
+
+    chartData.value = {
+      cumulativeFuelConsumption: res.map((item) => item.cumulativeFuelConsumption || 0),
+      cumulativeConstructWells: res.map((item) => item.cumulativeConstructWells || 0),
+      cumulativeCompletedWells: res.map((item) => item.cumulativeCompletedWells || 0),
+      cumulativePowerConsumption: res.map((item) => item.cumulativePowerConsumption || 0),
+      transitTime: res.map((item) => (item.transitTime || 0) * 100)
+    }
+
+    xAxisData.value = res.map((item) => item.reportDate || '')
+  } finally {
+    chartLoading.value = false
+  }
+}, 1000)
+
+const resizer = () => {
+  chart?.resize()
+}
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizer)
+})
+
+const render = () => {
+  if (!chartRef.value) return
+
+  chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+
+  window.addEventListener('resize', resizer)
+
+  const values: number[] = []
+
+  for (const [_name, key] of legend.value) {
+    values.push(...(chartData.value[key] || []))
+  }
+
+  const maxVal = values.length === 0 ? 10000 : Math.max(...values)
+  const minVal = values.length === 0 ? 0 : Math.min(...values)
+
+  const maxDigits = (Math.floor(maxVal) + '').length
+  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
+  const interval = Math.max(maxDigits, minDigits)
+
+  const maxInterval = interval
+  const minInterval = minDigits
+
+  const intervalArr = [0]
+  for (let i = 1; i <= interval; i++) {
+    intervalArr.push(Math.pow(10, i))
+  }
+
+  chart.setOption({
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'line'
+      },
+      formatter: (params) => {
+        let d = `${params[0].axisValueLabel}<br>`
+        let item = params.map((el) => {
+          return `<div class="flex items-center justify-between mt-1 gap-1">
+            <span>${el.marker} ${el.seriesName}</span>
+            <span>${chartData.value[legend.value[el.componentIndex][1]][el.dataIndex].toFixed(2)} ${el.seriesName.split(' ')[1]}</span>
+          </div>`
+        })
+
+        return d + item.join('')
+      }
+    },
+    legend: {
+      data: legend.value.map(([name]) => name),
+      show: true
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData.value
+    },
+    yAxis: {
+      type: 'value',
+      min: minInterval,
+      max: maxInterval,
+      interval: 1,
+      axisLabel: {
+        formatter: (v) => {
+          const num = v === 0 ? 0 : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v)
+
+          return num.toLocaleString()
+        }
+      }
+    },
+    series: legend.value.map(([name, key]) => ({
+      name,
+      type: 'line',
+      smooth: true,
+      showSymbol: true,
+      data: chartData.value[key].map((value) => {
+        // return value
+        if (value === 0) return 0
+
+        const isPositive = value > 0
+        const absItem = Math.abs(value)
+
+        const min_value = Math.max(...intervalArr.filter((v) => v <= absItem))
+        const min_index = intervalArr.findIndex((v) => v === min_value)
+
+        const new_value =
+          (absItem - min_value) / (intervalArr[min_index + 1] - intervalArr[min_index]) + min_index
+
+        return isPositive ? new_value : -new_value
+      })
+    }))
+  })
+}
+
 const handleDeptNodeClick = (node: any) => {
-  query.value.deptId = node.id
+  deptName.value = node.name
+  // query.value.deptId = node.id
   handleQuery()
 }
 
@@ -270,15 +344,18 @@ const handleQuery = (setPage = true) => {
   if (setPage) {
     query.value.pageNo = 1
   }
-  getTotal()
+  getChart().then(() => {
+    render()
+  })
   getList()
+  getTotal()
 }
 
 const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     createTime: [],
     projectClassification: 1
   }
@@ -287,18 +364,98 @@ 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(() => {
   handleQuery()
 })
+
+const exportChart = () => {
+  if (!chart) return
+  let img = new Image()
+  img.src = chart.getDataURL({
+    type: 'png',
+    pixelRatio: 1,
+    backgroundColor: '#fff'
+  })
+
+  img.onload = function () {
+    let canvas = document.createElement('canvas')
+    canvas.width = img.width
+    canvas.height = img.height
+    let ctx = canvas.getContext('2d')
+    ctx?.drawImage(img, 0, 0)
+    let dataURL = canvas.toDataURL('image/png')
+
+    let a = document.createElement('a')
+
+    let event = new MouseEvent('click')
+
+    a.href = dataURL
+    a.download = `瑞鹰修井日报统计数据.png`
+    a.dispatchEvent(event)
+  }
+}
+
+const exportData = async () => {
+  const res = await IotRyDailyReportApi.exportRyDailyReportStatistics(query.value)
+
+  download.excel(res, '瑞鹰修井日报统计数据.xlsx')
+}
+
+const exportAll = async () => {
+  if (tab.value === '看板') exportChart()
+  else exportData()
+}
+
+const message = useMessage()
+
+const unfilledDialogRef = ref()
+
+const openUnfilledDialog = () => {
+  // 检查是否选择了创建时间
+  if (!query.value.createTime || query.value.createTime.length === 0) {
+    message.warning('请先选择创建时间范围')
+    return
+  }
+
+  // 打开弹窗
+  unfilledDialogRef.value?.open()
+}
+
+const router = useRouter()
+
+const tolist = (id: number) => {
+  const { pageNo, pageSize, ...rest } = query.value
+
+  router.push({
+    path: '/iotdayilyreport/IotRyXjDailyReport',
+    query: {
+      ...rest,
+      deptId: id
+    }
+  })
+}
 </script>
 
 <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-5">
       <el-form
@@ -333,6 +490,7 @@ onMounted(() => {
               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>
@@ -350,43 +508,102 @@ onMounted(() => {
           class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4 flex flex-col items-center justify-center gap-2"
         >
           <div class="size-7.5" :class="info[3]"></div>
-          <count-to class="text-2xl font-medium" :start-val="0" :end-val="totalWork[info[0]]">
+          <count-to
+            class="text-2xl font-medium"
+            :start-val="0"
+            :end-val="totalWork[info[0]]"
+            :decimals="info[4]"
+            @click="info[2] === '未填报' ? openUnfilledDialog() : ''"
+          >
             <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
           </count-to>
           <div class="text-xs font-medium text-[var(--el-text-color-regular)]">{{ info[1] }}</div>
           <div class="text-sm font-medium text-[var(--el-text-color-regular)]">{{ info[2] }}</div>
         </div>
       </div>
-      <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow pt-4 px-8">
-        <el-table
-          v-loading="listLoading"
-          :data="list"
-          :stripe="true"
-          :style="{ width: '100%' }"
-          max-height="600"
-          class="min-h-143"
-          show-overflow-tooltip
+      <div
+        class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-4 grid grid-rows-[48px_1fr] gap-2"
+      >
+        <div class="flex items-center justify-between">
+          <el-button-group>
+            <el-button
+              size="default"
+              :type="tab === '表格' ? 'primary' : 'default'"
+              @click="handleSelectTab('表格')"
+              >表格
+            </el-button>
+            <el-button
+              size="default"
+              :type="tab === '看板' ? 'primary' : 'default'"
+              @click="handleSelectTab('看板')"
+              >看板
+            </el-button>
+          </el-button-group>
+          <h3 class="text-xl font-medium">{{ `${deptName}-${tab}` }}</h3>
+          <el-button size="default" type="primary" @click="exportAll">导出</el-button>
+        </div>
+        <!-- <el-auto-resizer>
+          <template #default="{ height, width }"> -->
+        <Motion
+          as="div"
+          :style="{ position: 'relative', overflow: 'hidden' }"
+          :animate="{ height: `${500}px`, width: `100%` }"
+          :transition="{ type: 'spring', stiffness: 200, damping: 25, duration: 0.3 }"
         >
-          <el-table-column
-            v-for="item in columns(type)"
-            :key="item.prop"
-            :label="item.label"
-            :prop="item.prop"
-            align="center"
-            :formatter="formatter"
-          />
-        </el-table>
-
-        <Pagination
-          class="mt-8"
-          :total="total"
-          v-model:page="query.pageNo"
-          v-model:limit="query.pageSize"
-          @pagination="getList"
-        />
+          <AnimatePresence :initial="false" mode="sync">
+            <Motion
+              :key="currentTab"
+              as="div"
+              :initial="{ x: direction === 'left' ? '-100%' : '100%', opacity: 0 }"
+              :animate="{ x: '0%', opacity: 1 }"
+              :exit="{ x: direction === 'left' ? '50%' : '-50%', opacity: 0 }"
+              :transition="{ type: 'tween', stiffness: 300, damping: 30, duration: 0.3 }"
+              :style="{ position: 'absolute', left: 0, right: 0, top: 0 }"
+            >
+              <div :style="{ width: `100%`, height: `${500}px` }">
+                <el-table
+                  v-if="currentTab === '表格'"
+                  v-loading="listLoading"
+                  :data="list"
+                  :stripe="true"
+                  :max-height="500"
+                  show-overflow-tooltip
+                >
+                  <template v-for="item in columns(type)" :key="item.prop">
+                    <el-table-column
+                      v-if="item.prop !== 'name'"
+                      :label="item.label"
+                      :prop="item.prop"
+                      align="center"
+                      :formatter="formatter"
+                    />
+                    <el-table-column v-else :label="item.label" :prop="item.prop" align="center">
+                      <template #default="{ row }">
+                        <el-button text type="primary" @click.prevent="tolist(row.id)">{{
+                          row.name
+                        }}</el-button>
+                      </template>
+                    </el-table-column>
+                  </template>
+                </el-table>
+                <div
+                  ref="chartRef"
+                  v-loading="chartLoading"
+                  :key="dayjs().valueOf()"
+                  v-else
+                  :style="{ width: `100%`, height: `${500}px` }"
+                >
+                </div>
+              </div>
+            </Motion>
+          </AnimatePresence>
+        </Motion>
+        <!-- </template>
+        </el-auto-resizer> -->
       </div>
     </div>
   </div>
+  <UnfilledReportDialog ref="unfilledDialogRef" :query-params="query" />
 </template>
 
 <style scoped>

+ 1 - 0
types/global.d.ts

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