fix: [document-templates] 양식 디자이너 미리보기 렌더러 분기 처리
- builder_type=block 템플릿은 buildBlockPreviewHtml() 사용 - 레거시 템플릿은 기존 buildDocumentPreviewHtml() 유지
This commit is contained in:
@@ -268,7 +268,12 @@ function handleTrashedFilter() {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
content.innerHTML = buildDocumentPreviewHtml(data.data);
|
||||
// builder_type에 따라 적절한 렌더러 사용
|
||||
if (data.data.builder_type === 'block') {
|
||||
content.innerHTML = buildBlockPreviewHtml(data.data);
|
||||
} else {
|
||||
content.innerHTML = buildDocumentPreviewHtml(data.data);
|
||||
}
|
||||
} else {
|
||||
content.innerHTML = '<p class="text-center text-red-500 py-8">미리보기를 불러올 수 없습니다.</p>';
|
||||
}
|
||||
|
||||
@@ -122,6 +122,155 @@ function _previewFormatFrequency(item) {
|
||||
* @param {Array} data.columns - 테이블 컬럼
|
||||
* @param {Function} [data.methodResolver] - 검사방법 코드→이름 변환 함수 (선택)
|
||||
*/
|
||||
/**
|
||||
* 블록 기반(양식 디자이너) 미리보기 HTML 생성
|
||||
*
|
||||
* @param {Object} data - 템플릿 데이터 (builder_type === 'block')
|
||||
* @param {Object} data.schema - 블록 스키마 { version, page, blocks[] }
|
||||
*/
|
||||
function buildBlockPreviewHtml(data) {
|
||||
const schema = data.schema;
|
||||
if (!schema || !schema.blocks || schema.blocks.length === 0) {
|
||||
return '<div class="text-center text-gray-400 py-12">블록이 없습니다. 양식 디자이너에서 블록을 추가하세요.</div>';
|
||||
}
|
||||
|
||||
const page = schema.page || {};
|
||||
const orientation = page.orientation || 'portrait';
|
||||
const blocks = schema.blocks;
|
||||
|
||||
function esc(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function nl2br(text) {
|
||||
return (text || '').replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
function renderBlock(block) {
|
||||
const type = block.type || '';
|
||||
const props = block.props || {};
|
||||
|
||||
switch (type) {
|
||||
case 'heading': {
|
||||
const level = Math.min(Math.max(parseInt(props.level || 2), 1), 6);
|
||||
const text = esc(props.text || '');
|
||||
const align = props.align || 'left';
|
||||
const sizes = {1:'1.5em',2:'1.25em',3:'1.1em',4:'1em',5:'0.9em',6:'0.85em'};
|
||||
return `<h${level} style="text-align:${align};font-size:${sizes[level]};font-weight:bold;margin:0.5em 0;">${text}</h${level}>`;
|
||||
}
|
||||
case 'paragraph': {
|
||||
const text = nl2br(esc(props.text || ''));
|
||||
const align = props.align || 'left';
|
||||
return `<p style="text-align:${align};margin:0.3em 0;font-size:0.9em;line-height:1.6;">${text}</p>`;
|
||||
}
|
||||
case 'table': {
|
||||
const headers = props.headers || [];
|
||||
const rows = props.rows || [];
|
||||
const showHeader = props.showHeader !== false;
|
||||
let html = '<table style="width:100%;border-collapse:collapse;border:1px solid #333;margin:0.5em 0;font-size:0.85em;">';
|
||||
if (showHeader && headers.length > 0) {
|
||||
html += '<thead><tr>';
|
||||
headers.forEach(h => { html += `<th style="border:1px solid #333;padding:4px 8px;background:#f5f5f5;font-weight:bold;text-align:center;">${esc(h)}</th>`; });
|
||||
html += '</tr></thead>';
|
||||
}
|
||||
html += '<tbody>';
|
||||
if (rows.length > 0) {
|
||||
rows.forEach(row => {
|
||||
html += '<tr>';
|
||||
(row || []).forEach(cell => { html += `<td style="border:1px solid #333;padding:4px 8px;">${esc(cell || '')}</td>`; });
|
||||
html += '</tr>';
|
||||
});
|
||||
} else {
|
||||
const colCount = headers.length || 3;
|
||||
html += '<tr>';
|
||||
for (let i = 0; i < colCount; i++) html += '<td style="border:1px solid #333;padding:4px 8px;color:#ccc;text-align:center;">(입력)</td>';
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
return html;
|
||||
}
|
||||
case 'columns': {
|
||||
const count = parseInt(props.count || 2);
|
||||
const children = props.children || [];
|
||||
let html = '<div style="display:flex;gap:10px;margin:0.5em 0;">';
|
||||
for (let i = 0; i < count; i++) {
|
||||
html += '<div style="flex:1;">';
|
||||
const colChildren = children[i] || [];
|
||||
colChildren.forEach(child => { html += renderBlock(child); });
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
case 'divider': {
|
||||
const style = (props.style || 'solid') === 'dashed' ? 'dashed' : 'solid';
|
||||
return `<hr style="border:none;border-top:1px ${style} #ccc;margin:0.8em 0;">`;
|
||||
}
|
||||
case 'spacer': {
|
||||
const height = Math.max(parseInt(props.height || 20), 0);
|
||||
return `<div style="height:${height}px;"></div>`;
|
||||
}
|
||||
case 'text_field': {
|
||||
const label = esc(props.label || '');
|
||||
const req = props.required ? ' <span style="color:red;">*</span>' : '';
|
||||
return `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}${req}</label><div style="border:1px solid #ccc;padding:4px 8px;min-height:24px;font-size:0.9em;color:#ccc;">(입력)</div></div>`;
|
||||
}
|
||||
case 'number_field': {
|
||||
const label = esc(props.label || '');
|
||||
const unit = props.unit ? ` (${esc(props.unit)})` : '';
|
||||
return `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}${unit}</label><div style="border:1px solid #ccc;padding:4px 8px;min-height:24px;font-size:0.9em;color:#ccc;">(숫자)</div></div>`;
|
||||
}
|
||||
case 'date_field': {
|
||||
const label = esc(props.label || '');
|
||||
return `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}</label><div style="border:1px solid #ccc;padding:4px 8px;min-height:24px;font-size:0.9em;color:#ccc;">(날짜)</div></div>`;
|
||||
}
|
||||
case 'select_field': {
|
||||
const label = esc(props.label || '');
|
||||
const options = (props.options || []).map(o => esc(o)).join(' / ');
|
||||
return `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}</label><div style="border:1px solid #ccc;padding:4px 8px;min-height:24px;font-size:0.9em;">${options || '<span style="color:#ccc;">(선택)</span>'}</div></div>`;
|
||||
}
|
||||
case 'checkbox_field': {
|
||||
const label = esc(props.label || '');
|
||||
const options = props.options || [];
|
||||
let html = `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}</label><div style="display:flex;gap:12px;flex-wrap:wrap;">`;
|
||||
options.forEach(opt => { html += `<label style="font-size:0.85em;display:flex;align-items:center;gap:4px;"><input type="checkbox" disabled> ${esc(opt)}</label>`; });
|
||||
html += '</div></div>';
|
||||
return html;
|
||||
}
|
||||
case 'textarea_field': {
|
||||
const label = esc(props.label || '');
|
||||
const rows = Math.max(parseInt(props.rows || 3), 1);
|
||||
const height = rows * 24;
|
||||
return `<div style="margin:0.3em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}</label><div style="border:1px solid #ccc;padding:4px 8px;min-height:${height}px;font-size:0.9em;color:#ccc;">(장문 입력)</div></div>`;
|
||||
}
|
||||
case 'signature_field': {
|
||||
const label = esc(props.label || '서명');
|
||||
return `<div style="margin:0.5em 0;"><label style="font-size:0.8em;font-weight:bold;display:block;margin-bottom:2px;">${label}</label><div style="border:2px dashed #ccc;height:60px;display:flex;align-items:center;justify-content:center;font-size:0.8em;color:#999;">서명 영역</div></div>`;
|
||||
}
|
||||
default:
|
||||
return `<div style="color:#999;font-size:0.8em;margin:0.3em 0;">[ ${esc(type)} 블록 ]</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
let html = `<div style="font-family:'Noto Sans KR',sans-serif;max-width:210mm;margin:0 auto;padding:20mm 15mm;background:white;">`;
|
||||
blocks.forEach(block => { html += renderBlock(block); });
|
||||
html += '</div>';
|
||||
|
||||
return `
|
||||
<div class="bg-white p-4" style="max-width:900px;margin:0 auto;">
|
||||
<div class="flex items-center gap-2 mb-3 pb-2 border-b">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-700">양식 디자이너</span>
|
||||
<span class="text-sm font-medium text-gray-700">${esc(data.name || '')}</span>
|
||||
<span class="text-xs text-gray-400 ml-auto">${blocks.length}개 블록</span>
|
||||
</div>
|
||||
<div class="border rounded-lg overflow-hidden">${html}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildDocumentPreviewHtml(data) {
|
||||
const title = data.title || '문서 양식';
|
||||
const companyName = data.company_name || '';
|
||||
|
||||
Reference in New Issue
Block a user