<script lang="ts" setup>
|
import type { MesQcReportApi } from '#/api/mes/qc/indicatorresult';
|
|
import { computed, nextTick, ref } from 'vue';
|
|
import { DICT_TYPE, MesQcTypeEnum } from '@vben/constants';
|
import { getDictLabel } from '@vben/hooks';
|
import { useUserStore } from '@vben/stores';
|
import { formatDateTime } from '@vben/utils';
|
|
import { getQcReport } from '#/api/mes/qc/indicatorresult';
|
|
/**
|
* MES 检验报告单打印组件(化工检验记录版式:表头 + 检验项矩阵 + 平行测量列 +
|
* 判定结论/质检专用章 + 备注 + 签字区)
|
*
|
* 打印样式参考 erp/sale/order/modules/print.vue(Teleport + body class + @media print)。
|
*/
|
defineOptions({ name: 'MesQcReportPrint' });
|
|
const userStore = useUserStore();
|
|
/** 扁平化后的报告行 */
|
interface PrintRow {
|
kind: 'group' | 'record';
|
indicatorId?: number;
|
name?: string;
|
unit?: string;
|
method?: string; // 分组=公式文本,录入项=检测方法
|
standard?: string; // 标准值(含阈值区间)
|
values: Record<number, string>; // measureNo -> 检测值
|
remarks: Record<number, string>; // measureNo -> 备注
|
}
|
|
const report = ref<MesQcReportApi.QcReport>({});
|
const measureNos = ref<number[]>([1]);
|
const printTime = ref('');
|
|
/** 标准值展示:标准值 + 阈值区间 */
|
function formatStandard(item: MesQcReportApi.ReportItem) {
|
const parts: string[] = [];
|
if (item.standardValue != null) {
|
parts.push(String(item.standardValue));
|
}
|
if (item.thresholdMin != null || item.thresholdMax != null) {
|
parts.push(`${item.thresholdMin ?? ''}~${item.thresholdMax ?? ''}`);
|
}
|
return parts.join(' ');
|
}
|
|
/** 将检验项树扁平化为打印行(分组行 + 其子录入行;顶级录入行独立成行) */
|
const printRows = computed<PrintRow[]>(() => {
|
const rows: PrintRow[] = [];
|
const pushRecord = (item: MesQcReportApi.ReportItem, method: string) => {
|
const values: Record<number, string> = {};
|
const remarks: Record<number, string> = {};
|
(item.measures ?? []).forEach((m) => {
|
if (m.measureNo != null) {
|
values[m.measureNo] = m.value ?? '';
|
remarks[m.measureNo] = m.remark ?? '';
|
}
|
});
|
rows.push({
|
kind: 'record',
|
indicatorId: item.indicatorId,
|
name: item.indicatorName,
|
unit: item.unitText,
|
method,
|
standard: formatStandard(item),
|
values,
|
remarks,
|
});
|
};
|
const walk = (items: MesQcReportApi.ReportItem[] = []) => {
|
items.forEach((item) => {
|
if (item.itemType === 2) {
|
rows.push({
|
kind: 'group',
|
indicatorId: item.indicatorId,
|
name: item.indicatorName,
|
method: item.formulaText,
|
values: {},
|
remarks: {},
|
});
|
walk(item.children ?? []);
|
} else {
|
pushRecord(item, item.checkMethod ?? '');
|
}
|
});
|
};
|
walk(report.value.items ?? []);
|
return rows;
|
});
|
|
/** 检测结果文案:优先用后端按质检类型解析的文案,缺失时按质检类型选字典兜底 */
|
const checkResultText = computed(() => {
|
if (report.value.checkResultText) {
|
return report.value.checkResultText;
|
}
|
if (report.value.checkResult == null) {
|
return '-';
|
}
|
const dictType =
|
report.value.qcType === MesQcTypeEnum.OQC
|
? DICT_TYPE.MES_OQC_CHECK_RESULT
|
: DICT_TYPE.MES_QC_CHECK_RESULT;
|
return getDictLabel(dictType, report.value.checkResult) || '-';
|
});
|
|
/** 测量列头:无平行测量时叫「测量值」,多批次时按「测量1」「测量2」区分 */
|
function measureHeader(no: number) {
|
return measureNos.value.length > 1 ? `测量${no}` : '测量值';
|
}
|
|
function waitForPaint() {
|
return new Promise<void>((resolve) => {
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
});
|
}
|
|
function removePrintMode() {
|
document.body.classList.remove('mes-qc-report-printing');
|
}
|
|
/** 打印:加载报告数据后切到打印模式调用浏览器打印 */
|
async function print(qcId: number, qcType: number, resultId?: number) {
|
const data = await getQcReport(qcId, qcType, resultId);
|
report.value = data;
|
measureNos.value = data.measureNos?.length ? data.measureNos : [1];
|
printTime.value = formatDateTime(new Date());
|
await nextTick();
|
await waitForPaint();
|
document.body.classList.add('mes-qc-report-printing');
|
window.addEventListener('afterprint', removePrintMode, { once: true });
|
window.print();
|
}
|
|
defineExpose({ print });
|
</script>
|
|
<template>
|
<Teleport to="body">
|
<div
|
id="mesQcReportPrint"
|
class="mes-qc-report-print pointer-events-none fixed left-0 top-0 z-[-1] w-full bg-white text-[#303133] opacity-0"
|
>
|
<h2 class="m-0 mb-1 text-center text-[1.6em] font-bold leading-[1.2]">
|
{{ report.docName || '检验报告' }}
|
</h2>
|
<div class="mb-2 text-center text-sm">
|
{{ report.itemName || '' }}
|
<span v-if="report.itemSpecification">/{{ report.itemSpecification }}</span>
|
<span v-if="report.executeStandard"> 执行标准:{{ report.executeStandard }}</span>
|
</div>
|
|
<!-- 表头信息栏 -->
|
<div
|
class="mb-3 grid grid-cols-3 gap-x-6 gap-y-1 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]"
|
>
|
<div>检验单号:{{ report.docCode || '-' }}</div>
|
<div>物料编码:{{ report.itemCode || '-' }}</div>
|
<div>批次号:{{ report.batchNo || '-' }}</div>
|
<div>采样地点:{{ report.samplingLocation || '-' }}</div>
|
<div>证书编号:{{ report.certificateNo || '-' }}</div>
|
<div>批量:{{ report.batchQuantity ?? '-' }}</div>
|
<div>
|
采样/检验时间:{{ formatDateTime(report.samplingTime) || '-' }}
|
</div>
|
<div>打印时间:{{ printTime || '-' }}</div>
|
</div>
|
|
<!-- 检验项矩阵 -->
|
<table class="w-full border-collapse text-[13px] leading-[1.5]">
|
<thead>
|
<tr>
|
<th class="cell bg">检验项目</th>
|
<th class="cell w-16 bg">单位</th>
|
<th class="cell bg">方法/公式</th>
|
<th class="cell w-24 bg">标准值</th>
|
<th v-for="no in measureNos" :key="`h-${no}`" class="cell w-20 bg">
|
{{ measureHeader(no) }}
|
</th>
|
<th class="cell bg">备注</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="(row, i) in printRows" :key="row.indicatorId ?? i">
|
<template v-if="row.kind === 'group'">
|
<td class="cell group-name" colspan="3">{{ row.name }}</td>
|
<td class="cell group-formula" :colspan="measureNos.length + 2">
|
{{ row.method || '' }}
|
</td>
|
</template>
|
<template v-else>
|
<td class="cell record-name">{{ row.name }}</td>
|
<td class="cell text-center">{{ row.unit || '-' }}</td>
|
<td class="cell">{{ row.method || '-' }}</td>
|
<td class="cell text-center">{{ row.standard || '-' }}</td>
|
<td
|
v-for="no in measureNos"
|
:key="`v-${row.indicatorId}-${no}`"
|
class="cell text-center"
|
>
|
{{ row.values[no] || '' }}
|
</td>
|
<td class="cell">
|
<template v-if="measureNos.length === 1">
|
{{ row.remarks[measureNos[0]!] || '' }}
|
</template>
|
<template v-else>
|
<div v-for="no in measureNos" :key="`r-${row.indicatorId}-${no}`">
|
{{ no }}:{{ row.remarks[no] || '' }}
|
</div>
|
</template>
|
</td>
|
</template>
|
</tr>
|
<tr v-if="printRows.length === 0">
|
<td class="cell text-center" :colspan="6 + measureNos.length">
|
暂无检验项
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
|
<!-- 判定结论 + 质检专用章(判定结论带出检验结果,盖章区留空打印后人工盖章) -->
|
<div class="mt-3 grid grid-cols-2 text-sm leading-[1.6]">
|
<div class="cell min-h-[90px]">判定结论:{{ checkResultText }}</div>
|
<div class="cell min-h-[90px]">质检专用章:</div>
|
</div>
|
|
<!-- 备注 + 签字区 -->
|
<div class="mt-3 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]">
|
备注:{{ report.remark || '-' }}
|
</div>
|
<div
|
class="mt-4 grid grid-cols-3 gap-x-10 gap-y-4 border border-dashed border-[#dcdfe6] p-4 text-sm leading-[1.8]"
|
>
|
<div>
|
分析者:{{ report.inspectorName || '-' }}
|
</div>
|
<div>
|
复核人:{{ report.auditName || '-' }}
|
</div>
|
<div>
|
审核人:{{ report.approveName || '-' }}
|
</div>
|
<div>
|
签字:<span class="sig"></span>
|
</div>
|
<div>
|
签字:<span class="sig"></span>
|
</div>
|
<div>
|
签字:<span class="sig"></span>
|
</div>
|
</div>
|
|
<div class="mt-4 flex justify-between text-xs text-[#909399]">
|
<div>打印人员:{{ userStore.userInfo?.nickname || '-' }}</div>
|
<div>打印时间:{{ printTime || '-' }}</div>
|
</div>
|
</div>
|
</Teleport>
|
</template>
|
|
<style scoped>
|
@page {
|
margin: 8mm 10mm;
|
}
|
|
.mes-qc-report-print {
|
.cell {
|
padding: 4px 6px;
|
text-align: left;
|
border: 1px solid #dcdfe6;
|
}
|
|
.cell.bg {
|
font-weight: bold;
|
background: #f5f7fa;
|
text-align: center;
|
}
|
|
.cell.text-center {
|
text-align: center;
|
}
|
|
.group-name {
|
font-weight: bold;
|
background: #fafafa;
|
}
|
|
.group-formula {
|
font-style: italic;
|
color: #555;
|
background: #fafafa;
|
}
|
|
.record-name {
|
padding-left: 20px;
|
}
|
|
.sig {
|
display: inline-block;
|
width: 90px;
|
border-bottom: 1px solid #303133;
|
}
|
}
|
|
@media print {
|
:global(body.mes-qc-report-printing) {
|
padding: 0 !important;
|
margin: 0 !important;
|
-webkit-print-color-adjust: exact;
|
print-color-adjust: exact;
|
}
|
|
:global(body.mes-qc-report-printing *) {
|
visibility: hidden !important;
|
}
|
|
:global(body.mes-qc-report-printing .mes-qc-report-print),
|
:global(body.mes-qc-report-printing .mes-qc-report-print *) {
|
visibility: visible !important;
|
}
|
|
:global(body.mes-qc-report-printing .mes-qc-report-print) {
|
position: absolute;
|
top: 0;
|
left: 0;
|
z-index: auto;
|
box-sizing: border-box;
|
width: 100%;
|
padding: 0 !important;
|
margin: 0 !important;
|
pointer-events: auto;
|
opacity: 1;
|
}
|
}
|
</style>
|