1. code.gs
function doGet(e) {const page = (e && e.parameter && e.parameter.page) ? e.parameter.page : 'index';let template;// หน้า HTML ที่อนุญาตconst validPages = ['index', 'admin', 'editor', 'user', 'guest'];if (validPages.includes(page)) {template = HtmlService.createTemplateFromFile(page);} else {template = HtmlService.createTemplateFromFile('index')}return template.evaluate().setTitle('ระบบล็อกอินตามบทบาท').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 doLogin(username, password) {try {const ss = SpreadsheetApp.getActiveSpreadsheet();const sheet = ss.getSheetByName('Users');const data = sheet.getDataRange().getValues();for (let i = 1; i < data.length; i++) {if (data[i][0] === username && data[i][1] === password) {return {username: data[i][0],name: data[i][2],role: data[i][3]};}}throw new Error('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง');} catch (error) {throw new Error(error.message);}}function checkSession(username) {try {const ss = SpreadsheetApp.getActiveSpreadsheet();const sheet = ss.getSheetByName('Users');const data = sheet.getDataRange().getValues();for (let i = 1; i < data.length; i++) {if (data[i][0] === username) {return true;}}return false;} catch (error) {return false;}}function include(file){return HtmlService.createHtmlOutputFromFile(file).getContent();}function getURL(){return ScriptApp.getService().getUrl();}
2. index.html
<!DOCTYPE html><html lang="th"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ระบบล็อกอินตามบทบาท</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css" rel="stylesheet"><style>body {background-color: #f8f9fa;height: 100vh;display: flex;align-items: center;justify-content: center;}.login-container {max-width: 400px;width: 100%;padding: 20px;background: white;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);}</style><?!=include('style.css')?></head><body><!-- Login Form --><div class="login-container" id='loginform' style="display: none;"><h2 class="text-center mb-4">ล็อกอิน</h2><div class="mb-3"><label for="username" class="form-label">ชื่อผู้ใช้</label><input type="text" class="form-control" id="username" placeholder="กรอกชื่อผู้ใช้"></div><div class="mb-3"><label for="password" class="form-label">รหัสผ่าน</label><input type="password" class="form-control" id="password" placeholder="กรอกรหัสผ่าน"></div><button class="btn btn-primary w-100" onclick="login()">ล็อกอิน</button></div><!-- Overlay --><div id="overlay"><div class="loader"><canvas id="spiral" width="100" height="100"></canvas><!-- เปลี่ยน src ให้เป็นโลโก้ของคุณ --><img id = "logoloader" logo src="https://semicon.github.io/img/web/logoxxx.png" alt="logo"></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script><?!=include('common.js')?><script>window.addEventListener("load", () => {getUrl(() => {const user = JSON.parse(sessionStorage.getItem('user'));if (user) {google.script.run.withSuccessHandler((isValid) => {if (isValid) {redirectByRole(user.role);} else {sessionStorage.removeItem('user');}}).withFailureHandler(() => {sessionStorage.removeItem('user');}).checkSession(user.username);}document.getElementById('loginform').style.display = 'block';});});function login() {const username = document.getElementById('username').value.trim();const password = document.getElementById('password').value.trim();if (!username || !password) {Swal.fire({icon: 'error',title: 'ข้อผิดพลาด',text: 'กรุณากรอกชื่อผู้ใช้และรหัสผ่าน'});return;}if (!mainUrl) {getUrl(() => doLogin(username, password));} else {doLogin(username, password);}}function doLogin(username, password) {showOverlay();google.script.run.withSuccessHandler(onLoginSuccess).withFailureHandler(onLoginFailure).doLogin(username, password);}function onLoginSuccess(user) {document.getElementById('loginform').style.display = 'none';if (user) {user.mainUrl = mainUrl;sessionStorage.setItem('user', JSON.stringify(user));hideOverlay();Swal.fire({icon: 'success',title: 'ล็อกอินสำเร็จ',text: `ยินดีต้อนรับ ${user.name} (บทบาท: ${user.role})`,timerProgressBar: true,timer: 2500,showConfirmButton: false}).then(() => {redirectByRole(user.role);showOverlay();});}}function onLoginFailure(error) {hideOverlay()Swal.fire({icon: 'error',title: 'ล็อกอินล้มเหลว',text: error});}</script></body></html>
3. style.css
<style>/* ----- Overlay เต็มจอ ----- */#overlay {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(255,255,255,0.8); /* พื้นหลังโปร่งแสง */display: flex;justify-content: center;align-items: center;z-index: 999;transition: opacity 0.5s ease;}/* Loader */.loader {position: relative;width: 100px;height: 100px;}.loader canvas {position: absolute;top: 0;left: 0;}.loader img {position: absolute;top: 50%;left: 50%;width: 35px;height: 35px;transform: translate(-50%, -50%);border-radius: 50%;}/* พื้นหลังโปร่งใสเฉพาะกล่องที่ใช้คลาสนี้ */.swal2-bg-transparent {background: transparent !important;box-shadow: none !important;}/* ให้ progress bar สีแดงเฉพาะกล่องนี้ */.swal2-bg-transparent .swal2-timer-progress-bar {background: red !important;}</style>
4. common.js
<script>const logourl = 'https://semicon.github.io/img/web/logoxxx.png';// โหลด SweetAlert2 ถ้ายังไม่มีในหน้าif (typeof Swal === "undefined") {const swalScript = document.createElement("script");swalScript.src = "https://cdn.jsdelivr.net/npm/sweetalert2@11";document.head.appendChild(swalScript);}let mainUrl = "";// ฟังก์ชันดึง URL จาก serverfunction getUrl(callback) {google.script.run.withSuccessHandler(r => {mainUrl = r;if (typeof callback === "function") callback();}).getURL();}// ฟังก์ชัน redirect (ทำงานได้ทั้งใน iframe และนอก iframe)function redirect(url) {if (window.top === window.self) {// ไม่อยู่ใน iframewindow.location.href = url;} else {hideOverlay()// อยู่ใน iframeSwal.fire({icon: "",title: "",text: "ดำเนินการต่อ...",confirmButtonText: "ตกลง",}).then(() => {showOverlay();setTimeout(() => {window.open(url, "_top");}, 500);});}}// ฟังก์ชัน redirect ตาม rolefunction redirectByRole(role) {const url = new URL(mainUrl);url.searchParams.set("page", role.toLowerCase());redirect(url.toString());}// ฟังก์ชัน logoutfunction logout() {sessionStorage.removeItem("user");Swal.fire({icon: "",title: "",showConfirmButton: false,timerProgressBar: true,timer: 1500,customClass: {popup: 'swal2-bg-transparent'}}).then(() => {redirect(`${mainUrl}`);});}
// ------loader spiral--------------------------------------------const canvas = document.getElementById('spiral');const ctx = canvas.getContext('2d');const w = canvas.width;const h = canvas.height;const cx = w / 2;const cy = h / 2;let rotation = 0;function drawSpiral(rot) {ctx.clearRect(0, 0, w, h);ctx.save();ctx.translate(cx, cy);ctx.rotate(rot);ctx.lineWidth = 2;const maxRadius = 45;const turns = 4.5;const steps = 1000;for (let i = 0; i<steps; i++) {const t1 = i / steps;const t2 = (i + 1) / steps;const theta1 = t1 * turns * Math.PI * 2;const theta2 = t2 * turns * Math.PI * 2;const radius1 = t1 * maxRadius;const radius2 = t2 * maxRadius;const x1 = radius1 * Math.cos(theta1);const y1 = radius1 * Math.sin(theta1);const x2 = radius2 * Math.cos(theta2);const y2 = radius2 * Math.sin(theta2);const hue = (t1 * 360) % 360;ctx.beginPath();ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;ctx.moveTo(x1, y1);ctx.lineTo(x2, y2);ctx.stroke();}ctx.restore();}function animate() {rotation += 0.05;drawSpiral(rotation);requestAnimationFrame(animate);}animate();/* ----- ปิด Overlay หลังโหลดเสร็จ ----- */window.addEventListener('load', () => {setTimeout(() => {const overlay = document.getElementById('overlay');overlay.style.opacity = '0';setTimeout(() => overlay.style.display = 'none', 500);}, 3000); // แสดง 3 วินาที});function showOverlay(){document.getElementById('logoloader').src = logourl;const overlay = document.getElementById('overlay');overlay.style.display = 'block'}function hideOverlay(){const overlay = document.getElementById('overlay');overlay.style.display = 'none'}</script>
5. สร้างไฟล์ admin.html, user.html, editor.html, guest.html
<!DOCTYPE html><html lang="th"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>หน้าผู้ดูแลระบบ</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css" rel="stylesheet"><style>body {background-color: #f8f9fa;padding: 20px;}</style><?!=include('style.css')?></head><body><div class="container"><div id="contentArea" class="d-none"><div class=" text-end"><button class="btn btn-danger" onclick="logout()">ออกจากระบบ</button></div><!-- add Content --><h2>ยินดีต้อนรับ ผู้ดูแลระบบ</h2><p>นี่คือหน้าสำหรับผู้ดูแลระบบ (Admin) คุณสามารถจัดการผู้ใช้และข้อมูลทั้งหมดได้ที่นี่</p></div></div><!-- Overlay --><div id="overlay"><div class="loader"><canvas id="spiral" width="100" height="100"></canvas><!-- เปลี่ยน src ให้เป็นโลโก้ของคุณ --><img id = "logoloader" logo src="https://semicon.github.io/img/web/logoxxx.png" alt="logo"></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script><?!=include('common.js')?><script>let user = null;window.addEventListener("load", () => {user = JSON.parse(sessionStorage.getItem("user"));getUrl(() => {checkAccess();});});function checkAccess() {if (!user || user.role.toLowerCase() !== "xxxx") { // xxxx แก้ไข ตาม role ไฟล์ => admin, user, editor, guestSwal.fire({icon: "error",title: "ไม่มีสิทธิการเข้าถึง",text: "คุณไม่มีสิทธิในการเข้าถึงหน้านี้",timer: 2000,showConfirmButton: false}).then(() => {sessionStorage.removeItem("user");redirect(`${mainUrl}`);});return;}google.script.run.withSuccessHandler((isValid) => {if (!isValid) {Swal.fire({icon: "error",title: "เซสชันไม่ถูกต้อง",text: "กรุณาล็อกอินใหม่",timer: 2000,showConfirmButton: false}).then(() => {sessionStorage.removeItem("user");redirect(`${mainUrl}`);});} else {document.getElementById("contentArea").classList.remove("d-none");}}).withFailureHandler(() => {sessionStorage.removeItem("user");redirect(`${mainUrl}`);}).checkSession(user.username);}</script></body></html>
6. Google Sheet
- ชื่อชีต Users
- ตาราง