依頼Form
GAS
const SHEET_ID = '●●●●●●●●●●●●●●●●●●●●●●●●●●';
const SHEET_NAME = 'シート1';
const RECIPIENT_EMAIL = '●●●●●●●●●●●●●●●●';
const CC_EMAIL = '●●●●●●●●●●●●●●●●'; // CCのメールアドレス
const DRIVE_FOLDER_ID = '●●●●●●●●●●●●●●●●●●●●●●●●●●'; // 画像を保存するドライブフォルダのID
function doGet() {
const template = HtmlService.createTemplateFromFile('form');
template.incompleteCount = getIncompleteCount(); // 未完了件数をテンプレートに渡す
template.userEmail = Session.getActiveUser().getEmail(); // ログインユーザーのメールアドレスを渡す
return template.evaluate().setTitle('依頼 Form');
}
function getIncompleteCount() {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const data = sheet.getRange('F:F').getValues();
let count = 0;
data.forEach(row => {
if (row[0] === '進行中' || row[0] === '順番待ち') {
count++;
}
});
return count;
}
function submitForm(data) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000); // 最大30秒待つ
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
const newRowNumber = lastRow + 1;
const now = new Date();
sheet.getRange(newRowNumber, 1).setValue(now); // A列に日時を設定
sheet.getRange(newRowNumber, 2, 1, 4).setValues([[data.requestType, data.requestContent, data.requester, data.preferredStart]]);
let imageLinks = [];
if (data.imageData) {
const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
for (let i = 0; i < data.imageData.length; i++) {
const blob = Utilities.newBlob(Utilities.base64Decode(data.imageData[i].data), data.imageData[i].mimeType, data.imageData[i].filename);
const file = folder.createFile(blob);
imageLinks.push(file.getUrl());
}
}
// 画像リンクをスプレッドシートに追加
for (let i = 0; i < imageLinks.length; i++) {
sheet.getRange(newRowNumber, 7 + i).setValue(imageLinks[i]);
}
// メール送信
const subject = '★★★依頼が来ました!';
const body = [
`依頼種類: ${data.requestType}`,
`依頼内容: ${data.requestContent}`,
`依頼者: ${data.requester}`,
`希望着手: ${data.preferredStart}`,
`画像リンク: ${imageLinks.join('\n')}`
].join('\n');
MailApp.sendEmail({
to: RECIPIENT_EMAIL,
cc: CC_EMAIL,
subject: subject,
body: body
});
} catch (e) {
Logger.log('Error: ' + e.message);
} finally {
lock.releaseLock();
}
}
form.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
margin: 0;
}
.form-container {
background-color: #e0f7fa;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 400px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.form-container h1 {
text-align: center;
width: 100%;
margin: 0;
}
.form-container .incomplete-count {
position: absolute;
top: 10px;
right: 10px;
font-size: 0.8em;
color: #333;
background-color: #ffeb3b;
padding: 5px 10px;
border-radius: 5px;
}
.form-container label, .form-container input, .form-container textarea, .form-container select {
display: block;
width: 100%;
margin-bottom: 10px;
}
.form-container textarea {
margin-bottom: 20px;
height: 150px;
}
.form-container input[type="text"], .form-container textarea, .form-container select {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.form-container input[type="file"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 20px;
}
.form-container input[type="button"] {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
cursor: pointer;
width: 100%;
}
.form-container input[type="button"]:hover {
background-color: #45a049;
}
#status {
text-align: center;
margin-top: 10px;
color: green;
}
</style>
</head>
<body>
<div class="form-container">
<h1>依頼 Form</h1>
<div class="incomplete-count">未完了件数: <?= incompleteCount ?></div>
<form id="contactForm">
<label for="requestType">依頼種類:</label>
<select id="requestType" name="requestType" oninput="clearStatus()">
<option value="作成">作成</option>
<option value="修正">修正</option>
<option value="相談">相談</option>
<option value="その他">その他</option>
</select>
<label for="requestContent">依頼内容:</label>
<textarea id="requestContent" name="requestContent" oninput="clearStatus()"></textarea>
<label for="requester">依頼者:</label>
<input type="text" id="requester" name="requester" value="<?= userEmail ?>" oninput="clearStatus()" readonly>
<label for="preferredStart">希望着手:</label>
<select id="preferredStart" name="preferredStart" oninput="clearStatus()">
<option value="なるはや">なるはや</option>
<option value="できれば早く">できれば早く</option>
<option value="手が空いたら">手が空いたら</option>
<option value="順番待ち">順番待ち</option>
<option value="いつでもいい">いつでもいい</option>
</select>
<label for="imageUpload">画像添付:</label>
<input type="file" id="imageUpload" multiple>
<input type="button" value="送信" onclick="submitForm()">
</form>
<div id="status"></div>
</div>
<script>
function clearStatus() {
document.getElementById('status').innerText = '';
}
function submitForm() {
const form = document.getElementById('contactForm');
const imageInput = document.getElementById('imageUpload');
const files = imageInput.files;
const data = {
requestType: form.requestType.value,
requestContent: form.requestContent.value,
requester: form.requester.value,
preferredStart: form.preferredStart.value,
imageData: []
};
const promises = Array.from(files).map(file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
data.imageData.push({
filename: file.name,
mimeType: file.type,
data: e.target.result.split(',')[1]
});
resolve();
};
reader.onerror = reject;
reader.readAsDataURL(file);
}));
Promise.all(promises).then(() => {
google.script.run.withSuccessHandler(function(response) {
document.getElementById('status').innerText = '送信完了! 席でお待ちください。';
form.reset();
imageInput.value = ""; // 画像選択フィールドをリセット
}).submitForm(data);
}).catch(error => {
console.error(error);
document.getElementById('status').innerText = '画像のアップロードに失敗しました';
});
}
</script>
</body>
</html>