GASでアクションロガーを作成。アクションをログし、毎日の行動を補足します。
GAS + Vue.js で、シンプルに書いています。
少しカスタマイズすると、色々面白いですね。
1 2 3 4 5 6 7 8 |
function doGet(){ var pageTemplate = HtmlService.createTemplateFromFile('index'); pageTemplate = pageTemplate.evaluate() pageTemplate.setTitle("ActionLogger") pageTemplate.setSandboxMode(HtmlService.SandboxMode.IFRAME); pageTemplate.addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'); return pageTemplate; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
<!DOCTYPE html> <html> <head> <base target="_top"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script> </head> <body> <a target="_blank" href="https://script.google.com/home/projects/1rbW1BLtEpILUbo07meO44YIQRcKQPc0cyyrfbMAj1e-hwZecZIXqHs-g/edit" >編集</a> <div id="app"> <!-- Action Log Form --> <div style="border:1px solid gray; border-radius:10px; position:absolute; top: 10px; right: 10px; padding:5px; min-width: 200px; text-align: left;"> <select v-model="selectedAction"> <option>出社</option> <option>退社</option> <option>ログ</option> </select> <input type="text" v-model="inputText" placeholder="アクションの詳細" /> <button @click="addLog" style="border:1px solid black; border-radius: 10px; color: gray; font-weight: bold; color: #ccc;">登録</button> </div> <!-- Action Logs List --> <div id="list-log" style="margin-top:10px;"> <table style="width: 60%; border-collapse: collapse; text-align: left;"> <thead> <tr style="background-color: #f2f2f2; border-bottom: 2px solid #ddd;"> <th style="padding: 8px; border: 1px solid #ddd;">日付</th> <th style="padding: 8px; border: 1px solid #ddd;">時刻</th> <th style="padding: 8px; border: 1px solid #ddd;">タイトル</th> <th style="padding: 8px; border: 1px solid #ddd;">詳細</th> <th style="display: none;">ID</th> <!-- IDを非表示 --> </tr> </thead> <tbody> <tr v-for="(log, index) in logs.filter(log => !log.deletedAt)" :key="log.id" style="border-bottom: 1px solid #ddd;"> <td style="padding: 8px; border: 1px solid #ddd;">{{ log.createdDateAt }}</td> <td style="padding: 8px; border: 1px solid #ddd;">{{ log.createdTimeAt }}</td> <td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">{{log.title != "出社" && log.title != "退社" ? " ": ""}}{{ log.title }}</td> <td style="padding: 8px; border: 1px solid #ddd;">{{ log.log }}</td> <td style="display: none;">{{ log.id }}</td> <!-- IDを非表示 --> </tr> </tbody> </table> </div> </div> <script> // 外部関数の定義 function getDateTime() { const now = new Date(); const date = `${now.getFullYear()}.${(now.getMonth() + 1).toString().padStart(2, '0')}.${now.getDate().toString().padStart(2, '0')}`; const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`; return [date, time]; } // 外部関数: ランダムなUIDを生成 function generateUid() { return Math.random().toString(36).substr(2, 9); // ランダムな文字列を生成 } function fetchActionLogs() { return new Promise((resolve, reject) => { google.script.run .withSuccessHandler((data) => { console.log('データ取得成功:', data); resolve(data); // 成功時にデータを返す }) .withFailureHandler((error) => { console.error('データ取得失敗:', error); reject(error); // エラー時に拒否 }) .loadAllActionLogs(); // GASの関数を呼び出す }); } // Vueインスタンス new Vue({ el: '#app', data: { selectedAction: '出社', // デフォルト選択されるアクション inputText: '', // 入力テキスト logs: [] // ActionLog の配列 }, methods: { // ActionLog を追加するメソッド addLog() { if (this.inputText.trim() !== '') { // 現在の日付と時刻を取得 const [date, time] = getDateTime(); // ランダムなUIDを生成 const id = generateUid(); // ログを追加 const newLog = { id: id, // 一意のID createdDateAt: `${date}`, // 日付 createdTimeAt: `${time}`, // 時刻 title: this.selectedAction, // 選択したアクション log: this.inputText // 入力した詳細 }; // ログをローカルに追加 this.logs.unshift(newLog); // サーバーに送信 google.script.run.insertActionLog(newLog); // 入力フィールドをクリア this.inputText = ''; } else { alert('詳細を入力してください'); } } }, mounted() { fetchActionLogs() .then((loadedLogs) => { this.logs = loadedLogs.map(log => ({ id: log.id || generateUid(), createdDateAt: log.createdDateAt || '', createdTimeAt: log.createdTimeAt || '', title: log.title || '', log: log.log || '', deletedAt: log.deletedAt || '' })); }) .catch((error) => { console.error('データのロードに失敗しました:', error); }); } }); </script> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
/** * アクションログをスプレッドシートに挿入する関数 * @param {Object[]} logs - ログの配列 [{ uid, createdDateAt, createdTimeAt, title, log, deletedAt }] */ function insertActionLog(logs) { const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート "${SHEET_NAME}" が見つかりません。`); } [logs].forEach(log => { // 2行目に新しいデータを挿入 sheet.insertRowAfter(1); // 常に2行目(1行目の下)に新しい行を挿入 sheet.getRange(2, 1, 1, 6).setValues([[ log.id || "", // uid log.createdDateAt || "", // createdDateAt log.createdTimeAt || "", // createdTimeAt log.title || "", // title log.log || "", // log log.deletedAt || "" // deletedAt ]]); }); } function loadAllActionLogs() { const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート "${SHEET_NAME}" が見つかりません。`); } // スプレッドシートからデータを取得 const data = sheet.getDataRange().getDisplayValues(); // すべてのデータを取得 const headers = data[0]; // 1行目はヘッダー const rows = data.slice(1); // 2行目以降がデータ // データを配列のオブジェクト形式に変換 const logs = rows.map(row => { const log = {}; headers.forEach((key, index) => { log[key] = row[index] || ""; // 値が空の場合は空文字を代入 }); return log; }); return logs; // 配列形式で返す } function updateActionLog(logs) { const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`シート "${SHEET_NAME}" が見つかりません。`); } // データを取得 const data = sheet.getDataRange().getDisplayValues(); // すべてのデータを取得 const headers = data[0]; // 1行目はヘッダー const rows = data.slice(1); // 2行目以降がデータ // ヘッダーを { key: index } 形式に変換 const headerMap = headers.reduce((map, key, index) => { map[key] = index; return map; }, {}); // logsから変更オーダーを作成 const changes = logs.map(log => { // 対応する行を検索 const rowIndex = rows.findIndex(row => row[headerMap["id"]] == log.id); if (rowIndex === -1) { throw new Error(`ID "${log.id}" がスプレッドシート内に見つかりません。`); } // 現在のデータをコピーして更新 const updatedRow = [...rows[rowIndex]]; Object.keys(log).forEach(key => { if (key in headerMap) { updatedRow[headerMap[key]] = log[key]; } }); return { rowIndex: rowIndex + 2, updatedRow }; // スプレッドシート行番号に変換 }); // スプレッドシートを更新 changes.forEach(change => { const { rowIndex, updatedRow } = change; sheet.getRange(rowIndex, 1, 1, updatedRow.length).setValues([updatedRow]); }); Logger.log("更新が完了しました!"); } |
vue.js 2.0における、GASでの使い方や、いくつかの最新のjs仕様を使えています。