From 3471290659516cf21db3211a9053daff5f283e03 Mon Sep 17 00:00:00 2001
From: zhuguifei <312353457@qq.com>
Date: 星期五, 20 三月 2026 15:50:18 +0800
Subject: [PATCH] feat: 基础数据仪器管理、判定依据、判定依据明细
---
ruoyi-plus-soybean/src/views/report/silk-storage-output/index.vue | 866 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 866 insertions(+), 0 deletions(-)
diff --git a/ruoyi-plus-soybean/src/views/report/silk-storage-output/index.vue b/ruoyi-plus-soybean/src/views/report/silk-storage-output/index.vue
new file mode 100755
index 0000000..a0585d7
--- /dev/null
+++ b/ruoyi-plus-soybean/src/views/report/silk-storage-output/index.vue
@@ -0,0 +1,866 @@
+<script setup lang="ts">
+import { computed, nextTick, onMounted, ref } from 'vue';
+import { jsonClone } from '@sa/utils';
+import dompdf from 'dompdf.js';
+import ExcelJS from 'exceljs';
+import '@/assets/fonts/SourceHanSansSC-Normal-Min-normal.js';
+import { fetchGetStoreSilkList } from '@/service/api/analy/store-silk';
+import { useNaiveForm } from '@/hooks/common/form';
+import { $t } from '@/locales';
+
+defineOptions({
+ name: 'BatchProductionReport'
+});
+
+const { formRef, validate, restoreValidation } = useNaiveForm();
+const zoom = ref(1);
+const exportLoading = ref(false);
+
+async function beginExportLoading() {
+ exportLoading.value = true;
+ await nextTick();
+ await new Promise<void>(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => resolve());
+ });
+ });
+}
+
+function formatNow() {
+ const now = new Date();
+ const y = now.getFullYear();
+ const m = String(now.getMonth() + 1).padStart(2, '0');
+ const d = String(now.getDate()).padStart(2, '0');
+ const hh = String(now.getHours()).padStart(2, '0');
+ const mm = String(now.getMinutes()).padStart(2, '0');
+ const ss = String(now.getSeconds()).padStart(2, '0');
+ return `${y}${m}${d}${hh}${mm}${ss}`;
+}
+
+// 鎼滅储鍙傛暟
+const searchParams = ref<Api.Analy.StoreSilkSearchParams>({
+ pageNum: 1,
+ pageSize: 1000, // 鎶ヨ〃閫氬父鏄剧ず杈冨鏁版嵁
+ materialname: null,
+ batchcode: null,
+ actualstarttime: null,
+ distimebegin: null,
+ distimeend: null,
+ siloid: null,
+ params: {
+ beginTime: undefined,
+ endTime: undefined
+ }
+});
+
+const defaultSearchParams = jsonClone(searchParams.value);
+
+// 鏃ユ湡鑼冨洿 (鐢ㄤ簬鎼滅储琛ㄥ崟)
+const searchDateRange = ref<[string, string] | null>(null);
+
+function onDateRangeUpdate(value: [string, string] | null) {
+ if (!searchParams.value.params) {
+ searchParams.value.params = {};
+ }
+ if (value?.[0] && value?.[1]) {
+ searchParams.value.params.beginTime = value[0];
+ searchParams.value.params.endTime = value[1];
+ } else {
+ searchParams.value.params.beginTime = undefined;
+ searchParams.value.params.endTime = undefined;
+ }
+}
+
+// 鏌滃瓙鍙烽�夐」
+const siloOptions = Array.from({ length: 12 }, (_, i) => ({
+ label: `${i + 1}鍙峰偍涓濇煖`,
+ value: String(i + 1)
+}));
+
+const selectedSilos = ref<string[]>([]);
+
+function handleSiloChange(val: string[]) {
+ selectedSilos.value = val;
+ searchParams.value.siloid = val.join(',');
+}
+
+// 琛ㄦ牸鏁版嵁
+const loading = ref(false);
+const tableRows = ref<any[]>([]);
+
+// 璁$畻鏄剧ず鏂囨湰鐢ㄧ殑鏃ユ湡
+const displayDateRange = ref<[string, string] | null>(null);
+
+// 鏃ユ湡鑼冨洿鏄剧ず鏂囨湰
+const dateRangeText = computed(() => {
+ if (displayDateRange.value && displayDateRange.value[0] && displayDateRange.value[1]) {
+ return `鏃ユ湡:${displayDateRange.value[0]}鈥�${displayDateRange.value[1]}`;
+ }
+ return '';
+});
+
+// 璁$畻浜ч噺杈呭姪鍑芥暟
+function calcRollerBox(val: unknown) {
+ if (val === null || val === undefined) return null;
+ const v = Number(val) / 50;
+ if (!Number.isFinite(v)) return null;
+ return v.toFixed(1);
+}
+
+function calcPackerBox(val: unknown) {
+ if (val === null || val === undefined) return null;
+ const v = Number(val) / 10 / 250;
+ if (!Number.isFinite(v)) return null;
+ return v.toFixed(1);
+}
+
+function setZoom(next: number) {
+ const v = Math.max(0.6, Math.min(1.2, Number(next)));
+ zoom.value = Number.isFinite(v) ? v : 1;
+}
+
+// 鏍煎紡鍖栨煖鍙� (鍙栨渶鍚庝竴涓嬩笅鍒掔嚎鍚庨潰鐨勬暟瀛�)
+function formatSiloId(val: string) {
+ if (!val) return '';
+ const parts = val.split('_');
+ return parts[parts.length - 1];
+}
+
+// 璁$畻鐑熶笣閲嶉噺 (jobinput - weight)
+function calculateSilkWeight(jobinput: any, weight: any) {
+ const res = Number(jobinput || 0) - Number(weight || 0);
+ return res.toFixed(2);
+}
+
+// 鏍煎紡鍖栫敓浜ф満缁� (鍙� rollerDetailList 閲岄潰鐨� equNo, 骞跺幓閲�)
+function formatProductionUnits(list: any[]) {
+ if (!Array.isArray(list)) return '';
+ const equNos = list.map(item => item.equNo).filter(Boolean);
+ const unique = Array.from(new Set(equNos));
+ unique.sort((a, b) => {
+ const aStr = String(a);
+ const bStr = String(b);
+ const aNum = Number(aStr);
+ const bNum = Number(bStr);
+ const aIsNum = Number.isFinite(aNum);
+ const bIsNum = Number.isFinite(bNum);
+ if (aIsNum && bIsNum) return aNum - bNum;
+ if (aIsNum) return -1;
+ if (bIsNum) return 1;
+ return aStr.localeCompare(bStr, 'zh-CN', { numeric: true });
+ });
+ return unique.join(',');
+}
+
+async function handleSearch() {
+ await validate();
+ loading.value = true;
+ try {
+ const { data, error } = await fetchGetStoreSilkList(searchParams.value);
+ if (!error && data) {
+ tableRows.value = data.rows || [];
+ // 鏇存柊鎶ヨ〃椤堕儴鐨勬樉绀烘棩鏈�
+ if (searchParams.value.params?.beginTime && searchParams.value.params?.endTime) {
+ displayDateRange.value = [searchParams.value.params.beginTime, searchParams.value.params.endTime];
+ } else {
+ displayDateRange.value = null;
+ }
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+function setDefaultDateRange() {
+ const now = new Date();
+ const daysAgo = new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000);
+ const beginTime = `${daysAgo.getFullYear()}-${String(daysAgo.getMonth() + 1).padStart(2, '0')}-${String(daysAgo.getDate()).padStart(2, '0')} 00:00:00`;
+ const endTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59`;
+
+ searchDateRange.value = [beginTime, endTime];
+ onDateRangeUpdate([beginTime, endTime]);
+}
+
+function toNumberOrNull(val: unknown) {
+ const num = Number(val);
+ return Number.isFinite(num) ? num : null;
+}
+
+async function handleExport() {
+ await beginExportLoading();
+
+ const workbook = new ExcelJS.Workbook();
+ const worksheet = workbook.addWorksheet('鎵规浜ч噺缁熻鎶ヨ〃');
+
+ worksheet.columns = [
+ { key: 'batchcode', width: 16 },
+ { key: 'materialname', width: 22 },
+ { key: 'distimebegin', width: 20 },
+ { key: 'distimeend', width: 20 },
+ { key: 'siloid', width: 8 },
+ { key: 'silkWeight', width: 12 },
+ { key: 'units', width: 14 },
+ { key: 'roller', width: 12 },
+ { key: 'packer', width: 12 },
+ { key: 'elevator', width: 12 }
+ ];
+
+ const totalColumns = 10;
+ const titleRow = worksheet.addRow(['鎵规浜ч噺缁熻鎶ヨ〃']);
+ worksheet.mergeCells(1, 1, 1, totalColumns);
+ titleRow.height = 26;
+ titleRow.getCell(1).font = { size: 16, bold: true, color: { argb: 'FF111827' } };
+ titleRow.getCell(1).alignment = { horizontal: 'center', vertical: 'middle' };
+
+ const dateText = dateRangeText.value || '';
+ const dateRow = worksheet.addRow([dateText]);
+ worksheet.mergeCells(2, 1, 2, totalColumns);
+ dateRow.height = 20;
+ dateRow.getCell(1).font = { size: 12, bold: true, color: { argb: 'FF0066CC' } };
+ dateRow.getCell(1).alignment = { horizontal: 'right', vertical: 'middle' };
+
+ const headerRow1 = worksheet.addRow([
+ '鎵规鍙�',
+ '鐗屽彿',
+ '鎵规寮�濮嬫椂闂�',
+ '鎵规缁撴潫鏃堕棿',
+ '鏌滃彿',
+ '鐑熶笣閲嶉噺',
+ '鐢熶骇鏈虹粍',
+ '鍗风儫鏈烘�讳骇',
+ '鍖呰鏈烘�讳骇',
+ '鎻愬崌鏈烘�讳骇'
+ ]);
+ const headerRow2 = worksheet.addRow(['', '', '', '', '', '', '', '閲忥紙绠憋級', '閲忥紙绠憋級', '閲忥紙绠憋級']);
+
+ for (let i = 1; i <= 7; i += 1) {
+ worksheet.mergeCells(3, i, 4, i);
+ }
+
+ const headerFill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF337AB7' } } as const;
+ const headerFont = { bold: true, color: { argb: 'FFFFFFFF' }, size: 12 } as const;
+ const headerBorder = {
+ top: { style: 'thin', color: { argb: 'FF4A90D9' } },
+ left: { style: 'thin', color: { argb: 'FF4A90D9' } },
+ bottom: { style: 'thin', color: { argb: 'FF4A90D9' } },
+ right: { style: 'thin', color: { argb: 'FF4A90D9' } }
+ } as const;
+
+ [headerRow1, headerRow2].forEach(row => {
+ row.height = 22;
+ row.eachCell({ includeEmpty: true }, cell => {
+ cell.fill = headerFill;
+ cell.font = headerFont;
+ cell.border = headerBorder;
+ cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
+ });
+ });
+
+ const dataBorder = {
+ top: { style: 'thin', color: { argb: 'FFCCCCCC' } },
+ left: { style: 'thin', color: { argb: 'FFCCCCCC' } },
+ bottom: { style: 'thin', color: { argb: 'FFCCCCCC' } },
+ right: { style: 'thin', color: { argb: 'FFCCCCCC' } }
+ } as const;
+
+ tableRows.value.forEach((row, index) => {
+ const dataRow = worksheet.addRow([
+ row.batchcode ?? '',
+ row.materialname ?? '',
+ row.distimebegin ?? '',
+ row.distimeend ?? '',
+ formatSiloId(row.siloid),
+ toNumberOrNull(calculateSilkWeight(row.jobinput, row.weight)),
+ formatProductionUnits(row.rollerDetailList),
+ toNumberOrNull(calcRollerBox(row.rollerOutput)),
+ toNumberOrNull(calcPackerBox(row.packerOutput)),
+ toNumberOrNull(calcPackerBox(row.packerOutput))
+ ]);
+
+ dataRow.height = 20;
+
+ dataRow.eachCell({ includeEmpty: true }, (cell, colNumber) => {
+ cell.border = dataBorder;
+ cell.alignment = { horizontal: 'center', vertical: 'middle' };
+
+ if (colNumber === 2) {
+ cell.alignment = { horizontal: 'left', vertical: 'middle' };
+ }
+
+ if (colNumber === 6) {
+ cell.numFmt = '0.00';
+ }
+
+ if (colNumber >= 8 && colNumber <= 10) {
+ cell.numFmt = '0.0';
+ }
+
+ if (index % 2 === 1) {
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF9F9F9' } };
+ }
+ });
+ });
+
+ worksheet.views = [{ state: 'frozen', ySplit: 4 }];
+
+ try {
+ const buffer = await workbook.xlsx.writeBuffer();
+ const blob = new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `鍌ㄤ笣鏌滃嚭鏂欎骇閲忔姤琛╛${formatNow()}.xlsx`;
+ link.click();
+ URL.revokeObjectURL(url);
+ } finally {
+ exportLoading.value = false;
+ }
+}
+
+async function handleExportCsv() {
+ await beginExportLoading();
+
+ const headers = [
+ '鎵规鍙�',
+ '鐗屽彿',
+ '鎵规寮�濮嬫椂闂�',
+ '鎵规缁撴潫鏃堕棿',
+ '鏌滃彿',
+ '鐑熶笣閲嶉噺',
+ '鐢熶骇鏈虹粍',
+ '鍗风儫鏈烘�讳骇閲忥紙绠憋級',
+ '鍖呰鏈烘�讳骇閲忥紙绠憋級',
+ '鎻愬崌鏈烘�讳骇閲忥紙绠憋級'
+ ];
+
+ const rows = tableRows.value.map(row => [
+ row.batchcode ?? '',
+ row.materialname ?? '',
+ row.distimebegin ?? '',
+ row.distimeend ?? '',
+ formatSiloId(row.siloid),
+ calculateSilkWeight(row.jobinput, row.weight),
+ formatProductionUnits(row.rollerDetailList),
+ calcRollerBox(row.rollerOutput),
+ calcPackerBox(row.packerOutput),
+ calcPackerBox(row.packerOutput)
+ ]);
+
+ const escapeCsv = (value: unknown) => {
+ const str = value === null || value === undefined ? '' : String(value);
+ if (/["\n,]/.test(str)) {
+ return `"${str.replace(/"/g, '""')}"`;
+ }
+ return str;
+ };
+
+ const csvContent = [headers, ...rows].map(row => row.map(escapeCsv).join(',')).join('\n');
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' });
+ try {
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `鍌ㄤ笣鏌滃嚭鏂欎骇閲忔姤琛╛${formatNow()}.csv`;
+ link.click();
+ URL.revokeObjectURL(url);
+ } finally {
+ exportLoading.value = false;
+ }
+}
+
+function printReportSheet() {
+ const sheet = document.querySelector('.report-sheet') as HTMLElement | null;
+ if (!sheet) return;
+
+ const styles = Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
+ .map(node => node.outerHTML)
+ .join('\n');
+
+ const printWindow = window.open('', '_blank');
+ if (!printWindow) return;
+
+ printWindow.document.open();
+ printWindow.document.write(`
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8" />
+ <title>鎵规浜ч噺缁熻鎶ヨ〃</title>
+ ${styles}
+ <style>
+ @page {
+ size: A4 landscape;
+ margin: 10mm;
+ }
+ body {
+ margin: 0;
+ padding: 16px;
+ background: #fff;
+ }
+ .print-container {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ }
+ .report-sheet {
+ transform: none !important;
+ box-shadow: none !important;
+ width: 100% !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ }
+ .report-table-scroll {
+ overflow: visible !important;
+ }
+ .report-table {
+ width: 100% !important;
+ min-width: 0 !important;
+ table-layout: fixed;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="print-container">${sheet.outerHTML}</div>
+ </body>
+ </html>
+ `);
+ printWindow.document.close();
+ printWindow.focus();
+ printWindow.addEventListener('load', () => {
+ setTimeout(() => {
+ printWindow.print();
+ }, 100);
+ });
+ printWindow.onafterprint = () => {
+ printWindow.close();
+ };
+}
+
+function handlePrint() {
+ printReportSheet();
+}
+
+async function handleExportPdf() {
+ await beginExportLoading();
+
+ setTimeout(async () => {
+ const sheet = document.querySelector('#report-capture') as HTMLElement | null;
+ if (!sheet) {
+ exportLoading.value = false;
+ return;
+ }
+
+ const sheetWidth = sheet.scrollWidth || sheet.getBoundingClientRect().width || 1200;
+ const scale = Math.min(1, 794 / sheetWidth);
+
+ const clone = sheet.cloneNode(true) as HTMLElement;
+ clone.style.transform = `scale(${scale})`;
+ clone.style.transformOrigin = 'top left';
+ clone.style.width = `${sheetWidth}px`;
+ clone.style.minWidth = '0';
+ clone.style.margin = '0';
+ clone.style.boxShadow = 'none';
+ clone.style.position = 'fixed';
+ clone.style.left = '-10000px';
+ clone.style.top = '0';
+ clone.style.background = '#ffffff';
+ clone.style.fontFamily = 'SourceHanSansSC-Normal-Min, sans-serif';
+
+ document.body.appendChild(clone);
+
+ try {
+ const blob = (await dompdf(clone, {
+ pagination: true,
+ format: 'a4',
+ useCORS: true,
+ backgroundColor: '#ffffff',
+ fontConfig: {
+ fontFamily: 'SourceHanSansSC-Normal-Min',
+ fontBase64: (window as any).fontBase64,
+ fontStyle: 'normal',
+ fontWeight: 400
+ }
+ })) as Blob;
+
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `鍌ㄤ笣鏌滃嚭鏂欎骇閲忔姤琛╛${formatNow()}.pdf`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ document.body.removeChild(clone);
+ exportLoading.value = false;
+ }
+ }, 0);
+}
+
+async function handleReset() {
+ await restoreValidation();
+ searchParams.value = jsonClone(defaultSearchParams);
+ selectedSilos.value = [];
+ setDefaultDateRange();
+ handleSearch();
+}
+
+onMounted(() => {
+ setDefaultDateRange();
+ handleSearch();
+});
+</script>
+
+<template>
+ <div class="h-full min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
+ <!-- 鎼滅储鍖哄煙 -->
+ <NCard :bordered="false" size="small" class="card-wrapper">
+ <NCollapse :default-expanded-names="['report-search']">
+ <NCollapseItem :title="$t('common.search')" name="report-search">
+ <NForm ref="formRef" :model="searchParams" label-placement="left" :label-width="80">
+ <NGrid responsive="screen" item-responsive>
+ <NFormItemGi span="24 s:12 m:8" label="鐗屽彿" label-width="auto" path="materialname" class="pr-24px">
+ <NInput v-model:value="searchParams.materialname" clearable placeholder="璇疯緭鍏ョ墝鍙�" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎵规鍙�" label-width="auto" path="batchcode" class="pr-24px">
+ <NInput v-model:value="searchParams.batchcode" clearable placeholder="璇疯緭鍏ユ壒娆″彿" />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鎶曟枡鏃ユ湡" label-width="auto" path="actualstarttime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="searchParams.actualstarttime"
+ type="date"
+ value-format="yyyy-MM-dd"
+ clearable
+ class="w-full"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鍑烘枡鏃堕棿" label-width="auto" path="params.beginTime" class="pr-24px">
+ <NDatePicker
+ v-model:formatted-value="searchDateRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ clearable
+ @update:formatted-value="onDateRangeUpdate"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8" label="鏌滃瓙鍙�" label-width="auto" path="siloid" class="pr-24px">
+ <NSelect
+ v-model:value="selectedSilos"
+ multiple
+ clearable
+ :options="siloOptions"
+ placeholder="璇烽�夋嫨鏌滃瓙鍙�"
+ @update:value="handleSiloChange"
+ />
+ </NFormItemGi>
+ <NFormItemGi span="24 s:12 m:8">
+ <NSpace>
+ <NButton type="primary" :loading="loading" @click="handleSearch">
+ <template #icon>
+ <icon-ic-round-search class="text-icon" />
+ </template>
+ 鏌ヨ
+ </NButton>
+ <NButton @click="handleReset">
+ <template #icon>
+ <icon-ic-round-refresh class="text-icon" />
+ </template>
+ 閲嶇疆
+ </NButton>
+ </NSpace>
+ </NFormItemGi>
+ </NGrid>
+ </NForm>
+ </NCollapseItem>
+ </NCollapse>
+ </NCard>
+
+ <!-- 鎶ヨ〃鏄剧ず鍖哄煙 -->
+ <NCard
+ :bordered="false"
+ size="small"
+ class="card-wrapper flex flex-col sm:flex-1-hidden"
+ content-style="padding: 0; flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden;"
+ >
+ <div class="report-root">
+ <div class="report-toolbar">
+ <div class="flex-y-center gap-8px">
+ <NButton size="small" type="warning" ghost @click="handleExport">
+ <template #icon>
+ <icon-mdi-export class="text-icon" />
+ </template>
+ 瀵煎嚭 Excel
+ </NButton>
+ <NButton size="small" secondary @click="handleExportPdf">
+ <template #icon>
+ <icon-mdi-file-pdf-box class="text-icon" />
+ </template>
+ 瀵煎嚭 PDF
+ </NButton>
+ <NButton size="small" secondary @click="handleExportCsv">
+ <template #icon>
+ <icon-mdi-file-delimited class="text-icon" />
+ </template>
+ 瀵煎嚭 CSV
+ </NButton>
+ <NButton size="small" type="primary" @click="handlePrint">
+ <template #icon>
+ <icon-mdi-printer class="text-icon" />
+ </template>
+ 鎵撳嵃
+ </NButton>
+ </div>
+ <div class="flex-y-center gap-8px">
+ <span class="report-toolbar__label">缂╂斁</span>
+ <NButton size="small" @click="setZoom(zoom - 0.1)">-</NButton>
+ <NInputNumber v-model:value="zoom" size="small" :min="0.6" :max="1.2" :step="0.1" class="w-90px" />
+ <NButton size="small" @click="setZoom(zoom + 0.1)">+</NButton>
+ </div>
+ </div>
+
+ <div class="report-stage">
+ <NSpin :show="exportLoading" size="large" description="瀵煎嚭涓�..." :delay="0">
+ <div id="report-capture" class="report-sheet" :style="{ transform: `scale(${zoom})` }">
+ <!-- 鎶ヨ〃鏍囬 -->
+ <div class="report-title">鎵规浜ч噺缁熻鎶ヨ〃</div>
+
+ <!-- 鏃ユ湡鑼冨洿鏄剧ず -->
+ <div class="report-date-range">
+ <span class="report-date-range__text">{{ dateRangeText }}</span>
+ </div>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <div class="report-table-scroll">
+ <table id="report-table" class="report-table">
+ <thead>
+ <tr>
+ <th rowspan="2" class="col-batch-no">鎵规鍙�</th>
+ <th rowspan="2" class="col-brand">鐗屽彿</th>
+ <th rowspan="2" class="col-time">鎵规寮�濮嬫椂闂�</th>
+ <th rowspan="2" class="col-time">鎵规缁撴潫鏃堕棿</th>
+ <th rowspan="2" class="col-cabinet">鏌滃彿</th>
+ <th rowspan="2" class="col-weight">鐑熶笣閲嶉噺</th>
+ <th rowspan="2" class="col-unit">鐢熶骇鏈虹粍</th>
+ <th class="col-output">鍗风儫鏈烘�讳骇</th>
+ <th class="col-output">鍖呰鏈烘�讳骇</th>
+ <th class="col-output">鎻愬崌鏈烘�讳骇</th>
+ </tr>
+ <tr>
+ <th class="col-output">閲忥紙绠憋級</th>
+ <th class="col-output">閲忥紙绠憋級</th>
+ <th class="col-output">閲忥紙绠憋級</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="row in tableRows" :key="row.id">
+ <td>{{ row.batchcode }}</td>
+ <td class="report-left">{{ row.materialname }}</td>
+ <td>{{ row.distimebegin }}</td>
+ <td>{{ row.distimeend }}</td>
+ <td>{{ formatSiloId(row.siloid) }}</td>
+ <td>{{ calculateSilkWeight(row.jobinput, row.weight) }}</td>
+ <td>{{ formatProductionUnits(row.rollerDetailList) }}</td>
+ <td>{{ calcRollerBox(row.rollerOutput) }}</td>
+ <td>{{ calcPackerBox(row.packerOutput) }}</td>
+ <td>{{ calcPackerBox(row.packerOutput) }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </NSpin>
+ </div>
+ </div>
+ </NCard>
+ </div>
+</template>
+
+<style scoped>
+/* ==================== 鎶ヨ〃瀹瑰櫒 ==================== */
+.report-root {
+ background: #f3f4f6;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+}
+
+.report-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 12px;
+ background: #fff;
+ border-bottom: 1px solid #e5e7eb;
+ flex: none;
+}
+
+.report-toolbar__label {
+ font-size: 12px;
+ color: #6b7280;
+}
+
+.report-stage {
+ padding: 12px;
+ overflow: auto;
+ flex: 1;
+ min-height: 0;
+}
+
+.report-sheet {
+ transform-origin: top center;
+ background: #fff;
+ width: 1200px;
+ margin: 0 auto;
+ padding: 18px 18px 16px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
+}
+
+/* ==================== 鎶ヨ〃鏍囬 ==================== */
+.report-title {
+ text-align: center;
+ font-size: 18px;
+ font-weight: 600;
+ padding: 10px 0 4px;
+ color: #111827;
+}
+
+/* ==================== 鏃ユ湡鑼冨洿 ==================== */
+.report-date-range {
+ display: flex;
+ justify-content: flex-end;
+ padding: 4px 0 8px;
+}
+
+.report-date-range__text {
+ font-size: 13px;
+ font-weight: 600;
+ color: #0066cc;
+}
+
+/* ==================== 琛ㄦ牸 ==================== */
+.report-table-scroll {
+ width: 100%;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+.report-table {
+ width: 100%;
+ min-width: 1100px;
+ border-collapse: collapse;
+ table-layout: fixed;
+ font-size: 12px;
+ color: #111827;
+}
+
+/* 鍒楀瀹氫箟 */
+.col-batch-no {
+ width: 120px;
+}
+
+.col-brand {
+ width: 170px;
+}
+
+.col-time {
+ width: 150px;
+}
+
+.col-cabinet {
+ width: 50px;
+}
+
+.col-weight {
+ width: 80px;
+}
+
+.col-unit {
+ width: 95px;
+}
+
+.col-output {
+ width: 90px;
+}
+
+/* 琛ㄥご鏍峰紡 - 钃濊壊鑳屾櫙鐧借壊鏂囧瓧 */
+.report-table th {
+ border: 1px solid #4a90d9;
+ padding: 6px 6px;
+ text-align: center;
+ vertical-align: middle;
+ line-height: 1.3;
+ background: #337ab7;
+ color: #fff;
+ font-weight: 600;
+ font-size: 12px;
+}
+
+/* 鏁版嵁鍗曞厓鏍� */
+.report-table td {
+ border: 1px solid #ccc;
+ padding: 6px 6px;
+ text-align: center;
+ vertical-align: middle;
+ line-height: 1.3;
+}
+
+/* 闅旇鍙樿壊 */
+.report-table tbody tr:nth-child(even) {
+ background: #f9f9f9;
+}
+
+.report-table tbody tr:hover {
+ background: #e8f0fe;
+}
+
+/* 宸﹀榻� */
+.report-left {
+ text-align: left;
+}
+
+/* ==================== 鎵撳嵃鏍峰紡 ==================== */
+@media print {
+ @page {
+ size: A4 landscape;
+ margin: 10mm;
+ }
+
+ .report-root {
+ background: #fff;
+ padding: 0;
+ }
+
+ .report-toolbar {
+ display: none;
+ }
+
+ .report-stage {
+ padding: 0;
+ overflow: visible;
+ }
+
+ .report-sheet {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ box-shadow: none;
+ transform: none !important;
+ }
+
+ .report-table-scroll {
+ overflow: visible;
+ }
+
+ .report-table {
+ width: 100%;
+ min-width: 0;
+ table-layout: fixed;
+ }
+}
+</style>
--
Gitblit v1.9.3