|
@@ -7,12 +7,22 @@ import type {
|
|
|
ReportMetricValue
|
|
ReportMetricValue
|
|
|
} from './types'
|
|
} from './types'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
|
|
+import { computed, ref, watch } from 'vue'
|
|
|
|
|
|
|
|
interface Props {
|
|
interface Props {
|
|
|
visible: boolean
|
|
visible: boolean
|
|
|
id?: number
|
|
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 props = defineProps<Props>()
|
|
|
const emits = defineEmits(['update:visible'])
|
|
const emits = defineEmits(['update:visible'])
|
|
|
|
|
|
|
@@ -77,9 +87,7 @@ const categoryRowSpanMap = computed(() => {
|
|
|
|
|
|
|
|
const firstRowIndexByCategory = computed(() => {
|
|
const firstRowIndexByCategory = computed(() => {
|
|
|
return metricRows.reduce<Record<string, number>>((acc, item, index) => {
|
|
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
|
|
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) {
|
|
async function loadDetail(id: number) {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
@@ -186,9 +211,7 @@ async function loadDetail(id: number) {
|
|
|
|
|
|
|
|
function handleVisibleChange(visible: boolean) {
|
|
function handleVisibleChange(visible: boolean) {
|
|
|
emits('update:visible', visible)
|
|
emits('update:visible', visible)
|
|
|
- if (!visible) {
|
|
|
|
|
- report.value = undefined
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!visible) report.value = undefined
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function formatDisplayValue(field: keyof QhseMonthReportItem) {
|
|
function formatDisplayValue(field: keyof QhseMonthReportItem) {
|
|
@@ -204,7 +227,6 @@ function formatDisplayValue(field: keyof QhseMonthReportItem) {
|
|
|
function getMetricCompanyValue(field: keyof QhseMonthReportItem, companyKey: string) {
|
|
function getMetricCompanyValue(field: keyof QhseMonthReportItem, companyKey: string) {
|
|
|
const rowData = mockMetricValueMap.value[String(field)] || {}
|
|
const rowData = mockMetricValueMap.value[String(field)] || {}
|
|
|
const value = rowData[companyKey]
|
|
const value = rowData[companyKey]
|
|
|
-
|
|
|
|
|
if (value === undefined || value === null || value === '') return '-'
|
|
if (value === undefined || value === null || value === '') return '-'
|
|
|
if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(2)
|
|
if (typeof value === 'number' && !Number.isInteger(value)) return value.toFixed(2)
|
|
|
return String(value)
|
|
return String(value)
|
|
@@ -217,15 +239,28 @@ function getMetricSummaryValue(field: keyof QhseMonthReportItem) {
|
|
|
.filter((value) => value !== undefined && value !== null && value !== '')
|
|
.filter((value) => value !== undefined && value !== null && value !== '')
|
|
|
|
|
|
|
|
if (!values.length) return '-'
|
|
if (!values.length) return '-'
|
|
|
-
|
|
|
|
|
if (values.every((value) => typeof value === 'number')) {
|
|
if (values.every((value) => typeof value === 'number')) {
|
|
|
const total = values.reduce((sum, value) => sum + Number(value), 0)
|
|
const total = values.reduce((sum, value) => sum + Number(value), 0)
|
|
|
return Number.isInteger(total) ? String(total) : total.toFixed(2)
|
|
return Number.isInteger(total) ? String(total) : total.toFixed(2)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return values.map((value) => String(value)).join(';')
|
|
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(
|
|
watch(
|
|
|
() => [props.visible, props.id] as const,
|
|
() => [props.visible, props.id] as const,
|
|
|
([visible, id]) => {
|
|
([visible, id]) => {
|
|
@@ -242,7 +277,6 @@ watch(
|
|
|
:size="'100%'"
|
|
:size="'100%'"
|
|
|
:with-header="false"
|
|
:with-header="false"
|
|
|
destroy-on-close
|
|
destroy-on-close
|
|
|
- append-to-body
|
|
|
|
|
body-class="qhse-report-preview-drawer__body"
|
|
body-class="qhse-report-preview-drawer__body"
|
|
|
@update:model-value="handleVisibleChange">
|
|
@update:model-value="handleVisibleChange">
|
|
|
<div class="qhse-report-preview" v-loading="loading">
|
|
<div class="qhse-report-preview" v-loading="loading">
|
|
@@ -255,36 +289,35 @@ watch(
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="qhse-report-preview__table-wrap">
|
|
<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>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -305,28 +338,6 @@ watch(
|
|
|
padding: 24px;
|
|
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 {
|
|
.qhse-report-preview__sheet {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
@@ -338,7 +349,6 @@ watch(
|
|
|
border-radius: 10px;
|
|
border-radius: 10px;
|
|
|
box-shadow: 0 18px 50px rgb(35 51 84 / 10%);
|
|
box-shadow: 0 18px 50px rgb(35 51 84 / 10%);
|
|
|
padding: 24px;
|
|
padding: 24px;
|
|
|
- // margin-bottom: 20px;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.qhse-report-preview__sheet-title {
|
|
.qhse-report-preview__sheet-title {
|
|
@@ -358,91 +368,22 @@ watch(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.qhse-report-preview__table-wrap {
|
|
.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 {
|
|
.qhse-report-preview__footer {
|
|
|
- position: fixed;
|
|
|
|
|
|
|
+ position: sticky;
|
|
|
bottom: 0;
|
|
bottom: 0;
|
|
|
z-index: 20;
|
|
z-index: 20;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- width: 100%;
|
|
|
|
|
- right: 0;
|
|
|
|
|
- top: 93vh;
|
|
|
|
|
justify-content: center;
|
|
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;
|
|
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) {
|
|
@media (width < 768px) {
|
|
@@ -451,11 +392,6 @@ watch(
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .qhse-report-preview__toolbar {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: stretch;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
.qhse-report-preview__sheet {
|
|
.qhse-report-preview__sheet {
|
|
|
padding: 14px;
|
|
padding: 14px;
|
|
|
border-radius: 14px;
|
|
border-radius: 14px;
|
|
@@ -465,11 +401,6 @@ watch(
|
|
|
font-size: 22px;
|
|
font-size: 22px;
|
|
|
letter-spacing: 1px;
|
|
letter-spacing: 1px;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- .qhse-report-preview__footer {
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
:deep(.qhse-report-preview-drawer__body) {
|
|
:deep(.qhse-report-preview-drawer__body) {
|
|
@@ -477,4 +408,40 @@ watch(
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
padding: 0;
|
|
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>
|
|
</style>
|