Explorar el Código

feat(rd-kb): 新增瑞都生产日报看板页

- 在瑞都看板生产日报 tab 中接入生产日报列表组件
- 新增 rdProductionBriefs 组件,复用看板面板、日期选择器和表格样式
- 默认查询昨天的生产日报数据,并固定传入瑞都顶级部门 deptId
- 请求瑞都日报分页接口时仅传 deptId 和 createTime,不传 pageNo/pageSize
- 按接口返回的 res.list 渲染列表数据
- 按 projectSort、teamSort 对生产日报列表排序
- 支持相同项目名称的项目列单元格合并
- 展示项目、队伍、状态、井号、工艺、使用设备、压裂层数、连油井数和施工简要字段
- 修正数值字段展示逻辑,压裂层数和连油井数为 0 时正常显示 0
Zimo hace 4 días
padre
commit
665131476a
Se han modificado 2 ficheros con 306 adiciones y 1 borrados
  1. 6 1
      src/views/pms/stat/rdkb.vue
  2. 300 0
      src/views/pms/stat/rdkb/rdProductionBriefs.vue

+ 6 - 1
src/views/pms/stat/rdkb.vue

@@ -10,6 +10,7 @@ import rdRate from './rdkb/rd-rate.vue'
 import rdUseStatus from './rdkb/rd-use-status.vue'
 import rdDeviceStatus from './rdkb/rd-device-status.vue'
 import rdDeviceCategory from './rdkb/rd-device-category.vue'
+import rdProductionBriefs from './rdkb/rdProductionBriefs.vue'
 
 defineOptions({
   name: 'IotRdStatt'
@@ -116,7 +117,11 @@ onUnmounted(() => {
             </div>
           </div>
 
-          <div v-else class="kb-production-page"> </div>
+          <div v-else class="kb-production-page">
+            <rd-production-briefs
+              class="kb-stage-card kb-stage-card--8 kb-stage-card--list"
+              page-mode="full" />
+          </div>
         </div>
       </div>
     </div>

+ 300 - 0
src/views/pms/stat/rdkb/rdProductionBriefs.vue

@@ -0,0 +1,300 @@
+<script lang="ts" setup>
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
+import dayjs from 'dayjs'
+import type { Ref } from 'vue'
+
+interface RdProductionBriefRow {
+  id?: number
+  projectName?: string
+  deptName?: string
+  rdStatusLabel?: string
+  taskName?: string
+  techniqueNames?: string
+  deviceNames?: string
+  cumulativeWorkingLayers?: number | null
+  cumulativeWorkingWell?: number | null
+  constructionBrief?: string
+  projectSort?: number | null
+  teamSort?: number | null
+}
+
+interface SpanMethodProps {
+  rowIndex: number
+  columnIndex: number
+}
+
+const TABLE_HEIGHT = 220
+const RD_DEPT_ID = 163
+const DEFAULT_DATE = dayjs().subtract(1, 'day').format('YYYY-MM-DD')
+
+const props = withDefaults(
+  defineProps<{
+    pageMode?: 'compact' | 'full'
+  }>(),
+  {
+    pageMode: 'compact'
+  }
+)
+
+const selectedDate = ref(DEFAULT_DATE)
+const loading = ref(false)
+const list = ref<RdProductionBriefRow[]>([])
+const kbScale = inject<Ref<number>>('rdKbScale', ref(1))
+const tableHeight = computed<number | string>(() =>
+  props.pageMode === 'full' ? '100%' : Math.round(TABLE_HEIGHT * kbScale.value)
+)
+
+const tableData = computed(() => {
+  return [...list.value].sort((a, b) => {
+    const projectSort = Number(a.projectSort ?? 9999) - Number(b.projectSort ?? 9999)
+    if (projectSort !== 0) return projectSort
+
+    return Number(a.teamSort ?? 9999) - Number(b.teamSort ?? 9999)
+  })
+})
+
+const projectSpanMap = computed(() =>
+  createSpanMap(tableData.value, (row) => row.projectName || '-')
+)
+
+function normalizeList(res: any): RdProductionBriefRow[] {
+  if (Array.isArray(res?.list)) return res.list
+  return []
+}
+
+function createSpanMap(
+  rows: RdProductionBriefRow[],
+  getKey: (row: RdProductionBriefRow) => string
+) {
+  const spanMap: number[] = []
+
+  rows.forEach((row, index) => {
+    const key = getKey(row)
+
+    if (index > 0 && getKey(rows[index - 1]) === key) {
+      spanMap[index] = 0
+      return
+    }
+
+    let span = 1
+    for (let nextIndex = index + 1; nextIndex < rows.length; nextIndex++) {
+      if (getKey(rows[nextIndex]) !== key) break
+      span += 1
+    }
+    spanMap[index] = span
+  })
+
+  return spanMap
+}
+
+function tableSpanMethod({ rowIndex, columnIndex }: SpanMethodProps) {
+  if (columnIndex !== 0) {
+    return {
+      rowspan: 1,
+      colspan: 1
+    }
+  }
+
+  const rowspan = projectSpanMap.value[rowIndex]
+
+  return {
+    rowspan,
+    colspan: rowspan > 0 ? 1 : 0
+  }
+}
+
+function getCreateTimeRange() {
+  const date = selectedDate.value || DEFAULT_DATE
+
+  return [
+    dayjs(date).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    dayjs(date).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+function formatText(value?: string | number | null) {
+  return value === null || value === undefined || value === '' ? '-' : value
+}
+
+function handleDateChange() {
+  getList()
+}
+
+async function getList() {
+  loading.value = true
+
+  try {
+    const res = await IotRdDailyReportApi.getIotRdDailyReportPage({
+      deptId: RD_DEPT_ID,
+      createTime: getCreateTimeRange()
+    })
+    list.value = normalizeList(res)
+  } catch (error) {
+    console.error('获取瑞都生产日报失败:', error)
+    list.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<template>
+  <div
+    class="panel device-list-panel production-brief-panel w-full flex flex-col"
+    :class="{ 'production-brief-panel--full': props.pageMode === 'full' }">
+    <div class="panel-title flex items-center justify-between">
+      <div class="kb-panel-title-text flex items-center">
+        <div class="icon-decorator">
+          <span></span>
+          <span></span>
+        </div>
+        生产日报
+      </div>
+      <div class="production-brief-panel__picker">
+        <el-date-picker
+          v-model="selectedDate"
+          value-format="YYYY-MM-DD"
+          type="date"
+          placeholder="选择日期"
+          :clearable="false"
+          class="production-brief-panel__picker-input"
+          @change="handleDateChange" />
+      </div>
+    </div>
+    <div class="device-list-panel__body flex-1 min-h-0">
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        :height="tableHeight"
+        :span-method="tableSpanMethod"
+        class="device-list-table production-brief-table"
+        :class="{ 'production-brief-table--full': props.pageMode === 'full' }">
+        <el-table-column prop="projectName" label="项目" min-width="150" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.projectName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="deptName" label="队伍" min-width="110" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.deptName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="rdStatusLabel" label="状态" min-width="90" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.rdStatusLabel) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="taskName" label="井号" min-width="120" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.taskName) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="techniqueNames" label="工艺" min-width="130" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.techniqueNames) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="deviceNames" label="使用设备" min-width="170" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.deviceNames) }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="cumulativeWorkingLayers"
+          label="压裂层数"
+          min-width="100"
+          align="center">
+          <template #default="{ row }">
+            {{ formatText(row.cumulativeWorkingLayers) }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="cumulativeWorkingWell"
+          label="连油井数"
+          min-width="100"
+          align="center">
+          <template #default="{ row }">
+            {{ formatText(row.cumulativeWorkingWell) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="constructionBrief" label="施工简要" min-width="220" align="center">
+          <template #default="{ row }">
+            {{ formatText(row.constructionBrief) }}
+          </template>
+        </el-table-column>
+
+        <template #empty>
+          <div class="h-full min-h-[220px] flex items-center justify-center">
+            <el-empty description="暂无数据" :image-size="72" />
+          </div>
+        </template>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import url('@/styles/kb.scss');
+
+.device-list-panel.production-brief-panel--full {
+  height: 100%;
+  margin-top: 0;
+}
+
+.production-brief-panel__picker {
+  display: flex;
+  width: calc(120px * var(--kb-scale, 1));
+  align-items: center;
+}
+
+.production-brief-panel__picker-input {
+  width: calc(120px * var(--kb-scale, 1)) !important;
+
+  :deep(.el-input__wrapper) {
+    min-height: calc(28px * var(--kb-scale, 1));
+    padding: 0 calc(10px * var(--kb-scale, 1));
+  }
+
+  :deep(.el-input__inner) {
+    font-size: calc(12px * var(--kb-scale, 1));
+  }
+
+  :deep(.el-input__prefix-inner),
+  :deep(.el-input__suffix-inner) {
+    font-size: calc(14px * var(--kb-scale, 1));
+  }
+}
+
+.production-brief-table {
+  width: 100%;
+
+  :deep(.el-table__header-wrapper th.el-table__cell) {
+    font-size: calc(16px * var(--kb-scale, 1));
+    line-height: 1.2;
+  }
+
+  :deep(.el-table__body td.el-table__cell) {
+    padding: calc(7px * var(--kb-scale, 1)) 0;
+    font-size: calc(14px * var(--kb-scale, 1));
+  }
+}
+
+.production-brief-table--full {
+  :deep(.el-scrollbar__view) {
+    display: block;
+    height: 100%;
+  }
+
+  :deep(.el-table__body) {
+    height: 100%;
+  }
+
+  :deep(.el-table__body tbody) {
+    height: 100%;
+  }
+}
+</style>