Kaynağa Gözat

月报汇总

yanghao 3 gün önce
ebeveyn
işleme
ed7611b5dc
1 değiştirilmiş dosya ile 117 ekleme ve 150 silme
  1. 117 150
      src/views/pms/qhse/reportSummary/preview-drawer.vue

+ 117 - 150
src/views/pms/qhse/reportSummary/preview-drawer.vue

@@ -7,12 +7,22 @@ import type {
   ReportMetricValue
 } from './types'
 import dayjs from 'dayjs'
+import { computed, ref, watch } from 'vue'
 
 interface Props {
   visible: boolean
   id?: number
 }
 
+interface TableRow {
+  category: string
+  label: string
+  unit: string
+  field: keyof QhseMonthReportItem
+  summary: string
+  [key: string]: string | keyof QhseMonthReportItem
+}
+
 const props = defineProps<Props>()
 const emits = defineEmits(['update:visible'])
 
@@ -77,9 +87,7 @@ const categoryRowSpanMap = computed(() => {
 
 const firstRowIndexByCategory = computed(() => {
   return metricRows.reduce<Record<string, number>>((acc, item, index) => {
-    if (acc[item.category] === undefined) {
-      acc[item.category] = index
-    }
+    if (acc[item.category] === undefined) acc[item.category] = index
     return acc
   }, {})
 })
@@ -174,6 +182,23 @@ const mockMetricValueMap = computed<Record<string, Record<string, ReportMetricVa
   }
 }))
 
+const tableRows = computed<TableRow[]>(() => {
+  return metricRows.map((row) => {
+    const companyValues = Object.fromEntries(
+      companyColumns.map((company) => [company.key, getMetricCompanyValue(row.field, company.key)])
+    )
+
+    return {
+      category: row.category,
+      label: row.label,
+      unit: row.unit,
+      field: row.field,
+      summary: getMetricSummaryValue(row.field),
+      ...companyValues
+    }
+  })
+})
+
 async function loadDetail(id: number) {
   loading.value = true
   try {
@@ -186,9 +211,7 @@ async function loadDetail(id: number) {
 
 function handleVisibleChange(visible: boolean) {
   emits('update:visible', visible)
-  if (!visible) {
-    report.value = undefined
-  }
+  if (!visible) report.value = undefined
 }
 
 function formatDisplayValue(field: keyof QhseMonthReportItem) {
@@ -204,7 +227,6 @@ function formatDisplayValue(field: keyof QhseMonthReportItem) {
 function getMetricCompanyValue(field: keyof QhseMonthReportItem, companyKey: string) {
   const rowData = mockMetricValueMap.value[String(field)] || {}
   const value = rowData[companyKey]
-
   if (value === undefined || value === null || value === '') return '-'
   if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(2)
   return String(value)
@@ -217,15 +239,28 @@ function getMetricSummaryValue(field: keyof QhseMonthReportItem) {
     .filter((value) => value !== undefined && value !== null && value !== '')
 
   if (!values.length) return '-'
-
   if (values.every((value) => typeof value === 'number')) {
     const total = values.reduce((sum, value) => sum + Number(value), 0)
     return Number.isInteger(total) ? String(total) : total.toFixed(2)
   }
-
   return values.map((value) => String(value)).join(';')
 }
 
+function tableSpanMethod({
+  row,
+  column,
+  rowIndex
+}: {
+  row: TableRow
+  column: { property?: string }
+  rowIndex: number
+}) {
+  if (column.property !== 'category') return { rowspan: 1, colspan: 1 }
+  const firstIndex = firstRowIndexByCategory.value[row.category]
+  if (firstIndex !== rowIndex) return { rowspan: 0, colspan: 0 }
+  return { rowspan: categoryRowSpanMap.value[row.category], colspan: 1 }
+}
+
 watch(
   () => [props.visible, props.id] as const,
   ([visible, id]) => {
@@ -242,7 +277,6 @@ watch(
     :size="'100%'"
     :with-header="false"
     destroy-on-close
-    append-to-body
     body-class="qhse-report-preview-drawer__body"
     @update:model-value="handleVisibleChange">
     <div class="qhse-report-preview" v-loading="loading">
@@ -255,36 +289,35 @@ watch(
         </div>
 
         <div class="qhse-report-preview__table-wrap">
-          <table class="qhse-report-preview__table">
-            <thead>
-              <tr>
-                <th class="is-sticky-col">基本信息</th>
-                <th>指标项</th>
-                <th>单位</th>
-                <th v-for="company in companyColumns" :key="company.key">{{ company.label }}</th>
-                <th>汇总</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(row, index) in metricRows" :key="`${row.category}-${row.field}`">
-                <th
-                  v-if="firstRowIndexByCategory[row.category] === index"
-                  class="is-sticky-col is-category"
-                  :rowspan="categoryRowSpanMap[row.category]">
-                  {{ row.category }}
-                </th>
-                <th class="is-label">{{ row.label }}</th>
-                <td>{{ row.unit }}</td>
-                <td
-                  v-for="company in companyColumns"
-                  :key="`${row.field}-${company.key}`"
-                  class="is-value">
-                  {{ getMetricCompanyValue(row.field, company.key) }}
-                </td>
-                <td class="is-summary">{{ getMetricSummaryValue(row.field) }}</td>
-              </tr>
-            </tbody>
-          </table>
+          <el-table
+            :data="tableRows"
+            :span-method="tableSpanMethod"
+            border
+            stripe
+            height="60vh"
+            class="qhse-report-preview__el-table">
+            <el-table-column
+              prop="category"
+              label="基本信息"
+              fixed="left"
+              width="140"
+              align="center" />
+            <el-table-column prop="label" label="指标项" fixed="left" width="260" align="center" />
+            <el-table-column prop="unit" label="单位" fixed="left" width="110" align="center" />
+            <el-table-column
+              v-for="company in companyColumns"
+              :key="company.key"
+              :prop="company.key"
+              :label="company.label"
+              min-width="140"
+              align="center" />
+            <el-table-column
+              prop="summary"
+              label="汇总"
+              min-width="180"
+              align="center"
+              fixed="right" />
+          </el-table>
         </div>
       </div>
 
@@ -305,28 +338,6 @@ watch(
   padding: 24px;
 }
 
-.qhse-report-preview__toolbar {
-  display: flex;
-  align-items: flex-start;
-  justify-content: space-between;
-  gap: 16px;
-}
-
-.qhse-report-preview__heading {
-  h2 {
-    margin: 0;
-    font-size: 26px;
-    font-weight: 700;
-    color: #1f2a44;
-  }
-
-  p {
-    margin: 8px 0 0;
-    color: #5f6b85;
-    font-size: 14px;
-  }
-}
-
 .qhse-report-preview__sheet {
   display: flex;
   min-height: 0;
@@ -338,7 +349,6 @@ watch(
   border-radius: 10px;
   box-shadow: 0 18px 50px rgb(35 51 84 / 10%);
   padding: 24px;
-  // margin-bottom: 20px;
 }
 
 .qhse-report-preview__sheet-title {
@@ -358,91 +368,22 @@ watch(
 }
 
 .qhse-report-preview__table-wrap {
-  overflow-x: auto;
-  overflow-y: visible;
-  border: 1px solid #cfd8e6;
-}
-
-.qhse-report-preview__table {
-  width: 100%;
-  min-width: 820px;
-  border-collapse: separate;
-  border-spacing: 0;
-  table-layout: fixed;
-  font-size: 14px;
-  color: #26334d;
-
-  th,
-  td {
-    border: 1px solid #cfd8e6;
-    padding: 12px 14px;
-    text-align: center;
-    vertical-align: middle;
-    background: #fff;
-  }
-
-  thead th {
-    position: sticky;
-    top: 0;
-    z-index: 3;
-    background: #dbe8ff;
-    color: #183153;
-    font-weight: 700;
-    box-shadow: inset 0 -1px 0 #cfd8e6;
-  }
-}
-
-.is-sticky-col {
-  position: sticky;
-  left: 0;
-  z-index: 2;
-}
-
-.qhse-report-preview__table thead .is-sticky-col {
-  z-index: 6;
-}
-
-.is-category {
-  background: #edf3ff !important;
-  font-weight: 700;
-  min-width: 140px;
-}
-
-.is-label {
-  background: #f8fbff;
-  font-weight: 600;
-}
-
-.is-value {
-  font-weight: 600;
-  color: #0f3f8f;
-}
-
-.is-summary {
-  background: #eef4ff !important;
-  font-weight: 700;
-  color: #14346b;
+  min-height: 0;
+  flex: 1;
 }
 
 .qhse-report-preview__footer {
-  position: fixed;
+  position: sticky;
   bottom: 0;
   z-index: 20;
   display: flex;
-  width: 100%;
-  right: 0;
-  top: 93vh;
   justify-content: center;
-  // padding: 10px 20px;
-
-  background: rgb(255 255 255 / 90%);
-  border: 1px solid rgb(216 224 239 / 90%);
-  // border-radius: 18px;
-  box-shadow: 0 14px 40px rgb(35 51 84 / 14%);
-  backdrop-filter: blur(12px);
-  display: flex;
   align-items: center;
-  justify-content: center;
+  margin-top: auto;
+  padding: 12px;
+  border-radius: 10px;
+  background: rgb(255 255 255 / 90%);
+  backdrop-filter: blur(8px);
 }
 
 @media (width < 768px) {
@@ -451,11 +392,6 @@ watch(
     gap: 12px;
   }
 
-  .qhse-report-preview__toolbar {
-    flex-direction: column;
-    align-items: stretch;
-  }
-
   .qhse-report-preview__sheet {
     padding: 14px;
     border-radius: 14px;
@@ -465,11 +401,6 @@ watch(
     font-size: 22px;
     letter-spacing: 1px;
   }
-
-  .qhse-report-preview__footer {
-    padding: 12px;
-    border-radius: 14px;
-  }
 }
 
 :deep(.qhse-report-preview-drawer__body) {
@@ -477,4 +408,40 @@ watch(
   overflow-y: auto;
   padding: 0;
 }
+
+:deep(.qhse-report-preview__el-table) {
+  --el-table-header-bg-color: #dbe8ff;
+  --el-table-header-text-color: #183153;
+  --el-table-row-hover-bg-color: #f8fbff;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__cell) {
+  padding: 0;
+}
+
+:deep(.qhse-report-preview__el-table th.el-table__cell) {
+  font-weight: 700;
+}
+
+:deep(.qhse-report-preview__el-table .cell) {
+  padding: 12px 14px;
+  line-height: 1.5;
+  word-break: break-word;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:last-child) {
+  background: #eef4ff;
+  color: #14346b;
+  font-weight: 700;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:nth-child(2)) {
+  background: #f8fbff;
+  font-weight: 600;
+}
+
+:deep(.qhse-report-preview__el-table .el-table__body-wrapper td:nth-child(n + 4):not(:last-child)) {
+  color: #0f3f8f;
+  font-weight: 600;
+}
 </style>