0523_前後台調整

This commit is contained in:
larry2701 2025-05-23 17:49:09 +08:00
parent 2909ed1f65
commit a560065aec
11 changed files with 1867 additions and 6 deletions

123
Backstage/activity_add.html Normal file
View File

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>新增活動資料</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">新增活動資料</h2>
<form id="addActivityForm" enctype="multipart/form-data">
<div class="mb-3">
<label for="title" class="form-label">活動名稱</label>
<input type="text" class="form-control" id="title" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="date" class="form-label">開始時間</label>
<input type="datetime-local" class="form-control" id="date" required>
</div>
<div class="col-md-6 mb-3">
<label for="endDate" class="form-label">結束時間</label>
<input type="datetime-local" class="form-control" id="endDate" required>
</div>
</div>
<div class="mb-3">
<label for="location" class="form-label">地點</label>
<input type="text" class="form-control" id="location" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">活動描述</label>
<textarea class="form-control" id="description" rows="4" required></textarea>
</div>
<div class="mb-3">
<label for="poster" class="form-label">上傳活動海報</label>
<input type="file" class="form-control" id="poster" accept="image/*">
</div>
<div class="mb-3">
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
<select id="needRegister" class="form-select">
<option value="否" selected></option>
<option value="是"></option>
</select>
</div>
<div class="mb-3">
<label for="repeatOption" class="form-label">是否重複舉辦</label>
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
<option value="none" selected></option>
<option value="weekly">每週一次</option>
<option value="biweekly">每兩週一次</option>
<option value="monthly">每月一次</option>
</select>
</div>
<div class="mb-3 d-none" id="repeatCountGroup">
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
</div>
<div class="mb-3">
<label for="approvalStatus" class="form-label">審核狀態</label>
<select id="approvalStatus" class="form-select">
<option value="審核中" selected>審核中</option>
<option value="通過">通過</option>
<option value="不通過">不通過</option>
</select>
</div>
<button type="submit" class="btn btn-primary">新增活動</button>
<a href="activity_list.html" class="btn btn-secondary ms-2">取消</a>
</form>
</div>
<script>
function toggleRepeatFields() {
const option = document.getElementById('repeatOption').value;
const group = document.getElementById('repeatCountGroup');
group.classList.toggle('d-none', option === 'none');
}
document.addEventListener('DOMContentLoaded', function() {
toggleRepeatFields();
document.getElementById("addActivityForm").addEventListener("submit", function (e) {
e.preventDefault();
const title = document.getElementById('title').value;
const startDate = document.getElementById('date').value;
const endDate = document.getElementById('endDate').value;
const location = document.getElementById('location').value;
const description = document.getElementById('description').value;
const posterFile = document.getElementById('poster').files[0];
const needRegister = document.getElementById('needRegister').value;
const repeatOption = document.getElementById('repeatOption').value;
const repeatCount = document.getElementById('repeatCount').value;
const approvalStatus = document.getElementById('approvalStatus').value; // Get approval status
if (endDate && startDate && new Date(endDate) <= new Date(startDate)) {
alert("結束時間必須晚於開始時間!");
return;
}
let alertMessage = `新活動已建立(模擬):\n名稱: ${title}\n開始: ${startDate}\n結束: ${endDate}\n地點: ${location}\n描述: ${description}\n是否需報名: ${needRegister}\n審核狀態: ${approvalStatus}`; // Added to alert
if (repeatOption !== 'none') {
const optionMap = { weekly: '每週', biweekly: '每兩週', monthly: '每月' };
alertMessage += `\n重複設定${optionMap[repeatOption]},共重複 ${repeatCount} 次`;
}
if (posterFile) {
alertMessage += `\n海報檔案: ${posterFile.name}`;
}
alert(alertMessage);
window.location.href = "activity_list.html";
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>編輯活動資料</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.current-poster-label {
font-size: 0.9em;
color: #6c757d;
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">編輯活動資料</h2>
<form id="editActivityForm" enctype="multipart/form-data">
<div class="mb-3">
<label for="title" class="form-label">活動名稱</label>
<input type="text" class="form-control" id="title" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="date" class="form-label">開始時間</label>
<input type="datetime-local" class="form-control" id="date" required>
</div>
<div class="col-md-6 mb-3">
<label for="endDate" class="form-label">結束時間</label>
<input type="datetime-local" class="form-control" id="endDate" required>
</div>
</div>
<div class="mb-3">
<label for="location" class="form-label">地點</label>
<input type="text" class="form-control" id="location" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">活動描述</label>
<textarea class="form-control" id="description" rows="4" required></textarea>
</div>
<div class="mb-3">
<label for="poster" class="form-label">更新活動海報</label>
<input type="file" class="form-control" id="poster" accept="image/*">
<div id="currentPosterDisplay" class="form-text"></div>
</div>
<div class="mb-3">
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
<select id="needRegister" class="form-select">
<option value="否"></option>
<option value="是"></option>
</select>
</div>
<div class="mb-3">
<label for="repeatOption" class="form-label">是否重複舉辦</label>
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
<option value="none"></option>
<option value="weekly">每週一次</option>
<option value="biweekly">每兩週一次</option>
<option value="monthly">每月一次</option>
</select>
</div>
<div class="mb-3 d-none" id="repeatCountGroup">
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
</div>
<div class="mb-3">
<label for="approvalStatus" class="form-label">審核狀態</label>
<select id="approvalStatus" class="form-select">
<option value="審核中">審核中</option>
<option value="通過">通過</option>
<option value="不通過">不通過</option>
</select>
</div>
<button type="submit" class="btn btn-primary">儲存變更</button>
<a href="activity_list.html" class="btn btn-secondary ms-2">取消</a>
</form>
</div>
<script>
const activities_data = [ // Sample data now includes approvalStatus
{
id: 1, title: "🎉 社區春季市集", startDate: "2025-04-27T10:00", endDate: "2025-04-27T16:00",
location: "中庭花園", description: "市集將有手作小物、美食攤販及親子遊戲活動,歡迎全體住戶參與!",
imageName: "spring_market.jpg", needRegister: "否", repeatOption: "none", repeatCount: 1, approvalStatus: "通過"
},
{
id: 2, title: "🧘‍♀️ 早晨瑜珈課程", startDate: "2025-05-03T07:00", endDate: "2025-05-03T08:00",
location: "社區多功能教室", description: "由專業老師帶領,適合各年齡層,請自備瑜珈墊。",
imageName: "yoga_class.png", needRegister: "是", repeatOption: "weekly", repeatCount: 4, approvalStatus: "審核中"
},
{
id: 3, title: "📚 二手書交換日", startDate: "2025-05-05T13:00", endDate: "2025-05-05T17:00",
location: "社區圖書區", description: "帶來你的二手書,一起交流閱讀樂趣!",
imageName: null, needRegister: "是", repeatOption: "none", repeatCount: 1, approvalStatus: "不通過"
}
];
function getParam(key) {
const url = new URL(window.location.href);
return url.searchParams.get(key);
}
function toggleRepeatFields() {
const option = document.getElementById('repeatOption').value;
const group = document.getElementById('repeatCountGroup');
group.classList.toggle('d-none', option === 'none');
}
document.addEventListener('DOMContentLoaded', function() {
const activityId = parseInt(getParam("id"));
const activity = activities_data.find(act => act.id === activityId);
if (activity) {
document.getElementById("title").value = activity.title;
document.getElementById("date").value = activity.startDate || '';
document.getElementById("endDate").value = activity.endDate || '';
document.getElementById("location").value = activity.location;
document.getElementById("description").value = activity.description;
document.getElementById("needRegister").value = activity.needRegister;
document.getElementById("repeatOption").value = activity.repeatOption || 'none';
document.getElementById("repeatCount").value = activity.repeatCount || 1;
document.getElementById("approvalStatus").value = activity.approvalStatus || '審核中'; // Populate status
if (activity.imageName) {
document.getElementById("currentPosterDisplay").textContent = `目前海報:${activity.imageName}`;
} else {
document.getElementById("currentPosterDisplay").textContent = `目前尚無海報。`;
}
toggleRepeatFields();
} else {
alert("找不到指定的活動資料!");
window.location.href = "activity_list.html";
}
document.getElementById("editActivityForm").addEventListener("submit", function (e) {
e.preventDefault();
const title = document.getElementById('title').value;
const startDate = document.getElementById('date').value;
const endDate = document.getElementById('endDate').value;
const approvalStatus = document.getElementById('approvalStatus').value; // Get status
const newPosterFile = document.getElementById('poster').files[0];
if (endDate && startDate && new Date(endDate) <= new Date(startDate)) {
alert("結束時間必須晚於開始時間!");
return;
}
let alertMessage = `活動資料已更新(模擬):\nID: ${activityId}\n名稱: ${title}\n開始: ${startDate}\n結束: ${endDate}\n審核狀態: ${approvalStatus}`; // Added to alert
if (newPosterFile) {
alertMessage += `\n新海報: ${newPosterFile.name}`;
}
alert(alertMessage);
const activityIndex = activities_data.findIndex(act => act.id === activityId);
if (activityIndex > -1) {
activities_data[activityIndex].title = title;
activities_data[activityIndex].startDate = startDate;
activities_data[activityIndex].endDate = endDate;
activities_data[activityIndex].location = document.getElementById('location').value;
activities_data[activityIndex].description = document.getElementById('description').value;
activities_data[activityIndex].needRegister = document.getElementById('needRegister').value;
activities_data[activityIndex].repeatOption = document.getElementById('repeatOption').value;
if (activities_data[activityIndex].repeatOption !== 'none') {
activities_data[activityIndex].repeatCount = document.getElementById('repeatCount').value;
}
activities_data[activityIndex].approvalStatus = approvalStatus; // Save status
if (newPosterFile) {
activities_data[activityIndex].imageName = newPosterFile.name;
}
}
window.location.href = "activity_list.html";
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,295 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>活動管理列表</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
.container.mt-4 {
flex-grow: 1;
}
</style>
</head>
<body>
<div class="container mt-4">
<h2 class="mb-4">活動列表</h2>
<div class="row g-2 mb-3">
<div class="col-4 col-md-2 col-lg-1">
<a href="activity_add.html" class="btn btn-outline-success w-100 rounded-pill">新增活動</a>
</div>
<div class="col-4 col-md-2 col-lg-1">
<button class="btn btn-outline-secondary w-100 rounded-pill" onclick="exportActivities()">匯出活動</button>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-sm-6 col-md-3 col-lg-2">
<input type="text" id="searchName" class="form-control" placeholder="搜尋活動名稱">
</div>
<div class="col-sm-6 col-md-4 col-lg-3">
<input type="text" id="searchLocation" class="form-control" placeholder="搜尋地點">
</div>
<div class="col-6 col-md-2 col-lg-2">
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
</div>
<div class="col-6 col-md-2 col-lg-1">
<button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
</div>
</div>
<table class="table table-bordered table-hover align-middle">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
<th>編號</th>
<th>活動名稱</th>
<th>活動時間</th>
<th>地點</th>
<th>是否需報名</th>
<th>審核狀態</th>
<th>操作</th>
</tr>
</thead>
<tbody id="activityTable">
</tbody>
</table>
<div class="d-flex justify-content-between align-items-center mt-3">
<div><span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span></div>
<div>
<button id="prevPageBtn" class="btn btn-sm btn-outline-secondary me-1" onclick="prevPage()">上一頁</button>
<button id="nextPageBtn" class="btn btn-sm btn-outline-secondary" onclick="nextPage()">下一頁</button>
</div>
</div>
</div>
<script>
const pageSize = 5; // Number of items per page
let currentPage = 1;
let currentFilteredData = [];
// Sample activity data. In a real application, this would come from a server.
// Note: The 'time' field can accommodate various formats.
let activities_data = [
{ id: 1, title: "🎉 社區春季市集", time: "2025/04/2710:00 - 16:00", location: "中庭花園", desc: "市集將有手作小物、美食攤販及親子遊戲活動,歡迎全體住戶參與!", image: "https://picsum.photos/id/1011/600/300", needRegister: "否", status: "審核通過" },
{ id: 2, title: "🧘‍♀️ 早晨瑜珈課程", time: "每週六 07:00 - 08:00", location: "社區多功能教室", desc: "由專業老師帶領,適合各年齡層,請自備瑜珈墊。", image: "https://picsum.photos/id/1015/600/300", needRegister: "是", status: "審核中" },
{ id: 3, title: "📚 二手書交換日", time: "2025/05/0513:00 - 17:00", location: "社區圖書區", desc: "帶來你的二手書,一起交流閱讀樂趣!", image: "https://picsum.photos/id/1033/600/300", needRegister: "是", status: "未審核" },
{ id: 4, title: "電影之夜:星際漫遊", time: "2025/06/1519:00 - 21:00", location: "社區視聽室", desc: "播放經典科幻電影,提供爆米花。", image: "https://picsum.photos/id/1043/600/300", needRegister: "是", status: "審核通過" },
{ id: 5, title: "夏季園藝工作坊", time: "2025/07/1014:00 - 16:00", location: "頂樓花園", desc: "學習基本園藝技巧,美化社區環境。", image: "https://picsum.photos/id/1053/600/300", needRegister: "否", status: "未審核" },
{ id: 6, title: "中秋晚會", time: "2025/09/1518:00 - 21:00", location: "戶外廣場", desc: "賞月、品嚐月餅、燈謎活動。", image: "https://picsum.photos/id/1063/600/300", needRegister: "是", status: "審核中" },
{ id: 7, title: "兒童故事時間", time: "每月第一個週六 10:00 - 11:00", location: "社區圖書區", desc: "專為兒童設計的趣味故事時間。", image: "https://picsum.photos/id/1073/600/300", needRegister: "否", status: "審核通過" }
];
function renderTable(dataToRender) {
const tbody = document.getElementById("activityTable");
tbody.innerHTML = "";
const start = (currentPage - 1) * pageSize;
const pageData = dataToRender.slice(start, start + pageSize);
pageData.forEach(act => {
const row = document.createElement("tr");
let statusBadgeClass = 'bg-secondary'; // Default badge
if (act.status === '審核通過') {
statusBadgeClass = 'bg-success';
} else if (act.status === '審核中') {
statusBadgeClass = 'bg-info';
} else if (act.status === '未審核') {
statusBadgeClass = 'bg-warning text-dark'; // text-dark for better contrast on yellow
}
row.innerHTML = `
<td><input type="checkbox" class="row-checkbox" data-id="${act.id}"></td>
<td>${act.id}</td>
<td>${act.title}</td>
<td>${act.time}</td>
<td>${act.location}</td>
<td><span class="badge bg-${act.needRegister === '是' ? 'success' : 'secondary'}">${act.needRegister}</span></td>
<td><span class="badge ${statusBadgeClass}">${act.status}</span></td>
<td>
<a href="activity_edit.html?id=${act.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
<button class="btn btn-outline-danger btn-sm" onclick="deleteActivity(${act.id})">刪除</button>
</td>
`;
tbody.appendChild(row);
});
updatePaginationControls(dataToRender.length);
const selectAllCheckbox = document.getElementById("selectAll");
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
}
function updatePaginationControls(totalItems) {
const totalPages = Math.ceil(totalItems / pageSize) || 1;
const currentPageDisplay = document.getElementById("currentPageInfo");
const totalPagesDisplay = document.getElementById("totalPagesInfo");
const prevButton = document.getElementById("prevPageBtn");
const nextButton = document.getElementById("nextPageBtn");
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
if (prevButton) prevButton.disabled = currentPage === 1;
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
if (currentPage > totalPages && totalPages > 0) {
currentPage = totalPages;
renderTable(currentFilteredData);
} else if (totalItems === 0) {
if (currentPageDisplay) currentPageDisplay.textContent = 1;
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
if (prevButton) prevButton.disabled = true;
if (nextButton) nextButton.disabled = true;
}
}
function prevPage() {
if (currentPage > 1) {
currentPage--;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function nextPage() {
const totalPages = Math.ceil(currentFilteredData.length / pageSize);
if (currentPage < totalPages) {
currentPage++;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function performSearch() {
const nameInput = document.getElementById("searchName").value.toLowerCase();
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
currentFilteredData = activities_data.filter(act =>
act.title.toLowerCase().includes(nameInput) &&
act.location.toLowerCase().includes(locationInput)
);
currentPage = 1;
renderTable(currentFilteredData);
}
function toggleAll(source) {
const checkboxes = document.querySelectorAll(".row-checkbox");
checkboxes.forEach(cb => cb.checked = source.checked);
}
function deleteSelected() {
if (!confirm("確定要刪除勾選的活動嗎?")) return;
const selected = document.querySelectorAll(".row-checkbox:checked");
if (selected.length === 0) {
alert("請先勾選要刪除的活動");
return;
}
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
activities_data = activities_data.filter(act => !idsToDelete.includes(act.id));
// Re-filter and re-render after deletion
const nameInput = document.getElementById("searchName").value.toLowerCase();
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
currentFilteredData = activities_data.filter(act =>
act.title.toLowerCase().includes(nameInput) &&
act.location.toLowerCase().includes(locationInput)
);
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
if (currentPage > totalPages) {
currentPage = totalPages;
}
if (currentFilteredData.length === 0 && currentPage > 1) { // If all items on current page are deleted
currentPage = Math.max(1, totalPages); // Go to previous page or 1
} else if (currentFilteredData.length === 0) {
currentPage = 1;
}
renderTable(currentFilteredData);
}
function deleteActivity(id) {
if (!confirm("確定要刪除此活動嗎?")) return;
activities_data = activities_data.filter(act => act.id !== id);
// Re-filter and re-render after deletion
const nameInput = document.getElementById("searchName").value.toLowerCase();
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
currentFilteredData = activities_data.filter(act =>
act.title.toLowerCase().includes(nameInput) &&
act.location.toLowerCase().includes(locationInput)
);
// Adjust current page if necessary (e.g. if last item on a page is deleted)
const totalItemsOnCurrentPage = currentFilteredData.slice((currentPage - 1) * pageSize, currentPage * pageSize).length;
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
if (totalItemsOnCurrentPage === 0 && currentPage > 1) {
currentPage--;
} else if (currentPage > totalPages) {
currentPage = totalPages;
}
if (currentFilteredData.length === 0) { // If no data left at all
currentPage = 1;
}
renderTable(currentFilteredData);
}
function exportActivities() {
const nameInput = document.getElementById("searchName").value.toLowerCase();
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
let dataToExport = activities_data;
if (nameInput || locationInput) { // If any search criteria is active
dataToExport = currentFilteredData; // Export filtered data
}
if (dataToExport.length === 0) {
alert("沒有資料可匯出。");
return;
}
// Basic CSV export for simplicity
let csvContent = "data:text/csv;charset=utf-8,\uFEFF"; // \uFEFF for BOM to ensure UTF-8 in Excel
csvContent += "編號,活動名稱,活動時間,地點,是否需報名,審核狀態,描述,圖片連結\n"; // CSV Header
dataToExport.forEach(act => {
// Ensure all fields are properly quoted and escaped for CSV
const cleanTitle = `"${act.title.replace(/"/g, '""')}"`;
const cleanTime = `"${act.time.replace(/"/g, '""')}"`;
const cleanLocation = `"${act.location.replace(/"/g, '""')}"`;
const cleanStatus = `"${act.status.replace(/"/g, '""')}"`;
const cleanDesc = `"${act.desc.replace(/"/g, '""')}"`;
const cleanImage = `"${act.image ? act.image.replace(/"/g, '""') : ''}"`;
const row = [act.id, cleanTitle, cleanTime, cleanLocation, act.needRegister, cleanStatus, cleanDesc, cleanImage];
csvContent += row.join(",") + "\n";
});
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "activities_export.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
alert(`準備匯出 ${dataToExport.length} 筆活動資料(${nameInput || locationInput ? "依搜尋條件" : "全部"}`);
}
// Initial load: apply no filters (show all) and render
performSearch();
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>新增郵寄廠商</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">新增郵寄廠商</h2>
<form id="createVendorForm">
<div class="mb-3">
<label for="vendorName" class="form-label">廠商名稱</label>
<input type="text" class="form-control" id="vendorName" required>
</div>
<button type="submit" class="btn btn-success">新增廠商</button>
<a href="mailing_firm_list.html" class="btn btn-secondary ms-2">取消</a>
</form>
</div>
<script>
document.getElementById("createVendorForm").addEventListener("submit", function (e) {
e.preventDefault();
const newVendor = {
name: document.getElementById("vendorName").value
};
// In a real application, you would generate a unique ID here or get it from the backend.
// For demonstration, we'll assume the list page's script handles ID generation or can work without it initially.
console.log("新增的郵寄廠商資料:", newVendor);
alert("郵寄廠商資料已新增(此為前端演示,實際需串接後端 API");
// Typically, you'd pass the new data to the list page or have the list page re-fetch.
// For now, we'll just redirect.
window.location.href = "mailing_firm_list.html";
});
</script>
</body>
</html>

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>編輯郵寄廠商資料</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">編輯郵寄廠商資料</h2>
<form id="editVendorForm">
<div class="mb-3">
<label for="vendorName" class="form-label">廠商名稱</label>
<input type="text" class="form-control" id="vendorName" required>
</div>
<button type="submit" class="btn btn-primary">儲存變更</button>
<a href="mailing_firm_list.html" class="btn btn-secondary ms-2">取消</a>
</form>
</div>
<script>
// Function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
const results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// In a real application, you would fetch the vendor data based on the ID from the URL.
// For this example, we'll simulate fetching data.
// You would need a way to access the 'vendors' array from mailing_firm_list.html or use localStorage/backend.
// For now, let's use a placeholder name based on the ID or a default.
const vendorId = getUrlParameter('id');
let currentVendorName = "範例郵寄廠商"; // Default name
// This is a simplified placeholder.
// In a real scenario, you'd retrieve this from localStorage or an API using vendorId.
// For example, if you stored vendors in localStorage from the list page:
// let vendors = JSON.parse(localStorage.getItem('mailingVendors')) || [];
// const vendorToEdit = vendors.find(v => v.id === parseInt(vendorId));
// if (vendorToEdit) {
// currentVendorName = vendorToEdit.name;
// } else if (vendorId) {
// currentVendorName = `廠商 #${vendorId}`; // Fallback if not found but ID exists
// }
// Populate the form field
document.getElementById("vendorName").value = currentVendorName;
if (vendorId) {
// If you have a way to get the actual name, display it, e.g.
// document.getElementById("vendorName").value = actualVendorNameFetched;
console.log("Editing vendor with ID:", vendorId); // Log the ID for context
}
document.getElementById("editVendorForm").addEventListener("submit", function (e) {
e.preventDefault();
const updatedVendor = {
id: vendorId ? parseInt(vendorId) : null, // Keep the ID if it exists
name: document.getElementById("vendorName").value
};
console.log("更新後的郵寄廠商資料:", updatedVendor);
alert("郵寄廠商資料已更新(此為前端演示,實際需串接後端 API 並更新對應資料)");
// In a real application, you would send this data to a backend to update.
// Then, redirect or update the list page.
window.location.href = "mailing_firm_list.html";
});
</script>
</body>
</html>

View File

@ -0,0 +1,214 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>郵寄廠商列表</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
.container.mt-4 {
flex-grow: 1;
}
</style>
</head>
<body>
<div class="container mt-4">
<h2 class="mb-4">郵寄廠商</h2>
<div class="row g-2 mb-3">
<div class="col-auto">
<a href="mailing_firm_add.html" class="btn btn-outline-success rounded-pill">新增廠商</a>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-sm-8 col-md-5 col-lg-4">
<input type="text" id="searchName" class="form-control" placeholder="搜尋廠商名稱">
</div>
<div class="col-auto">
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
</div>
<div class="col-auto">
<button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
</div>
</div>
<table class="table table-bordered table-hover align-middle">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
<th>廠商名稱</th>
<th>操作</th>
</tr>
</thead>
<tbody id="vendorTable"></tbody>
</table>
<div class="d-flex justify-content-between align-items-center mt-3">
<div><span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span></div>
<div>
<button id="prevPageBtn" class="btn btn-sm btn-outline-secondary me-1" onclick="prevPage()">上一頁</button>
<button id="nextPageBtn" class="btn btn-sm btn-outline-secondary" onclick="nextPage()">下一頁</button>
</div>
</div>
</div>
<script>
const pageSize = 5; // Adjusted page size for simpler data
let currentPage = 1;
let currentFilteredData = [];
// Sample data for mailing vendors
let vendors = [
{ id: 1, name: "快捷郵運" },
{ id: 2, name: "順風物流" },
{ id: 3, name: "平安包裹" },
{ id: 4, name: "台灣郵政合作夥伴" },
{ id: 5, name: "環球快遞" },
{ id: 6, name: "城市通郵寄" },
{ id: 7, name: "鄉里配送" },
{ id: 8, name: "文件急送專家" },
{ id: 9, name: "省心郵寄服務" },
{ id: 10, name: "國際郵件通" },
{ id: 11, name: "本地包裹王" }
];
function renderTable(dataToRender) {
const tbody = document.getElementById("vendorTable");
tbody.innerHTML = "";
const start = (currentPage - 1) * pageSize;
const pageData = dataToRender.slice(start, start + pageSize);
pageData.forEach(v => {
const row = document.createElement("tr");
row.innerHTML = `
<td><input type="checkbox" class="row-checkbox" data-id="${v.id}"></td>
<td>${v.name}</td>
<td>
<a href="mailing_firm_edit.html?id=${v.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
<button class="btn btn-outline-danger btn-sm" onclick="deleteVendor(${v.id})">刪除</button>
</td>
`;
tbody.appendChild(row);
});
updatePaginationControls(dataToRender.length);
}
function updatePaginationControls(totalItems) {
const totalPages = Math.ceil(totalItems / pageSize) || 1;
const currentPageDisplay = document.getElementById("currentPageInfo");
const totalPagesDisplay = document.getElementById("totalPagesInfo");
const prevButton = document.getElementById("prevPageBtn");
const nextButton = document.getElementById("nextPageBtn");
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
if (prevButton) prevButton.disabled = currentPage === 1;
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
if (currentPage > totalPages && totalPages > 0) {
currentPage = totalPages;
renderTable(currentFilteredData);
} else if (totalItems === 0) {
if (currentPageDisplay) currentPageDisplay.textContent = 1;
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
if (prevButton) prevButton.disabled = true;
if (nextButton) nextButton.disabled = true;
}
}
function prevPage() {
if (currentPage > 1) {
currentPage--;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function nextPage() {
const totalPages = Math.ceil(currentFilteredData.length / pageSize);
if (currentPage < totalPages) {
currentPage++;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function performSearch() {
const nameInput = document.getElementById("searchName").value.toLowerCase();
currentFilteredData = vendors.filter(v =>
v.name.toLowerCase().includes(nameInput)
);
currentPage = 1;
renderTable(currentFilteredData);
}
function toggleAll(source) {
const checkboxes = document.querySelectorAll(".row-checkbox");
checkboxes.forEach(cb => cb.checked = source.checked);
}
function deleteSelected() {
if (!confirm("確定要刪除勾選的資料嗎?")) return;
const selected = document.querySelectorAll(".row-checkbox:checked");
if (selected.length === 0) {
alert("請先勾選要刪除的資料");
return;
}
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
vendors = vendors.filter(v => !idsToDelete.includes(v.id));
const nameInput = document.getElementById("searchName").value.toLowerCase();
currentFilteredData = vendors.filter(v =>
v.name.toLowerCase().includes(nameInput)
);
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
if (currentPage > totalPages) {
currentPage = totalPages;
}
if (currentFilteredData.length === 0) {
currentPage = 1;
}
renderTable(currentFilteredData);
document.getElementById("selectAll").checked = false;
}
function deleteVendor(id) {
if (!confirm("確定要刪除此筆資料嗎?")) return;
vendors = vendors.filter(v => v.id !== id);
const nameInput = document.getElementById("searchName").value.toLowerCase();
currentFilteredData = vendors.filter(v =>
v.name.toLowerCase().includes(nameInput)
);
const itemsOnCurrentPageAfterDelete = currentFilteredData.slice((currentPage - 1) * pageSize, currentPage * pageSize).length;
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
if (itemsOnCurrentPageAfterDelete === 0 && currentPage > 1) {
currentPage--;
} else if (currentPage > totalPages) {
currentPage = totalPages;
}
if (currentFilteredData.length === 0) {
currentPage = 1;
}
renderTable(currentFilteredData);
}
// Initial load
performSearch();
</script>
</body>
</html>

272
Backstage/parcel_add.html Normal file
View File

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>新增信件包裹</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Styles for the custom suggestions box - Retained as it's specific to this page's functionality */
.suggestions-container {
position: relative; /* Needed for absolute positioning of the box */
}
.suggestions-box {
position: absolute;
border: 1px solid #ced4da;
border-top: none;
max-height: 200px; /* Limit height and make it scrollable */
overflow-y: auto;
background-color: white;
width: 100%; /* Match the width of the input field */
z-index: 1000; /* Ensure it's above other elements */
border-radius: 0 0 0.25rem 0.25rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.suggestion-item {
padding: 0.5rem 0.75rem;
cursor: pointer;
}
.suggestion-item:hover, .suggestion-item.active {
background-color: #e9ecef;
}
.suggestions-box:empty { /* Hide box if empty */
display: none;
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">新增信件包裹</h2>
<form id="addPackageForm">
<div class="mb-3">
<label for="dateReceived" class="form-label">收件日期</label>
<input type="date" class="form-control" id="dateReceived" required>
</div>
<div class="mb-3 suggestions-container">
<label for="recipient" class="form-label">收件人 (可輸入搜尋)</label>
<input type="text" class="form-control" id="recipient" placeholder="輸入收件人姓名開始搜尋" required autocomplete="off">
<div id="suggestionsBox" class="suggestions-box">
</div>
</div>
<div class="mb-3">
<label for="amountToCollect" class="form-label">代收金額</label>
<input type="number" class="form-control" id="amountToCollect" placeholder="輸入代收金額 (若無則填0)" value="0" min="0" required>
</div>
<div class="mb-3">
<label for="deliveryService" class="form-label">配送廠商</label>
<select class="form-select" id="deliveryService" required>
<option value="" disabled selected>請選擇配送廠商</option>
<option value="黑貓宅急便">黑貓宅急便</option>
<option value="郵局">郵局</option>
<option value="7-11 交貨便">7-11 交貨便</option>
<option value="全家店到店">全家店到店</option>
<option value="OK便利店">OK便利店</option>
<option value="萊爾富">萊爾富</option>
<option value="嘉里大榮">嘉里大榮</option>
<option value="新竹物流">新竹物流</option>
<option value="宅配通">宅配通</option>
<option value="Uber Connect">Uber Connect</option>
<option value="Lalamove">Lalamove</option>
<option value="其他">其他</option>
</select>
<input type="text" class="form-control mt-2" id="otherDeliveryService" placeholder="請輸入其他配送廠商名稱" style="display: none;">
</div>
<div class="mb-3">
<label for="packageNumber" class="form-label">包裹編號</label>
<input type="text" class="form-control" id="packageNumber" placeholder="輸入包裹追蹤編號" required>
</div>
<div class="mb-3">
<label for="status" class="form-label">狀態</label>
<select class="form-select" id="status" required>
<option value="待領取" selected>待領取</option>
<option value="已領取">已領取</option>
<option value="已退回">已退回</option>
</select>
</div>
<div class="mb-3">
<label for="notes" class="form-label">備註</label>
<textarea class="form-control" id="notes" rows="3" placeholder="輸入其他備註事項 (選填)"></textarea>
</div>
<button type="submit" class="btn btn-primary">儲存包裹</button>
<a href="parcel_list.html" class="btn btn-secondary ms-2">返回列表</a>
</form>
</div>
<script>
// For hundreds of residents, this list ideally comes from a server
const allResidents = [
"林小安 (A101)", "陳大明 (B203)", "王小美 (C502)", "李四 (A102)", "張三 (D301)",
"趙六 (B205)", "孫七 (E101)", "吳八金 (F405)", "鄭九玲 (G1201)", "周十全 (H707)",
"劉一一 (A103)", "黃二二 (B204)", "蔡三三 (C503)", "許四四 (A104)", "洪五五 (D302)",
"曾六六 (B206)", "蕭七七 (E102)", "葉八八 (F406)", "楊九九 (G1202)", "呂零零 (H708)",
"高才 (A201)", "簡明 (B301)", "施美 (C601)", "溫柔 (A202)", "方正 (D401)",
"管理委員會", "社區服務中心", "訪客登記處", "保全室", "清潔公司"
];
const recipientInput = document.getElementById('recipient');
const suggestionsBox = document.getElementById('suggestionsBox');
let activeSuggestionIndex = -1;
recipientInput.addEventListener('input', function() {
const inputText = this.value.toLowerCase();
suggestionsBox.innerHTML = '';
activeSuggestionIndex = -1;
if (inputText.length === 0) {
suggestionsBox.style.display = 'none';
return;
}
const filteredResidents = allResidents.filter(resident =>
resident.toLowerCase().includes(inputText)
);
if (filteredResidents.length > 0) {
filteredResidents.forEach((resident, index) => {
const item = document.createElement('div');
item.classList.add('suggestion-item');
item.textContent = resident;
item.addEventListener('click', function() {
recipientInput.value = resident;
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
});
item.setAttribute('data-index', index);
suggestionsBox.appendChild(item);
});
suggestionsBox.style.display = 'block';
} else {
suggestionsBox.style.display = 'none';
}
});
recipientInput.addEventListener('keydown', function(e) {
const items = suggestionsBox.querySelectorAll('.suggestion-item');
if (items.length === 0 || suggestionsBox.style.display === 'none') return;
if (e.key === 'ArrowDown') {
e.preventDefault();
activeSuggestionIndex = (activeSuggestionIndex + 1) % items.length;
updateActiveSuggestion(items);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
activeSuggestionIndex = (activeSuggestionIndex - 1 + items.length) % items.length;
updateActiveSuggestion(items);
} else if (e.key === 'Enter') {
e.preventDefault();
if (activeSuggestionIndex > -1 && items[activeSuggestionIndex]) {
items[activeSuggestionIndex].click();
} else {
document.getElementById('addPackageForm').dispatchEvent(new Event('submit', {cancelable: true}));
}
} else if (e.key === 'Escape') {
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
}
});
function updateActiveSuggestion(items) {
items.forEach(item => item.classList.remove('active'));
if (items[activeSuggestionIndex]) {
items[activeSuggestionIndex].classList.add('active');
items[activeSuggestionIndex].scrollIntoView({ block: 'nearest' });
}
}
document.addEventListener('click', function(event) {
if (!recipientInput.contains(event.target) && !suggestionsBox.contains(event.target)) {
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
}
});
// --- Delivery Service Dropdown Logic ---
const deliveryServiceSelect = document.getElementById('deliveryService');
const otherDeliveryServiceInput = document.getElementById('otherDeliveryService');
deliveryServiceSelect.addEventListener('change', function() {
if (this.value === '其他') {
otherDeliveryServiceInput.style.display = 'block';
otherDeliveryServiceInput.required = true; // Make it required if "Other" is selected
otherDeliveryServiceInput.focus();
} else {
otherDeliveryServiceInput.style.display = 'none';
otherDeliveryServiceInput.required = false; // Not required if a predefined option is selected
otherDeliveryServiceInput.value = ''; // Clear its value
}
});
document.addEventListener('DOMContentLoaded', (event) => {
const today = new Date().toISOString().split('T')[0];
const dateReceivedInput = document.getElementById('dateReceived');
if(dateReceivedInput) {
dateReceivedInput.value = today;
}
// Ensure "Other" field is hidden and not required on initial load
otherDeliveryServiceInput.style.display = 'none';
otherDeliveryServiceInput.required = false;
});
document.getElementById('addPackageForm').addEventListener('submit', function(event) {
event.preventDefault();
let deliveryServiceValue = deliveryServiceSelect.value;
if (deliveryServiceValue === '其他') {
deliveryServiceValue = otherDeliveryServiceInput.value.trim();
if (!deliveryServiceValue) {
alert("請輸入「其他配送廠商」的名稱。");
otherDeliveryServiceInput.focus();
return;
}
}
const newPackage = {
id: Date.now(),
dateReceived: document.getElementById('dateReceived').value,
recipient: recipientInput.value,
amountToCollect: parseFloat(document.getElementById('amountToCollect').value),
deliveryService: deliveryServiceValue,
packageNumber: document.getElementById('packageNumber').value,
status: document.getElementById('status').value,
notes: document.getElementById('notes').value,
notificationCount: 0 // Assuming 0 for new packages
};
console.log("新包裹資料:", newPackage);
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
existingPackages.push(newPackage);
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
alert("包裹資料已「模擬」儲存!\n收件人" + newPackage.recipient + "\n包裹編號" + newPackage.packageNumber);
this.reset(); // Resets all form fields to their initial values
// Explicitly reset custom fields and state after form.reset()
const dateReceivedInput = document.getElementById('dateReceived');
if(dateReceivedInput) {
dateReceivedInput.value = new Date().toISOString().split('T')[0];
}
document.getElementById('status').value = "待領取"; // Default status
document.getElementById('amountToCollect').value = "0"; // Default amount
deliveryServiceSelect.value = ""; // Reset select to default placeholder
otherDeliveryServiceInput.style.display = 'none';
otherDeliveryServiceInput.required = false;
otherDeliveryServiceInput.value = '';
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
recipientInput.focus(); // Set focus back to recipient for quick new entry
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

332
Backstage/parcel_edit.html Normal file
View File

@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>編輯信件包裹 (測試模式)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Styles for the custom suggestions box - Retained as it's specific to this page's functionality */
.suggestions-container {
position: relative; /* Needed for absolute positioning of the box */
}
.suggestions-box {
position: absolute;
border: 1px solid #ced4da;
border-top: none;
max-height: 200px; /* Limit height and make it scrollable */
overflow-y: auto;
background-color: white;
width: 100%; /* Match the width of the input field */
z-index: 1000; /* Ensure it's above other elements */
border-radius: 0 0 0.25rem 0.25rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.suggestion-item {
padding: 0.5rem 0.75rem;
cursor: pointer;
}
.suggestion-item:hover, .suggestion-item.active {
background-color: #e9ecef;
}
.suggestions-box:empty { /* Hide box if empty */
display: none;
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">編輯信件包裹 (測試模式)</h2>
<form id="editPackageForm">
<input type="hidden" id="packageId" name="packageId">
<div class="mb-3">
<label for="dateReceived" class="form-label">收件日期</label>
<input type="date" class="form-control" id="dateReceived" required>
</div>
<div class="mb-3 suggestions-container">
<label for="recipient" class="form-label">收件人 (可輸入搜尋)</label>
<input type="text" class="form-control" id="recipient" placeholder="輸入收件人姓名開始搜尋" required autocomplete="off">
<div id="suggestionsBox" class="suggestions-box">
</div>
</div>
<div class="mb-3">
<label for="amountToCollect" class="form-label">代收金額</label>
<input type="number" class="form-control" id="amountToCollect" placeholder="輸入代收金額 (若無則填0)" value="0" min="0" required>
</div>
<div class="mb-3">
<label for="deliveryService" class="form-label">配送廠商</label>
<select class="form-select" id="deliveryService" required>
<option value="" disabled>請選擇配送廠商</option>
<option value="黑貓宅急便">黑貓宅急便</option>
<option value="郵局">郵局</option>
<option value="7-11 交貨便">7-11 交貨便</option>
<option value="全家店到店">全家店到店</option>
<option value="OK便利店">OK便利店</option>
<option value="萊爾富">萊爾富</option>
<option value="嘉里大榮">嘉里大榮</option>
<option value="新竹物流">新竹物流</option>
<option value="宅配通">宅配通</option>
<option value="Uber Connect">Uber Connect</option>
<option value="Lalamove">Lalamove</option>
<option value="其他">其他</option>
</select>
<input type="text" class="form-control mt-2" id="otherDeliveryService" placeholder="請輸入其他配送廠商名稱" style="display: none;">
</div>
<div class="mb-3">
<label for="packageNumber" class="form-label">包裹編號</label>
<input type="text" class="form-control" id="packageNumber" placeholder="輸入包裹追蹤編號" required>
</div>
<div class="mb-3">
<label for="status" class="form-label">狀態</label>
<select class="form-select" id="status" required>
<option value="待領取">待領取</option>
<option value="已領取">已領取</option>
<option value="已退回">已退回</option>
</select>
</div>
<div class="row mb-3">
<div class="col-md-9">
<label for="notes" class="form-label">備註</label>
<textarea class="form-control" id="notes" rows="3" placeholder="輸入其他備註事項 (選填)"></textarea>
</div>
<div class="col-md-3">
<label for="notificationCountDisplay" class="form-label">通知次數</label>
<input type="text" class="form-control" id="notificationCountDisplay" readonly disabled>
<button type="button" class="btn btn-outline-info btn-sm mt-2 w-100" id="resendNotificationBtn">再寄一次到貨通知</button>
</div>
</div>
<button type="submit" class="btn btn-primary">更新包裹</button>
<a href="parcel_list.html" class="btn btn-secondary ms-2">返回列表</a>
</form>
</div>
<script>
let currentEditingPackage = {};
const allResidents = [
"林小安 (A101)", "陳大明 (B203)", "王小美 (C502)", "李四 (A102)", "張三 (D301)",
"趙六 (B205)", "孫七 (E101)", "吳八金 (F405)", "鄭九玲 (G1201)", "周十全 (H707)",
"劉一一 (A103)", "黃二二 (B204)", "蔡三三 (C503)", "許四四 (A104)", "洪五五 (D302)",
"曾六六 (B206)", "蕭七七 (E102)", "葉八八 (F406)", "楊九九 (G1202)", "呂零零 (H708)",
"高才 (A201)", "簡明 (B301)", "施美 (C601)", "溫柔 (A202)", "方正 (D401)",
"管理委員會", "社區服務中心", "訪客登記處", "保全室", "清潔公司", "測試用戶 (Z999)"
];
const recipientInput = document.getElementById('recipient');
const suggestionsBox = document.getElementById('suggestionsBox');
const notificationCountDisplay = document.getElementById('notificationCountDisplay');
const resendNotificationBtn = document.getElementById('resendNotificationBtn');
const deliveryServiceSelectEdit = document.getElementById('deliveryService');
const otherDeliveryServiceInputEdit = document.getElementById('otherDeliveryService');
let activeSuggestionIndex = -1;
recipientInput.addEventListener('input', function() {
const inputText = this.value.toLowerCase();
suggestionsBox.innerHTML = '';
activeSuggestionIndex = -1;
if (inputText.length === 0) {
suggestionsBox.style.display = 'none';
return;
}
const filteredResidents = allResidents.filter(resident =>
resident.toLowerCase().includes(inputText)
);
if (filteredResidents.length > 0) {
filteredResidents.forEach((resident, index) => {
const item = document.createElement('div');
item.classList.add('suggestion-item');
item.textContent = resident;
item.addEventListener('click', function() {
recipientInput.value = resident;
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
});
item.setAttribute('data-index', index);
suggestionsBox.appendChild(item);
});
suggestionsBox.style.display = 'block';
} else {
suggestionsBox.style.display = 'none';
}
});
recipientInput.addEventListener('keydown', function(e) {
const items = suggestionsBox.querySelectorAll('.suggestion-item');
if (items.length === 0 || suggestionsBox.style.display === 'none') return;
if (e.key === 'ArrowDown') {
e.preventDefault();
activeSuggestionIndex = (activeSuggestionIndex + 1) % items.length;
updateActiveSuggestion(items);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
activeSuggestionIndex = (activeSuggestionIndex - 1 + items.length) % items.length;
updateActiveSuggestion(items);
} else if (e.key === 'Enter') {
e.preventDefault();
if (activeSuggestionIndex > -1 && items[activeSuggestionIndex]) {
items[activeSuggestionIndex].click();
} else {
document.getElementById('editPackageForm').dispatchEvent(new Event('submit', {cancelable: true}));
}
} else if (e.key === 'Escape') {
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
}
});
function updateActiveSuggestion(items) {
items.forEach(item => item.classList.remove('active'));
if (items[activeSuggestionIndex]) {
items[activeSuggestionIndex].classList.add('active');
items[activeSuggestionIndex].scrollIntoView({ block: 'nearest' });
}
}
document.addEventListener('click', function(event) {
if (!recipientInput.contains(event.target) && !suggestionsBox.contains(event.target)) {
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
}
});
deliveryServiceSelectEdit.addEventListener('change', function() {
if (this.value === '其他') {
otherDeliveryServiceInputEdit.style.display = 'block';
otherDeliveryServiceInputEdit.required = true;
otherDeliveryServiceInputEdit.focus();
} else {
otherDeliveryServiceInputEdit.style.display = 'none';
otherDeliveryServiceInputEdit.required = false;
otherDeliveryServiceInputEdit.value = '';
}
});
document.addEventListener('DOMContentLoaded', () => {
currentEditingPackage = {
id: 99901,
dateReceived: "2025-05-20",
recipient: "測試用戶 (Z999)",
amountToCollect: 150,
deliveryService: "測試快遞公司", // This will be handled by the logic below
packageNumber: "TESTPKG00123XYZ",
status: "已通知住戶",
notes: "這是一筆用於編輯頁面測試的包裹備註。",
notificationCount: 1
};
// Attempt to retrieve from localStorage first
const urlParams = new URLSearchParams(window.location.search);
const packageIdFromUrl = parseInt(urlParams.get('id'));
if (packageIdFromUrl) {
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
const foundPackage = existingPackages.find(p => p.id === packageIdFromUrl);
if (foundPackage) {
currentEditingPackage = { ...foundPackage }; // Make a copy to avoid modifying the stored object directly
} else {
alert("找不到指定的包裹資料!將使用範例資料。 ID: " + packageIdFromUrl);
// Fallback to sample if not found, or handle error differently
}
}
document.getElementById('packageId').value = currentEditingPackage.id;
document.getElementById('dateReceived').value = currentEditingPackage.dateReceived;
recipientInput.value = currentEditingPackage.recipient;
document.getElementById('amountToCollect').value = currentEditingPackage.amountToCollect;
document.getElementById('packageNumber').value = currentEditingPackage.packageNumber;
document.getElementById('status').value = currentEditingPackage.status;
document.getElementById('notes').value = currentEditingPackage.notes;
notificationCountDisplay.value = currentEditingPackage.notificationCount || 0;
// Populate delivery service dropdown
const deliveryServiceValueFromData = currentEditingPackage.deliveryService;
let isOtherService = true;
if (deliveryServiceValueFromData) {
for (let i = 0; i < deliveryServiceSelectEdit.options.length; i++) {
if (deliveryServiceSelectEdit.options[i].value === deliveryServiceValueFromData) {
deliveryServiceSelectEdit.value = deliveryServiceValueFromData;
isOtherService = false;
break;
}
}
if (isOtherService) {
deliveryServiceSelectEdit.value = '其他';
otherDeliveryServiceInputEdit.value = deliveryServiceValueFromData;
otherDeliveryServiceInputEdit.style.display = 'block';
otherDeliveryServiceInputEdit.required = true;
} else {
otherDeliveryServiceInputEdit.style.display = 'none';
otherDeliveryServiceInputEdit.required = false;
}
} else {
deliveryServiceSelectEdit.value = ""; // Set to default "請選擇配送廠商"
otherDeliveryServiceInputEdit.style.display = 'none';
otherDeliveryServiceInputEdit.required = false;
}
});
resendNotificationBtn.addEventListener('click', function() {
currentEditingPackage.notificationCount = (parseInt(currentEditingPackage.notificationCount) || 0) + 1;
notificationCountDisplay.value = currentEditingPackage.notificationCount;
alert(`已模擬重新發送第 ${currentEditingPackage.notificationCount} 次到貨通知給 ${currentEditingPackage.recipient}`);
});
document.getElementById('editPackageForm').addEventListener('submit', function(event) {
event.preventDefault();
let updatedDeliveryServiceValue = deliveryServiceSelectEdit.value;
if (updatedDeliveryServiceValue === '其他') {
updatedDeliveryServiceValue = otherDeliveryServiceInputEdit.value.trim();
if (!updatedDeliveryServiceValue) {
alert("請輸入「其他配送廠商」的名稱。");
otherDeliveryServiceInputEdit.focus();
return;
}
}
currentEditingPackage.dateReceived = document.getElementById('dateReceived').value;
currentEditingPackage.recipient = recipientInput.value;
currentEditingPackage.amountToCollect = parseFloat(document.getElementById('amountToCollect').value);
currentEditingPackage.deliveryService = updatedDeliveryServiceValue;
currentEditingPackage.packageNumber = document.getElementById('packageNumber').value;
currentEditingPackage.status = document.getElementById('status').value;
currentEditingPackage.notes = document.getElementById('notes').value;
// notificationCount is already part of currentEditingPackage
console.log("更新後包裹資料 (測試模式):", currentEditingPackage);
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
const packageIndex = existingPackages.findIndex(p => p.id === currentEditingPackage.id);
if (packageIndex > -1) {
existingPackages[packageIndex] = { ...currentEditingPackage };
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
alert("包裹資料已「模擬更新」至 LocalStorage\n收件人" + currentEditingPackage.recipient + "\n通知次數" + currentEditingPackage.notificationCount);
} else {
// This case should ideally not happen if we are editing an existing one,
// but as a fallback, it adds it.
existingPackages.push({ ...currentEditingPackage });
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
alert("包裹資料 (ID: " + currentEditingPackage.id + ") 在列表中未找到,已「模擬新增」至 LocalStorage\n通知次數" + currentEditingPackage.notificationCount);
}
suggestionsBox.innerHTML = '';
suggestionsBox.style.display = 'none';
activeSuggestionIndex = -1;
// Optionally redirect or give other feedback
// window.location.href = 'parcel_list.html';
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

268
Backstage/parcel_list.html Normal file
View File

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>信件包裹管理</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
}
.container.mt-4 {
flex-grow: 1;
}
</style>
</head>
<body>
<div class="container mt-4">
<h2 class="mb-4">信件包裹管理</h2>
<div class="row g-2 mb-3">
<div class="col-auto"> <a href="parcel_add.html" class="btn btn-outline-success rounded-pill px-3">新增包裹</a> </div>
<div class="col-auto"> <button class="btn btn-outline-secondary rounded-pill px-3" onclick="exportPackages()">匯出列表</button>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-sm-6 col-md-3 col-lg-3">
<input type="text" id="searchRecipient" class="form-control" placeholder="搜尋收件人">
</div>
<div class="col-sm-6 col-md-3 col-lg-3">
<input type="text" id="searchPackageNumber" class="form-control" placeholder="搜尋包裹編號">
</div>
<div class="col-sm-6 col-md-2 col-lg-2">
<select id="searchStatus" class="form-select">
<option value="">所有狀態</option>
<option value="待領取">待領取</option>
<option value="已領取">已領取</option>
<option value="已退回">已退回</option>
</select>
</div>
<div class="col-6 col-md-2 col-lg-2">
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
</div>
<div class="col-6 col-md-2 col-lg-2"> <button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
</div>
</div>
<table class="table table-bordered table-hover align-middle">
<thead class="table-dark">
<tr>
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
<th>編號</th>
<th>收件日期</th>
<th>收件人</th>
<th>代收金額</th>
<th>配送廠商</th>
<th>包裹編號</th>
<th>狀態</th>
<th>操作</th>
</tr>
</thead>
<tbody id="packageTable">
</tbody>
</table>
<div class="d-flex justify-content-between align-items-center mt-3">
<div><span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span></div>
<div>
<button id="prevPageBtn" class="btn btn-sm btn-outline-secondary me-1" onclick="prevPage()">上一頁</button>
<button id="nextPageBtn" class="btn btn-sm btn-outline-secondary" onclick="nextPage()">下一頁</button>
</div>
</div>
</div>
<script>
const pageSize = 5; // Number of items per page
let currentPage = 1;
let currentFilteredData = [];
// Sample package data. In a real application, this would come from a server.
let packages_data = [
{ id: 1, dateReceived: "2025/04/16", recipient: "林小安", amountToCollect: 250, deliveryService: "宅配 - 7-11 交貨便", packageNumber: "P20240416001", status: "待領取", notes: "易碎品" },
{ id: 2, dateReceived: "2025/04/14", recipient: "陳大明", amountToCollect: 120, deliveryService: "黑貓宅急便", packageNumber: "P20240414002", status: "待領取", notes: "" },
{ id: 3, dateReceived: "2025/04/10", recipient: "王小美", amountToCollect: 0, deliveryService: "郵局掛號", packageNumber: "P20240410001", status: "已領取", notes: "文件" },
{ id: 4, dateReceived: "2025/04/09", recipient: "李四", amountToCollect: 500, deliveryService: "嘉里大榮", packageNumber: "P20240409003", status: "待領取", notes: "" },
{ id: 5, dateReceived: "2025/04/08", recipient: "張三", amountToCollect: 0, deliveryService: "新竹物流", packageNumber: "P20240408001", status: "已退回", notes: "查無此人" },
{ id: 6, dateReceived: "2025/04/17", recipient: "趙六", amountToCollect: 80, deliveryService: "店到店", packageNumber: "P20240417005", status: "待領取", notes: "" },
{ id: 7, dateReceived: "2025/04/18", recipient: "孫七", amountToCollect: 1000, deliveryService: "Uber Connect", packageNumber: "P20240418001", status: "待領取", notes: "代收金額高" }
];
function renderTable(dataToRender) {
const tbody = document.getElementById("packageTable");
tbody.innerHTML = "";
const start = (currentPage - 1) * pageSize;
const pageData = dataToRender.slice(start, start + pageSize);
pageData.forEach(pkg => {
const row = document.createElement("tr");
let statusBadgeClass = 'bg-secondary'; // Default badge
let statusText = pkg.status;
switch (pkg.status) {
case '待領取':
statusBadgeClass = 'bg-warning text-dark';
break;
case '已領取':
statusBadgeClass = 'bg-success';
break;
case '已退回':
statusBadgeClass = 'bg-danger';
break;
}
row.innerHTML = `
<td><input type="checkbox" class="row-checkbox" data-id="${pkg.id}"></td>
<td>${pkg.id}</td>
<td>${pkg.dateReceived}</td>
<td>${pkg.recipient}</td>
<td>$${pkg.amountToCollect}</td>
<td>${pkg.deliveryService}</td>
<td>${pkg.packageNumber}</td>
<td><span class="badge ${statusBadgeClass}">${statusText}</span></td>
<td>
<a href="parcel_edit.html?id=${pkg.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
<button class="btn btn-outline-danger btn-sm" onclick="deletePackage(${pkg.id})">刪除</button>
</td>
`;
tbody.appendChild(row);
});
updatePaginationControls(dataToRender.length);
const selectAllCheckbox = document.getElementById("selectAll");
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
}
function updatePaginationControls(totalItems) {
const totalPages = Math.ceil(totalItems / pageSize) || 1;
const currentPageDisplay = document.getElementById("currentPageInfo");
const totalPagesDisplay = document.getElementById("totalPagesInfo");
const prevButton = document.getElementById("prevPageBtn");
const nextButton = document.getElementById("nextPageBtn");
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
if (prevButton) prevButton.disabled = currentPage === 1;
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
if (currentPage > totalPages && totalPages > 0) {
currentPage = totalPages;
renderTable(currentFilteredData);
} else if (totalItems === 0) {
if (currentPageDisplay) currentPageDisplay.textContent = 1;
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
if (prevButton) prevButton.disabled = true;
if (nextButton) nextButton.disabled = true;
}
}
function prevPage() {
if (currentPage > 1) {
currentPage--;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function nextPage() {
const totalPages = Math.ceil(currentFilteredData.length / pageSize);
if (currentPage < totalPages) {
currentPage++;
renderTable(currentFilteredData);
window.scrollTo(0, 0);
}
}
function performSearch() {
const recipientInput = document.getElementById("searchRecipient").value.toLowerCase();
const packageNumberInput = document.getElementById("searchPackageNumber").value.toLowerCase();
const statusInput = document.getElementById("searchStatus").value;
currentFilteredData = packages_data.filter(pkg =>
pkg.recipient.toLowerCase().includes(recipientInput) &&
pkg.packageNumber.toLowerCase().includes(packageNumberInput) &&
(statusInput === "" || pkg.status === statusInput)
);
currentPage = 1;
renderTable(currentFilteredData);
}
function toggleAll(source) {
const checkboxes = document.querySelectorAll(".row-checkbox");
checkboxes.forEach(cb => cb.checked = source.checked);
}
function deleteSelected() {
if (!confirm("確定要刪除勾選的包裹紀錄嗎?")) return;
const selected = document.querySelectorAll(".row-checkbox:checked");
if (selected.length === 0) {
alert("請先勾選要刪除的包裹紀錄");
return;
}
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
packages_data = packages_data.filter(pkg => !idsToDelete.includes(pkg.id));
performSearch(); // Re-filter and re-render
}
function deletePackage(id) {
if (!confirm("確定要刪除此包裹紀錄嗎?")) return;
packages_data = packages_data.filter(pkg => pkg.id !== id);
performSearch(); // Re-filter and re-render
}
function exportPackages() {
const recipientInput = document.getElementById("searchRecipient").value.toLowerCase();
const packageNumberInput = document.getElementById("searchPackageNumber").value.toLowerCase();
const statusInput = document.getElementById("searchStatus").value;
let dataToExport = packages_data;
// If any search criteria is active, export filtered data
if (recipientInput || packageNumberInput || statusInput) {
dataToExport = currentFilteredData;
}
if (dataToExport.length === 0) {
alert("沒有資料可匯出。");
return;
}
let csvContent = "data:text/csv;charset=utf-8,\uFEFF"; // \uFEFF for BOM
csvContent += "編號,收件日期,收件人,代收金額,配送廠商,包裹編號,狀態,備註\n";
dataToExport.forEach(pkg => {
const cleanRecipient = `"${pkg.recipient.replace(/"/g, '""')}"`;
const cleanService = `"${pkg.deliveryService.replace(/"/g, '""')}"`;
const cleanPkgNum = `"${pkg.packageNumber.replace(/"/g, '""')}"`;
const cleanStatus = `"${pkg.status.replace(/"/g, '""')}"`;
const cleanNotes = `"${pkg.notes ? pkg.notes.replace(/"/g, '""') : ''}"`;
const row = [pkg.id, pkg.dateReceived, cleanRecipient, pkg.amountToCollect, cleanService, cleanPkgNum, cleanStatus, cleanNotes];
csvContent += row.join(",") + "\n";
});
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "packages_export.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
alert(`準備匯出 ${dataToExport.length} 筆包裹資料(${recipientInput || packageNumberInput || statusInput ? "依搜尋條件" : "全部"}`);
}
// Initial load: apply no filters (show all) and render
performSearch();
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -205,7 +205,6 @@
<li onclick="requestNavigation('repair_firm_list.html', this)">報修廠商</li>
</ul>
</li>
<li onclick="requestNavigation('feedback_list.html', this)">
<div class="li-content">
<span>意見回饋</span>
@ -213,7 +212,24 @@
<span class="submenu-arrow">&emsp;</span>
</div>
</li>
<li onclick="requestNavigation('activity_list.html', this)">
<div class="li-content">
<span>社區活動</span>
<span class="badge" data-count="99">99</span>
<span class="submenu-arrow">&emsp;</span>
</div>
</li>
<li class="has-submenu">
<div class="submenu-toggle" onclick="toggleSubmenu(this)">
<span>郵件管理</span>
<span class="parent-badge"></span>
<span class="submenu-arrow"></span>
</div>
<ul>
<li onclick="requestNavigation('parcel_list.html', this)">郵件管理</li>
<li onclick="requestNavigation('mailing_firm_list.html', this)">郵寄廠商</li>
</ul>
</li>
<li onclick="requestNavigation('some_page6.html', this)">報表匯出</li>
<li onclick="requestNavigation('some_page7.html', this)">設定</li>
</ul>

View File

@ -27,9 +27,13 @@
<input type="text" class="form-control" id="title" required>
</div>
<div class="mb-3">
<label for="date" class="form-label">日期與時間</label>
<label for="date" class="form-label">開始時間</label>
<input type="datetime-local" class="form-control" id="date" required>
</div>
<div class="mb-3">
<label for="endDate" class="form-label">結束時間</label>
<input type="datetime-local" class="form-control" id="endDate" required>
</div>
<div class="mb-3">
<label for="location" class="form-label">地點</label>
<input type="text" class="form-control" id="location" required>
@ -42,19 +46,35 @@
<label for="poster" class="form-label">上傳活動海報</label>
<input type="file" class="form-control" id="poster" accept="image/*">
</div>
<div class="mb-4">
<div class="mb-3">
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
<select id="needRegister" class="form-select">
<option value="否" selected></option>
<option value="是"></option>
</select>
</div>
<div class="mb-3">
<label for="repeatOption" class="form-label">是否重複舉辦</label>
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
<option value="none" selected></option>
<option value="weekly">每週一次</option>
<option value="biweekly">每兩週一次</option>
<option value="monthly">每月一次</option>
</select>
</div>
<div class="mb-3 d-none" id="repeatCountGroup">
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-success">送出申請</button>
</div>
</form>
</div>
<br/><br/><br/><br/><br/>
<nav class="navbar fixed-bottom navbar-light bg-light border-top">
<div class="container justify-content-around">
@ -66,17 +86,41 @@
</nav>
<script>
function toggleRepeatFields() {
const option = document.getElementById('repeatOption').value;
const group = document.getElementById('repeatCountGroup');
group.classList.toggle('d-none', option === 'none');
}
function submitActivity(event) {
event.preventDefault();
const title = document.getElementById('title').value;
const date = document.getElementById('date').value;
const endDate = document.getElementById('endDate').value;
const location = document.getElementById('location').value;
const description = document.getElementById('description').value;
const poster = document.getElementById('poster').files[0];
const needRegister = document.getElementById('needRegister').value;
const repeatOption = document.getElementById('repeatOption').value;
const repeatCount = document.getElementById('repeatCount').value;
if (new Date(endDate) <= new Date(date)) {
alert("結束時間必須晚於開始時間!");
return;
}
let message = `活動申請已送出\n名稱${title}\n開始時間${date}\n結束時間${endDate}\n地點${location}\n描述${description}\n需報名統計${needRegister}`;
if (repeatOption !== 'none') {
const optionMap = {
weekly: '每週',
biweekly: '每兩週',
monthly: '每月'
};
message += `\n重複設定${optionMap[repeatOption]},共重複 ${repeatCount} 次`;
}
let message = `活動申請已送出\n名稱${title}\n時間${date}\n地點${location}\n描述${description}\n需報名統計${needRegister}`;
if (poster) {
message += `\n檔案名稱${poster.name}`;
}