247 lines
7.0 KiB
TypeScript
247 lines
7.0 KiB
TypeScript
import QRCode from 'qrcode';
|
|
|
|
export interface QRPrintItem {
|
|
type: 'asset' | 'location';
|
|
code: string;
|
|
title: string; // e.g. "[ HM IT ASSET ]" or "[ HM LOCATION ]"
|
|
subtitle?: string; // e.g. "가을-PC(i5-12400F)" or "기술개발센터 서버실"
|
|
dept?: string; // e.g. "전산" or "B-03 랙"
|
|
user?: string; // e.g. "박노석"
|
|
date?: string; // e.g. "2024-08-05"
|
|
}
|
|
|
|
/**
|
|
* QR 라벨 인쇄 유틸리티 클래스
|
|
*/
|
|
export class QRPrinter {
|
|
private static styleId = 'qr-print-style';
|
|
private static containerId = 'label-print-container';
|
|
|
|
/**
|
|
* 인쇄 전용 CSS 스타일 주입
|
|
*/
|
|
private static injectStyles() {
|
|
if (document.getElementById(this.styleId)) return;
|
|
|
|
const style = document.createElement('style');
|
|
style.id = this.styleId;
|
|
style.innerHTML = `
|
|
/* 화면에서는 인쇄 컨테이너 숨김 */
|
|
#${this.containerId} {
|
|
display: none;
|
|
}
|
|
|
|
@media print {
|
|
/* 화면 내 모든 요소 숨김 */
|
|
body > *:not(#${this.containerId}) {
|
|
display: none !important;
|
|
}
|
|
|
|
/* 인쇄 전용 컨테이너 표시 */
|
|
#${this.containerId} {
|
|
display: block !important;
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 50mm;
|
|
height: 30mm;
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
background: #fff;
|
|
}
|
|
|
|
/* 페이지 규격 설정 */
|
|
@page {
|
|
size: 50mm 30mm;
|
|
margin: 0;
|
|
}
|
|
|
|
/* 개별 라벨 스타일 */
|
|
.print-label-item {
|
|
display: flex !important;
|
|
flex-direction: row;
|
|
width: 50mm;
|
|
height: 30mm;
|
|
box-sizing: border-box;
|
|
padding: 2.5mm;
|
|
page-break-after: always;
|
|
font-family: 'Pretendard Variable', sans-serif;
|
|
color: #000;
|
|
background: #fff;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.print-label-item:last-child {
|
|
page-break-after: avoid;
|
|
}
|
|
|
|
/* 왼쪽 명세 영역 */
|
|
.label-details {
|
|
width: 30mm;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
font-size: 6.5pt;
|
|
line-height: 1.25;
|
|
text-align: left;
|
|
padding-right: 1mm;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.label-header {
|
|
font-size: 7.5pt;
|
|
font-weight: 800;
|
|
border-bottom: 0.5px solid #000;
|
|
padding-bottom: 0.5mm;
|
|
margin-bottom: 0.5mm;
|
|
color: #000;
|
|
}
|
|
|
|
.label-row {
|
|
display: flex;
|
|
margin-bottom: 0.2mm;
|
|
}
|
|
|
|
.label-row .row-title {
|
|
font-weight: 700;
|
|
width: 9.5mm;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.label-row .row-value {
|
|
flex: 1;
|
|
}
|
|
|
|
/* 오른쪽 QR 영역 */
|
|
.label-qr-wrapper {
|
|
width: 15mm;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.label-qr-canvas {
|
|
width: 14mm !important;
|
|
height: 14mm !important;
|
|
display: block;
|
|
}
|
|
|
|
.label-qr-code-text {
|
|
font-size: 5.5pt;
|
|
font-weight: 700;
|
|
margin-top: 1mm;
|
|
text-align: center;
|
|
width: 15mm;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
/**
|
|
* 라벨 인쇄 실행
|
|
*/
|
|
public static async print(items: QRPrintItem[]): Promise<void> {
|
|
if (items.length === 0) return;
|
|
|
|
this.injectStyles();
|
|
|
|
// 기존 컨테이너 제거
|
|
const oldContainer = document.getElementById(this.containerId);
|
|
if (oldContainer) oldContainer.remove();
|
|
|
|
// 새 인쇄 컨테이너 생성
|
|
const container = document.createElement('div');
|
|
container.id = this.containerId;
|
|
document.body.appendChild(container);
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
const labelDiv = document.createElement('div');
|
|
labelDiv.className = 'print-label-item';
|
|
|
|
// QR 접속 URL 정의
|
|
const paramName = item.type === 'asset' ? 'asset' : 'loc';
|
|
const qrUrl = `${window.location.origin}/mobile?${paramName}=${encodeURIComponent(item.code)}`;
|
|
|
|
// HTML 구성
|
|
if (item.type === 'asset') {
|
|
labelDiv.innerHTML = `
|
|
<div class="label-details">
|
|
<div class="label-header">${item.title}</div>
|
|
<div class="label-row">
|
|
<span class="row-title">자산번호 :</span>
|
|
<span class="row-value">${item.code}</span>
|
|
</div>
|
|
<div class="label-row">
|
|
<span class="row-title">자 산 명 :</span>
|
|
<span class="row-value">${item.subtitle || '-'}</span>
|
|
</div>
|
|
<div class="label-row">
|
|
<span class="row-title">부 서 :</span>
|
|
<span class="row-value">${item.dept || '-'}</span>
|
|
</div>
|
|
<div class="label-row">
|
|
<span class="row-title">사 용 자 :</span>
|
|
<span class="row-value">${item.user || '-'}</span>
|
|
</div>
|
|
</div>
|
|
<div class="label-qr-wrapper">
|
|
<canvas class="label-qr-canvas" id="qr-canvas-${i}"></canvas>
|
|
<div class="label-qr-code-text">${item.code}</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
// Location 라벨 레이아웃
|
|
labelDiv.innerHTML = `
|
|
<div class="label-details" style="justify-content: center; gap: 1mm;">
|
|
<div class="label-header">${item.title}</div>
|
|
<div class="label-row">
|
|
<span class="row-title">위치코드 :</span>
|
|
<span class="row-value" style="font-weight: 700;">${item.code}</span>
|
|
</div>
|
|
<div class="label-row">
|
|
<span class="row-title">구 역 :</span>
|
|
<span class="row-value">${item.subtitle || '-'}</span>
|
|
</div>
|
|
<div class="label-row">
|
|
<span class="row-title">상세위치 :</span>
|
|
<span class="row-value">${item.dept || '-'}</span>
|
|
</div>
|
|
</div>
|
|
<div class="label-qr-wrapper">
|
|
<canvas class="label-qr-canvas" id="qr-canvas-${i}"></canvas>
|
|
<div class="label-qr-code-text">${item.code}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
container.appendChild(labelDiv);
|
|
|
|
// QR 코드 렌더링
|
|
const canvas = document.getElementById(`qr-canvas-${i}`) as HTMLCanvasElement;
|
|
if (canvas) {
|
|
await QRCode.toCanvas(canvas, qrUrl, {
|
|
margin: 0,
|
|
width: 100,
|
|
errorCorrectionLevel: 'H'
|
|
});
|
|
}
|
|
}
|
|
|
|
// 약간의 딜레이를 주어 QR 코드가 완전히 렌더링되도록 함
|
|
setTimeout(() => {
|
|
window.print();
|
|
// 인쇄 완료 후 컨테이너 정리
|
|
window.onafterprint = () => {
|
|
container.remove();
|
|
};
|
|
}, 250);
|
|
}
|
|
}
|