博赫香 · 後台管理
總覽
系統總覽
手作配方
配方管理
身體油
身體油文案
折價券
折價券金額
顧客資料
顧客記錄
水晶
水晶管理
系統
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 }; } // 信件 sendEmail 函數 — 完整替換版 // 改善: // 1. 寄件名稱顯示品牌名而不是 Gmail 帳號 // 2. 信件排版重新設計,更清楚的段落 // 3. intro 改用 <br> 換行而不是 pre-line(手機相容性更好) function sendEmail(c, couponAmt) { var quiz = parseInt(c.quiz) || 1; var name = c.name || '親愛的朋友'; var configs = { 1: { emoji: '🌿', title: '氣味森林', subtitle: '你的精油配方已送達', color: '#1e3d1a', accent: '#6aaa52', bg: '#f2f8f0', couponType: '下次回購折價券', tagline: '讓這抹森林的呼吸,隨你回家。', intro: ['你在氣味的森林裡,找到了屬於你的那棵樹。', '這份氣味會繼續守護你的空間,', '當你想念森林時,它隨時都在。'], footer: '願這份大地的氣息,在你最需要的時刻悄悄出現。', }, 2: { emoji: '🌊', title: '深海與夢境', subtitle: '你的身體油已送達', color: '#082038', accent: '#3aaabb', bg: '#f0f6fa', couponType: '下次回購折價券', tagline: '帶走這份水中的輕盈感。', intro: ['你的身體記住了那份被包裹的感覺。', '這款身體油,會在每一次使用後,', '把你帶回那個無重力的深藍。'], footer: '願這份水中的寧靜,在你卸下一天之後等著你。', }, 3: { emoji: '📬', title: '整理抽屜', subtitle: '你的手作折價券已送達', color: '#2a1018', accent: '#c87888', bg: '#fdf5f6', couponType: '下次回購折價券', tagline: '把這份思念,化作能隨時陪伴他的氣味。', intro: ['既然想起了那個人,', '就讓這份氣味成為你們之間溫柔的連結。', '帶著這張折價券,', '讓他在日常中也能感受到你想給他的溫暖。'], footer: '最好的禮物,是那個讓他每天都能感受到的氣味。', }, 4: { emoji: '🕯️', title: '微光心願', subtitle: '你的手作折價券已送達', color: '#180d28', accent: '#cc7830', bg: '#fdf8f2', couponType: '下次回購折價券', tagline: '讓這份氣味,繼續陪伴你的心願。', intro: ['你許下的願望,值得被好好守護。', '帶著這張折價券,', '讓這個氣味在你的日常裡,', '持續為你帶來那份力量。'], footer: '你許下的那個願望,已經在路上了。', }, 5: { emoji: '✨', title: '靈魂剪影', subtitle: '你的水晶折價券已送達', color: '#0e0520', accent: '#9966dd', bg: '#f8f4ff', couponType: '下次回購折價券', tagline: '讓屬於你靈魂的氣味,繼續守護你。', intro: ['你的靈魂頻率,已經帶領你找到了這份能量。', '帶著這張折價券,', '讓這個氣味成為你在日常中的守護。'], footer: '每一個氣味,都在等待與它頻率相符的靈魂。', }, }; var cfg = configs[quiz] || configs[1]; var introHtml = cfg.intro.map(function(line){ return '<p style="margin:0 0 10px 0;">'+line+'</p>'; }).join(''); var subject = '【博赫香】' + cfg.emoji + ' ' + cfg.title + ' — ' + cfg.subtitle; var html = '<!DOCTYPE html>' + '<html><head>' + '<meta charset="UTF-8">' + '<meta name="viewport" content="width=device-width,initial-scale=1">' + '</head>' + '<body style="margin:0;padding:0;background:' + cfg.bg + ';">' + '<div style="max-width:520px;margin:0 auto;font-family:\'Helvetica Neue\',Arial,sans-serif;">' + // Header '<div style="background:' + cfg.color + ';padding:36px 28px 28px;text-align:center;">' + '<div style="font-size:32px;margin-bottom:10px;">' + cfg.emoji + '</div>' + '<div style="font-size:10px;letter-spacing:6px;color:rgba(255,255,255,.4);margin-bottom:6px;">PERFUN · 博赫香</div>' + '<div style="font-size:11px;letter-spacing:3px;color:' + cfg.accent + ';margin-bottom:8px;">' + cfg.title + '</div>' + '<div style="font-size:18px;color:#fff;font-weight:300;line-height:1.5;">' + cfg.subtitle + '</div>' + '</div>' + // Body '<div style="background:#fff;padding:32px 28px;">' + // Greeting '<p style="font-size:15px;color:#333;margin:0 0 20px 0;">親愛的 ' + name + ',</p>' + // Tagline '<div style="border-left:3px solid ' + cfg.accent + ';padding:8px 0 8px 16px;margin:0 0 24px 0;">' + '<p style="font-size:16px;color:' + cfg.color + ';font-style:italic;margin:0;line-height:1.6;">' + cfg.tagline + '</p>' + '</div>' + // Intro paragraphs '<div style="font-size:14px;color:#555;line-height:1.9;margin-bottom:28px;">' + introHtml + '</div>' + // Coupon card '<div style="border:2px solid ' + cfg.accent + ';padding:24px 20px;text-align:center;margin:0 0 28px 0;background:' + cfg.bg + ';">' + '<div style="font-size:10px;letter-spacing:4px;color:' + cfg.accent + ';margin-bottom:12px;">' + cfg.couponType + '</div>' + '<div style="font-size:44px;color:' + cfg.color + ';font-weight:300;letter-spacing:2px;line-height:1;">NT$ ' + couponAmt + '</div>' + '<div style="font-size:12px;color:#999;letter-spacing:2px;margin-top:6px;">元 折 抵</div>' + '<div style="border-top:1px dashed ' + cfg.accent + ';margin:18px 0;opacity:.4;"></div>' + '<div style="font-size:10px;letter-spacing:3px;color:#aaa;margin-bottom:4px;">持 券 人</div>' + '<div style="font-size:16px;color:#333;">' + name + '</div>' + '</div>' + // Footer text '<p style="font-size:13px;color:#aaa;line-height:1.8;font-style:italic;text-align:center;margin:0;">' + cfg.footer + '</p>' + '</div>' + // end body // Bottom bar '<div style="background:' + cfg.color + ';padding:20px 28px;text-align:center;">' + '<div style="font-size:10px;letter-spacing:5px;color:rgba(255,255,255,.4);">PERFUN · 博赫香</div>' + '<div style="font-size:11px;color:rgba(255,255,255,.25);margin-top:5px;letter-spacing:1px;">讓氣味,陪你走進內心的旅行</div>' + '</div>' + '</div>' + // end wrap '</body></html>'; // Plain text var plain = '親愛的 ' + name + ',\n\n' + cfg.tagline + '\n\n' + cfg.intro.join('\n') + '\n\n' + '━━━━━━━━━━━━━━━━━━\n' + '【 ' + cfg.couponType + ' 】\n' + '折抵金額:NT$ ' + couponAmt + ' 元\n' + '持券人:' + name + '\n' + '━━━━━━━━━━━━━━━━━━\n\n' + cfg.footer + '\n\n' + 'PERFUN · 博赫香\n' + '讓氣味,陪你走進內心的旅行 ' + cfg.emoji; MailApp.sendEmail({ to: c.email, subject: subject, body: plain, htmlBody: html, name: '博赫香 PERFUN', // 顯示寄件名稱,不顯示 Gmail 帳號名 }); Logger.log('Email sent to: ' + 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'); }