DEMO
1. สร้าง Sheet setConf
2. สร้าง Sheet Data
3. สร้างไฟล์ index.html
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UPLOAD SLIP PAYMENT PAPER WORK</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@700&family=K2D:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');
body {
margin: 0;
padding: 0;
font-family: 'K2D', sans-serif;
background: linear-gradient(90deg, rgba(2, 0, 36, 1) 0%, rgba(9, 9, 121, 1) 0%, rgba(0, 212, 255, 1) 50%, rgba(9, 9, 121, 1) 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: auto;
}
/* Content */
.container {
margin: 20px 0px;
background: rgba(9, 9, 121, 1);
border-radius: 20px;
padding: 40px 30px;
max-width: 400px;
text-align: center;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.5);
border: 2px solid rgba(0, 212, 255, 0.8);
}
.image-container {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 15px;
}
h1 {
font-size: 21px;
color: #ccc;
margin-bottom: 20px;
}
a {
color: #ddd;
text-decoration: none;
}
.upload-text-label {
font-size: 18px;
font-weight: 600;
color: #00d4ff;
cursor: pointer;
}
#file-input {
display: none;
}
.preview-container {
margin-top: 30px;
display: none;
}
.preview {
max-width: 100%;
border-radius: 10px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
display: block;
}
.card-footer {
color: #ddd;
text-align: center;
margin-top: 30px;
font-family: 'Caveat', sans-serif;
font-size: 18px;
display: grid;
align-items: center;
justify-content: center;
}
/* Preloader */
.loading-container {
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
display: none;
}
.loading-text {
width: 100%;
font-size: 20px;
font-weight: 600;
color: rgb(255, 0, 242);
animation: fadeText 1.5s infinite alternate;
padding: 10px 0px;
}
@keyframes fadeText {
0% {
opacity: 0.3;
}
100% {
opacity: 1;
}
}
.dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 5px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
animation: bounce 1.5s infinite ease-in-out;
}
.dot:nth-child(1) {
background: green;
animation-delay: 0s;
}
.dot:nth-child(2) {
background: red;
animation-delay: 0.2s;
}
.dot:nth-child(3) {
background: orange;
animation-delay: 0.4s;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
/* Sweet alert */
.colored-toast.swal2-icon-success {
background-color: #a5dc86 !important;
}
.colored-toast.swal2-icon-error {
background-color: #f27474 !important;
}
.colored-toast.swal2-icon-warning {
background-color: #f8bb86 !important;
}
.colored-toast.swal2-icon-info {
background-color: #3fc3ee !important;
}
.colored-toast.swal2-icon-question {
background-color: #87adbd !important;
}
.colored-toast .swal2-title {
color: white;
}
.colored-toast .swal2-close {
color: white;
}
.colored-toast .swal2-html-container {
color: white;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="image-container">
<img src="https://semicon.github.io/img/LOGOKRUCHIANgrow.png" height="50" alt="logo">
</div>
<h1>ยืนยันสลิปการชำระเงินของธนาคาร</h1>
<!-- Upload File -->
<label for="file-input" class="upload-text-label">คลิกเพื่อเลือกสลิป</label>
<input type="file" id="file-input" accept=".jpg, .jpeg, .png, image/jpeg, image/png">
<div class="preview-container">
<!-- Bank Slip -->
<div class="image-container">
<img id="preview" class="preview" src="" alt="ตัวอย่างภาพ">
</div>
<!-- Pre-loader -->
<div class="loading-container" id="loading-container">
<div class="loading-text" id="loading-text"></div>
<div class="dots">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
</div>
<!-- Copyright -->
<div class="card-footer">
<p>Copyleft
<img src="https://mirrors.creativecommons.org/presskit/icons/heart.red.png" width="30" height="30" alt="cc">
2022 -
<script>document.write(new Date().getFullYear())</script>,
by <a href="https://guruchian.blogspot.com/" target="_blank"> Dr.Wichian Ph.</a></p>
<p>วิเชียร พุ่มพวง</p>
</div>
</div>
<!-- JS -->
<script src="https://unpkg.com/jsqr/dist/jsQR.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tesseract.js/6.0.1/tesseract.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/tesseract.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const fileInput = document.getElementById('file-input');
const preview = document.getElementById('preview');
const previewContainer = document.querySelector('.preview-container');
const loadingContainer = document.getElementById('loading-container');
const loadingText = document.getElementById('loading-text');
/**
* 0 เปลี่ยนพื้นหลังเป็นขาว
* */
function changeBackgroundToWhite(imageData) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = imageData;
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width+50;
canvas.height = img.height+50;
const ctx = canvas.getContext('2d');
// เติมสีขาวให้เป็นพื้นหลังเริ่มต้น
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// วาดรูปทับลงบนพื้นหลังสีขาว
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// ค่า Threshold สำหรับตรวจจับพื้นหลังสีอ่อนและตัวอักษรสีเทา
const backgroundThreshold = 200; // สำหรับพื้นหลังสีอ่อน
const textThreshold = 130; // สำหรับตัวอักษรสีเทา
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// เปลี่ยนพื้นหลังสีอ่อนเป็นสีขาว
if (r > backgroundThreshold && g > backgroundThreshold && b > backgroundThreshold) {
data[i] = 255; // Red
data[i + 1] = 255; // Green
data[i + 2] = 255; // Blue
data[i + 3] = 255; // Opacity
}
// เปลี่ยนตัวอักษรสีเทาเป็นสีดำ
else if (r < textThreshold && g < textThreshold && b < textThreshold) {
data[i] = 0; // Red
data[i + 1] = 0; // Green
data[i + 2] = 0; // Blue
data[i + 3] = 255; // Opacity
}
}
ctx.putImageData(imageData, 0, 0);
const newImage = canvas.toDataURL("image/png");
preview.src = newImage; // แสดงผลภาพที่ปรับสีแล้ว
// ส่งค่า newImage กลับไปเมื่อกระบวนการเสร็จสิ้น
resolve(newImage);
};
img.onerror = (error) => {
reject(error); // หากเกิดข้อผิดพลาด
};
});
}
/**
* 1. เรียกใช้งานเมื่อคลิกเลือกรูป
* */
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
previewContainer.style.display = 'block';
const reader = new FileReader();
reader.onload = async (event) => {
preview.src = event.target.result;
loadingContainer.style.display = 'block';
loadingText.innerHTML = 'กำลังปรับสีภาพ...';
try {
const newImage = await changeBackgroundToWhite(event.target.result); // รอให้ปรับสีเสร็จ
loadingText.innerHTML = 'กำลังตรวจสอบคิวอาร์โค้ด...';
checkQRCode(newImage); // เรียกฟังก์ชันตรวจสอบ QR Code
} catch (error) {
console.error('เกิดข้อผิดพลาดในการปรับสีภาพ:', error);
Swal.fire({
toast: true,
position: 'center',
iconColor: 'white',
customClass: {
popup: 'colored-toast',
},
title: 'เกิดข้อผิดพลาด',
text: 'ไม่สามารถปรับสีภาพได้',
icon: 'error',
confirmButtonText: 'ตกลง',
});
}
};
reader.readAsDataURL(file);
}
});
/**
* 2. ตรวจสอบคิวอาร์โค้ด
*/
function checkQRCode(imageData) {
const img = new Image();
img.src = imageData;
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const qrCode = jsQR(imgData.data, canvas.width, canvas.height);
if (qrCode && qrCode.data) {
const dataCode = qrCode.data;
// ตรวจสอบคิวอาร์โค้ด
const data = dataCode.includes("TH") ? dataCode.split("TH")[1] : null;
if (data) {
checkName(canvas,qrCode.data);
} else {
Swal.fire({
toast: true,
position: 'center',
iconColor: 'white',
customClass: {
popup: 'colored-toast',
},
title: 'พบ QR Code!',
html: `รหัส QR Code ไม่ถูกต้อง! <br>รหัส: ${dataCode}`,
icon: 'error',
confirmButtonText: 'ลองใหม่',
}).then(() => clearForm());
}
} else {
Swal.fire({
toast: true,
position: 'center',
iconColor: 'white',
customClass: {
popup: 'colored-toast',
},
title: 'ไม่พบ QR Code!',
text: 'สลิปไม่มี QR Code หรือ อ่าน QR Code ไม่ได้!',
icon: 'error',
confirmButtonText: 'ลองใหม่',
}).then(() => clearForm());
}
};
}
/**
* 3. ตรวจสอบชื่อบัญชีของผู้รับโอน
*/
function checkName(canvas, qrCodeStr) {
loadingContainer.style.display = 'block';
loadingText.innerHTML = 'กำลังตรวจสอบรหัสปลายทาง...';
Tesseract.recognize(canvas, 'tha+eng', {
langPath: 'https://tessdata.projectnaptha.com/4.0.0_best/',
logger: (m) => console.log(m)
}).then(({ data: { text } }) => {
const imageBase64 = canvas.toDataURL("image/png").replace(/^data:image\/(png|jpg);base64,/, ""); // Convert canvas to Base64
const slipText = text.replace(/\s/g, '');
console.log(slipText)
if (slipText) {
google.script.run.withSuccessHandler(function(res){
clearForm()
if(res.success){
Swal.fire({
toast: true,
position: 'center',
iconColor: 'white',
customClass: {
popup: 'colored-toast',
},
title: 'สำเร็จ!',
text: 'ข้อมูลตรงกับในระบบ',
icon: 'success',
timer: 1500,
showConfirmButton: false,
timerProgressBar: true,
})
} else {
Swal.fire({
toast: true,
position: 'center',
iconColor: 'white',
customClass: {
popup: 'colored-toast',
},
title: 'ไม่สำเร็จ!',
text: 'ข้อมูลไม่ตรงกับระบบ',
icon: 'error',
confirmButtonText: 'ลองใหม่',
})
}
}).saveToSheet({slipText, qrCodeStr, imageBase64});
}
});
}
/**
* 4. Clear form
*/
function clearForm() {
fileInput.value = "";
preview.src = "";
previewContainer.style.display = "none";
}
</script>
</body>
</html>
4. สร้างไฟล์ code.gs
function doGet() {
return HtmlService.createTemplateFromFile('index').evaluate()
.setTitle("Zoom_09_02_2568_read_payment_slips")
.setFaviconUrl("https://semicon.github.io/img/logo2small.png")
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
/*** save to Sheet ***/
function saveToSheet(obj) { // {slipText, qrCodeStr, imageBase64}
Logger.log(obj.slipText)
const sheetConf = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("setConf");
const accountName = sheetConf.getRange("B2").getValue().trim();
const folderId = sheetConf.getRange("B3").getValue()
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
const blob = Utilities.newBlob(Utilities.base64Decode(obj.imageBase64), 'image/png',
'Slip-'+obj.qrCodeStr.split("TH")[1]+'.png'); // ชื่อไฟล์
const folder = DriveApp.getFolderById(folderId);
const date = new Date();
const options = { day: 'numeric', month: 'long', year: 'numeric'};
const normalize = str => str.replace(/\s+/g, '');
if (normalize(obj.slipText).includes(normalize(accountName))) {
const file = folder.createFile(blob);
const fileUrl = file.getUrl();
sheet.appendRow([
'',
date.toLocaleDateString('th-TH', options),
obj.slipText,
obj.qrCodeStr,
fileUrl
]);
return { success: true };
}else{
return {success: false}
}
}
หากถูกใจโปรดสนับสนุนเป็นกำลังใจกันด้วยนะครับ