일반견적서 템플릿 변경, 견적서 pdf 변환하여 메일 첨부
This commit is contained in:
@@ -42,6 +42,17 @@
|
||||
<property name="suffix" value=".jsp"/>
|
||||
<property name="order" value="1"/>
|
||||
</bean>
|
||||
|
||||
<!-- MultipartResolver 설정 (파일 업로드) -->
|
||||
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
|
||||
<!-- 최대 업로드 크기: 50MB -->
|
||||
<property name="maxUploadSize" value="52428800"/>
|
||||
<!-- 메모리에서 처리할 최대 크기: 1MB -->
|
||||
<property name="maxInMemorySize" value="1048576"/>
|
||||
<!-- 기본 인코딩 -->
|
||||
<property name="defaultEncoding" value="UTF-8"/>
|
||||
</bean>
|
||||
|
||||
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
|
||||
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
|
||||
<property name="messageConverters">
|
||||
|
||||
@@ -668,21 +668,169 @@ function fn_sendEstimateMail(contractObjId){
|
||||
return;
|
||||
}
|
||||
|
||||
// 1단계: 견적서 템플릿 정보 조회
|
||||
Swal.fire({
|
||||
title: '메일 발송 중...',
|
||||
title: '견적서 조회 중...',
|
||||
text: '잠시만 기다려주세요.',
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
onOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: "/contractMgmt/sendEstimateMail.do",
|
||||
url: "/contractMgmt/getEstimateTemplateList.do",
|
||||
type: "POST",
|
||||
data: { objId: contractObjId },
|
||||
dataType: "json",
|
||||
success: function(data){
|
||||
if(data.result === "success" && data.list && data.list.length > 0){
|
||||
// 최종 차수 견적서 찾기
|
||||
var latestEstimate = data.list[0]; // 이미 차수 내림차순으로 정렬되어 있음
|
||||
var templateObjId = latestEstimate.OBJID || latestEstimate.objid;
|
||||
var templateType = latestEstimate.TEMPLATE_TYPE || latestEstimate.template_type || latestEstimate.templateType;
|
||||
|
||||
// 2단계: 견적서 페이지를 새 창으로 열고 PDF 생성
|
||||
fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType);
|
||||
} else {
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '견적서를 찾을 수 없습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error){
|
||||
Swal.close();
|
||||
console.error("견적서 조회 오류:", xhr, status, error);
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '견적서 조회 중 오류가 발생했습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PDF 생성 및 메일 발송
|
||||
function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
|
||||
Swal.fire({
|
||||
title: 'PDF 생성 중...',
|
||||
text: '견적서를 PDF로 변환하고 있습니다.',
|
||||
allowOutsideClick: false,
|
||||
onOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// 견적서 페이지 URL 생성
|
||||
var url = "";
|
||||
if(templateType === "1"){
|
||||
url = "/contractMgmt/estimateTemplate1.do?templateObjId=" + templateObjId;
|
||||
} else if(templateType === "2"){
|
||||
url = "/contractMgmt/estimateTemplate2.do?templateObjId=" + templateObjId;
|
||||
}
|
||||
|
||||
// 숨겨진 iframe으로 페이지 로드
|
||||
var iframe = $('<iframe>', {
|
||||
id: 'pdfGeneratorFrame',
|
||||
src: url,
|
||||
style: 'position:absolute;width:0;height:0;border:none;'
|
||||
}).appendTo('body');
|
||||
|
||||
// iframe 로드 완료 대기
|
||||
iframe.on('load', function(){
|
||||
try {
|
||||
var iframeWindow = this.contentWindow;
|
||||
|
||||
// iframe 내의 PDF 생성 함수 호출
|
||||
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
|
||||
iframeWindow.fn_generateAndUploadPdf(function(pdfBlob){
|
||||
// iframe 제거
|
||||
$('#pdfGeneratorFrame').remove();
|
||||
|
||||
if(pdfBlob){
|
||||
// PDF Blob과 함께 메일 발송 요청
|
||||
fn_sendMailWithPdf(contractObjId, pdfBlob);
|
||||
} else {
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: 'PDF 생성에 실패했습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// iframe 제거
|
||||
$('#pdfGeneratorFrame').remove();
|
||||
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '견적서 페이지 로드에 실패했습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('PDF 생성 오류:', e);
|
||||
$('#pdfGeneratorFrame').remove();
|
||||
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: 'PDF 생성 중 오류가 발생했습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 타임아웃 설정 (30초)
|
||||
setTimeout(function(){
|
||||
if($('#pdfGeneratorFrame').length > 0){
|
||||
$('#pdfGeneratorFrame').remove();
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: '타임아웃',
|
||||
text: 'PDF 생성 시간이 초과되었습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
// PDF Blob과 함께 메일 발송 (FormData 사용)
|
||||
function fn_sendMailWithPdf(contractObjId, pdfBlob){
|
||||
console.log('===== 메일 발송 시작 =====');
|
||||
console.log('contractObjId:', contractObjId);
|
||||
console.log('PDF Blob 크기:', pdfBlob ? pdfBlob.size : 0, 'bytes');
|
||||
console.log('========================');
|
||||
|
||||
Swal.fire({
|
||||
title: '메일 발송 중...',
|
||||
text: '잠시만 기다려주세요.',
|
||||
allowOutsideClick: false,
|
||||
onOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// FormData 생성
|
||||
var formData = new FormData();
|
||||
formData.append('objId', contractObjId);
|
||||
formData.append('pdfFile', pdfBlob, 'estimate.pdf');
|
||||
|
||||
$.ajax({
|
||||
url: "/contractMgmt/sendEstimateMail.do",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false, // FormData 사용 시 필수
|
||||
contentType: false, // FormData 사용 시 필수
|
||||
dataType: "json",
|
||||
timeout: 60000, // 60초 타임아웃
|
||||
success: function(data){
|
||||
console.log('메일 발송 응답:', data);
|
||||
Swal.close();
|
||||
if(data.result === "success"){
|
||||
Swal.fire({
|
||||
@@ -706,9 +854,10 @@ function fn_sendEstimateMail(contractObjId){
|
||||
error: function(xhr, status, error){
|
||||
Swal.close();
|
||||
console.error("메일 발송 오류:", xhr, status, error);
|
||||
console.error("xhr.responseText:", xhr.responseText);
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '메일 발송 중 시스템 오류가 발생했습니다.',
|
||||
text: '메일 발송 중 시스템 오류가 발생했습니다: ' + status,
|
||||
icon: 'error',
|
||||
confirmButtonText: '확인'
|
||||
});
|
||||
|
||||
@@ -165,9 +165,10 @@ body {
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #000;
|
||||
padding: 6px 8px;
|
||||
padding: 3px 5px;
|
||||
text-align: center;
|
||||
font-size: 9pt;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
@@ -265,11 +266,12 @@ textarea {
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 30px;
|
||||
min-height: 25px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.editable {
|
||||
background-color: #fffef0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@@ -462,8 +464,8 @@ function fn_calculateAmount(row) {
|
||||
function fn_calculateTotal() {
|
||||
var total = 0;
|
||||
|
||||
// 품목 행만 순회 (계 행, 원화환산 행, 비고 행 제외)
|
||||
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(){
|
||||
// 품목 행만 순회 (계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외)
|
||||
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(){
|
||||
var amount = $(this).find(".item-amount").val() || "0";
|
||||
// 콤마와 통화 기호 제거 후 숫자로 변환
|
||||
amount = amount.replace(/,/g, "").replace(/₩/g, "").replace(/\$/g, "").replace(/€/g, "").replace(/¥/g, "");
|
||||
@@ -580,8 +582,8 @@ function fn_initItemDescSelect(itemId) {
|
||||
|
||||
// 행 추가 함수
|
||||
function fn_addItemRow() {
|
||||
// 계 행, 원화환산 행, 비고 행 제외하고 품목 행 개수 계산
|
||||
var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row");
|
||||
// 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행 개수 계산
|
||||
var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row");
|
||||
var nextNo = itemRows.length + 1;
|
||||
|
||||
// 새 행 생성
|
||||
@@ -856,12 +858,6 @@ function fn_loadTemplateData(templateObjId){
|
||||
$("#manager_contact").val(managerContact);
|
||||
}
|
||||
|
||||
// 하단 비고 로드
|
||||
$("#note1").val(note1);
|
||||
$("#note2").val(note2);
|
||||
$("#note3").val(note3);
|
||||
$("#note4").val(note4);
|
||||
|
||||
// 테이블 내 비고는 나중에 설정 (textarea 생성 후)
|
||||
var noteRemarks = template.NOTE_REMARKS || template.note_remarks || template.noteRemarks || "";
|
||||
|
||||
@@ -930,6 +926,24 @@ function fn_loadTemplateData(templateObjId){
|
||||
itemsHtml += '</td>';
|
||||
itemsHtml += '</tr>';
|
||||
|
||||
// 참조사항 행 추가
|
||||
itemsHtml += '<tr class="notes-row">';
|
||||
itemsHtml += '<td colspan="8" style="vertical-align: top; padding: 10px; text-align: left; border: 1px solid #000;">';
|
||||
itemsHtml += '<div style="font-weight: bold; margin-bottom: 10px; text-align: left;"><참조사항></div>';
|
||||
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note1" value="1. 견적유효기간: 일" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
|
||||
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
|
||||
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note3" value="3. VAT 별도" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
|
||||
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름." style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
|
||||
itemsHtml += '</td>';
|
||||
itemsHtml += '</tr>';
|
||||
|
||||
// 하단 회사명 행 추가
|
||||
itemsHtml += '<tr class="footer-row">';
|
||||
itemsHtml += '<td colspan="8" style="text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;">';
|
||||
itemsHtml += '㈜알피에스';
|
||||
itemsHtml += '</td>';
|
||||
itemsHtml += '</tr>';
|
||||
|
||||
// HTML 삽입
|
||||
$("#itemsTableBody").html(itemsHtml);
|
||||
|
||||
@@ -976,11 +990,17 @@ function fn_loadTemplateData(templateObjId){
|
||||
fn_initItemDescSelect(itemId);
|
||||
}
|
||||
|
||||
// 테이블 내 비고 값 설정 (textarea 생성 직후)
|
||||
$("#note_remarks").val(noteRemarks);
|
||||
|
||||
// 합계 계산
|
||||
fn_calculateTotal();
|
||||
// 테이블 내 비고 값 설정 (textarea 생성 직후)
|
||||
$("#note_remarks").val(noteRemarks);
|
||||
|
||||
// 참조사항 값 설정 (input 생성 직후)
|
||||
$("#note1").val(note1 || "1. 견적유효기간: 일");
|
||||
$("#note2").val(note2 || "2. 납품기간: 발주 후 1주 이내");
|
||||
$("#note3").val(note3 || "3. VAT 별도");
|
||||
$("#note4").val(note4 || "4. 결제 조건 : 기존 결제조건에 따름.");
|
||||
|
||||
// 합계 계산
|
||||
fn_calculateTotal();
|
||||
|
||||
// 결재상태에 따라 버튼 제어
|
||||
fn_controlButtons();
|
||||
@@ -1025,8 +1045,8 @@ function fn_loadCustomerContact(customerObjId) {
|
||||
|
||||
function fn_save() {
|
||||
var items = [];
|
||||
// 계 행, 원화환산 행, 비고 행 제외하고 품목 행만 저장
|
||||
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(idx) {
|
||||
// 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행만 저장
|
||||
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(idx) {
|
||||
var row = $(this);
|
||||
var quantity = row.find(".item-qty").val() || "";
|
||||
var unitPrice = row.find(".item-price").val() || "";
|
||||
@@ -1128,6 +1148,7 @@ function fn_save() {
|
||||
<colgroup>
|
||||
<col width="80px" />
|
||||
<col width="*" />
|
||||
<col width="50px" />
|
||||
<col width="300px" />
|
||||
</colgroup>
|
||||
<tr>
|
||||
@@ -1135,6 +1156,7 @@ function fn_save() {
|
||||
<td class="editable">
|
||||
<input type="text" id="executor" class="date_icon" value="" style="width: 150px;">
|
||||
</td>
|
||||
<td rowspan="4" style="border: none"></td>
|
||||
<td rowspan="4" style="text-align: center; border: none; vertical-align: middle; padding: 0;">
|
||||
<div style="width: 100%; text-align: center; margin-bottom: 5px;">
|
||||
<img src="/images/company_stamp.png" alt="회사 도장" style="width: 100%; height: auto;"
|
||||
@@ -1143,10 +1165,7 @@ function fn_save() {
|
||||
<div class="company-stamp-text">㈊알피에스<br>RPS CO., LTD<br>대표이사이동준</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: left; font-size: 9pt; line-height: 1.8; padding: 0 5px;">
|
||||
담당자 : <input type="text" id="manager_name" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br>
|
||||
연락처 : <input type="text" id="manager_contact" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;">
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1173,15 +1192,19 @@ function fn_save() {
|
||||
</table>
|
||||
|
||||
<!-- 인사말 -->
|
||||
<div style="margin-bottom: 10px; padding: 0px 5px; line-height: 1.6; font-size: 10pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; padding: 0px 5px;">
|
||||
<!-- 왼쪽: 인사말 -->
|
||||
<div style="line-height: 1.6; font-size: 10pt;">
|
||||
견적을 요청해 주셔서 대단히 감사합니다.<br>
|
||||
하기와 같이 견적서를 제출합니다.
|
||||
</div>
|
||||
|
||||
<!-- 부가세 별도 표시 -->
|
||||
<div style="text-align: right; margin-top: -30px; margin-bottom: 20px; padding-right: 10px; font-size: 10pt;">
|
||||
부가세 별도
|
||||
<!-- 오른쪽: 담당자 정보 및 부가세 별도 -->
|
||||
<div style="text-align: right; font-size: 9pt; line-height: 1.8;">
|
||||
담당자 : <input type="text" id="manager_name" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br>
|
||||
연락처 : <input type="text" id="manager_contact" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br><br>
|
||||
<span style="font-size: 10pt; margin-top: 5px; display: inline-block;">부가세 별도</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 품목 테이블 -->
|
||||
<table class="items-table">
|
||||
@@ -1245,31 +1268,224 @@ function fn_save() {
|
||||
<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 참조사항 행 -->
|
||||
<tr class="notes-row">
|
||||
<td colspan="8" style="vertical-align: top; padding: 10px; text-align: left; border: 1px solid #000;">
|
||||
<div style="font-weight: bold; margin-bottom: 10px; text-align: left;"><참조사항></div>
|
||||
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note1" value="1. 견적유효기간: 일" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
|
||||
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
|
||||
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note3" value="3. VAT 별도" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
|
||||
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름." style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 하단 회사명 행 -->
|
||||
<tr class="footer-row">
|
||||
<td colspan="8" style="text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;">
|
||||
㈜알피에스
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 참조사항 섹션 -->
|
||||
<div class="notes-section">
|
||||
<div class="notes-title"><참조사항></div>
|
||||
<div class="editable"><input type="text" id="note1" value="1. 견적유효기간: 일"></div>
|
||||
<div class="editable"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내"></div>
|
||||
<div class="editable"><input type="text" id="note3" value="3. VAT 별도"></div>
|
||||
<div class="editable"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름."></div>
|
||||
</div>
|
||||
|
||||
<!-- 하단 회사명 -->
|
||||
<div class="footer-company">
|
||||
㈜알피에스
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 영역 -->
|
||||
<div class="btn-area no-print">
|
||||
<button type="button" id="btnAddRow" class="estimate-btn">행 추가</button>
|
||||
<button type="button" id="btnPrint" class="estimate-btn">인쇄</button>
|
||||
<button type="button" id="btnDownloadPdf" class="estimate-btn">PDF 다운로드</button>
|
||||
<button type="button" id="btnSave" class="estimate-btn">저장</button>
|
||||
<button type="button" id="btnClose" class="estimate-btn">닫기</button>
|
||||
</div>
|
||||
|
||||
<!-- html2canvas 및 jsPDF 라이브러리 -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>
|
||||
<script>
|
||||
|
||||
// PDF 다운로드 버튼 클릭 이벤트
|
||||
$("#btnDownloadPdf").click(function(){
|
||||
fn_generatePdf();
|
||||
});
|
||||
|
||||
// PDF 생성 함수
|
||||
function fn_generatePdf() {
|
||||
// 라이브러리 로드 확인
|
||||
if(typeof html2canvas === 'undefined') {
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: 'html2canvas 라이브러리가 로드되지 않았습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof jsPDF === 'undefined') {
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: 'jsPDF 라이브러리가 로드되지 않았습니다.',
|
||||
icon: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: 'PDF 생성 중...',
|
||||
text: '잠시만 기다려주세요.',
|
||||
allowOutsideClick: false,
|
||||
onOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// 버튼 영역 임시 숨김
|
||||
$('.btn-area').hide();
|
||||
|
||||
// 견적서 컨테이너 캡처
|
||||
html2canvas(document.querySelector('.estimate-container'), {
|
||||
scale: 2, // 고해상도
|
||||
useCORS: true,
|
||||
logging: true, // 디버깅을 위해 true로 변경
|
||||
backgroundColor: '#ffffff'
|
||||
}).then(function(canvas) {
|
||||
// 버튼 영역 다시 표시
|
||||
$('.btn-area').show();
|
||||
|
||||
try {
|
||||
// Canvas를 이미지로 변환
|
||||
var imgData = canvas.toDataURL('image/png');
|
||||
|
||||
// PDF 생성 (A4 크기)
|
||||
var pdf = new jsPDF('p', 'mm', 'a4');
|
||||
var imgWidth = 210; // A4 width in mm
|
||||
var pageHeight = 297; // A4 height in mm
|
||||
var imgHeight = canvas.height * imgWidth / canvas.width;
|
||||
var heightLeft = imgHeight;
|
||||
var position = 0;
|
||||
|
||||
// 첫 페이지 추가
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// 페이지가 넘어가면 추가 페이지 생성
|
||||
while (heightLeft >= 0) {
|
||||
position = heightLeft - imgHeight;
|
||||
pdf.addPage();
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
// 파일명 생성
|
||||
var estimateNo = $("#estimate_no").val() || "견적서";
|
||||
var fileName = estimateNo + '.pdf';
|
||||
|
||||
// PDF 다운로드
|
||||
pdf.save(fileName);
|
||||
|
||||
Swal.close();
|
||||
Swal.fire({
|
||||
title: 'PDF 생성 완료',
|
||||
text: 'PDF 파일이 다운로드되었습니다.',
|
||||
icon: 'success',
|
||||
timer: 2000
|
||||
});
|
||||
} catch(pdfError) {
|
||||
Swal.close();
|
||||
console.error('PDF 생성 오류:', pdfError);
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: 'PDF 생성 중 오류가 발생했습니다: ' + pdfError.message,
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}).catch(function(error) {
|
||||
$('.btn-area').show();
|
||||
Swal.close();
|
||||
console.error('Canvas 캡처 오류:', error);
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '페이지 캡처 중 오류가 발생했습니다: ' + error.message,
|
||||
icon: 'error'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// PDF를 Blob으로 생성하여 서버로 전송하는 함수
|
||||
function fn_generateAndUploadPdf(callback) {
|
||||
console.log('fn_generateAndUploadPdf 호출됨');
|
||||
|
||||
// 라이브러리 로드 확인
|
||||
if(typeof html2canvas === 'undefined' || typeof jsPDF === 'undefined') {
|
||||
console.error('필요한 라이브러리가 로드되지 않았습니다.');
|
||||
if(callback && typeof callback === 'function') {
|
||||
callback(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 버튼 영역 임시 숨김
|
||||
$('.btn-area').hide();
|
||||
|
||||
// 견적서 컨테이너 캡처
|
||||
html2canvas(document.querySelector('.estimate-container'), {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
logging: true,
|
||||
backgroundColor: '#ffffff'
|
||||
}).then(function(canvas) {
|
||||
console.log('Canvas 캡처 완료');
|
||||
|
||||
// 버튼 영역 다시 표시
|
||||
$('.btn-area').show();
|
||||
|
||||
try {
|
||||
// Canvas를 이미지로 변환
|
||||
var imgData = canvas.toDataURL('image/png');
|
||||
console.log('이미지 변환 완료');
|
||||
|
||||
// PDF 생성
|
||||
var pdf = new jsPDF('p', 'mm', 'a4');
|
||||
var imgWidth = 210;
|
||||
var pageHeight = 297;
|
||||
var imgHeight = canvas.height * imgWidth / canvas.width;
|
||||
var heightLeft = imgHeight;
|
||||
var position = 0;
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
while (heightLeft >= 0) {
|
||||
position = heightLeft - imgHeight;
|
||||
pdf.addPage();
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
console.log('PDF 생성 완료');
|
||||
|
||||
// PDF를 Blob으로 변환 (Base64 대신 바이너리)
|
||||
var pdfBlob = pdf.output('blob');
|
||||
console.log('PDF Blob 생성 완료, 크기:', pdfBlob.size, 'bytes');
|
||||
|
||||
// 콜백 함수 호출 (메일 발송 등에 사용)
|
||||
if(callback && typeof callback === 'function') {
|
||||
callback(pdfBlob);
|
||||
}
|
||||
} catch(pdfError) {
|
||||
console.error('PDF 생성 중 오류:', pdfError);
|
||||
if(callback && typeof callback === 'function') {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
}).catch(function(error) {
|
||||
$('.btn-area').show();
|
||||
console.error('Canvas 캡처 오류:', error);
|
||||
if(callback && typeof callback === 'function') {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user