DEMO
วิธีใช้
1. เปิดกูเกิลชีต สร้างชีต ชื่อ data
2. สร้างหัวตาราง
2. สร้างหัวตาราง
3. เปิด ส่วนขยาย > Apps Script
4. สร้างไฟล์ และคัดลอกโค้ดด้านล่าง
5. รูปหลัก จะใช้ขนาด ความสูงไม่ต่ำกว่า 620 พิกเซล ความกว้าง 1035 พิกเซล รูปจะจัดกลาง และชิดขอบบน
6. การเปลี่ยนรูปหลัก (index.html)
7. การเปลี่ยนรูปพื้นหลังการ์ด (index.html)
8.เปลี่ยนไอดีชีต (code.gs)
9. เปลี่ยนชื่อชีต (ถ้าต้องการ) (code.gs)
const sheetName = 'data'; // <- แทนที่ชื่อชีทถ้าจำเป็น
.hero-section {
background: linear-gradient(#a3a2a200, #f7f5f500),
url('https://semicon.github.io/img/web/mothersDay.png') top center no-repeat;
color: hsla(0, 0%, 100%, .622);
overflow: hidden;
padding: 80px 0 0 0;
position: relative;
height: 620px;
}
.card-bg {
background: linear-gradient(#ececec33, #fefefe33),
url('https://semicon.github.io/img/web/jasmin.png');
background-size: cover;
background-position: center;
color: blue;
}
// ID Google Sheets
const SPREADSHEET_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'; // <- แทนที่ตรงนี้
const sheetName = 'data'; // <- แทนที่ชื่อชีทถ้าจำเป็น
10. เปลี่ยนพื้นหลังเกียรติบัตร (index.html)
11. เปลี่ยนรูปโลโก้ (index.html <!-- Modal แสดงเกียรติบัตร -->)
#certificate {
width: 297mm;
/* A4 แนวนอน */
height: 210mm;
/* A4 แนวนอน */
position: relative;
background: url('https://semicon.github.io/img/web/bg_cert.png') no-repeat center center;
background-size: cover;
box-sizing: border-box;
padding: 40px;
text-align: center;
}
<img src="https://semicon.github.io/img/web/logoxxx.png" class="logo" alt="Logo">
12. เปลี่ยนรูปลายเซ็น (index.html <!-- Modal แสดงเกียรติบัตร -->)
<img src="https://semicon.github.io/img/web/sign3.png" alt="sign" style=" height:60px;">
13. ถ้าต้องการใช้รูปจากกูเกิลไดรฟให้ใช้ URL ด้านข้างนี้
https://lh3.googleusercontent.com/d/ไอดีของรูปภาพที่จะใช้ในกูเกิลไดรฟ
เช่น https://lh3.googleusercontent.com/d/1UC0cAJdipsckgFFpcmIH2wzZ7ymazrDc
https://lh3.googleusercontent.com/d/ไอดีของรูปภาพที่จะใช้ในกูเกิลไดรฟ
เช่น https://lh3.googleusercontent.com/d/1UC0cAJdipsckgFFpcmIH2wzZ7ymazrDc
โค้ดทั้งหมด
code.gs
// ID Google Sheetsconst SPREADSHEET_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'; // <- แทนที่ตรงนี้function doGet() {return HtmlService.createTemplateFromFile('index').evaluate().setTitle("Project Kru Chian").setFaviconUrl("https://semicon.github.io/img/logo2small.png").addMetaTag('viewport', 'width=device-width , initial-scale=1').setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL).setSandboxMode(HtmlService.SandboxMode.IFRAME)}function include(file){return HtmlService.createHtmlOutputFromFile(file).getContent()}/*** ดึงข้อมูลจาก Google Sheet แล้วคืนเป็น array ของ object* ปรับ SPREADSHEET_ID และ sheetName ตามจริง*/function getSheetData() {const sheetName = 'data'; // <- แทนที่ชื่อชีทถ้าจำเป็นconst ss = SpreadsheetApp.openById(SPREADSHEET_ID);const sheet = ss.getSheetByName(sheetName);if (!sheet) return [];const values = sheet.getDataRange().getDisplayValues();if (values.length <= 1) return []; // ไม่มีข้อมูลหลัง headerconst headers = values[0].map(h => h.toString().trim().toLowerCase());const rows = values.slice(1).reverse();// สมมติคอลัมน์เรียง: timestamp | ชื่อ นามสกุล | หน่วยงาน | จังหวัด | ข้อความreturn rows.map(r => ({timestamp: r[0],fullname: r[1],unit: r[2],province: r[3],message: r[4]}));}// บันทึกข้อมูลลงชีตfunction saveToSheet(data) {try {const sheetName = 'data';const ss = SpreadsheetApp.openById(SPREADSHEET_ID);const sheet = ss.getSheetByName(sheetName);const now = new Date();sheet.appendRow([Utilities.formatDate(now, Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss"),data.fullname,data.unit,data.province,data.message]);return { success: true, message: 'บันทึกข้อมูลเรียบร้อย' };} catch (err) {return { success: false, message: err.toString() };}}
index.html
<!DOCTYPE html><html lang="th"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>การ์ดจาก Google Sheet</title><!-- Bootstrap 5.3 CDN (ตามที่คุณชอบใช้ CDN) --><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css"><!-- Bootstrap JS (ไม่จำเป็นแต่เก็บไว้) --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script><!-- html2pdf --><script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script><style>@import url('https://fonts.googleapis.com/css2?family=Sriracha&display=swap');body {font-family: "Sriracha", sans-serif;background: linear-gradient(135deg, rgba(7, 181, 250, 1) 0%, rgba(116, 221, 247, 1) 20%, rgba(186, 234, 247, 1) 50%, rgba(116, 221, 247, 1) 80%, rgba(7, 181, 250, 1) 100%);margin: 0;}.card {margin-bottom: 16px;}.border-l {border-left: 5px solid #33d;}#pagination .page-item .page-link {cursor: pointer;}.card-message {white-space: pre-wrap;}.card-bg {background: linear-gradient(#ececec33, #fefefe33),url('https://semicon.github.io/img/web/jasmin.png');background-size: cover;background-position: center;color: blue;}h6.card-subtitle {border-bottom: 1px dashed #555;}.hero-section {background: linear-gradient(#a3a2a200, #f7f5f500),url('https://semicon.github.io/img/web/mothersDay.png') top center no-repeat;color: hsla(0, 0%, 100%, .622);overflow: hidden;padding: 80px 0 0 0;position: relative;height: 620px;}.text-title {margin-top: 340px;margin-bottom: 50px;}.svg-text {text-shadow:1px 0 0 #ccc, -1px 0 0 #fff,0 1px 0 #ccc, 0 -1px 0 #fff,2px 0 0 #ccc, -2px 0 0 #fff,0 2px 0 #ccc, 0 -2px 0 #fff,1px 1px 0 #ccc, -1px -1px 0 #fff,1px -1px 0 #ccc, -1px 1px 0 #fff;}/* ======== loader ========= */.overlay-loader {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background: rgba(255, 255, 255, 0.8);z-index: 999;display: flex;flex-direction: column;align-items: center;justify-content: center;font-size: 1.2rem;display: none;backdrop-filter: blur(4px);}.loader-text {font-family: "Sriracha", cursive;margin-top: 15px;color: #333;}.spinner-border.gradient {width: 4rem;height: 4rem;border-width: 0.25em;border-style: solid;border-color: transparent;border-top-color: #fff0;border-radius: 50%;animation: spin 0.8s linear infinite;background: conic-gradient(red, orange, yellow, green, cyan, blue, violet, red);-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 0.3em), black calc(100% - 0.3em));mask: radial-gradient(farthest-side, transparent calc(100% - 0.3em), black calc(100% - 0.3em));}.no-scroll {overflow: hidden !important;}@keyframes spin {to {transform: rotate(360deg);}}/* ======= เกียรติบัตร ======= */.item-center {display: flex;justify-content: center;}#certificate-content {text-align: center;font-family: "Sriracha", sans-serif;color: #000;}#certificate {width: 297mm;/* A4 แนวนอน */height: 210mm;/* A4 แนวนอน */position: relative;background: url('https://semicon.github.io/img/web/bg_cert.png') no-repeat center center;background-size: cover;box-sizing: border-box;padding: 40px;text-align: center;}.logo {height: 100px;margin-bottom: 20px;margin-top: 40px;}.sign {margin-top: 40px;}.cer-title {font-size: 32px;font-weight: bold;margin-top: 8px;}.cer-text {font-size: 20px;margin-top: 8px;line-height: 1.6;}</style></head><body><div class="hero-section"><div class="container"><div class="hero-content text-center"><div class="text-title"><h2 class="text-primary fw-bold mb-3 svg-text">ลงนามถวายพระพรชัยมงคล</h2><h3 class="text-primary mb-4 svg-text nowrap">สมเด็จพระนางเจ้าสิริกิติ์ พระบรมราชินีนาถ พระบรมราชชนนีพันปีหลวง</h3><p class="lead text-primary svg-text">เนื่องในโอกาสวันเฉลิมพระชนมพรรษา วันที่ ๑๒ สิงหาคม ๒๕๖๘</p></div></div></div></div><div class="container"><div id="viewer" style="display: block;"><div class="mt-4 text-center p-3"><a href="#" class="btn btn-primary btn-lg me-3" onclick="toggleDiv('formSubmit'); event.preventDefault();"><i class="bi bi-pen"></i>ลงนามถวายพระพร</a></div><h3 class="mb-4">รายชื่อผู้ลงนามถวายพระพร</h3><!-- ช่องแสดงการ์ด --><div id="cardsRow" class="row"></div><!-- ถ้ายังไม่มีข้อมูล --><div id="noData" class="text-center text-muted" style="display:none;">ยังไม่มีข้อมูล</div><!-- เพจจิเนชั่น --><nav aria-label="Page navigation" class="pt-3"><ul id="pagination" class="pagination justify-content-center"></ul></nav></div><div id="formSubmit" class="mt-5" style="display: none;"><?!= include('form') ?></div></div><footer class="bg-primary text-center text-light p-3 mt-5">Copyright © 2025 KruChian</footer><!-- ****** Preloader Overlay ****** --><div id="overlayLoader" class="overlay-loader"><div class="spinner-border gradient" role="status"><span class="visually-hidden">กำลังโหลด...</span></div><div class="loader-text text-danger">กำลังประมวลผล กรุณารอสักครู่...</div></div><!-- ***** Modal แสดงเกียรติบัตร ***** --><div class="modal fade" id="certificateModal" tabindex="-1"><div class="modal-dialog modal-fullscreen modal-dialog-scrollable"><div class="modal-content"><div class="modal-body item-center"><div id="certificate"><div id="certificate-content"><img src="https://semicon.github.io/img/web/logoxxx.png" class="logo" alt="Logo"><div class="cer-title mx-3">โรงเรียนวิเชียรวิทยา</div><div class="cer-text">ขอมอบเกียรติบัตรฉบับนี้ให้ไว้ เพื่อแสดงว่า</div><div class="cer-title mx-2" id="cer-name"></div><div class="cer-text ">ได้ร่วมลงนามถวายพระพร<br>เนื่องในโอกาสวันเฉลิมพระชนมพรรษา<br>สมเด็จพระนางเจ้าสิริกิติ์ พระบรมราชินีนาถ พระบรมราชชนนีพันปีหลวง<br></div><div class="cer-text">เมื่อวันที่ <span id="cer-date"></span></div><div class="sign"><img src="https://semicon.github.io/img/web/sign3.png" alt="sign" style=" height:60px;"></div><div class="cer-text">(นายวิเชียร พุ่มพวง)</div><div class="cer-text">ผู้อำนวยการ</div></div></div></div><div class="modal-footer" style="justify-content: center;"><button class="btn btn-primary me-3" onclick="printCertificate()">พิมพ์เกียรติบัตร</button><button class="btn btn-danger" data-bs-dismiss="modal">ปิด</button></div></div></div></div><script>let allData = [];const pageSize = 9;let currentPage = 1;function loadData() {google.script.run.withSuccessHandler(function(data){allData = data || [];if (allData.length === 0) {document.getElementById('noData').style.display = 'block';document.getElementById('cardsRow').innerHTML = '';document.getElementById('pagination').innerHTML = '';hideLoader()} else {document.getElementById('noData').style.display = 'none';renderPage(1);renderPagination();}}).getSheetData();}function renderPage(page) {currentPage = page;const start = (page - 1) * pageSize;const pageItems = allData.slice(start, start + pageSize);const row = document.getElementById('cardsRow');row.innerHTML = '';pageItems.forEach((item, index) => {const col = document.createElement('div');col.className = 'col-12 col-md-6 col-lg-4 py-2';const card = document.createElement('div');card.className = 'card h-100';card.classList.add('card-bg');if (index % 2 === 0) {card.style.borderLeft = '5px solid #0D6EFD';} else {card.style.borderLeft = '5px solid #3a3';}const cardBody = document.createElement('div');cardBody.className = 'card-body d-flex flex-column';const title = document.createElement('h5');title.className = 'card-title';title.className = 'pt-2';title.innerHTML = `<i class="bi bi-person-vcard"></i> ${item.fullname}` || '-';const sub = document.createElement('h6');sub.className = 'card-subtitle pb-2 text-dark';sub.innerHTML = `<i class="bi bi-buildings"></i> ${item.unit || '-'} <br><i class="bi bi-geo-alt"></i> ${item.province || '-'}`;const msg = document.createElement('p');msg.className = 'card-message mt-3 mb-0';msg.textContent = item.message || '';const time = document.createElement('small');time.className = 'text-muted text-end pt-2';time.textContent = item.timestamp || '';const btn = document.createElement('button');btn.className = 'btn btn-success btn-sm mt-3';btn.innerHTML = '<i class="bi bi-award"></i> ดูเกียรติบัตร';btn.addEventListener('click', () => showCertificate(item));cardBody.appendChild(title);cardBody.appendChild(sub);cardBody.appendChild(msg);cardBody.appendChild(time);cardBody.appendChild(btn);card.appendChild(cardBody);col.appendChild(card);row.appendChild(col);});updatePaginationActive();window.scrollTo({ top: 0, behavior: 'smooth' });hideLoader()}function renderPagination() {const total = allData.length;const totalPages = Math.ceil(total / pageSize);const pag = document.getElementById('pagination');pag.innerHTML = '';if (totalPages <= 1) return;const prevLi = document.createElement('li');prevLi.className = 'page-item' + (currentPage === 1 ? ' disabled' : '');prevLi.innerHTML = `<a class="page-link" href="javascript:void(0)" aria-label="Previous">«</a>`;prevLi.addEventListener('click', () => { if (currentPage > 1) renderPage(currentPage - 1); });pag.appendChild(prevLi);for (let i = 1; i <= totalPages; i++) {const li = document.createElement('li');li.className = 'page-item' + (i === currentPage ? ' active' : '');li.innerHTML = `<a class="page-link" href="javascript:void(0)">${i}</a>`;li.addEventListener('click', () => renderPage(i));pag.appendChild(li);}const nextLi = document.createElement('li');nextLi.className = 'page-item' + (currentPage === totalPages ? ' disabled' : '');nextLi.innerHTML = `<a class="page-link" href="javascript:void(0)" aria-label="Next">»</a>`;nextLi.addEventListener('click', () => { if (currentPage < totalPages) renderPage(currentPage + 1); });pag.appendChild(nextLi);}function updatePaginationActive() {const pag = document.getElementById('pagination');if (!pag) return;const items = pag.querySelectorAll('.page-item');items.forEach(li => li.classList.remove('active'));const pageLinks = pag.querySelectorAll('.page-link');pageLinks.forEach(link => {if (parseInt(link.textContent) === currentPage) {link.parentElement.classList.add('active');}});const totalPages = Math.ceil(allData.length / pageSize);if (pageLinks.length > 0) {const firstLi = pag.querySelector('li:first-child');const lastLi = pag.querySelector('li:last-child');if (firstLi) firstLi.classList.toggle('disabled', currentPage === 1);if (lastLi) lastLi.classList.toggle('disabled', currentPage === totalPages);}}function toggleDiv(idToShow) {const viewer = document.getElementById('viewer');const formSubmit = document.getElementById('formSubmit');if (idToShow === 'formSubmit') {viewer.style.display = 'none';formSubmit.style.display = 'block';} else {viewer.style.display = 'block';formSubmit.style.display = 'none';}}document.getElementById('dataForm').addEventListener('submit', function(e) {e.preventDefault();showLoader()const formData = Object.fromEntries(new FormData(this).entries());google.script.run.withSuccessHandler(function(res) {const status = document.getElementById('status');if (res.success) {status.innerHTML = `<div class="alert alert-success">${res.message}</div>`;document.getElementById('dataForm').reset();loadData()setTimeout(() => {toggleDiv("viewer")hideLoader()status.innerHTML = ""}, 1000);} else {status.innerHTML = `<div class="alert alert-danger">${res.message}</div>`;}}).saveToSheet(formData);});function showCertificate(item) {document.getElementById('cer-name').textContent = item.fullname;document.getElementById('cer-date').textContent = item.timestamp;new bootstrap.Modal(document.getElementById('certificateModal')).show();}function printCertificate() {window.print();}function showLoader() {document.body.classList.add('no-scroll');document.getElementById("overlayLoader").style.display = "flex";}function hideLoader() {document.body.classList.remove('no-scroll');document.getElementById("overlayLoader").style.display = "none";}document.addEventListener('DOMContentLoaded', loadData);showLoader()/** print PDF*/function printCertificate() {const element = document.getElementById('certificate');const opt = {margin: 0,filename: 'certificate.pdf',image: { type: 'jpeg', quality: 1 },html2canvas: { scale: 2, useCORS: true, scrollY: 0 },jsPDF: { unit: 'mm', format: 'a4', orientation: 'landscape' }};html2pdf().set(opt).from(element).save();}</script></body></html>
form.html
<div class="container px-3"><div class="row justify-content-center"><div class="col-md-8"><div class="card p-3"><h3 class="mb-4">ลงนามถวายพระพรชัยมงคล</h3><form id="dataForm"><div class="mb-3"><label class="form-label">ชื่อ นามสกุล</label><input type="text" class="form-control" name="fullname" required></div><div class="mb-3"><label class="form-label">หน่วยงาน</label><input type="text" class="form-control" name="unit" required></div><div class="mb-3"><label class="form-label">จังหวัด</label><input type="text" class="form-control" name="province" required></div><div class="mb-3"><label class="form-label">ข้อความถวายพระพร</label><select class="form-select" name="message" required><option value="" selected>เลือก...</option><option value="ขอพระองค์ทรงพระเจริญ">ขอพระองค์ทรงพระเจริญ</option><option value="ขอพระองค์ทรงพระเจริญยิ่งยืนนาน">ขอพระองค์ทรงพระเจริญยิ่งยืนนาน</option><option value="ขอพระองค์ทรงมีพระพลานามัยที่สมบูรณ์แข็งแรง">ขอพระองค์ทรงมีพระพลานามัยที่สมบูรณ์แข็งแรง</option><option value="ขออานุภาพแห่งคุณพระศรีรัตนตรัย โปรดดลบันดาลให้ทรงพระเกษมสำราญ">ขออานุภาพแห่งคุณพระศรีรัตนตรัย โปรดดลบันดาลให้ทรงพระเกษมสำราญ</option><option value="ทรงพระเจริญ ด้วยเกล้าด้วยกระหม่อม ขอเดชะ">ทรงพระเจริญ ด้วยเกล้าด้วยกระหม่อม ขอเดชะ</option><option value="ข้าพระพุทธเจ้า ขอถวายพระพรชัยมงคล ขอจงทรงพระสิริสวัสดิ์">ข้าพระพุทธเจ้า ขอถวายพระพรชัยมงคล ขอจงทรงพระสิริสวัสดิ์</option></select></div><!-- <div class="mb-3"><label class="form-label">ข้อความ</label><textarea class="form-control" name="message" rows="3" required></textarea></div> --><div class="text-center"><button type="submit" class="btn btn-primary">บันทึก</button><a href="#" class="btn btn-success" onclick="toggleDiv('viewer'); event.preventDefault();">ดูข้อมูล</a></div></form></div></div></div><div id="status" class="mt-3"></div></div>