워드 export 레이아웃 및 바코드/서명 렌더링 개선

This commit is contained in:
dohyeons 2025-12-23 13:56:15 +09:00
parent da195200a8
commit e1567d3f77
2 changed files with 39 additions and 12 deletions

View File

@ -30,6 +30,7 @@ import {
Header,
Footer,
HeadingLevel,
TableLayoutType,
} from "docx";
import { WatermarkConfig } from "../types/report";
import bwipjs from "bwip-js";
@ -592,8 +593,12 @@ export class ReportController {
// mm를 twip으로 변환
const mmToTwip = (mm: number) => convertMillimetersToTwip(mm);
// px를 twip으로 변환 (1px = 15twip at 96DPI)
const pxToTwip = (px: number) => Math.round(px * 15);
// 프론트엔드와 동일한 MM_TO_PX 상수 (캔버스에서 mm를 px로 변환할 때 사용하는 값)
const MM_TO_PX = 4;
// 1mm = 56.692913386 twip (docx 라이브러리 기준)
// px를 twip으로 변환: px -> mm -> twip
const pxToTwip = (px: number) => Math.round((px / MM_TO_PX) * 56.692913386);
// 쿼리 결과 맵
const queryResultsMap: Record<
@ -726,6 +731,9 @@ export class ReportController {
const base64Data =
component.imageBase64.split(",")[1] || component.imageBase64;
const imageBuffer = Buffer.from(base64Data, "base64");
// 서명 이미지 크기: 라벨 옆에 인라인으로 표시될 수 있도록 적절한 크기로 조정
const sigImageHeight = 30; // 고정 높이 (약 40px)
const sigImageWidth = Math.round((component.width / component.height) * sigImageHeight) || 80;
result.push(
new ParagraphRef({
children: [
@ -733,8 +741,8 @@ export class ReportController {
new ImageRunRef({
data: imageBuffer,
transformation: {
width: Math.round(component.width * 0.75),
height: Math.round(component.height * 0.75),
width: sigImageWidth,
height: sigImageHeight,
},
type: "png",
}),
@ -1443,7 +1451,11 @@ export class ReportController {
try {
const barcodeType = component.barcodeType || "CODE128";
const barcodeColor = (component.barcodeColor || "#000000").replace("#", "");
const barcodeBackground = (component.barcodeBackground || "#ffffff").replace("#", "");
// transparent는 bwip-js에서 지원하지 않으므로 흰색으로 변환
let barcodeBackground = (component.barcodeBackground || "#ffffff").replace("#", "");
if (barcodeBackground === "transparent" || barcodeBackground === "") {
barcodeBackground = "ffffff";
}
// 바코드 값 결정 (쿼리 바인딩 또는 고정값)
let barcodeValue = component.barcodeValue || "SAMPLE123";
@ -1739,6 +1751,7 @@ export class ReportController {
const rowTable = new Table({
rows: [new TableRow({ children: cells })],
width: { size: 100, type: WidthType.PERCENTAGE },
layout: TableLayoutType.FIXED, // 셀 너비 고정
borders: {
top: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
bottom: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
@ -1821,6 +1834,7 @@ export class ReportController {
const textTable = new Table({
rows: [new TableRow({ children: [textCell] })],
width: { size: pxToTwip(component.width), type: WidthType.DXA },
layout: TableLayoutType.FIXED, // 셀 너비 고정
indent: { size: indentLeft, type: WidthType.DXA },
borders: {
top: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
@ -1970,6 +1984,10 @@ export class ReportController {
component.imageBase64.split(",")[1] || component.imageBase64;
const imageBuffer = Buffer.from(base64Data, "base64");
// 서명 이미지 크기: 라벨 옆에 인라인으로 표시될 수 있도록 적절한 크기로 조정
const sigImageHeight = 30; // 고정 높이
const sigImageWidth = Math.round((component.width / component.height) * sigImageHeight) || 80;
const paragraph = new Paragraph({
spacing: { before: spacingBefore, after: 0 },
indent: { left: indentLeft },
@ -1978,8 +1996,8 @@ export class ReportController {
new ImageRun({
data: imageBuffer,
transformation: {
width: Math.round(component.width * 0.75),
height: Math.round(component.height * 0.75),
width: sigImageWidth,
height: sigImageHeight,
},
type: "png",
}),

View File

@ -1052,7 +1052,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
description: "WORD 파일을 생성하고 있습니다...",
});
// 이미지를 Base64로 변환하여 컴포넌트 데이터에 포함
// 이미지 및 바코드를 Base64로 변환하여 컴포넌트 데이터에 포함
const pagesWithBase64 = await Promise.all(
layoutConfig.pages.map(async (page) => {
const componentsWithBase64 = await Promise.all(
@ -1066,12 +1066,21 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
return component;
}
}
// 바코드/QR코드 컴포넌트는 이미지로 변환
if (component.type === "barcode") {
try {
const barcodeImage = await generateBarcodeImage(component);
return { ...component, barcodeImageBase64: barcodeImage };
} catch {
return component;
}
}
return component;
}),
);
})
);
return { ...page, components: componentsWithBase64 };
}),
);
})
);
// 쿼리 결과 수집
const queryResults: Record<string, { fields: string[]; rows: Record<string, unknown>[] }> = {};