Сейчас узнаем, какой подарок выпадет именно Вам
Крутите колесо
Как скачать состав корзины в pdf на Tilda

Как скачать состав корзины в pdf на Tilda

1
Создали товары в магазине и блок корзины ST100
2
Создали блок GL02 и заполнили как показано на скрине
3
Вставили код на страницу в блок Т123
Библиотека для примера
<style>
.invoice-wrapper {
    max-width: 800px;
    margin: 0 auto;
    background: #fff;
    box-shadow: 0 2px 15px rgba(0,0,0,0.1);
    border-radius: 8px;
    overflow: hidden;
}

.main-content {
    padding: 0 20px;
}

.full-width-image {
    width: 100%;
    height: auto;
    display: block;
    object-fit: contain;
}

.invoice-header {
    margin-top: 30px;
}

.invoice-table {
    width: 100%;
    border-collapse: collapse;
    margin: 15px 0;
    font-size: 12px;
    page-break-inside: auto;
}

.invoice-table th {
    background: #8f8f8f;
    color: white;
    padding: 8px 4px;
    text-align: left;
    font-weight: 400;
}

.invoice-table td {
    padding: 8px 4px;
    border-bottom: 1px solid #eee;
    vertical-align: middle;
}

.invoice-table tr:nth-child(even) {
    background: #f9f9f9;
}

.invoice-table tr {
    page-break-inside: avoid;
}

.num-cell {
    width: 25px;
    text-align: center;
    font-weight: 500;
}

.photo-cell {
    width: 70px;
    text-align: center;
}

.title-cell {
    font-size: 13px;
    line-height: 1.4;
}

.price-cell,
.total-cell {
    text-align: right;
    white-space: nowrap;
    font-weight: 400;
}

.qty-cell {
    text-align: center;
    width: 60px;
}

.title-cell .t706__product-title {
    font-size: 12px;
    padding: 5px 0;
}

td.totals-cell .t706__cartwin-totalamount-wrap,
td.totals-cell .t706__cartpage-totals {
    padding-top: 0;
    font-size: 16px;
}

.uc-pdf-data{
    display: none;
}


.invoice-table th:first-child,
.invoice-table td:last-child {
    padding-left: 10px;
    padding-right: 10px;
}

.product-thumb {
    width: 50px;
    height: 50px;
    object-fit: cover;
    border-radius: 4px;
    border: 1px solid #ddd;
    display: block;
    margin: 0 auto;
}

.product-thumb.no-image {
    background: #f5f5f5;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #999;
    font-size: 18px;
    width: 50px;
    height: 50px;
    border-radius: 4px;
    border: 1px solid #ddd;
    margin: 0 auto;
}

.total-row {
    background: #ececec !important;
}

.invoice-footer {
    color: #555;
    margin-bottom: 35px;
    line-height: 1.5;
    padding-top: 8px;
}

.invoice-wrapper a,
.invoice-wrapper .t706__product-title a {
    text-decoration: none !important;
    color: inherit !important;
    border-bottom: none !important;
}

.print-price-button {
    position: relative;
    cursor: pointer;
    transition: all 0.2s ease;
    user-select: none;
}

.print-price-button.is-generating {
    opacity: 0.65;
    pointer-events: none;
    transform: scale(0.97);
    background-color: #e8e8e8 !important;
    border-color: #ccc !important;
}

.print-price-button.is-generating .button-text::after {
    content: '...';
    display: inline-block;
    animation: dots 1.2s infinite steps(4, end);
    width: 12px;
}


.t-rec .t706__cartpage-info .print-price-button {
    margin-top: 15px;
}


.t706__cartpage-totals {
    background: transparent;
    border-radius: 0;
    padding: 0;
    margin-top: 0px;
    margin-bottom: 0px;
    bottom: 0;
}


span.t706__cartwin-discounts__description-wrapper {
    display: none;
}

@keyframes dots {
    0% { content: '.'; width: 6px; }
    33% { content: '..'; width: 10px; }
    66% { content: '...'; width: 14px; }
}

@media print {
    body {
        background: white;
        padding: 0;
    }
}

.t706 .t706__cartpage-totals {
    padding-bottom: 0px;
}

.t706 .t706__cartwin-bottom+div {
    margin-top: 0;
}

.uc-download-data {
    display: none;
}

.load-time:after {
    content: "";
    opacity: 0.5;
    background-image: url(https://tilda.ru/tpl/img/ajax-loader.gif);
    background-size: 35px;
    background-position: center;
    background-repeat: no-repeat;
    height: 100%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
}

.load-time div {
    opacity: 0.2;
}

.js-successbox.load-time.t-form__successbox {
    opacity: 0.6;
}

.print-price-button.t-text {
    border-radius: 0px;
    margin-top: 5px;
    font-weight: 600;
    color: #000000;
    border: 1px solid #dddddd;
    cursor: pointer;
    transition: all 0.2s;
    width: 100%;
    box-sizing: border-box;
}

.button-wrapper {
    padding: 8px 0;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 16px;
    font-weight: 400;
}

.print-price-button.t-text:not(.load-time):hover {
    background-color: #f5f5f5;
}

.button-icon {
    background-image: url(https://static.tildacdn.com/tild6238-3065-4138-b936-303137363262/2849806-copy-interfa.svg);
    width: 25px;
    height: 25px;
    background-position: center;
    background-size: contain;
    background-repeat: no-repeat;
    margin-right: 12px;
}

@media screen and (max-width: 960px) {
    .print-price-button.t-text {
        width: calc(100% - 0px);
        margin: auto;
    }

    .t706__cartpage-form .print-price-button.t-text {
        width: 100%;
        margin: auto;
    }
}

@media screen and (max-width: 640px) {
    .t706__cartpage-form .t-form__submit {
        padding-bottom: 5px;
    }
}
</style>

<script>
document.addEventListener("DOMContentLoaded", function() {
    t_onReady(function() {
        setTimeout(function() {
            t_onFuncLoad('tcart__init', function() {
                function loadScript(src) {
                    return new Promise((resolve, reject) => {
                        if(document.querySelector(`script[src="${src}"]`)) return resolve();
                        const s = document.createElement('script');
                        s.src = src; s.onload = resolve; s.onerror = reject;
                        document.head.appendChild(s);
                    });
                }
                async function ensureLibs() {
                    if(typeof window.jspdf === 'undefined') await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js');
                    if(typeof window.html2canvas === 'undefined') await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');
                }

                function getCurrentDateFormatted() {
                    const now = new Date();
                    return [String(now.getDate()).padStart(2, '0'), String(now.getMonth() + 1).padStart(2, '0'), now.getFullYear()].join('.');
                }
                function cloneAndReplaceDate(element) {
                    if(!element) return null;
                    const clone = element.cloneNode(true);
                    function replaceInTextNodes(node) {
                        if(node.nodeType === Node.TEXT_NODE && node.textContent.includes('{date}')) {
                            node.textContent = node.textContent.replace(/{date}/g, getCurrentDateFormatted());
                        } else { node.childNodes.forEach(replaceInTextNodes); }
                    }
                    replaceInTextNodes(clone); return clone;
                }
                function extractBgImageUrl(element) {
                    if(!element) return '';
                    const style = element.getAttribute('style') || '';
                    const match = style.match(/background-image:\s*url\(['"]?([^'")]+)['"]?\)/i);
                    return match ? match[1] : '';
                }
                function formatPrice(num) {
                    return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
                }
    
               function getProjectFont() {
                    const selectors = ['.t-text', '.t-descr', '.t-title', '.t-body', 'body'];
                    for (let sel of selectors) {
                        const el = document.querySelector(sel);
                        if (!el) continue;
                        let fam = window.getComputedStyle(el).fontFamily;
                        // Убираем кавычки, берём только первый шрифт из цепочки
                        let clean = fam.replace(/['"]/g, '').split(',')[0].trim();
                        if (clean && clean !== 'inherit' && clean !== 'initial') return clean;
                    }
                    return 'Arial';
                }

                function collectPdfMetaData() {
                    const pdfDataBlock = document.querySelector('.uc-pdf-data');
                    let headerImage = '', footerImage = '';
                    let pdfTitleEl = null, pdfDescrEl = null;
                    if(pdfDataBlock) {
                        const rowWrapper = pdfDataBlock.querySelector('.t667__row');
                        if(rowWrapper) {
                            const bgImages = rowWrapper.querySelectorAll('.t-bgimg');
                            if(bgImages[0]) headerImage = bgImages[0].getAttribute('data-original') || '';
                            if(bgImages[1]) footerImage = bgImages[1].getAttribute('data-original') || '';
                        }
                        const titleEl = pdfDataBlock.querySelector('.js-block-header-title');
                        const descrEl = pdfDataBlock.querySelector('.js-block-header-descr');
                        pdfTitleEl = titleEl ? cloneAndReplaceDate(titleEl) : null;
                        pdfDescrEl = descrEl ? cloneAndReplaceDate(descrEl) : null;
                    }
                    return { headerImage, footerImage, pdfTitleEl, pdfDescrEl };
                }
                function collectCartProducts() {
                    const products = [];
                    if(typeof tcart === 'undefined' || !tcart.products) return products;
                    const productsContainers = document.querySelectorAll('[class*="-products"]');
                    let productsWrapper = null;
                    for(let c of productsContainers) { if(c.querySelector('.t706__product')) { productsWrapper = c; break; } }
                    if(!productsWrapper) return products;
                    productsWrapper.querySelectorAll('.t706__product').forEach(prodEl => {
                        let productImage = '';
                        const thumb = prodEl.querySelector('.t706__product-thumb');
                        if(thumb) { const imgDiv = thumb.querySelector('.t706__product-imgdiv'); if(imgDiv) productImage = extractBgImageUrl(imgDiv); }
                        const titleEl = prodEl.querySelector('.t706__product-title');
                        const titleElement = cloneAndReplaceDate(titleEl);
                        const productIndex = prodEl.getAttribute('data-cart-product-i');
                        let pricePerOne = 0, totalPrice = 0, quantity = 0;
                        if(productIndex !== null && tcart.products[productIndex]) {
                            const p = tcart.products[productIndex]; pricePerOne = p.price || 0; totalPrice = p.amount || 0; quantity = p.quantity || 0;
                        }
                        products.push({ index: productIndex, image: productImage, titleElement, pricePerOne, totalPrice, quantity, pricePerOneFormatted: formatPrice(pricePerOne), totalPriceFormatted: formatPrice(totalPrice) });
                    });
                    return products;
                }
                function collectCartTotals() {
                    const el = document.querySelector('.t706__cartpage-totals') || document.querySelector('.t706__cartwin-totalamount-wrap');
                    return el ? cloneAndReplaceDate(el) : null;
                }

         
                function buildInvoiceHTML(orderData, fontFam) {
                    const { meta, products, currency, totalsElement } = orderData;
                    const headerImg = meta.headerImage ? `<img src="${meta.headerImage}" alt="Header" class="full-width-image" crossorigin="anonymous" onerror="this.style.display='none'">` : '';
                    const titleHTML = meta.pdfTitleEl ? `<div class="header-title">${meta.pdfTitleEl.outerHTML}</div>` : '<div class="header-title"><h1>КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ</h1></div>';
                    
                    let rows = '';
                    products.forEach((p, i) => {
                        const photo = p.image ? `<img src="${p.image}" class="product-thumb" crossorigin="anonymous" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'"><div class="product-thumb no-image" style="display:none">—</div>` : `<div class="product-thumb no-image">—</div>`;
                        const title = p.titleElement ? p.titleElement.outerHTML : '<span>Без названия</span>';
                        
                        rows += `<tr>
                            <td class="num-cell">${p.index !== null ? Number(p.index) + 1 : i + 1}</td>
                            <td class="photo-cell">${photo}</td>
                            <td class="title-cell">${title}</td>
                            <td class="price-cell">${p.pricePerOneFormatted} ${currency}</td>
                            <td class="qty-cell">${p.quantity}</td>
                            <td class="total-cell">${p.totalPriceFormatted} ${currency}</td>
                        </tr>`;
                    });
                
                    const totalRow = totalsElement ? `<tr class="total-row"><td colspan="6" class="totals-cell">${totalsElement.outerHTML}</td></tr>` : '';
                    const footerDesc = meta.pdfDescrEl ? `<div class="footer-desc">${meta.pdfDescrEl.outerHTML}</div>` : '';
                    const footerImg = meta.footerImage ? `<img src="${meta.footerImage}" alt="Footer" class="full-width-image padding-image" crossorigin="anonymous" onerror="this.style.display='none'">` : '';
                    
                 
                    return `<style>.invoice-wrapper, .invoice-wrapper * { font-family: ${fontFam}; -webkit-font-smoothing: antialiased; }</style>
                    <div class="invoice-wrapper t-text">${headerImg}<div class="main-content"><div class="invoice-header">${titleHTML}</div>
                    <table class="invoice-table">
                        <thead><tr>
                            <th class="num-cell">№</th>
                            <th class="photo-cell">Фото</th>
                            <th class="title-cell">Наименование товара / услуги</th>
                            <th class="price-cell">Цена</th>
                            <th class="qty-cell">Кол-во</th>
                            <th class="total-cell">Сумма</th>
                        </tr></thead>
                        <tbody>${rows}${totalRow}</tbody>
                    </table><div class="invoice-footer">${footerDesc}</div></div>${footerImg}</div>`;
                }

                async function preloadImages(element) {
                    const imgs = element.querySelectorAll('img');
                    const promises = Array.from(imgs).map(img => new Promise(resolve => {
                        if(img.complete && img.naturalHeight !== 0) return resolve();
                        const cleanup = () => { img.removeEventListener('load', ok); img.removeEventListener('error', err); resolve(); };
                        const ok = () => cleanup(); const err = () => cleanup();
                        img.addEventListener('load', ok, { once: true }); img.addEventListener('error', err, { once: true });
                        if(img.src.startsWith('http') && !img.crossOrigin) img.crossOrigin = 'anonymous';
                    }));
                    await Promise.all(promises); await new Promise(r => setTimeout(r, 200));
                }
                
                
                

                 async function directGeneratePDF(clickedBtn) {
                    await ensureLibs();
                    if (!window.jspdf || !window.html2canvas) {
                        alert('Библиотеки не загрузились. Проверьте интернет.');
                        return;
                    }
                
                    clickedBtn.classList.add('is-generating');
                    const btnTextEl = clickedBtn.querySelector('.button-text');
                    const originalText = btnTextEl.textContent.trim();
                    btnTextEl.textContent = 'Создание PDF';
                
                    try {
                        const meta = collectPdfMetaData();
                        const products = collectCartProducts();
                        const currency = (typeof tcart !== 'undefined' && tcart.currency_txt) ? tcart.currency_txt : '₽';
                        const totalsElement = collectCartTotals();
                        const orderData = { meta, products, currency, totalsElement };
                        const projectFont = getProjectFont();
                
                       
                        const tempContainer = document.createElement('div');
                        tempContainer.style.cssText = 'position:absolute; left:-10000px; top:0; width:800px; visibility:visible; z-index:-9999; overflow:hidden;';
                        tempContainer.innerHTML = buildInvoiceHTML(orderData, projectFont);
                        document.body.appendChild(tempContainer);
                
                   
                        await document.fonts.ready;
                        void tempContainer.offsetHeight; 
                        await new Promise(r => requestAnimationFrame(r)); 
                
                        await preloadImages(tempContainer);
                
                        const wrapper = tempContainer.querySelector('.invoice-wrapper');
                
                        
                        const canvas = await html2canvas(wrapper, {
                            scale: 2.5,
                            useCORS: true,
                            allowTaint: true,
                            logging: false,
                            backgroundColor: '#ffffff',
                            windowWidth: 800,
                                onclone: (clonedDoc) => {
                                 
                                    const f = projectFont;
                                    clonedDoc.body.style.fontFamily = f;
                                    clonedDoc.querySelectorAll('.invoice-wrapper *').forEach(el => {
                                        el.style.fontFamily = f;
                                    });
                                }
                        });
                
                        if (canvas.width === 0 || canvas.height === 0) throw new Error('Canvas пустой');
                
                        const imgData = canvas.toDataURL('image/jpeg', 0.92);
                        const { jsPDF } = window.jspdf;
                        const pdf = new jsPDF('p', 'mm', 'a4');
                        const pageW = pdf.internal.pageSize.getWidth();
                        const pageH = pdf.internal.pageSize.getHeight();
                        const imgW_mm = pageW;
                        const imgH_mm = (canvas.height * imgW_mm) / canvas.width;
                
                        let heightLeft = imgH_mm;
                        let position_mm = 0;
                        pdf.addImage(imgData, 'JPEG', 0, position_mm, imgW_mm, imgH_mm);
                        heightLeft -= pageH;
                        while (heightLeft > 0.5) {
                            position_mm = heightLeft - imgH_mm;
                            pdf.addPage();
                            pdf.addImage(imgData, 'JPEG', 0, position_mm, imgW_mm, imgH_mm);
                            heightLeft -= pageH;
                        }
                
                        pdf.save(`КП-${getCurrentDateFormatted().replace(/\./g, '-')}.pdf`);
                    } catch (e) {
                        console.error('PDF generation error:', e);
                        alert('⚠️ Ошибка генерации. Попробуйте обновить страницу или Ctrl+P → "Сохранить как PDF"');
                    } finally {
                        const temp = document.querySelector('[style*="-10000px"]');
                        if (temp) temp.remove();
                        clickedBtn.classList.remove('is-generating');
                        btnTextEl.textContent = originalText;
                    }
                }
                
                
                
                
                

                function createPrintBtn() {
                    const div = document.createElement('div'); div.className = 'print-price-button t-text';
                    div.innerHTML = `<div class="button-wrapper"><div class="button-icon"></div><div class="button-text">Распечатать заказ</div></div>`; 
                    return div;
                }
                function addBtnToCart() {
                    const btn = createPrintBtn(); 
                    const cp = document.querySelector('.t706__cartpage'); 
                    const simple = document.querySelector('.t706__cartwin-content');
                    if(cp) {
                        let wrap = cp.querySelector('.t706__cartpage-info-wrapper');
                        if(wrap) wrap.insertAdjacentElement('afterend', btn.cloneNode(true));
                    } else if(simple) { 
                        const wrap = simple.querySelector('.t706__cartwin-bottom'); 
                        if(wrap) wrap.insertAdjacentElement('afterend', btn.cloneNode(true)); 
                    }
                }
                addBtnToCart();

                document.addEventListener('click', function(e) {
                    const btn = e.target.closest('.print-price-button');
                    if(btn) {
                        e.preventDefault();
                        directGeneratePDF(btn);
                    }
                });
            });
        }, 200);
    });
});
</script>
КОММЕРЧЕСКОЕ ПРЕДЛОЖЕНИЕ ОТ {date}

ООО "СтройТехноМаш"
ИНН 7701234567
КПП 770101001
Реквизиты для оплаты:
Банк: ПАО Сбербанк
БИК: 044525225
Оплата в течение 10 банковских дней. НДС не облагается
Made on
Tilda