EasyGAS IDE

โค้ดที่แชร์

E-Portfolio

bohi******@gmail.comPRO· แชร์เมื่อ 18 ต.ค. 69
ระบบแฟ้มสะสมผลงานและรางวัลสำหรับสถานศึกษา พร้อมหน้าสรุปสถิติและส่งออกข้อมูลเป็น Excel เหมาะสำหรับรวบรวมผลงานครู นักเรียน และผู้บริหาร หลังโคลนต้องเตรียม Google Sheet และโฟลเดอร์ Google Drive แล้วใส่รหัสอ้างอิงในโค้ด ตั้งชื่อผู้ใช้และรหัสผ่านผู้ดูแลใหม่ พร้อมปรับโลโก้และลิงก์ให้เป็นของตนเองก่อนเผยแพร่เป็นเว็บแอป
โค้ดที่แชร์ · E-Portfolio3 ไฟล์ · 72.5 KB · เวอร์ชัน 1 · 2 คำเตือน · โคลนแล้ว 0 ครั้ง
easygaside.tech/s/sc324gxqbq
ไฟล์Code.gs423 บรรทัดappsscript.json23 บรรทัดindex.html870 บรรทัด
สิทธิ์ที่ Google จะถามตอนเผยแพร่ไม่ได้ระบุใน appsscript.json — Apps Script จะขอตามที่โค้ดใช้SheetsDrive
ก่อนใช้ เช็กให้ดี: โค้ดนี้มีค่าที่เป็นของคนแชร์ ต้องเปลี่ยนเป็นของคุณหลังโคลน — ID ไฟล์ Google (Sheets/Drive/Docs) (Code.gs:2) · ID ไฟล์ Google (Sheets/Drive/Docs) (Code.gs:3)
โดย bohi******@gmail.comPRO

ไฟล์ทั้งหมด

อ่านก่อนโคลนได้ ใน EasyGAS IDE จะได้ไฟล์ชุดนี้เป็นโปรเจกต์ใหม่ แล้วค่อยเผยแพร่เข้าบัญชี Google ของคุณ

1// --- การตั้งค่าหลัก ---2const SHEET_ID = '1aCNz_GawlSucEA2dU2JTQdbiH369d0isxCRKNeoN3wY'; // <<< ใส่ Sheet ID ของคุณที่นี่3const ROOT_DRIVE_FOLDER_ID = '1DHetP91PYS80RtTTVXL-IyyaBzTDjoWf'; // <<< ใส่ Folder ID หลักของคุณที่นี่45// --- ตั้งค่าผู้ดูแลระบบ ---6const ADMIN_USER = 'admin';7const ADMIN_PASS = '34020077';89const ss = SpreadsheetApp.openById(SHEET_ID);10let dataSheet;1112/**13 * ฟังก์ชัน doGet หลักสำหรับแสดงผลหน้าเว็บ14 */15function doGet(e) {16  const template = HtmlService.createTemplateFromFile('index');17  return template.evaluate()18    .setTitle('E- Portfolio')19    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);20}2122/**23 * ฟังก์ชันสำหรับสร้างและส่งออกไฟล์ Excel24 * @param {string} period ช่วงเวลาที่ต้องการส่งออก ('YYYY-MM', 'month', 'week', 'all')25 * @returns {object} อ็อบเจกต์ที่ประกอบด้วยข้อมูลไฟล์ Excel แบบ base64 และชื่อไฟล์26 */27function generateExcelExport(period) {28  try {29    initializeSheet_();30    const allValues = dataSheet.getDataRange().getValues();31    if (allValues.length <= 1) {32      throw new Error('ไม่มีข้อมูลสำหรับส่งออก');33    }34    const headers = allValues[0];35    const dataRows = allValues.slice(1);36    const timestampIndex = headers.indexOf('timestamp');3738    // กรองข้อมูลตาม period ที่เลือก39    const filteredRows = filterDataByPeriod_(dataRows, timestampIndex, period);4041    if (filteredRows.length === 0) {42      return { success: false, message: 'ไม่พบข้อมูลในช่วงเวลาที่เลือก' };43    }4445    // สร้าง Spreadsheet ชั่วคราว46    const exportSs = SpreadsheetApp.create(`Export_Awards_${new Date().getTime()}`);47    const exportSheet = exportSs.getSheets()[0];48    49    // เพิ่ม Headers และข้อมูลที่กรองแล้ว50    exportSheet.appendRow(headers);51    filteredRows.forEach(row => exportSheet.appendRow(row));52    53    exportSheet.getRange(1, 1, 1, headers.length).setFontWeight('bold');54    SpreadsheetApp.flush(); // บังคับให้บันทึกการเปลี่ยนแปลง5556    const exportFileId = exportSs.getId();57    const url = `https://docs.google.com/spreadsheets/d/${exportFileId}/export?format=xlsx`;58    const params = {59      method: 'get',60      headers: { 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken() },61      muteHttpExceptions: true62    };63    64    const blob = UrlFetchApp.fetch(url, params).getBlob();65    const base64Data = Utilities.base64Encode(blob.getBytes());66    const fileName = `สรุปผลรางวัล_${period}_${Utilities.formatDate(new Date(), 'GMT+7', 'yyyy-MM-dd')}.xlsx`;67    68    // ลบ Spreadsheet ชั่วคราว69    DriveApp.getFileById(exportFileId).setTrashed(true);7071    return { success: true, data: base64Data, filename: fileName };7273  } catch (error) {74    console.error('Error in generateExcelExport:', error.stack);75    return { success: false, message: `เกิดข้อผิดพลาดในการสร้าง Excel: ${error.message}` };76  }77}7879/**80 * Helper function สำหรับกรองข้อมูลตามช่วงเวลา81 */82function filterDataByPeriod_(dataRows, timestampIndex, period) {83    if (!period || period === 'all') {84      return dataRows;85    }86    87    const now = new Date();88    let startDate, endDate;8990    if (/^\d{4}-\d{2}$/.test(period)) { // รูปแบบ YYYY-MM91      const [year, month] = period.split('-').map(Number);92      startDate = new Date(year, month - 1, 1);93      endDate = new Date(year, month, 0, 23, 59, 59, 999);94    } else if (period === 'month') {95      startDate = new Date(now.getFullYear(), now.getMonth(), 1);96      endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);97    } else if (period === 'week') {98      startDate = new Date(now);99      startDate.setDate(startDate.getDate() - startDate.getDay());100      startDate.setHours(0, 0, 0, 0);101      endDate = new Date(startDate);102      endDate.setDate(endDate.getDate() + 6);103      endDate.setHours(23, 59, 59, 999);104    }105106    if (startDate && endDate) {107      return dataRows.filter(row => {108        const rowDate = new Date(row[timestampIndex]);109        return rowDate >= startDate && rowDate <= endDate;110      });111    }112    return dataRows; // ถ้าไม่มีเงื่อนไข ให้คืนค่าทั้งหมด113}114115116function getSummaryData(period = 'month') {117  try {118    initializeSheet_();119    const range = dataSheet.getDataRange();120    const values = range.getValues();121    if (values.length <= 1) {122      return { success: true, total: 0, teacher: 0, student: 0, executive: 0, levels: { 'ระดับสถานศึกษา': 0, 'ระดับจังหวัด': 0, 'ระดับภาค': 0, 'ระดับชาติ': 0 }, periodTitle: 'ยังไม่มีข้อมูล' };123    }124    const headers = values[0];125    const dataRows = values.slice(1);126    const timestampIndex = headers.indexOf('timestamp');127    const typeIndex = headers.indexOf('type');128    const levelIndex = headers.indexOf('level');129130    if (timestampIndex === -1 || typeIndex === -1 || levelIndex === -1) {131      throw new Error('ไม่พบคอลัมน์ที่จำเป็นในชีต');132    }133    134    let periodTitle = '';135    const filteredRows = filterDataByPeriod_(dataRows, timestampIndex, period);136    const now = new Date();137138    if (/^\d{4}-\d{2}$/.test(period)) {139        const [year, month] = period.split('-').map(Number);140        const date = new Date(year, month - 1, 1);141        periodTitle = 'สรุปผล' + new Intl.DateTimeFormat('th-TH', { month: 'long', year: 'numeric' }).format(date);142    } else if (period === 'month') {143        periodTitle = 'สรุปผล' + new Intl.DateTimeFormat('th-TH', { month: 'long', year: 'numeric' }).format(now);144    } else if (period === 'week') {145        let startDate = new Date(now);146        startDate.setDate(startDate.getDate() - startDate.getDay());147        const startFormatted = new Intl.DateTimeFormat('th-TH', { day: 'numeric', month: 'long', year: 'numeric' }).format(startDate);148        periodTitle = `สรุปผลประจำสัปดาห์ (เริ่ม ${startFormatted})`;149    } else {150        periodTitle = 'สรุปผลรางวัลทั้งหมด';151    }152153    let teacherCount = 0;154    let studentCount = 0;155    let executiveCount = 0;156    const levelsCount = { 'ระดับสถานศึกษา': 0, 'ระดับจังหวัด': 0, 'ระดับภาค': 0, 'ระดับชาติ': 0 };157    filteredRows.forEach(row => {158      const type = row[typeIndex];159      if (type === 'ครู') teacherCount++;160      else if (type === 'นักเรียน') studentCount++;161      else if (type === 'ผู้บริหาร') executiveCount++;162      const level = row[levelIndex];163      if (levelsCount.hasOwnProperty(level)) levelsCount[level]++;164    });165    return { success: true, total: filteredRows.length, teacher: teacherCount, student: studentCount, executive: executiveCount, levels: levelsCount, periodTitle: periodTitle };166  } catch (error) {167    console.error('Error in getSummaryData:', error.stack);168    return { success: false, message: error.message };169  }170}171172// --- ฟังก์ชันจัดการโฟลเดอร์ ---173function getOrCreateFolder_(parentFolder, folderName) {174  const folders = parentFolder.getFoldersByName(folderName);175  if (folders.hasNext()) {176    return folders.next();177  } else {178    return parentFolder.createFolder(folderName);179  }180}181182function getTargetFolder_(level, type) {183  const rootFolder = DriveApp.getFolderById(ROOT_DRIVE_FOLDER_ID);184  185  if (!level || !type) {186      throw new Error('กรุณาระบุ "ระดับรางวัล" และ "ประเภท" ให้ครบถ้วน');187  }188189  const levelFolder = getOrCreateFolder_(rootFolder, level);190  const typeFolder = getOrCreateFolder_(levelFolder, type);191  192  return typeFolder;193}194195196// --- ฟังก์ชันอื่นๆ ---197function updateAward(awardData, certificateFileObject, photoFileObjects) {198  try {199    if (!awardData || !awardData.id) {200      throw new Error('ไม่พบ ID ของข้อมูลที่ต้องการแก้ไข');201    }202    initializeSheet_();203    const range = dataSheet.getDataRange();204    const values = range.getValues();205    const headers = values[0];206    const idColIndex = headers.indexOf('id');207    if (idColIndex === -1) {208      throw new Error('ไม่พบคอลัมน์ "id" ในชีต');209    }210    const rowIndexToUpdate = values.findIndex(row => row[idColIndex] === awardData.id);211    if (rowIndexToUpdate === -1) {212      return { success: false, message: 'ไม่พบข้อมูลที่ต้องการแก้ไข' };213    }214    215    const folder = getTargetFolder_(awardData.level, awardData.type);216217    let currentCertUrl = values[rowIndexToUpdate][headers.indexOf('certificateUrl')];218    let currentPhotoUrls = values[rowIndexToUpdate][headers.indexOf('photoUrls')];219220    if (certificateFileObject) {221      if (currentCertUrl) deleteFileFromDrive_(currentCertUrl);222      const certFileId = uploadFile_(certificateFileObject, folder, 'certificate');223      currentCertUrl = certFileId ? `https://drive.google.com/uc?id=${certFileId}` : '';224    }225    if (photoFileObjects && photoFileObjects.length > 0) {226      if (currentPhotoUrls) {227        currentPhotoUrls.toString().split(',').forEach(url => {228          if (url.trim()) deleteFileFromDrive_(url.trim());229        });230      }231      const newPhotoUrls = [];232      photoFileObjects.forEach((file, i) => {233        if (file && file.name) {234          const photoFileId = uploadFile_(file, folder, `photo_${i + 1}`);235          if (photoFileId) {236            newPhotoUrls.push(`https://drive.google.com/thumbnail?id=${photoFileId}&sz=w1200`);237          }238        }239      });240      currentPhotoUrls = newPhotoUrls.join(',');241    }242    const newRow = headers.map(header => {243      if (awardData.hasOwnProperty(header)) {244        return awardData[header];245      }246      switch(header) {247        case 'certificateUrl': return currentCertUrl;248        case 'photoUrls': return currentPhotoUrls;249        default: return values[rowIndexToUpdate][headers.indexOf(header)];250      }251    });252    dataSheet.getRange(rowIndexToUpdate + 1, 1, 1, newRow.length).setValues([newRow]);253    return { success: true, message: 'แก้ไขข้อมูลสำเร็จแล้ว' };254  } catch (error) {255    console.error('Error in updateAward:', error.stack);256    return { success: false, message: `เกิดข้อผิดพลาดในการแก้ไข: ${error.message}` };257  }258}259function checkAdminLogin(username, password) {260  try {261    if (username === ADMIN_USER && password === ADMIN_PASS) {262      return { success: true, message: 'เข้าสู่ระบบสำเร็จ' };263    }264    return { success: false, message: 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง' };265  } catch (error) {266    console.error('Error in checkAdminLogin:', error);267    return { success: false, message: 'เกิดข้อผิดพลาดในการเข้าสู่ระบบ' };268  }269}270function initializeSheet_() {271  try {272    dataSheet = ss.getSheetByName('data');273    if (!dataSheet) {274      dataSheet = ss.insertSheet('data');275      const headers = [276        'timestamp', 'id', 'academicYear', 'awardDate', 'ownerName', 'type', 277        'level', 'awardTitle', 'organization', 'description', 278        'certificateUrl', 'photoUrls', 'status', 'createdBy'279      ];280      dataSheet.appendRow(headers);281      const headerRange = dataSheet.getRange(1, 1, 1, headers.length);282      headerRange.setBackground('#4c1d95').setFontColor('white').setFontWeight('bold');283    }284    return true;285  } catch (error) {286    console.error('Error in initializeSheet_:', error);287    throw new Error(`ไม่สามารถเตรียมชีตข้อมูลได้: ${error.message}`);288  }289}290function saveData(formData, certificateFileObject, photoFileObjects) {291  try {292    initializeSheet_();293    if (!formData || !formData.awardTitle || !formData.ownerName || !formData.awardDate) {294      throw new Error('กรุณากรอกข้อมูลที่จำเป็นให้ครบถ้วน');295    }296    297    const folder = getTargetFolder_(formData.level, formData.type);298    299    let certificateUrl = '';300    if (certificateFileObject) {301      const certFileId = uploadFile_(certificateFileObject, folder, 'certificate');302      certificateUrl = certFileId ? `https://drive.google.com/uc?id=${certFileId}` : '';303    }304    let photoUrls = [];305    if (photoFileObjects && Array.isArray(photoFileObjects) && photoFileObjects.length > 0) {306      photoFileObjects.forEach((file, i) => {307        if (file && file.name) {308          const photoFileId = uploadFile_(file, folder, `photo_${i + 1}`);309          if (photoFileId) {310            photoUrls.push(`https://drive.google.com/thumbnail?id=${photoFileId}&sz=w1200`);311          }312        }313      });314    }315    const timestamp = new Date();316    const uniqueId = Utilities.getUuid();317    let userEmail = 'anonymous';318    try {319      userEmail = Session.getActiveUser().getEmail() || 'anonymous';320    } catch (e) {321      console.log('Cannot get user email, using anonymous');322    }323    const newRow = [324      timestamp.toISOString(), uniqueId, formData.academicYear, formData.awardDate, formData.ownerName, formData.type, 325      formData.level, formData.awardTitle, formData.organization, formData.description, 326      certificateUrl || '', photoUrls.join(','), 'แสดงผล', userEmail327    ];328    dataSheet.appendRow(newRow);329    return { success: true, message: 'บันทึกข้อมูลสำเร็จแล้ว', id: uniqueId };330  } catch (error) {331    console.error('Error in saveData:', error.stack);332    return { success: false, message: `เกิดข้อผิดพลาด: ${error.message}` };333  }334}335function uploadFile_(fileObject, folder, prefix = '') {336  try {337    if (!fileObject || !fileObject.data || !fileObject.name) return null;338    const base64Data = fileObject.data.split(',')[1];339    const decodedData = Utilities.base64Decode(base64Data);340    const blob = Utilities.newBlob(decodedData, fileObject.type, fileObject.name);341    const timestamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyyMMdd_HHmmss');342    const newFileName = prefix ? `${prefix}_${timestamp}_${fileObject.name}` : `${timestamp}_${fileObject.name}`;343    const file = folder.createFile(blob);344    file.setName(newFileName);345    file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);346    return file.getId();347  } catch (error) {348    console.error('Error in uploadFile_:', error);349    throw new Error(`ไม่สามารถอัปโหลดไฟล์ได้: ${error.message}`);350  }351}352function getAwards() {353  try {354    initializeSheet_();355    const range = dataSheet.getDataRange();356    const values = range.getValues();357    if (values.length <= 1) return JSON.stringify([]);358    const headers = values[0];359    const dataRows = values.slice(1);360    const data = dataRows.map(row => {361      const obj = {};362      headers.forEach((header, i) => {363        obj[header] = row[i] || '';364      });365      return obj;366    });367    data.sort((a, b) => new Date(b.timestamp || 0) - new Date(a.timestamp || 0));368    return JSON.stringify(data);369  } catch (error) {370    console.error('Error in getAwards:', error);371    return JSON.stringify({ error: true, message: `เกิดข้อผิดพลาด: ${error.message}` });372  }373}374function deleteAward(awardId) {375  try {376    if (!awardId) throw new Error('ไม่พบ ID ของข้อมูลที่ต้องการลบ');377    initializeSheet_();378    const range = dataSheet.getDataRange();379    const values = range.getValues();380    if (values.length <= 1) return { success: false, message: 'ไม่มีข้อมูลในระบบ' };381    const headers = values[0];382    const idColumnIndex = headers.indexOf('id');383    const certUrlIndex = headers.indexOf('certificateUrl');384    const photoUrlsIndex = headers.indexOf('photoUrls');385    if (idColumnIndex === -1) throw new Error('ไม่พบคอลัมน์ ID ในชีต');386    for (let i = values.length - 1; i >= 1; i--) {387      if (values[i][idColumnIndex] === awardId) {388        const rowToDelete = values[i];389        try {390          if (certUrlIndex !== -1 && rowToDelete[certUrlIndex]) {391            deleteFileFromDrive_(rowToDelete[certUrlIndex]);392          }393          if (photoUrlsIndex !== -1 && rowToDelete[photoUrlsIndex]) {394            const photoUrls = rowToDelete[photoUrlsIndex].toString().split(',');395            photoUrls.forEach(url => {396              if (url.trim()) deleteFileFromDrive_(url.trim());397            });398          }399        } catch (fileError) {400          console.error('Error deleting files:', fileError);401        }402        dataSheet.deleteRow(i + 1);403        return { success: true, message: 'ลบข้อมูลสำเร็จ' };404      }405    }406    return { success: false, message: 'ไม่พบข้อมูลที่ต้องการลบ' };407  } catch (error) {408    console.error('Error in deleteAward:', error);409    return { success: false, message: `เกิดข้อผิดพลาด: ${error.message}` };410  }411}412function deleteFileFromDrive_(url) {413  try {414    if (!url || typeof url !== 'string') return;415    const fileIdMatch = url.match(/id=([^&]+)/);416    if (fileIdMatch && fileIdMatch[1]) {417      const file = DriveApp.getFileById(fileIdMatch[1]);418      file.setTrashed(true);419    }420  } catch (error) {421    console.error('ไม่สามารถลบไฟล์ได้:', url, error.message);422  }423}

รีวิว

รีวิวได้หลังโคลนไปใช้ · ทำได้ในโปรแกรม EasyGAS IDE

ยังไม่มีรีวิว

ถามตอบ

ถามคนแชร์ เล่าว่าเอาไปใช้แล้วเป็นยังไง หรือแนบเวอร์ชันต่อยอดของคุณ

ยังไม่มี

ยังไม่มีใครถาม เป็นคนแรกก็ได้

ถามหรือเล่าผลการใช้งาน