• July 3, 2026

    เป็นระบบฟอร์มเมล

    มี signature pad

    อัพโหลดไฟล์ภาพ,pdf (มี preview ถ้าเป็นภาพ)

    เมื่อกรอกเสร็จ ส่งเข้าเมล และ มีปุ่มดาวน์โหลดหน้านี้เป็น pdf

    ขณะส่งขึ้น progress bar แบบ real time

    ขณะดาวน์โหลด pdf กฌขึ้นสถานะให้เหร้ความคืบหน้า %

    ถ้าเปิดในไลน์ จะมีขึ้นเตือนให้เปิดใน browser

    ระบบกันสแปม กันกดส่งซ้ำ

    เก็บค่าใน localhost 

    เพื่อตอบโจทย์ระบบที่ “ง่าย เร็ว เสถียร ใช้ได้ทุกอุปกรณ์” และทำงานฝั่ง Client (Localhost) เป็นหลัก โครงสร้างที่เหมาะสมที่สุดคือการใช้ HTML5 + Tailwind CSS (ดีไซน์สะอาดยืดหยุ่น) + Vanilla JavaScript (ไม่ต้องพึ่ง Framework หนาๆ ให้ช้า)

    ส่วนการสร้าง PDF แนะนำให้ใช้ html2pdf.js และการวาดลายเซ็นใช้ signature_pad ซึ่งเสถียรที่สุดในฝั่งเบราว์เซอร์ครับ

    นี่คือโครงสร้างระบบและตัวอย่างโค้ดที่คุณสามารถนำไปวางในไฟล์ index.html เพื่อรันบน Localhost ได้ทันทีครับ:

    1. โครงสร้างระบบ (HTML & JavaScript)

    HTML

    <!DOCTYPE html>
    <html lang="th">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>ระบบฟอร์มและบันทึกข้อมูล (Fast & Stable)</title>
        <!-- Tailwind CSS สำหรับดีไซน์ที่คลีนและรองรับทุกหน้าจอ -->
        <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
        <!-- Libraries สำคัญ: Signature Pad และ HTML2PDF -->
        <script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.7/dist/signature_pad.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
    </head>
    <body class="bg-gray-50 text-gray-800 font-sans min-h-screen p-4 sm:p-8">
    
        <!-- แจ้งเตือนเมื่อเปิดใน LINE Webview -->
        <div id="line-alert" class="hidden fixed top-0 left-0 w-full bg-amber-500 text-white text-center py-3 px-4 z-50 shadow-md font-semibold flex justify-between items-center">
            <span>⚠️ เพื่อการใช้งานที่เสถียรที่สุด (การอัพโหลด/ดาวน์โหลดไฟล์) กรุณากดเปิดด้วยเบราว์เซอร์ปกติ (เช่น Chrome, Safari)</span>
            <button onclick="document.getElementById('line-alert').remove()" class="bg-black/20 hover:bg-black/40 px-2 py-1 rounded text-sm">ปิด</button>
        </div>
    
        <div class="max-w-3xl mx-auto bg-white rounded-xl shadow-sm p-6 sm:p-8 border border-gray-100" id="form-content">
            <h2 class="text-2xl font-bold mb-6 text-gray-900 border-b pb-4">ฟอร์มบันทึกข้อมูลและส่งเอกสาร</h2>
            
            <form id="main-form" onsubmit="handleSubmit(event)" class="space-y-6">
                <!-- ข้อมูลผู้กรอก -->
                <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">ชื่อ-นามสกุล *</label>
                        <input type="text" id="fullname" required class="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">อีเมลผู้ส่ง *</label>
                        <input type="email" id="email" required class="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
                    </div>
                </div>
    
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">รายละเอียดเพิ่มเติม</label>
                    <textarea id="details" rows="3" class="w-full p-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"></textarea>
                </div>
    
                <!-- อัพโหลดไฟล์ + Preview -->
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">อัพโหลดไฟล์ (รูปภาพ หรือ PDF) *</label>
                    <input type="file" id="file-upload" accept="image/*,application/pdf" required onchange="handleFilePreview(this)" class="w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
                    
                    <!-- พื้นที่ Preview -->
                    <div id="preview-container" class="mt-3 hidden border border-dashed border-gray-300 p-2 rounded-lg text-center bg-gray-50">
                        <img id="image-preview" class="max-h-48 mx-auto hidden rounded" />
                        <div id="pdf-preview" class="hidden text-sm text-gray-600 flex items-center justify-center gap-2">
                            📄 <span id="pdf-name"></span>
                        </div>
                    </div>
                </div>
    
                <!-- Signature Pad -->
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">ลงลายมือชื่อ *</label>
                    <div class="border border-gray-300 rounded-lg bg-gray-50 p-1">
                        <canvas id="signature-pad" class="w-full h-40 bg-white rounded border border-gray-100 touch-none"></canvas>
                    </div>
                    <div class="mt-2 text-right">
                        <button type="button" onclick="clearSignature()" class="text-xs text-red-600 hover:underline">ล้างลายเซ็น</button>
                    </div>
                </div>
    
                <!-- ป้องกันสแปมด้วย Honeypot (ซ่อนไว้ไม่ให้คนเห็น แต่บอทจะชอบกรอก) -->
                <input type="text" id="honeypot" class="hidden" autocomplete="off">
    
                <!-- Progress Bar สำหรับการส่งเมล์ / โหลด PDF -->
                <div id="progress-wrapper" class="hidden space-y-2">
                    <div class="flex justify-between text-xs font-semibold text-blue-700">
                        <span id="progress-status">กำลังดำเนินการ...</span>
                        <span id="progress-percent">0%</span>
                    </div>
                    <div class="w-full bg-gray-200 rounded-full h-2.5">
                        <div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full transition-all duration-200" style="width: 0%"></div>
                    </div>
                </div>
    
                <!-- ปุ่มแอคชั่น (ไม่แสดงใน PDF) -->
                <div id="action-buttons" class="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-100">
                    <button type="submit" id="btn-submit" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-4 rounded-lg transition disabled:opacity-50">
                        ส่งข้อมูลเข้าอีเมล
                    </button>
                    <button type="button" id="btn-pdf" onclick="downloadPDF()" class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2.5 px-4 rounded-lg transition disabled:opacity-50">
                        ดาวน์โหลดหน้านี้เป็น PDF
                    </button>
                </div>
            </form>
        </div>
    
        <script>
            let signaturePad;
            let isSubmitting = false;
    
            // 1. ตรวจสอบ LINE และโหลดค่าจาก LocalStorage เมื่อเปิดหน้าเว็บ
            document.addEventListener("DOMContentLoaded", function() {
                // ตรวจสอบว่าเป็น LINE Webview หรือไม่
                const ua = navigator.userAgent || navigator.vendor || window.opera;
                if (ua.indexOf("Line") > -1) {
                    document.getElementById('line-alert').classList.remove('hidden');
                }
    
                // ตั้งค่า Signature Pad
                const canvas = document.getElementById('signature-pad');
                signaturePad = new SignaturePad(canvas);
    
                // ปรับขนาด Canvas ให้เหมาะกับหน้าจออุปกรณ์
                function resizeCanvas() {
                    const ratio = Math.max(window.devicePixelRatio || 1, 1);
                    canvas.width = canvas.offsetWidth * ratio;
                    canvas.height = canvas.offsetHeight * ratio;
                    canvas.getContext("2d").scale(ratio, ratio);
                    signaturePad.clear(); 
                }
                window.addEventListener("resize", resizeCanvas);
                resizeCanvas();
    
                // ดึงข้อมูลเก่าจาก LocalStorage (Localhost) มาใส่ในฟอร์มให้อัตโนมัติ เพื่อความเร็ว
                if(localStorage.getItem('formData')) {
                    const savedData = JSON.parse(localStorage.getItem('formData'));
                    document.getElementById('fullname').value = savedData.fullname || '';
                    document.getElementById('email').value = savedData.email || '';
                    document.getElementById('details').value = savedData.details || '';
                }
            });
    
            // 2. จัดการ Preview ไฟล์
            function handleFilePreview(input) {
                const file = input.files[0];
                const container = document.getElementById('preview-container');
                const imgPreview = document.getElementById('image-preview');
                const pdfPreview = document.getElementById('pdf-preview');
                const pdfName = document.getElementById('pdf-name');
    
                container.classList.remove('hidden');
                imgPreview.classList.add('hidden');
                pdfPreview.classList.add('hidden');
    
                if (file) {
                    if (file.type.startsWith('image/')) {
                        const reader = new FileReader();
                        reader.onload = function(e) {
                            imgPreview.src = e.target.result;
                            imgPreview.classList.remove('hidden');
                        }
                        reader.readAsDataURL(file);
                    } else if (file.type === 'application/pdf') {
                        pdfName.textContent = file.name;
                        pdfPreview.classList.remove('hidden');
                    }
                }
            }
    
            function clearSignature() {
                signaturePad.clear();
            }
    
            // 3. ควบคุมสถานะ Progress Bar
            function updateProgress(status, percent) {
                const wrapper = document.getElementById('progress-wrapper');
                const textStatus = document.getElementById('progress-status');
                const textPercent = document.getElementById('progress-percent');
                const bar = document.getElementById('progress-bar');
    
                wrapper.classList.remove('hidden');
                textStatus.textContent = status;
                textPercent.textContent = `${percent}%`;
                bar.style.width = `${percent}%`;
    
                if(percent >= 100) {
                    setTimeout(() => wrapper.classList.add('hidden'), 1500);
                }
            }
    
            // 4. บันทึกข้อมูลและจำลองการส่งเมล (กันกดซ้ำ + กันสแปม)
            async function handleSubmit(event) {
                event.preventDefault();
                
                // กันกดซ้ำ (Double-click Protection)
                if (isSubmitting) return;
    
                // กันสแปม (Honeypot Protection) - ถ้ามีบอทมากรอกช่องที่ซ่อนไว้ จะปฏิเสธทันที
                if (document.getElementById('honeypot').value !== '') {
                    alert('Spam detected!');
                    return;
                }
    
                if (signaturePad.isEmpty()) {
                    alert('กรุณาลงลายมือชื่อก่อนส่งเอกสาร');
                    return;
                }
    
                isSubmitting = true;
                setButtonsDisabled(true);
    
                // เก็บค่าเข้า LocalStorage ไว้บนคอม/มือถือเครื่องนี้ทันที
                const formData = {
                    fullname: document.getElementById('fullname').value,
                    email: document.getElementById('email').value,
                    details: document.getElementById('details').value
                };
                localStorage.setItem('formData', JSON.stringify(formData));
    
                // จำลองการอัพโหลดและส่ง Mail API ด้วย Real-time Progress Bar
                let progress = 0;
                const interval = setInterval(() => {
                    progress += 10;
                    updateProgress('กำลังเตรียมข้อมูลและส่งอีเมล...', progress);
                    
                    if (progress >= 100) {
                        clearInterval(interval);
                        
                        /**
                         * [NOTE สำหรับระบบหลังบ้าน] 
                         * ตรงนี้ใช้ส่งข้อมูลผ่าน Fetch API ไปยัง Backend (เช่น PHP, Node.js หรือ Email Service เช่น EmailJS)
                         * ตัวอย่าง:
                         * fetch('/api/send-email', { method: 'POST', body: JSON.stringify(...) })
                         */
                        
                        alert('บันทึกข้อมูลและส่งอีเมลเรียบร้อยแล้ว!');
                        isSubmitting = false;
                        setButtonsDisabled(false);
                    }
                }, 150);
            }
    
            // 5. ดาวน์โหลดหน้านี้เป็น PDF + เช็ค % ความคืบหน้า
            function downloadPDF() {
                const element = document.getElementById('form-content');
                const buttons = document.getElementById('action-buttons');
                
                // ซ่อนปุ่มกดไม่ให้ติดเข้าไปในไฟล์ PDF
                buttons.style.display = 'none';
    
                const opt = {
                    margin:       [10, 10, 10, 10],
                    filename:     `Document_${Date.now()}.pdf`,
                    image:        { type: 'jpeg', quality: 0.98 },
                    html2canvas:  { scale: 2, useCORS: true },
                    jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
                };
    
                // แสดง Progress ดาวน์โหลดแบบจำลองให้เข้ากับ html2pdf lifecycle
                updateProgress('กำลังสร้างไฟล์ PDF...', 20);
                
                setTimeout(() => updateProgress('กำลังประมวลผลหน้ากระดาษ...', 50), 300);
                setTimeout(() => updateProgress('จัดเรียงรูปแบบเอกสาร...', 80), 600);
    
                html2pdf().set(opt).from(element).save().then(() => {
                    updateProgress('ดาวน์โหลดสำเร็จ!', 100);
                    // แสดงปุ่มกดกลับมาเหมือนเดิม
                    buttons.style.display = 'flex';
                }).catch(err => {
                    console.error(err);
                    buttons.style.display = 'flex';
                    updateProgress('เกิดข้อผิดพลาด', 0);
                });
            }
    
            function setButtonsDisabled(status) {
                document.getElementById('btn-submit').disabled = status;
                document.getElementById('btn-pdf').disabled = status;
            }
        </script>
    </body>
    </html>
    

    2. จุดเด่นในการทำงานของระบบนี้

    • เสถียรและเร็วเพราะแยกการประมวลผล: การทำ Preview และแปลงหน้าเว็บเป็น PDF จัดการเสร็จสรรพที่ฝั่งผู้ใช้งาน (Client-side) ทำให้ทำงานได้ทันทีโดยไม่สร้างภาระให้ฝั่ง Server ในส่วนนี้
    • การเก็บข้อมูลใน Localhost: ใช้กลไก localStorage ในการเซฟค่าฟิลด์อย่าง ชื่อ หรือ อีเมล ไว้ในเครื่องผู้ใช้ทันทีเมื่อกดปุ่มส่ง ทำให้หากกลับมาเปิดหน้าเว็บนี้อีกครั้งในอนาคต ข้อมูลที่เคยกรอกบ่อยๆ จะถูก Auto-fill ให้เองโดยไม่ต้องพิมพ์ซ้ำ
    • ระบบความปลอดภัยและการป้องกัน:
      • กันกดซ้ำ (Debounce/Lock): ใช้ตัวแปร isSubmitting ปิดการทำงานของปุ่มทันทีที่กดครั้งแรกเพื่อไม่ให้เกิดการส่งข้อมูลซ้อนซ้ำซ้อนในระบบฐานข้อมูล
      • กันสแปมบอท (Honeypot): ใช้ Input ที่ซ่อนไว้ บอทอัตโนมัติจะกรอกฟิลด์นี้โดยไม่รู้ตัว แต่ถ้าผู้ใช้ทั่วไปส่งข้อมูล ฟิลด์นี้จะว่างเปล่า ถ้าระบบพบว่ามีข้อมูลในฟิลด์นี้จะตัดการทำงานทันที
    • การรองรับแอปพลิเคชัน LINE: เพิ่มสคริปต์ตรวจจับ User Agent หากเปิดผ่าน In-App Browser ของ LINE จะแสดงแถบเตือนสีส้มด้านบน แนะนำให้ย้ายไปเปิดที่ Chrome หรือ Safari เพื่อหลีกเลี่ยงปัญหาของระบบ Webview ในตัวแปรแอปที่มักจะจำกัดสิทธิ์ในการสั่งเซฟไฟล์และดาวน์โหลดลงเครื่องโดยตรงครับ


เวอไนน์ไอคอร์ส

ประหยัดเวลากว่า 100 เท่า!






เวอไนน์เว็บไซต์⚡️
สร้างเว็บไซต์ ดูแลเว็บไซต์

Categories


Uncategorized