301 lines
8.8 KiB
JavaScript
301 lines
8.8 KiB
JavaScript
import { bindData } from "./dynamicBinder";
|
|
|
|
function generateTextElement(el, data) {
|
|
const fontWeight = el.bold ? "bold" : el.fontWeight || "normal";
|
|
const fontStyle = el.italic ? "italic" : "normal";
|
|
const textDecoration = el.underline ? "underline" : "none";
|
|
|
|
return `
|
|
<div style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
font-size: ${el.fontSize}px;
|
|
font-family: ${el.fontFamily || 'Arial'};
|
|
font-weight: ${fontWeight};
|
|
font-style: ${fontStyle};
|
|
text-decoration: ${textDecoration};
|
|
color: ${el.color || '#000000'};
|
|
text-align: ${el.align || 'left'};
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
white-space: pre-wrap;
|
|
">
|
|
${bindData(el.text, data)}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function generateImageElement(el, data) {
|
|
if (!el.src) return '';
|
|
|
|
return `
|
|
<img src="${bindData(el.src, data)}" style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
width: ${el.width}px;
|
|
height: ${el.height}px;
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
" />
|
|
`;
|
|
}
|
|
|
|
function generateQRCodeElement(el, data) {
|
|
// For HTML export, we'll use a QR code API or placeholder
|
|
const qrData = encodeURIComponent(bindData(el.data, data));
|
|
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${el.size}x${el.size}&data=${qrData}`;
|
|
|
|
return `
|
|
<img src="${qrUrl}" style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
width: ${el.size}px;
|
|
height: ${el.size}px;
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
" />
|
|
`;
|
|
}
|
|
|
|
function generateLineElement(el) {
|
|
return `
|
|
<div style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
width: ${el.width}px;
|
|
height: ${el.thickness}px;
|
|
background-color: ${el.color || '#000000'};
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
"></div>
|
|
`;
|
|
}
|
|
|
|
function generateRectangleElement(el) {
|
|
return `
|
|
<div style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
width: ${el.width}px;
|
|
height: ${el.height}px;
|
|
background-color: ${el.fill || 'transparent'};
|
|
border: ${el.strokeWidth || 0}px solid ${el.stroke || '#000000'};
|
|
border-radius: ${el.cornerRadius || 0}px;
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
box-sizing: border-box;
|
|
"></div>
|
|
`;
|
|
}
|
|
|
|
function generateTableElement(el, data) {
|
|
const visibleColumns = el.columns.filter(col => col.visible);
|
|
const tableData = data?.tableData || el.sampleData || [];
|
|
|
|
let tableHtml = `
|
|
<div style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
">
|
|
<table style="
|
|
border-collapse: collapse;
|
|
width: auto;
|
|
">
|
|
<thead>
|
|
<tr>
|
|
`;
|
|
|
|
// Header row
|
|
visibleColumns.forEach(col => {
|
|
tableHtml += `
|
|
<th style="
|
|
width: ${col.width}px;
|
|
height: ${el.rowHeight}px;
|
|
background-color: ${el.headerBg || '#f0f0f0'};
|
|
border: 1px solid ${el.borderColor || '#000000'};
|
|
padding: 5px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
text-align: left;
|
|
">
|
|
${col.label}
|
|
</th>
|
|
`;
|
|
});
|
|
|
|
tableHtml += `
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
// Data rows
|
|
if (el.showSampleData && tableData.length > 0) {
|
|
tableData.forEach(row => {
|
|
tableHtml += '<tr>';
|
|
visibleColumns.forEach(col => {
|
|
const cellValue = bindData(row[col.key] || '', data);
|
|
tableHtml += `
|
|
<td style="
|
|
width: ${col.width}px;
|
|
height: ${el.rowHeight}px;
|
|
border: 1px solid ${el.borderColor || '#000000'};
|
|
padding: 5px;
|
|
font-size: 11px;
|
|
text-align: left;
|
|
">
|
|
${cellValue}
|
|
</td>
|
|
`;
|
|
});
|
|
tableHtml += '</tr>';
|
|
});
|
|
}
|
|
|
|
tableHtml += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
|
|
return tableHtml;
|
|
}
|
|
|
|
function generateSummaryElement(el, data) {
|
|
const visibleFields = el.fields.filter(field => field.visible);
|
|
|
|
let summaryHtml = `
|
|
<div style="
|
|
position: absolute;
|
|
top: ${el.y}px;
|
|
left: ${el.x}px;
|
|
transform: rotate(${el.rotation || 0}deg);
|
|
transform-origin: top left;
|
|
">
|
|
`;
|
|
|
|
visibleFields.forEach((field, index) => {
|
|
const yPos = index * (el.fontSize + el.spacing);
|
|
const value = bindData(`{{${field.key}}}`, data);
|
|
|
|
summaryHtml += `
|
|
<div style="
|
|
position: relative;
|
|
margin-bottom: ${el.spacing}px;
|
|
display: flex;
|
|
align-items: center;
|
|
">
|
|
<span style="
|
|
font-size: ${el.fontSize}px;
|
|
color: ${el.labelColor || '#000000'};
|
|
font-weight: bold;
|
|
display: inline-block;
|
|
width: ${field.width}px;
|
|
">
|
|
${field.label}:
|
|
</span>
|
|
<span style="
|
|
font-size: ${el.fontSize}px;
|
|
color: ${el.valueColor || '#000000'};
|
|
margin-left: 10px;
|
|
">
|
|
${value}
|
|
</span>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
summaryHtml += '</div>';
|
|
return summaryHtml;
|
|
}
|
|
|
|
export function generateHtml(template, data = {}) {
|
|
const elementsHtml = template.elements
|
|
.map((el) => {
|
|
switch (el.type) {
|
|
case "text":
|
|
return generateTextElement(el, data);
|
|
case "image":
|
|
return generateImageElement(el, data);
|
|
case "qrcode":
|
|
return generateQRCodeElement(el, data);
|
|
case "line":
|
|
return generateLineElement(el);
|
|
case "rectangle":
|
|
return generateRectangleElement(el);
|
|
case "table":
|
|
return generateTableElement(el, data);
|
|
case "summary":
|
|
return generateSummaryElement(el, data);
|
|
default:
|
|
return "";
|
|
}
|
|
})
|
|
.join("");
|
|
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Invoice</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
background: #f5f5f5;
|
|
padding: 20px;
|
|
}
|
|
|
|
.page-container {
|
|
position: relative;
|
|
width: ${template.page.width}px;
|
|
height: ${template.page.height}px;
|
|
background: white;
|
|
margin: 0 auto;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
@media print {
|
|
body {
|
|
background: white;
|
|
padding: 0;
|
|
}
|
|
|
|
.page-container {
|
|
width: ${template.page.width}px;
|
|
height: ${template.page.height}px;
|
|
box-shadow: none;
|
|
margin: 0;
|
|
page-break-after: always;
|
|
}
|
|
|
|
@page {
|
|
size: ${template.page.width}px ${template.page.height}px;
|
|
margin: 0;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page-container">
|
|
${elementsHtml}
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
} |