Explorar el Código

✨ feat(日报): 瑞鹰日报审批和填报

Zimo hace 4 días
padre
commit
8338843af6

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

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

+ 5 - 1
src/components/DeptTreeSelect/index.vue

@@ -51,7 +51,10 @@ const loadTree = async () => {
 
     if (id !== props.topId) {
       const depts = await DeptApi.specifiedSimpleDepts(props.topId)
-      if (depts.length) {
+
+      const self = depts.find((item) => item.id === props.deptId)
+
+      if (depts.length && !self) {
         id = props.topId
       }
     }
@@ -120,6 +123,7 @@ watch(
 
 /** 初始化 */
 onMounted(() => {
+  console.log('props :>> ', props)
   loadTree()
 })
 </script>

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

@@ -92,6 +92,20 @@ const routerDetail = (item) => {
       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 }
+    })
   }
 }
 // ========== 初始化 =========

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

@@ -52,7 +52,11 @@ const businessRoutes: Record<string, string> = {
   generateMaintain: '',
   rdReportApproval: 'rdReportApproval',
   rhDailyReport: 'rhDailyReport',
-  rhReportApproval: 'rhReportApproval'
+  rhReportApproval: 'rhReportApproval',
+  ryDailyReport: 'ryDailyReport',
+  ryReportApproval: 'ryReportApproval',
+  ryXjDailyReport: 'ryXjDailyReport',
+  ryXjReportApproval: 'ryXjReportApproval'
 }
 
 // const href = ref('')
@@ -177,6 +181,20 @@ onMounted(async () => {
         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 {
     // 默认跳转

+ 4 - 2
src/views/pms/iotrhdailyreport/approval.vue

@@ -301,7 +301,7 @@ const query = ref<Query>({
   pageSize: 10,
   deptId: id,
   createTime: [
-    ...rangeShortcuts[3].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ]
 })
 
@@ -440,7 +440,9 @@ function handleOpenForm(id: number, type: 'approval' | 'readonly') {
   formType.value = type
 
   dialogVisible.value = true
-  loadDetail(id)
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
 }
 
 const route = useRoute()

+ 16 - 8
src/views/pms/iotrhdailyreport/fill.vue

@@ -301,7 +301,7 @@ const query = ref<Query>({
   pageSize: 10,
   deptId: id,
   createTime: [
-    ...rangeShortcuts[3].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ]
 })
 
@@ -371,6 +371,9 @@ watch(
 
 const FORM_KEYS = [
   'id',
+  'deptId',
+  'projectId',
+  'taskId',
   'deptName',
   'contractName',
   'taskName',
@@ -385,9 +388,6 @@ const FORM_KEYS = [
   'relocationDays',
   'capacity',
   'createTime',
-  'deptId',
-  'projectId',
-  'taskId',
   'opinion'
 ] as const
 
@@ -439,7 +439,9 @@ function handleOpenForm(id: number, type: 'edit' | 'readonly') {
   formType.value = type
 
   dialogVisible.value = true
-  loadDetail(id)
+  loadDetail(id).then(() => {
+    formRef.value?.validate()
+  })
 }
 
 const route = useRoute()
@@ -488,9 +490,11 @@ const timeRuleItem = [
 ]
 
 const rules = reactive<FormRules>({
-  dailyGasInjection: [{ required: true, message: '请输入当日注气量', trigger: 'blur' }],
-  dailyWaterInjection: [{ required: true, message: '请输入当日注水量', trigger: 'blur' }],
-  productionStatus: [{ required: true, message: '请输入生产动态', trigger: 'blur' }],
+  dailyGasInjection: [{ required: true, message: '请输入当日注气量', trigger: ['change', 'blur'] }],
+  dailyWaterInjection: [
+    { required: true, message: '请输入当日注水量', trigger: ['change', 'blur'] }
+  ],
+  productionStatus: [{ required: true, message: '请输入生产动态', trigger: ['change', 'blur'] }],
 
   // 复用规则
   dailyInjectGasTime: timeRuleItem,
@@ -832,6 +836,10 @@ const submitForm = async () => {
   }
 }
 
+:deep(.el-input-number) {
+  width: 100%;
+}
+
 :deep(.el-input-number__decrease) {
   display: none !important;
 }

+ 1 - 0
src/views/pms/iotrhdailyreport/index.vue

@@ -849,6 +849,7 @@ const route = useRoute()
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   // 重置后需要重新获取统计数据
   getStatistics()
   // 重新获取工作量统计数据

+ 1 - 1
src/views/pms/iotrhdailyreport/summary.vue

@@ -362,7 +362,7 @@ const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     contractName: '',
     taskName: '',
     createTime: []

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

@@ -0,0 +1,1125 @@
+<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
+}
+
+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 HH:mm:ss')
+  },
+  {
+    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: '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 ? 30 : 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'
+] 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 timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  currentDepth: [{ required: true, message: '请输入当前深度', 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"
+            />
+          </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>
+      <div class="grid grid-cols-4 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>
+        <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-2 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>

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

@@ -0,0 +1,1125 @@
+<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
+}
+
+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 HH:mm:ss')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px'
+  },
+  {
+    label: '设备型号',
+    prop: 'equipmentType',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'rigStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE
+  },
+  {
+    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: '进尺工作时间(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 ? 30 : 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'
+] 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 timeRuleItem = [
+  { required: true, message: '请输入时间', trigger: 'blur' },
+  { validator: validateTotalTime, trigger: 'blur' }
+]
+
+const rules = reactive<FormRules>({
+  currentDepth: [{ required: true, message: '请输入当前深度', 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"
+            />
+          </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>
+      <div class="grid grid-cols-4 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>
+        <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-2 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>

+ 20 - 13
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>
@@ -424,12 +432,13 @@ import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyrep
 import IotRyDailyReportForm from './IotRyDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 import { useDebounceFn } from '@vueuse/core'
 
+import { useUserStore } from '@/store/modules/user'
+
 dayjs.extend(quarterOfYear)
 
 /** 瑞鹰日报 列表 */
@@ -441,7 +450,7 @@ const { t } = useI18n() // 国际化
 // 添加 selectedRowData 响应式变量
 const selectedRowData = ref<Record<string, any> | null>(null)
 
-const rootDeptId = ref(158)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 const loading = ref(true) // 列表的加载中
 const list = ref<IotRyDailyReportVO[]>([]) // 列表的数据
@@ -449,7 +458,7 @@ const total = ref(0) // 列表的总页数
 let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -652,13 +661,13 @@ const columnWidths = ref<
     width: '120px'
   },
   selfStopTime: {
-    label: '修理(H)',
+    label: '自停(H)',
     prop: 'selfStopTime',
     width: '120px'
   },
   complexityTime: {
     label: '复杂(H)',
-    prop: 'selfStopTime',
+    prop: 'complexityTime',
     width: '120px'
   },
   relocationTime: {
@@ -837,6 +846,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -880,7 +890,7 @@ const selectedDept = ref<{ id: number; name: string }>()
 const handleDeptNodeClick = async (row) => {
   // 记录选中的部门信息
   selectedDept.value = { id: row.id, name: row.name }
-  queryParams.deptId = row.id
+  // queryParams.deptId = row.id
   await getList()
 }
 
@@ -907,11 +917,8 @@ const route = useRoute()
 /** 初始化 **/
 onMounted(() => {
   if (Object.keys(route.query).length > 0) {
-    queryParams = {
-      ...queryParams,
-      ...route.query,
-      deptId: Number(route.query.deptId) as any
-    }
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
     handleQuery()
   } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化

+ 23 - 79
src/views/pms/iotrydailyreport/summary.vue

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

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

@@ -0,0 +1,968 @@
+<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 HH:mm:ss')
+  },
+  {
+    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: '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 ? 30 : 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<'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()
+  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 (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"
+            />
+          </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>
+        <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>

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

@@ -0,0 +1,934 @@
+<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 HH:mm:ss')
+  },
+  {
+    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: '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 ? 30 : 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"
+            />
+          </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>
+        <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>

+ 18 - 10
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>
@@ -384,9 +392,10 @@ import { IotRyDailyReportApi, IotRyDailyReportVO } from '@/api/pms/iotrydailyrep
 import IotRyXjDailyReportForm from './IotRyXjDailyReportForm.vue'
 import { DICT_TYPE } from '@/utils/dict'
 import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import { useDebounceFn } from '@vueuse/core'
 
+import { useUserStore } from '@/store/modules/user'
+
 import dayjs from 'dayjs'
 import quarterOfYear from 'dayjs/plugin/quarterOfYear'
 
@@ -407,7 +416,7 @@ const total = ref(0) // 列表的总页数
 let queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  deptId: undefined,
+  deptId: useUserStore().getUser.deptId,
   contractName: undefined,
   projectId: undefined,
   taskName: undefined,
@@ -449,7 +458,7 @@ let queryParams = reactive({
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中
 
-const rootDeptId = ref(158)
+const rootDeptId = ref(useUserStore().getUser.deptId)
 
 // 表格引用
 const tableRef = ref()
@@ -834,6 +843,7 @@ const getList = async () => {
 // 百分比格式化函数
 const percentageFormatter = (row: any, column: any, cellValue: any, index: number | null) => {
   if (cellValue === null || cellValue === undefined || cellValue === '') return ''
+  console.log('cellValue :>> ', cellValue)
   const value = parseFloat(cellValue)
   if (isNaN(value)) return '-'
   // 将小数转换为百分比,保留两位小数
@@ -849,6 +859,7 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
+  queryParams.deptId = useUserStore().getUser.deptId
   handleQuery()
 }
 
@@ -920,11 +931,8 @@ const route = useRoute()
 /** 初始化 **/
 onMounted(() => {
   if (Object.keys(route.query).length > 0) {
-    queryParams = {
-      ...queryParams,
-      ...route.query,
-      deptId: Number(route.query.deptId) as any
-    }
+    queryParams.deptId = Number(route.query.deptId) as any
+    queryParams.createTime = route.query.createTime as string[]
     handleQuery()
   } else getList()
   // 创建 ResizeObserver 监听表格容器尺寸变化

+ 25 - 8
src/views/pms/iotrydailyreport/xsummary.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import DeptTree2 from '@/views/pms/iotrhdailyreport/DeptTree2.vue'
 import dayjs from 'dayjs'
 import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
 import { useDebounceFn } from '@vueuse/core'
@@ -12,6 +11,10 @@ import { Motion, AnimatePresence } from 'motion-v'
 import { rangeShortcuts } from '@/utils/formatTime'
 import download from '@/utils/download'
 
+import { useUserStore } from '@/store/modules/user'
+
+const deptId = useUserStore().getUser.deptId
+
 interface Query {
   pageNo: number
   pageSize: number
@@ -22,12 +25,12 @@ interface Query {
   projectClassification: 1 | 2
 }
 
-const id = 158
+const id = deptId
 
 const query = ref<Query>({
   pageNo: 1,
   pageSize: 10,
-  deptId: 158,
+  deptId: deptId,
   createTime: [
     ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
   ],
@@ -81,7 +84,7 @@ const getTotal = useDebounceFn(async () => {
 
   try {
     let res1: any[]
-    if (query.value.createTime.length !== 0) {
+    if (query.value.createTime && query.value.createTime.length === 2) {
       res1 = await IotRyDailyReportApi.ryDailyReportStatistics({
         createTime: query.value.createTime,
         projectClassification: query.value.projectClassification
@@ -332,7 +335,8 @@ const render = () => {
 }
 
 const handleDeptNodeClick = (node: any) => {
-  query.value.deptId = node.id
+  deptName.value = node.name
+  // query.value.deptId = node.id
   handleQuery()
 }
 
@@ -351,7 +355,7 @@ const resetQuery = () => {
   query.value = {
     pageNo: 1,
     pageSize: 10,
-    deptId: 157,
+    deptId: deptId,
     createTime: [],
     projectClassification: 1
   }
@@ -360,7 +364,14 @@ const resetQuery = () => {
 
 watch(
   () => query.value.createTime,
-  () => handleQuery(false)
+  (val) => {
+    if (!val) {
+      totalWork.value.totalCount = 0
+      totalWork.value.notReported = 0
+      totalWork.value.alreadyReported = 0
+    }
+    handleQuery(false)
+  }
 )
 
 onMounted(() => {
@@ -438,7 +449,13 @@ const tolist = (id: number) => {
 <template>
   <div class="grid grid-cols-[16%_1fr] gap-5">
     <div class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow p-4">
-      <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" />
+      <!-- <DeptTree2 :deptId="id" @node-click="handleDeptNodeClick" /> -->
+      <DeptTreeSelect
+        :deptId="id"
+        :top-id="157"
+        v-model="query.deptId"
+        @node-click="handleDeptNodeClick"
+      />
     </div>
     <div class="grid grid-rows-[62px_164px_1fr] h-full gap-5">
       <el-form