博赫香 · 後台管理
總覽
系統總覽
手作配方
配方管理
身體油
身體油文案
折價券
折價券金額
顧客資料
顧客記錄
系統
GAS 設定
尚未有變更
重新載入
儲存所有變更
// ═══════════════════════════════════════════════════════ // 博赫香 PERFUN — Google Apps Script v2 // 五個測驗各自不同的信件設計 // ═══════════════════════════════════════════════════════ const SHEET_CONFIG = 'perfun_config'; const SHEET_CUSTOMERS = 'customers'; const BRAND_EMAIL = 'your@gmail.com'; // ← 改成你自己的 Gmail const BRAND_NAME = '博赫香 PERFUN'; // ── CORS preflight ─────────────────────────────────── function doOptions(e) { return ContentService.createTextOutput(JSON.stringify({ok:true})) .setMimeType(ContentService.MimeType.JSON); } // ── GET ────────────────────────────────────────────── function doGet(e) { var action = e.parameter.action; var result; try { if (action === 'getAll') result = getAllConfig(); else if (action === 'getCustomers') result = getCustomers(); else if (action === 'saveCustomer') { // Support GET-based saveCustomer (avoids CORS POST issues) var dataStr = e.parameter.data; if (dataStr) { var customer = JSON.parse(dataStr); result = saveCustomer(customer); } else { result = { error: 'no data param' }; } } else result = { error: 'unknown action' }; } catch(err) { result = { error: err.toString() }; } return ContentService .createTextOutput(JSON.stringify(result)) .setMimeType(ContentService.MimeType.JSON); } // ── POST ───────────────────────────────────────────── function doPost(e) { var data; try { data = JSON.parse(e.postData.contents); } catch(err) { return json({ error: 'invalid JSON' }); } var result; try { if (data.action === 'saveAll') result = saveAllConfig(data.payload); else if (data.action === 'saveCustomer') result = saveCustomer(data.customer); else result = { error: 'unknown action' }; } catch(err) { result = { error: err.toString() }; } return json(result); } function json(obj) { var out = ContentService.createTextOutput(JSON.stringify(obj)); out.setMimeType(ContentService.MimeType.JSON); return out; } // ── 讀取設定 ────────────────────────────────────────── function getAllConfig() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName(SHEET_CONFIG); if (!sheet) { sheet = ss.insertSheet(SHEET_CONFIG); sheet.getRange('A1').setValue('{}'); } var val = sheet.getRange('A1').getValue(); try { return val ? JSON.parse(val) : {}; } catch(e) { return {}; } } // ── 儲存設定 ────────────────────────────────────────── function saveAllConfig(payload) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName(SHEET_CONFIG) || ss.insertSheet(SHEET_CONFIG); sheet.getRange('A1').setValue(JSON.stringify(payload)); sheet.getRange('B1').setValue(new Date().toLocaleString('zh-TW', {timeZone:'Asia/Taipei'})); return { ok: true }; } // ── 儲存顧客 + 寄信 ─────────────────────────────────── function saveCustomer(c) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName(SHEET_CUSTOMERS); if (!sheet) { sheet = ss.insertSheet(SHEET_CUSTOMERS); var headers = ['姓名','性別','生日','電話','Email','測驗','測驗結果','送禮對象','填寫時間']; sheet.getRange(1,1,1,headers.length).setValues([headers]); sheet.getRange(1,1,1,headers.length).setFontWeight('bold'); sheet.getRange(1,1,1,headers.length).setBackground('#f0ebe2'); sheet.setFrozenRows(1); } sheet.appendRow([ c.name||'', c.gender||'', c.birthday||'', c.phone||'', c.email||'', c.quiz_name||'', c.result_key||'', c.person_type||'', new Date().toLocaleString('zh-TW',{timeZone:'Asia/Taipei'}) ]); // 讀折價券金額 var couponAmt = 200; try { var cfg = getAllConfig(); if (cfg.coupon_amounts && cfg.coupon_amounts[c.quiz]) { couponAmt = cfg.coupon_amounts[c.quiz]; } } catch(e) {} if (c.email) { try { sendEmail(c, couponAmt); } catch(err) { Logger.log('Email error: ' + err); } } return { ok: true }; } // ── 讀取顧客 ────────────────────────────────────────── function getCustomers() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName(SHEET_CUSTOMERS); if (!sheet) return { customers: [] }; var rows = sheet.getDataRange().getValues(); if (rows.length <= 1) return { customers: [] }; var fields = ['name','gender','birthday','phone','email','quiz_name','result_key','person_type','timestamp']; var customers = rows.slice(1).map(function(row) { var obj = {}; fields.forEach(function(f,i){ obj[f] = String(row[i]||''); }); return obj; }); return { customers: customers }; } // ══════════════════════════════════════════════════════ // 信件設計 — 五個測驗各自不同 // ══════════════════════════════════════════════════════ function sendEmail(c, couponAmt) { var quiz = parseInt(c.quiz) || 1; var name = c.name || '親愛的朋友'; var configs = { 1: { emoji: '🌿', title: '氣味森林 · 你的精油配方已送達', color: '#2d5a27', accent: '#82c868', couponType: '下次回購折價券', tagline: '讓這抹森林的呼吸,隨你回家。', intro: '你在氣味的森林裡,找到了屬於你的那棵樹。\n這份氣味會繼續守護你的空間,當你想念森林時,它隨時都在。', footer: '願這份大地的氣息,在你最需要的時刻悄悄出現。', }, 2: { emoji: '🌊', title: '深海與夢境 · 你的身體油已送達', color: '#0a2a45', accent: '#41becc', couponType: '下次回購折價券', tagline: '帶走這份水中的輕盈感。', intro: '你的身體記住了那份被包裹的感覺。\n這款身體油,會在每一次使用後,把你帶回那個無重力的深藍。', footer: '願這份水中的寧靜,在你卸下一天之後等著你。', }, 3: { emoji: '📬', title: '整理抽屜 · 你的手作折價券已送達', color: '#3d1a0d', accent: '#da8a94', couponType: '現場手作折抵券', tagline: '現在,親手為他做一份禮物吧。', intro: '你想起了那個人,想把這份思念變成能觸摸的溫度。\n帶著這張折價券來到我們的工作台,把心意親手裝進瓶子裡。', footer: '最好的禮物,是親手調製的那份氣味。', }, 4: { emoji: '🕯️', title: '微光心願 · 你的手作折價券已送達', color: '#1a0a28', accent: '#e08838', couponType: '現場手作折抵券', tagline: '把心願帶進現實,親手點亮它。', intro: '火焰雖然微小,但你的願望很有力量。\n帶著這張折價券,現在就動手製作屬於你的願望禮物。', footer: '你許下的那個願望,已經在路上了。', }, 5: { emoji: '✨', title: '靈魂剪影 · 你的水晶折價券已送達', color: '#0d0520', accent: '#b690fc', couponType: '現場水晶折抵券', tagline: '與你的守護石,在現實中相遇。', intro: '你的靈魂頻率,已經帶領你找到了這份能量。\n帶著這張折價券去挑選那顆與你相契的礦石,讓它從今天起成為你的守護。', footer: '每一顆石頭,都在等待與它頻率相符的靈魂。', }, }; var cfg = configs[quiz] || configs[1]; var subject = BRAND_NAME + ' ' + cfg.emoji + ' ' + cfg.title; // HTML 信件 var html = [ '<!DOCTYPE html>', '<html><head><meta charset="UTF-8">', '<meta name="viewport" content="width=device-width,initial-scale=1">', '<style>', 'body{margin:0;padding:0;background:#f5f0e8;font-family:"Noto Serif TC",serif;}', '.wrap{max-width:560px;margin:0 auto;background:#fff;}', '.header{background:' + cfg.color + ';padding:40px 32px 32px;text-align:center;}', '.header-emoji{font-size:36px;margin-bottom:12px;}', '.header-brand{font-size:11px;letter-spacing:6px;color:rgba(255,255,255,.45);margin-bottom:8px;}', '.header-title{font-size:20px;color:#fff;font-weight:300;letter-spacing:2px;line-height:1.5;}', '.body{padding:36px 32px;}', '.greeting{font-size:16px;color:#333;margin-bottom:20px;}', '.tagline{font-size:18px;color:' + cfg.color + ';font-style:italic;border-left:3px solid ' + cfg.accent + ';padding-left:14px;margin:24px 0;line-height:1.6;}', '.intro{font-size:14px;color:#555;line-height:2;white-space:pre-line;margin-bottom:28px;}', '.coupon{border:2px solid ' + cfg.accent + ';padding:28px;text-align:center;margin:28px 0;position:relative;}', '.coupon-type{font-size:11px;letter-spacing:4px;color:' + cfg.accent + ';margin-bottom:8px;}', '.coupon-amount{font-size:48px;color:' + cfg.color + ';font-weight:300;line-height:1;letter-spacing:2px;}', '.coupon-unit{font-size:13px;color:#999;margin-top:6px;letter-spacing:2px;}', '.coupon-divider{border:none;border-top:1px dashed ' + cfg.accent + ';margin:20px 0;opacity:.4;}', '.coupon-holder{font-size:12px;letter-spacing:3px;color:#999;margin-bottom:4px;}', '.coupon-name{font-size:16px;color:#333;}', '.footer-text{font-size:13px;color:#888;line-height:1.9;font-style:italic;text-align:center;padding:20px 32px;}', '.bottom{background:' + cfg.color + ';padding:24px 32px;text-align:center;}', '.bottom-brand{font-size:12px;letter-spacing:4px;color:rgba(255,255,255,.5);}', '.bottom-slogan{font-size:12px;color:rgba(255,255,255,.3);margin-top:6px;letter-spacing:1px;}', '</style></head><body>', '<div class="wrap">', // Header '<div class="header">', '<div class="header-emoji">' + cfg.emoji + '</div>', '<div class="header-brand">PERFUN · 博赫香</div>', '<div class="header-title">' + cfg.title + '</div>', '</div>', // Body '<div class="body">', '<div class="greeting">親愛的 ' + name + ',</div>', '<div class="tagline">' + cfg.tagline + '</div>', '<div class="intro">' + cfg.intro + '</div>', // Coupon card '<div class="coupon">', '<div class="coupon-type">' + cfg.couponType + '</div>', '<div class="coupon-amount">NT$ ' + couponAmt + '</div>', '<div class="coupon-unit">元折抵</div>', '<hr class="coupon-divider">', '<div class="coupon-holder">持券人</div>', '<div class="coupon-name">' + name + '</div>', '</div>', '</div>', // end .body // Footer text '<div class="footer-text">' + cfg.footer + '</div>', // Bottom bar '<div class="bottom">', '<div class="bottom-brand">PERFUN · 博赫香</div>', '<div class="bottom-slogan">讓氣味,陪你走進內心的旅行</div>', '</div>', '</div>', // end .wrap '</body></html>' ].join('\n'); // 純文字版備用 var plain = [ '親愛的 ' + name + ',', '', cfg.tagline, '', cfg.intro, '', '━━━━━━━━━━━━━━━━━━', '【 ' + cfg.couponType + ' 】', '折抵金額:NT$ ' + couponAmt + ' 元', '持券人:' + name, '━━━━━━━━━━━━━━━━━━', '', cfg.footer, '', BRAND_NAME, '讓氣味,陪你走進內心的旅行 ' + cfg.emoji, ].join('\n'); MailApp.sendEmail({ to: c.email, subject: subject, body: plain, htmlBody: html, name: BRAND_NAME, }); Logger.log('Email sent: ' + c.email + ' quiz=' + quiz); } // ── 測試用(在 Apps Script 裡直接執行這個函數測試寄信)── function testEmail() { sendEmail({ name: '測試顧客', email: 'your@gmail.com', // ← 改成你要收測試信的 email quiz: 1, quiz_name: '氣味森林', result_key:'lemon', person_type:'', }, 200); Logger.log('Test email sent'); }