Browse Source

Merge branch 'feature-report-statistics'

Zimo 9 giờ trước cách đây
mục cha
commit
bb8048152e

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

@@ -2,7 +2,7 @@ import request from '@/config/axios'
 
 // 瑞都日报 VO
 export interface IotRdDailyReportVO {
-  createTime:any,
+  createTime: any
   id: number // 主键id
   deptId: number // 施工队伍id
   projectId: number // 项目id
@@ -51,6 +51,14 @@ export const IotRdDailyReportApi = {
     return await request.get({ url: `/pms/iot-rd-daily-report/page`, params })
   },
 
+  getIotRdDailyReportTeamPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/teamReports`, params })
+  },
+
+  getIotRdDailyReportWellPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rd-daily-report/wellReports`, params })
+  },
+
   // 查询瑞都日报 汇总统计
   statistics: async (params: any) => {
     return await request.get({ url: `/pms/iot-rd-daily-report/statistics`, params })
@@ -94,5 +102,5 @@ export const IotRdDailyReportApi = {
   // 查询项目任务实际进度列表
   taskActualProgress: async (params: any) => {
     return await request.get({ url: `/pms/iot-rd-daily-report/taskActualProgress`, params })
-  },
+  }
 }

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

@@ -43,6 +43,14 @@ export const IotRhDailyReportApi = {
     return await request.get({ url: `/pms/iot-rh-daily-report/page`, params })
   },
 
+  getIotRhDailyReportTeamPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rh-daily-report/teamReports`, params })
+  },
+
+  getIotRhDailyReportWellPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-rh-daily-report/wellReports`, params })
+  },
+
   getIotRhDailyReportSummary: async (params: any) => {
     return await request.get({ url: `/pms/iot-rh-daily-report/statistics`, params })
   },

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

@@ -61,6 +61,14 @@ export const IotRyDailyReportApi = {
     })
   },
 
+  getIotRyDailyReportTeamPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/teamReports`, params })
+  },
+
+  getIotRyDailyReportWellPage: async (params: any) => {
+    return await request.get({ url: `/pms/iot-ry-daily-report/wellReports`, params })
+  },
+
   ryUnReportDetails: async (params: any) => {
     return await request.get({ url: `/pms/iot-ry-daily-report/ryUnReportDetails`, params })
   },

+ 11 - 0
src/api/pms/report/index.ts

@@ -0,0 +1,11 @@
+import request from '@/config/axios'
+
+export const IotReportApi = {
+  getCostsFee: async (params: any) => {
+    return await request.get({ url: `/rq/report/repair/fee`, params })
+  },
+
+  getCostsPage: async (params: any) => {
+    return await request.get({ url: `/rq/report/repair/page`, params })
+  }
+}

+ 7 - 1
src/api/system/dept/index.ts

@@ -22,7 +22,13 @@ export const getSimpleDeptList = async (): Promise<DeptVO[]> => {
 
 // 获取指定部门精简信息列表
 export const specifiedSimpleDepts = async (deptId: number): Promise<DeptVO[]> => {
-  return await request.get({ url: '/system/dept/specifiedSimpleDepts?deptId=' + deptId  })
+  return await request.get({ url: '/system/dept/specifiedSimpleDepts?deptId=' + deptId })
+}
+
+export const getTaskWellNames = async (deptId: number, wellName: string) => {
+  return await request.get({
+    url: `/rq/iot-project-task/taskWellNames?companyId=${deptId}&wellName=${wellName}`
+  })
 }
 
 // 查询当前用户所属的公司级部门列表 项目 日报

+ 12 - 2
src/components/DeptTreeSelect/index.vue

@@ -16,6 +16,14 @@ const props = defineProps({
   topId: {
     type: Number,
     required: true
+  },
+  title: {
+    type: String,
+    default: '部门'
+  },
+  initSelect: {
+    type: Boolean,
+    default: true
   }
 })
 
@@ -59,7 +67,9 @@ const loadTree = async () => {
       }
     }
 
-    emits('update:modelValue', id)
+    if (props.initSelect) {
+      emits('update:modelValue', id)
+    }
 
     const res = await DeptApi.specifiedSimpleDepts(id)
     deptList.value = sortTreeBySort(handleTree(res))
@@ -130,7 +140,7 @@ onMounted(() => {
 
 <template>
   <div class="gap-4 flex flex-col h-full">
-    <h1 class="text-lg font-medium">部门</h1>
+    <h1 class="text-lg font-medium">{{ props.title }}</h1>
     <el-input
       v-model="deptName"
       size="default"

+ 169 - 0
src/components/WellSelect/index.vue

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

+ 306 - 0
src/views/report-statistics/costs.vue

@@ -0,0 +1,306 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import dayjs from 'dayjs'
+import CountTo from '@/components/count-to1.vue'
+import { IotReportApi } from '@/api/pms/report'
+import { useDebounceFn } from '@vueuse/core'
+
+// 定义时间类型
+type TimeType = 'year' | 'month' | 'day'
+
+interface Query {
+  deptId?: number
+  createTime?: [string, string]
+  pageNo: number
+  pageSize: number
+}
+
+// 选项配置数组
+const timeOptions: { label: string; value: TimeType }[] = [
+  { label: '年', value: 'year' },
+  { label: '月', value: 'month' },
+  { label: '日', value: 'day' }
+]
+
+const activeTimeType = ref<TimeType>('year')
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10
+})
+
+const handleTimeChange = (type: TimeType, init = false) => {
+  activeTimeType.value = type
+
+  const formatStr = 'YYYY-MM-DD HH:mm:ss'
+  const endTime = dayjs().endOf('day').format(formatStr)
+  let startTime = ''
+
+  switch (type) {
+    case 'year':
+      startTime = dayjs().startOf('year').format(formatStr)
+      break
+    case 'month':
+      startTime = dayjs().startOf('month').format(formatStr)
+      break
+    case 'day':
+      startTime = dayjs().startOf('day').format(formatStr)
+      break
+  }
+
+  query.value.createTime = [startTime, endTime]
+
+  console.log(`切换为[${type}]:`, query.value.createTime)
+
+  if (!init) loadData()
+}
+
+interface StatItem {
+  key: string
+  title: string
+  icon: string
+  class: { bg1: string; bg2: string; text: string }
+  value: number
+  trend: number
+}
+
+const defaultStats: StatItem[] = [
+  {
+    key: 'repair',
+    title: '维修成本',
+    icon: 'i-material-symbols:home-repair-service-outline',
+    class: {
+      bg1: 'bg-[var(--el-color-success-light-9)]',
+      bg2: 'bg-[var(--el-color-success-light-8)]',
+      text: 'text-[var(--el-color-success)]'
+    },
+    value: 0,
+    trend: 0
+  },
+  {
+    key: 'msg',
+    title: '保养成本',
+    icon: 'i-material-symbols:construction-rounded',
+    class: {
+      bg1: 'bg-[var(--el-color-primary-light-9)]',
+      bg2: 'bg-[var(--el-color-primary-light-8)]',
+      text: 'text-[var(--el-color-primary)]'
+    },
+    value: 0,
+    trend: 0
+  },
+  {
+    key: 'out',
+    title: '委外维修',
+    icon: 'i-material-symbols:work-outline',
+    class: {
+      bg1: 'bg-[var(--el-color-warning-light-9)]',
+      bg2: 'bg-[var(--el-color-warning-light-8)]',
+      text: 'text-[var(--el-color-warning)]'
+    },
+    value: 0,
+    trend: 0
+  }
+]
+
+const statList = ref<StatItem[]>(defaultStats)
+const dataLoading = ref(false)
+
+const loadData = useDebounceFn(async function () {
+  dataLoading.value = true
+  try {
+    const { pageNo, pageSize, ...other } = query.value
+    const res = await IotReportApi.getCostsFee(other)
+    statList.value.forEach((item) => {
+      item.value = res[item.key] || 0
+      item.trend = res[`${item.key}Trend`] || 0
+    })
+  } catch (error) {
+    console.log('error :>> ', error)
+  } finally {
+    dataLoading.value = false
+  }
+}, 500)
+
+const list = ref<any[]>([])
+const loading = ref(false)
+const total = ref(0)
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  query.value.pageNo = 1
+  loadList()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+  loadData()
+}
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    const data = await IotReportApi.getCostsPage(query.value)
+    list.value = data.list
+    total.value = data.total
+
+    // nextTick(() => {
+    //   calculateColumnWidths(columns.value)
+    // })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+onMounted(() => {
+  handleTimeChange('year', true)
+})
+
+watch(
+  [() => query.value.createTime, () => query.value.deptId],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+</script>
+
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[196px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
+      <DeptTreeSelect
+        :top-id="156"
+        :deptId="156"
+        v-model="query.deptId"
+        title="队伍"
+        :init-select="false"
+      />
+    </div>
+    <div class="grid grid-rows-[1fr_32px] gap-4">
+      <div class="grid grid-cols-3 gap-4" v-loading="dataLoading">
+        <!-- 使用 v-for 循环渲染 -->
+        <section
+          v-for="item in statList"
+          :key="item.key"
+          class="flex flex-col items-center gap-y-4 rounded-lg shadow p-4"
+          :class="item.class.bg1"
+        >
+          <!-- 头部:图标 + 标题 -->
+          <div class="flex items-center gap-x-3">
+            <div class="rounded-2 p-2" :class="[item.class.text, item.class.bg2]">
+              <div :class="item.icon"></div>
+            </div>
+            <div class="text-[var(--el-text-color-primary)] text-sm font-medium">
+              {{ item.title }}
+            </div>
+          </div>
+
+          <!-- 数值区域:CountTo -->
+          <count-to
+            class="text-3xl font-semibold"
+            :start-val="0"
+            :end-val="item.value"
+            :decimals="2"
+          >
+            <!-- 插槽内容:当数据为空或0时的显示 (根据 count-to 组件的具体实现决定是否显示) -->
+            <span class="text-xs leading-8 text-[var(--el-text-color-regular)]">暂无数据</span>
+          </count-to>
+
+          <!-- 底部:环比数据 -->
+          <!-- 根据 trend 正负动态改变图标和颜色 -->
+          <div class="mt-2 flex items-center gap-x-1 text-xs font-medium" :class="item.class.text">
+            <!-- 动态图标:大于等于0向上,小于0向下 -->
+            <div
+              :class="
+                item.trend >= 0
+                  ? 'i-material-symbols:arrow-warm-up-rounded'
+                  : 'i-material-symbols:arrow-cool-down-rounded'
+              "
+            ></div>
+
+            <span class="vertical-middle">
+              {{ item.trend > 0 ? '+' + item.trend : item.trend }}
+            </span>
+            <span>环比</span>
+          </div>
+        </section>
+      </div>
+      <el-button-group size="default">
+        <el-button
+          v-for="item in timeOptions"
+          :key="item.value"
+          :type="activeTimeType === item.value ? 'primary' : ''"
+          @click="handleTimeChange(item.value)"
+        >
+          {{ item.label }}
+        </el-button>
+      </el-button-group>
+    </div>
+    <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg p-4 flex flex-col mt-4">
+      <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"
+              :height="height"
+              show-overflow-tooltip
+              :width="width"
+              scrollbar-always-on
+            >
+              <el-table-column label="序号" type="index" width="50" align="center" />
+              <el-table-column label="日期" prop="date" align="center" />
+              <el-table-column label="类别" prop="type" align="center" />
+              <el-table-column label="设备编号" prop="deviceCode" align="center" />
+              <el-table-column label="设备名称" prop="deviceName" align="center" />
+              <el-table-column label="成本" prop="cost" align="center" />
+            </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>
+</template>
+
+<style scoped>
+:deep(.el-table) {
+  border-top-right-radius: 8px;
+  border-top-left-radius: 8px;
+
+  .el-table__cell {
+    height: 52px;
+  }
+
+  .el-table__header-wrapper {
+    .el-table__cell {
+      background: var(--el-fill-color-light);
+    }
+  }
+}
+</style>

+ 588 - 0
src/views/report-statistics/daily-report.vue

@@ -0,0 +1,588 @@
+<script lang="ts" setup>
+import { IotRhDailyReportApi } from '@/api/pms/iotrhdailyreport'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+
+defineOptions({ name: 'DailyReport' })
+
+const tab = ref('井')
+const tabOptions = ['井', '队伍']
+
+interface List {
+  createTime: number // 日期
+  deptName: string // 施工队伍
+  contractName: string // 项目
+  taskName: string // 任务
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  relocationDays: number // 搬迁安装天数
+  designInjection: string // 设计注气量(万方)
+  transitTime: number // 运行时效
+  dailyGasInjection: number // 注气量(万方)
+  dailyWaterInjection: number // 注水量(方)
+  dailyInjectGasTime: number // 注气时间(H)
+  dailyInjectWaterTime: number // 注水时间(H)
+  dailyPowerUsage: number // 日耗电量(度)
+  dailyOilUsage: number // 日耗油量(升)
+  nonProductionTime: number // 非生产时间(小时)
+  nptReason: string // 非生产时间原因
+  constructionStartDate: number // 施工开始日期
+  constructionEndDate: number // 施工结束日期
+  productionStatus: string // 生产动态
+  constructionStatus: string // 施工状态
+  totalGasInjection: number // 注气量(万方)
+  totalWaterInjection: number // 注水量(方)
+  cumulativeCompletion: number // 完工井次
+  capacity: number // 产能(万方)
+  remark: string // 备注
+  auditStatus: number // 审核状态
+  status: number // 状态
+  opinion: string // 审核意见
+  lastGroupIdFlag: boolean
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    fixed: 'left',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    fixed: 'left',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '搬迁安装天数',
+    prop: 'relocationDays',
+    'min-width': '120px',
+    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
+  },
+  {
+    label: '设计注气量(万方)',
+    prop: 'designInjection',
+    'min-width': '120px',
+    formatter: (row: List) => row.designInjection || '0'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'dailyGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'dailyWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '注气时间(H)',
+        prop: 'dailyInjectGasTime',
+        'min-width': '120px'
+      },
+      {
+        label: '注水时间(H)',
+        prop: 'dailyInjectWaterTime',
+        'min-width': '120px'
+      },
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(L)',
+        prop: 'dailyOilUsage',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时间原因',
+    prop: 'nptReason',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '累计',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'totalGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'totalWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '完工井次',
+        prop: 'cumulativeCompletion',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '产能(万方)',
+    prop: 'capacity',
+    'min-width': '120px',
+    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  const span = document.createElement('span')
+  span.style.visibility = 'hidden'
+  span.style.position = 'absolute'
+  span.style.whiteSpace = 'nowrap'
+  span.style.fontSize = `${fontSize}px`
+  span.style.fontFamily = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                let tagLabel = ''
+                if (col.dictType) {
+                  const option = getDictOptions(col.dictType).find(
+                    (item) => item.value === v[prop!]
+                  )
+                  if (option) tagLabel = option.label
+                }
+                return getTextWidth(
+                  formatter ? formatter(v) : Boolean(tagLabel) ? tagLabel : v[prop!]
+                )
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          ...(isTag ? [] : [200])
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const gasTime = row.dailyInjectGasTime || 0
+  const waterTime = row.dailyInjectWaterTime || 0
+  const nonProdTime = row.nonProductionTime || 0
+
+  // 计算总和
+  const sum = gasTime + waterTime + nonProdTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    const originalValue = row.transitTime ?? 0
+
+    if (originalValue > 1.2)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+function rowClassName(data: { row: List; rowIndex: number }) {
+  const { row } = data
+  if (!row.lastGroupIdFlag) {
+    return 'hide-expand-icon'
+  }
+  return ''
+}
+
+const id = useUserStore().getUser.deptId ?? 157
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  wellName?: string
+  createTime: string[]
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    if (tab.value === '井') {
+      const { deptId, taskName, ...other } = query.value
+      const data = await IotRhDailyReportApi.getIotRhDailyReportTeamPage({
+        ...other,
+        taskName: query.value.wellName
+      })
+      list.value = data.list
+      total.value = data.total
+    } else {
+      const data = await IotRhDailyReportApi.getIotRhDailyReportWellPage(query.value)
+      list.value = data.list
+      total.value = data.total
+    }
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ]
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName,
+    () => query.value.wellName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const expandRowKeys = computed(() => {
+  return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
+})
+</script>
+
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <el-form
+      size="default"
+      class="col-span-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center"
+    >
+      <div class="flex items-center gap-8">
+        <el-segmented class="h-12" v-model="tab" :options="tabOptions" @change="handleQuery()" />
+      </div>
+      <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 v-show="tab !== '井'" label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
+      </div>
+      <el-form-item class="ml-auto">
+        <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>
+
+    <!-- 第二行左侧:自动落入第 2 行第 1 列 -->
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+      <DeptTreeSelect
+        v-show="tab === '队伍'"
+        :top-id="157"
+        :deptId="deptId"
+        v-model="query.deptId"
+        title="队伍"
+      />
+      <WellSelect v-show="tab === '井'" :deptId="157" v-model="query.wellName" />
+    </div>
+
+    <!-- 第二行右侧:自动落入第 2 行第 2 列 -->
+    <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"
+              :height="height"
+              show-overflow-tooltip
+              :width="width"
+              :cell-style="cellStyle"
+              :row-class-name="rowClassName"
+              row-key="id"
+              scrollbar-always-on
+              :expand-row-keys="expandRowKeys"
+              border
+            >
+              <el-table-column type="expand" fixed="left">
+                <template #default="{ row }">
+                  <div
+                    class="flex items-center gap-8 h-10 px-14 sticky left-0 w-fit box-border bg-[var(--el-bg-color)]"
+                  >
+                    <el-tag size="default">累计注气量(方): {{ row.groupIdGasInjection }} </el-tag>
+                    <el-tag size="default">累计注水量(方): {{ row.groupIdWaterInjection }} </el-tag>
+                    <el-tag size="default">累计用电量(kWh) : {{ row.groupIdPower }} </el-tag>
+                    <el-tag size="default">累计油耗(L) : {{ row.groupIdFuel }} </el-tag>
+                    <el-tag size="default">累计注气时间H : {{ row.groupIdGasTime }} </el-tag>
+                    <el-tag size="default"
+                      >累计非生产时间H : {{ row.groupIdNoProductTime }}
+                    </el-tag>
+                  </div>
+                </template>
+              </el-table-column>
+              <DailyTableColumn :columns="columns" />
+              <!-- <el-table-column label="操作" width="120px" align="center" fixed="right">
+                <template #default="{ row }">
+                  <el-button link type="success" v-hasPermi="['pms:iot-rh-daily-report:query']">
+                    查看
+                  </el-button>
+                  <el-button
+                    v-show="row.status === 0"
+                    link
+                    type="primary"
+                    v-hasPermi="['pms:iot-rh-daily-report:create']"
+                  >
+                    编辑
+                  </el-button>
+                </template>
+              </el-table-column> -->
+            </el-table>
+          </template>
+        </el-auto-resizer>
+      </div>
+      <div class="h-10 mt-4 flex items-center justify-end">
+        <el-pagination
+          size="default"
+          v-show="total > 0"
+          v-model:current-page="query.pageNo"
+          v-model:page-size="query.pageSize"
+          :background="true"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.el-segmented {
+  --el-border-radius-base: 8px;
+  --el-segmented-padding: 8px;
+}
+
+:deep(.hide-expand-icon) {
+  .el-table__expand-icon {
+    display: none;
+  }
+}
+</style>

+ 587 - 0
src/views/report-statistics/rd-daily-report.vue

@@ -0,0 +1,587 @@
+<script lang="ts" setup>
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+
+defineOptions({ name: 'DailyReport' })
+
+const tab = ref('井')
+const tabOptions = ['井', '队伍']
+
+interface List {
+  createTime: number // 日期
+  deptName: string // 施工队伍
+  contractName: string // 项目
+  taskName: string // 任务
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  relocationDays: number // 搬迁安装天数
+  designInjection: string // 设计注气量(万方)
+  transitTime: number // 运行时效
+  dailyGasInjection: number // 注气量(万方)
+  dailyWaterInjection: number // 注水量(方)
+  dailyInjectGasTime: number // 注气时间(H)
+  dailyInjectWaterTime: number // 注水时间(H)
+  dailyPowerUsage: number // 日耗电量(度)
+  dailyOilUsage: number // 日耗油量(升)
+  nonProductionTime: number // 非生产时间(小时)
+  nptReason: string // 非生产时间原因
+  constructionStartDate: number // 施工开始日期
+  constructionEndDate: number // 施工结束日期
+  productionStatus: string // 生产动态
+  constructionStatus: string // 施工状态
+  totalGasInjection: number // 注气量(万方)
+  totalWaterInjection: number // 注水量(方)
+  cumulativeCompletion: number // 完工井次
+  capacity: number // 产能(万方)
+  remark: string // 备注
+  auditStatus: number // 审核状态
+  status: number // 状态
+  opinion: string // 审核意见
+  lastGroupIdFlag: boolean
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    fixed: 'left',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD')
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    fixed: 'left',
+    'min-width': '120px'
+  },
+  {
+    label: '施工状态',
+    prop: 'constructionStatus',
+    fixed: 'left',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_SCHEDULE
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '搬迁安装天数',
+    prop: 'relocationDays',
+    'min-width': '120px',
+    formatter: (row: List) => (row.relocationDays < 0 ? '0' : String(row.relocationDays))
+  },
+  {
+    label: '设计注气量(万方)',
+    prop: 'designInjection',
+    'min-width': '120px',
+    formatter: (row: List) => row.designInjection || '0'
+  },
+  {
+    label: '运行时效',
+    prop: 'transitTime',
+    'min-width': '120px',
+    formatter: (row: List) => (row.transitTime * 100).toFixed(2) + '%'
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'dailyGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.dailyGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'dailyWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '注气时间(H)',
+        prop: 'dailyInjectGasTime',
+        'min-width': '120px'
+      },
+      {
+        label: '注水时间(H)',
+        prop: 'dailyInjectWaterTime',
+        'min-width': '120px'
+      },
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(L)',
+        prop: 'dailyOilUsage',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '非生产时间(H)',
+    prop: 'nonProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时间原因',
+    prop: 'nptReason',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_NPT_REASON
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '累计',
+    children: [
+      {
+        label: '注气量(万方)',
+        prop: 'totalGasInjection',
+        'min-width': '120px',
+        formatter: (row: List) => (row.totalGasInjection / 10000).toFixed(2)
+      },
+      {
+        label: '注水量(方)',
+        prop: 'totalWaterInjection',
+        'min-width': '120px'
+      },
+      {
+        label: '完工井次',
+        prop: 'cumulativeCompletion',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '产能(万方)',
+    prop: 'capacity',
+    'min-width': '120px',
+    formatter: (row: List) => (row.capacity / 10000).toFixed(2)
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  const span = document.createElement('span')
+  span.style.visibility = 'hidden'
+  span.style.position = 'absolute'
+  span.style.whiteSpace = 'nowrap'
+  span.style.fontSize = `${fontSize}px`
+  span.style.fontFamily = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                let tagLabel = ''
+                if (col.dictType) {
+                  const option = getDictOptions(col.dictType).find(
+                    (item) => item.value === v[prop!]
+                  )
+                  if (option) tagLabel = option.label
+                }
+                return getTextWidth(
+                  formatter ? formatter(v) : Boolean(tagLabel) ? tagLabel : v[prop!]
+                )
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          ...(isTag ? [] : [200])
+        ]
+      ) + 'px'
+
+    col['min-width'] = minWidth
+  }
+}
+
+function checkTimeSumEquals24(row: List) {
+  // 获取三个字段的值,转换为数字,如果为空则视为0
+  const gasTime = row.dailyInjectGasTime || 0
+  const waterTime = row.dailyInjectWaterTime || 0
+  const nonProdTime = row.nonProductionTime || 0
+
+  // 计算总和
+  const sum = gasTime + waterTime + nonProdTime
+
+  // 返回是否等于24(允许一定的浮点数误差)
+  return Math.abs(sum - 24) < 0.01 // 使用0.01作为误差范围
+}
+
+function cellStyle(data: {
+  row: List
+  column: TableColumnCtx<List>
+  rowIndex: number
+  columnIndex: number
+}) {
+  const { row, column } = data
+
+  if (column.property === 'transitTime') {
+    const originalValue = row.transitTime ?? 0
+
+    if (originalValue > 1.2)
+      return {
+        color: 'red',
+        fontWeight: 'bold'
+      }
+  }
+
+  const timeFields = ['dailyInjectGasTime', 'dailyInjectWaterTime', 'nonProductionTime']
+  if (timeFields.includes(column.property)) {
+    if (!checkTimeSumEquals24(row)) {
+      return {
+        color: 'orange',
+        fontWeight: 'bold'
+      }
+    }
+  }
+
+  // 默认返回空对象,不应用特殊样式
+  return {}
+}
+
+function rowClassName(data: { row: List; rowIndex: number }) {
+  const { row } = data
+  if (!row.lastGroupIdFlag) {
+    return 'hide-expand-icon'
+  }
+  return ''
+}
+
+const id = useUserStore().getUser.deptId ?? 157
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  wellName?: string
+  createTime: string[]
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    if (tab.value === '井') {
+      const { deptId, taskName, ...other } = query.value
+      const data = await IotRdDailyReportApi.getIotRdDailyReportTeamPage({
+        ...other,
+        taskName: query.value.wellName
+      })
+      list.value = data.list
+      total.value = data.total
+    } else {
+      const data = await IotRdDailyReportApi.getIotRdDailyReportWellPage(query.value)
+      list.value = data.list
+      total.value = data.total
+    }
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ]
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName,
+    () => query.value.wellName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const expandRowKeys = computed(() => {
+  return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
+})
+</script>
+
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <el-form
+      size="default"
+      class="col-span-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center"
+    >
+      <div class="flex items-center gap-8">
+        <el-segmented class="h-12" v-model="tab" :options="tabOptions" @change="handleQuery()" />
+      </div>
+      <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 v-show="tab !== '井'" label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
+      </div>
+      <el-form-item class="ml-auto">
+        <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>
+
+    <!-- 第二行左侧:自动落入第 2 行第 1 列 -->
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+      <DeptTreeSelect
+        v-show="tab === '队伍'"
+        :top-id="157"
+        :deptId="deptId"
+        v-model="query.deptId"
+        title="队伍"
+      />
+      <WellSelect v-show="tab === '井'" :deptId="157" v-model="query.wellName" />
+    </div>
+
+    <!-- 第二行右侧:自动落入第 2 行第 2 列 -->
+    <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"
+              :height="height"
+              show-overflow-tooltip
+              :width="width"
+              :cell-style="cellStyle"
+              :row-class-name="rowClassName"
+              :tree-props="{ hasChildren: 'lastGroupIdFlag' }"
+              row-key="id"
+              scrollbar-always-on
+              :expand-row-keys="expandRowKeys"
+              border
+            >
+              <el-table-column type="expand" fixed="left">
+                <template #default="{ row }">
+                  <div
+                    class="flex items-center gap-8 h-10 px-14 sticky left-0 w-fit box-border bg-[var(--el-bg-color)]"
+                  >
+                    <el-tag>累计注气量(方): {{ row.groupIdGasInjection }} </el-tag>
+                    <el-tag>累计注水量(方): {{ row.groupIdWaterInjection }} </el-tag>
+                    <el-tag>累计用电量(kWh) : {{ row.groupIdPower }} </el-tag>
+                    <el-tag>累计油耗(L) : {{ row.groupIdFuel }} </el-tag>
+                    <el-tag>累计注气时间H : {{ row.groupIdGasTime }} </el-tag>
+                    <el-tag>累计非生产时间H : {{ row.groupIdNoProductTime }} </el-tag>
+                  </div>
+                </template>
+              </el-table-column>
+              <DailyTableColumn :columns="columns" />
+              <!-- <el-table-column label="操作" width="120px" align="center" fixed="right">
+                <template #default="{ row }">
+                  <el-button link type="success" v-hasPermi="['pms:iot-rh-daily-report:query']">
+                    查看
+                  </el-button>
+                  <el-button
+                    v-show="row.status === 0"
+                    link
+                    type="primary"
+                    v-hasPermi="['pms:iot-rh-daily-report:create']"
+                  >
+                    编辑
+                  </el-button>
+                </template>
+              </el-table-column> -->
+            </el-table>
+          </template>
+        </el-auto-resizer>
+      </div>
+      <div class="h-10 mt-4 flex items-center justify-end">
+        <el-pagination
+          size="default"
+          v-show="total > 0"
+          v-model:current-page="query.pageNo"
+          v-model:page-size="query.pageSize"
+          :background="true"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.el-segmented {
+  --el-border-radius-base: 8px;
+  --el-segmented-padding: 8px;
+}
+
+:deep(.hide-expand-icon) {
+  .el-table__expand-icon {
+    display: none;
+  }
+}
+</style>

+ 711 - 0
src/views/report-statistics/ry-daily-report.vue

@@ -0,0 +1,711 @@
+<script lang="ts" setup>
+import { IotRyDailyReportApi } from '@/api/pms/iotrydailyreport'
+import { rangeShortcuts } from '@/utils/formatTime'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { useUserStore } from '@/store/modules/user'
+
+defineOptions({ name: 'DailyReport' })
+
+const tab = ref('井')
+const tabOptions = ['井', '队伍']
+
+interface List {
+  id: number
+  deptId: number
+  projectId: number
+  taskId: number
+  projectClassification: string
+  relocationDays: number
+  latestWellDoneTime: number
+  currentDepth: number
+  dailyFootage: number
+  monthlyFootage: number
+  annualFootage: number
+  dailyPowerUsage: number
+  monthlyPowerUsage: number
+  dailyFuel: number
+  monthlyFuel: number
+  dailyOilVolume: number
+  remainDieselVolume: number
+  productionTime: number
+  nonProductionTime: number
+  ryNptReason: string
+  drillingWorkingTime: number
+  otherProductionTime: number
+  accidentTime: number
+  repairTime: number
+  selfStopTime: number
+  complexityTime: number
+  relocationTime: number
+  rectificationTime: number
+  waitingStopTime: number
+  winterBreakTime: number
+  constructionStartDate: number
+  constructionEndDate: number
+  productionStatus: string
+  currentOperation: string
+  nextPlan: string
+  rigStatus: string
+  repairStatus: string
+  personnel: string
+  totalStaffNum: number
+  leaveStaffNum: number
+  mudDensity: number
+  mudViscosity: number
+  lateralLength: number
+  wellInclination: number
+  azimuth: number
+  remark: string
+  status: number
+  processInstanceId: string
+  auditStatus: number
+  opinion: string
+  createTime: number
+  deptName: string
+  contractName: string
+  taskName: string
+  designWellDepth: number
+  designWellStruct: number
+  totalConstructionWells: number
+  completedWells: number
+  equipmentType: string
+  transitTime: number
+  lastCurrentDepth: number
+  lastGroupIdFlag: boolean
+}
+
+interface Column {
+  prop?: keyof List
+  label: string
+  'min-width'?: string
+  isTag?: boolean
+  fixed?: 'left' | 'right'
+  formatter?: (row: List) => any
+  children?: Column[]
+  dictType?: string
+}
+
+const columns = ref<Column[]>([
+  {
+    label: '日期',
+    prop: 'createTime',
+    'min-width': '120px',
+    formatter: (row: List) => dayjs(row.createTime).format('YYYY-MM-DD'),
+    fixed: 'left'
+  },
+  {
+    label: '施工队伍',
+    prop: 'deptName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '任务',
+    prop: 'taskName',
+    'min-width': '120px',
+    fixed: 'left'
+  },
+  {
+    label: '施工状态',
+    prop: 'rigStatus',
+    'min-width': '120px',
+    isTag: true,
+    dictType: DICT_TYPE.PMS_PROJECT_TASK_RY_SCHEDULE,
+    fixed: 'left'
+  },
+  {
+    label: '审批状态',
+    prop: 'auditStatus',
+    'min-width': '120px',
+    isTag: true,
+    formatter: (row: List) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待提交'
+        case 10:
+          return '待审批'
+        case 20:
+          return '审批通过'
+        case 30:
+          return '审批拒绝'
+        default:
+          return ''
+      }
+    }
+  },
+  {
+    label: '设备型号',
+    prop: 'equipmentType',
+    'min-width': '120px'
+  },
+  {
+    label: '上井次完井时间',
+    prop: 'latestWellDoneTime',
+    'min-width': '120px',
+    formatter: (row: List) =>
+      row.latestWellDoneTime ? dayjs(row.latestWellDoneTime).format('YYYY-MM-DD') : ''
+  },
+  {
+    label: '井深(m)',
+    children: [
+      {
+        label: '设计',
+        prop: 'designWellDepth',
+        'min-width': '120px'
+      },
+      {
+        label: '当前',
+        prop: 'currentDepth',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '进尺(m)',
+    children: [
+      {
+        label: '日',
+        prop: 'dailyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '月',
+        prop: 'monthlyFootage',
+        'min-width': '120px'
+      },
+      {
+        label: '年累计',
+        prop: 'annualFootage',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '总施工井数',
+    prop: 'totalConstructionWells',
+    'min-width': '120px'
+  },
+  {
+    label: '完工井数',
+    prop: 'completedWells',
+    'min-width': '120px'
+  },
+  {
+    label: '泥浆性能',
+    children: [
+      {
+        label: '密度(g/cm³)',
+        prop: 'mudDensity',
+        'min-width': '120px'
+      },
+      {
+        label: '粘度(S)',
+        prop: 'mudViscosity',
+        'min-width': '120px'
+      }
+    ]
+  },
+  {
+    label: '当日',
+    children: [
+      {
+        label: '用电量(kWh)',
+        prop: 'dailyPowerUsage',
+        'min-width': '120px'
+      },
+      {
+        label: '油耗(吨)',
+        prop: 'dailyFuel',
+        'min-width': '120px'
+      }
+    ]
+  },
+  // {
+  //   label: '施工开始日期',
+  //   prop: 'constructionStartDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionStartDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  // {
+  //   label: '施工结束日期',
+  //   prop: 'constructionEndDate',
+  //   'min-width': '120px',
+  //   formatter: (row: List) => dayjs(row.constructionEndDate).format('YYYY-MM-DD HH:mm:ss')
+  // },
+  {
+    label: '水平段长度(m)',
+    prop: 'lateralLength',
+    'min-width': '120px'
+  },
+  {
+    label: '井斜(°)',
+    prop: 'wellInclination',
+    'min-width': '120px'
+  },
+  {
+    label: '方位(°)',
+    prop: 'azimuth',
+    'min-width': '120px'
+  },
+  {
+    label: '设计井身结构',
+    prop: 'designWellStruct',
+    'min-width': '120px'
+  },
+  {
+    label: '生产动态',
+    prop: 'productionStatus',
+    'min-width': '120px'
+  },
+  {
+    label: '项目',
+    prop: 'contractName',
+    'min-width': '120px'
+  },
+  {
+    label: '进尺工作时间(H)',
+    prop: 'drillingWorkingTime',
+    'min-width': '120px'
+  },
+  {
+    label: '其它生产时间(H)',
+    prop: 'otherProductionTime',
+    'min-width': '120px'
+  },
+  {
+    label: '非生产时间',
+    children: [
+      {
+        label: '事故(H)',
+        prop: 'accidentTime',
+        'min-width': '120px'
+      },
+      {
+        label: '修理(H)',
+        prop: 'repairTime',
+        'min-width': '120px'
+      },
+      {
+        label: '自停(H)',
+        prop: 'selfStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '复杂(H)',
+        prop: 'complexityTime',
+        'min-width': '120px'
+      },
+      {
+        label: '搬迁(H)',
+        prop: 'relocationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '整改(H)',
+        prop: 'rectificationTime',
+        'min-width': '120px'
+      },
+      {
+        label: '等停(H)',
+        prop: 'waitingStopTime',
+        'min-width': '120px'
+      },
+      {
+        label: '冬休(H)',
+        prop: 'winterBreakTime',
+        'min-width': '120px'
+      }
+    ]
+  }
+])
+
+const getTextWidth = (text: string, fontSize = 12) => {
+  const span = document.createElement('span')
+  span.style.visibility = 'hidden'
+  span.style.position = 'absolute'
+  span.style.whiteSpace = 'nowrap'
+  span.style.fontSize = `${fontSize}px`
+  span.style.fontFamily = 'PingFang SC'
+  span.innerText = text
+
+  document.body.appendChild(span)
+  const width = span.offsetWidth
+  document.body.removeChild(span)
+
+  return width
+}
+
+const calculateColumnWidths = (colums: Column[]) => {
+  for (const col of colums) {
+    let { formatter, prop, label, 'min-width': minWidth, isTag, children } = col
+
+    if (children && children.length > 0) {
+      calculateColumnWidths(children)
+      continue
+    }
+
+    minWidth =
+      Math.min(
+        ...[
+          Math.max(
+            ...[
+              getTextWidth(label),
+              ...list.value.map((v) => {
+                let tagLabel = ''
+                if (col.dictType) {
+                  const option = getDictOptions(col.dictType).find(
+                    (item) => item.value === v[prop!]
+                  )
+                  if (option) tagLabel = option.label
+                }
+                return getTextWidth(
+                  formatter ? formatter(v) : Boolean(tagLabel) ? tagLabel : v[prop!]
+                )
+              })
+            ]
+          ) + (isTag ? 40 : 20),
+          ...(isTag ? [] : [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 {}
+}
+
+function rowClassName(data: { row: List; rowIndex: number }) {
+  const { row } = data
+  if (!row.lastGroupIdFlag) {
+    return 'hide-expand-icon'
+  }
+  return ''
+}
+
+const id = useUserStore().getUser.deptId
+
+const deptId = id
+
+interface Query {
+  pageNo: number
+  pageSize: number
+  deptId: number
+  contractName?: string
+  taskName?: string
+  wellName?: string
+  createTime: string[]
+}
+
+const query = ref<Query>({
+  pageNo: 1,
+  pageSize: 10,
+  deptId: id,
+  createTime: [
+    ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+  ]
+})
+
+function handleSizeChange(val: number) {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+function handleCurrentChange(val: number) {
+  query.value.pageNo = val
+  loadList()
+}
+
+const loading = ref(false)
+
+const list = ref<List[]>([])
+const total = ref(0)
+
+const loadList = useDebounceFn(async function () {
+  loading.value = true
+  try {
+    if (tab.value === '井') {
+      const { deptId, taskName, wellName, ...other } = query.value
+      const data = await IotRyDailyReportApi.getIotRyDailyReportTeamPage({
+        ...other,
+        taskName: wellName
+      })
+      list.value = data.list
+      total.value = data.total
+    } else {
+      const data = await IotRyDailyReportApi.getIotRyDailyReportWellPage(query.value)
+      list.value = data.list
+      total.value = data.total
+    }
+
+    nextTick(() => {
+      calculateColumnWidths(columns.value)
+    })
+  } finally {
+    loading.value = false
+  }
+}, 500)
+
+function handleQuery(setPage = true) {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+function resetQuery() {
+  query.value = {
+    pageNo: 1,
+    pageSize: 10,
+    deptId: 157,
+    contractName: '',
+    taskName: '',
+    createTime: [
+      ...rangeShortcuts[2].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
+    ]
+  }
+  handleQuery()
+}
+
+watch(
+  [
+    () => query.value.createTime,
+    () => query.value.deptId,
+    () => query.value.taskName,
+    () => query.value.contractName,
+    () => query.value.wellName
+  ],
+  () => {
+    handleQuery()
+  },
+  { immediate: true }
+)
+
+const expandRowKeys = computed(() => {
+  return list.value.filter((item) => item.lastGroupIdFlag).map((item) => item.id.toString())
+})
+</script>
+
+<template>
+  <div
+    class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+  >
+    <el-form
+      size="default"
+      class="col-span-2 bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center"
+    >
+      <div class="flex items-center gap-8">
+        <el-segmented class="h-12" v-model="tab" :options="tabOptions" @change="handleQuery()" />
+      </div>
+      <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 v-show="tab !== '井'" label="任务">
+          <el-input
+            v-model="query.taskName"
+            placeholder="请输入任务"
+            clearable
+            @keyup.enter="handleQuery()"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="query.createTime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            type="daterange"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="rangeShortcuts"
+            :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            class="!w-220px"
+          />
+        </el-form-item>
+      </div>
+      <el-form-item class="ml-auto">
+        <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>
+
+    <!-- 第二行左侧:自动落入第 2 行第 1 列 -->
+    <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg h-full">
+      <DeptTreeSelect
+        v-show="tab === '队伍'"
+        :top-id="158"
+        :deptId="deptId"
+        v-model="query.deptId"
+        title="队伍"
+      />
+      <WellSelect v-show="tab === '井'" :deptId="158" v-model="query.wellName" />
+    </div>
+
+    <!-- 第二行右侧:自动落入第 2 行第 2 列 -->
+    <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"
+              :height="height"
+              show-overflow-tooltip
+              :width="width"
+              :cell-style="cellStyle"
+              :row-class-name="rowClassName"
+              :tree-props="{ hasChildren: 'lastGroupIdFlag' }"
+              row-key="id"
+              scrollbar-always-on
+              :expand-row-keys="expandRowKeys"
+              border
+            >
+              <el-table-column type="expand" fixed="left">
+                <template #default="{ row }">
+                  <div
+                    class="flex items-center gap-8 h-10 px-14 sticky left-0 w-fit box-border bg-[var(--el-bg-color)]"
+                  >
+                    <el-tag>累计进尺: {{ row.groupIdFootage }} </el-tag>
+                    <el-tag>累计进尺工作时间: {{ row.groupIdFootageTime }} </el-tag>
+                    <el-tag>累计其它生产时间H(钻井) : {{ row.groupIdOtherProductTime }} </el-tag>
+                    <el-tag>累计非生产时间H(钻井) : {{ row.groupIdZjNoProductTime }} </el-tag>
+                    <el-tag>累计生产时间(修井) : {{ row.groupIdProductTime }} </el-tag>
+                    <el-tag>累计非生产时间H(修井) : {{ row.groupIdXjNoProductTime }} </el-tag>
+                    <el-tag>累计用电量kWh : {{ row.groupIdPower }} </el-tag>
+                    <el-tag>累计油耗 吨 : {{ row.groupIdFuel }} </el-tag>
+                    <el-tag>平均运行时效 : {{ row.groupIdTransitTime }} </el-tag>
+                    <el-tag>累计施工井数 : {{ row.groupConstructionWells }} </el-tag>
+                    <el-tag>累计完工井数 : {{ row.groupCompletedWells }} </el-tag>
+                  </div>
+                </template>
+              </el-table-column>
+              <DailyTableColumn :columns="columns" />
+              <!-- <el-table-column label="操作" width="120px" align="center" fixed="right">
+                <template #default="{ row }">
+                  <el-button link type="success" v-hasPermi="['pms:iot-rh-daily-report:query']">
+                    查看
+                  </el-button>
+                  <el-button
+                    v-show="row.status === 0"
+                    link
+                    type="primary"
+                    v-hasPermi="['pms:iot-rh-daily-report:create']"
+                  >
+                    编辑
+                  </el-button>
+                </template>
+              </el-table-column> -->
+            </el-table>
+          </template>
+        </el-auto-resizer>
+      </div>
+      <div class="h-10 mt-4 flex items-center justify-end">
+        <el-pagination
+          size="default"
+          v-show="total > 0"
+          v-model:current-page="query.pageNo"
+          v-model:page-size="query.pageSize"
+          :background="true"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+:deep(.el-form-item) {
+  margin-bottom: 0;
+}
+
+.el-segmented {
+  --el-border-radius-base: 8px;
+  --el-segmented-padding: 8px;
+}
+
+:deep(.hide-expand-icon) {
+  .el-table__expand-icon {
+    display: none;
+  }
+}
+</style>