리포트 디자이너에 바코드/QR코드 컴포넌트 추가
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
PageOrientation,
|
||||
convertMillimetersToTwip,
|
||||
} from "docx";
|
||||
import bwipjs from "bwip-js";
|
||||
|
||||
export class ReportController {
|
||||
/**
|
||||
@@ -1326,6 +1327,43 @@ export class ReportController {
|
||||
);
|
||||
}
|
||||
|
||||
// Barcode 컴포넌트 (바코드 이미지가 미리 생성되어 전달된 경우)
|
||||
else if (component.type === "barcode" && component.barcodeImageBase64) {
|
||||
try {
|
||||
const base64Data =
|
||||
component.barcodeImageBase64.split(",")[1] || component.barcodeImageBase64;
|
||||
const imageBuffer = Buffer.from(base64Data, "base64");
|
||||
result.push(
|
||||
new ParagraphRef({
|
||||
children: [
|
||||
new ImageRunRef({
|
||||
data: imageBuffer,
|
||||
transformation: {
|
||||
width: Math.round(component.width * 0.75),
|
||||
height: Math.round(component.height * 0.75),
|
||||
},
|
||||
type: "png",
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
// 바코드 이미지 생성 실패 시 텍스트로 대체
|
||||
const barcodeValue = component.barcodeValue || "BARCODE";
|
||||
result.push(
|
||||
new ParagraphRef({
|
||||
children: [
|
||||
new TextRunRef({
|
||||
text: `[${barcodeValue}]`,
|
||||
size: pxToHalfPtFn(12),
|
||||
font: "맑은 고딕",
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Divider - 테이블 셀로 감싸서 정확한 너비 적용
|
||||
else if (
|
||||
component.type === "divider" &&
|
||||
@@ -1354,6 +1392,82 @@ export class ReportController {
|
||||
return result;
|
||||
};
|
||||
|
||||
// 바코드 이미지 생성 헬퍼 함수
|
||||
const generateBarcodeImage = async (
|
||||
component: any,
|
||||
queryResultsMapRef: Record<string, { fields: string[]; rows: Record<string, unknown>[] }>
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const barcodeType = component.barcodeType || "CODE128";
|
||||
const barcodeColor = (component.barcodeColor || "#000000").replace("#", "");
|
||||
const barcodeBackground = (component.barcodeBackground || "#ffffff").replace("#", "");
|
||||
|
||||
// 바코드 값 결정 (쿼리 바인딩 또는 고정값)
|
||||
let barcodeValue = component.barcodeValue || "SAMPLE123";
|
||||
if (component.barcodeFieldName && component.queryId && queryResultsMapRef[component.queryId]) {
|
||||
const qResult = queryResultsMapRef[component.queryId];
|
||||
if (qResult.rows && qResult.rows.length > 0) {
|
||||
const row = qResult.rows[0];
|
||||
const val = row[component.barcodeFieldName];
|
||||
if (val !== null && val !== undefined) {
|
||||
barcodeValue = String(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bwip-js 바코드 타입 매핑
|
||||
const bcidMap: Record<string, string> = {
|
||||
"CODE128": "code128",
|
||||
"CODE39": "code39",
|
||||
"EAN13": "ean13",
|
||||
"EAN8": "ean8",
|
||||
"UPC": "upca",
|
||||
"QR": "qrcode",
|
||||
};
|
||||
|
||||
const bcid = bcidMap[barcodeType] || "code128";
|
||||
const isQR = barcodeType === "QR";
|
||||
|
||||
// 바코드 옵션 설정
|
||||
const options: any = {
|
||||
bcid: bcid,
|
||||
text: barcodeValue,
|
||||
scale: 3,
|
||||
includetext: !isQR && component.showBarcodeText !== false,
|
||||
textxalign: "center",
|
||||
barcolor: barcodeColor,
|
||||
backgroundcolor: barcodeBackground,
|
||||
};
|
||||
|
||||
// QR 코드 옵션
|
||||
if (isQR) {
|
||||
options.eclevel = component.qrErrorCorrectionLevel || "M";
|
||||
}
|
||||
|
||||
// 바코드 이미지 생성
|
||||
const png = await bwipjs.toBuffer(options);
|
||||
const base64 = png.toString("base64");
|
||||
return `data:image/png;base64,${base64}`;
|
||||
} catch (error) {
|
||||
console.error("바코드 생성 오류:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 모든 페이지의 바코드 컴포넌트에 대해 이미지 생성
|
||||
for (const page of layoutConfig.pages) {
|
||||
if (page.components) {
|
||||
for (const component of page.components) {
|
||||
if (component.type === "barcode") {
|
||||
const barcodeImage = await generateBarcodeImage(component, queryResultsMap);
|
||||
if (barcodeImage) {
|
||||
component.barcodeImageBase64 = barcodeImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 섹션 생성 (페이지별)
|
||||
const sortedPages = layoutConfig.pages.sort(
|
||||
(a: any, b: any) => a.page_order - b.page_order
|
||||
@@ -2624,6 +2738,77 @@ export class ReportController {
|
||||
lastBottomY = adjustedY + component.height;
|
||||
}
|
||||
|
||||
// Barcode 컴포넌트
|
||||
else if (component.type === "barcode") {
|
||||
if (component.barcodeImageBase64) {
|
||||
try {
|
||||
const base64Data =
|
||||
component.barcodeImageBase64.split(",")[1] || component.barcodeImageBase64;
|
||||
const imageBuffer = Buffer.from(base64Data, "base64");
|
||||
|
||||
// spacing을 위한 빈 paragraph
|
||||
if (spacingBefore > 0) {
|
||||
children.push(
|
||||
new Paragraph({
|
||||
spacing: { before: spacingBefore, after: 0 },
|
||||
children: [],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
children.push(
|
||||
new Paragraph({
|
||||
indent: { left: indentLeft },
|
||||
children: [
|
||||
new ImageRun({
|
||||
data: imageBuffer,
|
||||
transformation: {
|
||||
width: Math.round(component.width * 0.75),
|
||||
height: Math.round(component.height * 0.75),
|
||||
},
|
||||
type: "png",
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
} catch (imgError) {
|
||||
console.error("바코드 이미지 오류:", imgError);
|
||||
// 바코드 이미지 생성 실패 시 텍스트로 대체
|
||||
const barcodeValue = component.barcodeValue || "BARCODE";
|
||||
children.push(
|
||||
new Paragraph({
|
||||
spacing: { before: spacingBefore, after: 0 },
|
||||
indent: { left: indentLeft },
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `[${barcodeValue}]`,
|
||||
size: pxToHalfPt(12),
|
||||
font: "맑은 고딕",
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 바코드 이미지가 없는 경우 텍스트로 대체
|
||||
const barcodeValue = component.barcodeValue || "BARCODE";
|
||||
children.push(
|
||||
new Paragraph({
|
||||
spacing: { before: spacingBefore, after: 0 },
|
||||
indent: { left: indentLeft },
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `[${barcodeValue}]`,
|
||||
size: pxToHalfPt(12),
|
||||
font: "맑은 고딕",
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
lastBottomY = adjustedY + component.height;
|
||||
}
|
||||
|
||||
// Table 컴포넌트
|
||||
else if (component.type === "table" && component.queryId) {
|
||||
const queryResult = queryResultsMap[component.queryId];
|
||||
|
||||
Reference in New Issue
Block a user