/** * Posts Management Page for KSTools License Manager * 貼文發布管理頁面 */ class PostsPage { constructor() { this.posts = []; this.users = []; // 用戶列表(用於選擇收件人) this.filters = { productType: 'all', status: 'all' }; this.currentEditPost = null; this.selectedEmails = []; // 當前選中的收件人 } async render(container) { container.innerHTML = `
- 總貼文數
- 草稿
- 已發送

載入貼文中...

`; this.setupEventHandlers(); await this.loadPosts(); } setupEventHandlers() { // 產品類型篩選 const productTypeFilter = document.getElementById('productTypeFilter'); if (productTypeFilter) { productTypeFilter.addEventListener('click', (e) => { const btn = e.target.closest('.filter-btn'); if (btn) { productTypeFilter.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); this.filters.productType = btn.dataset.value; this.displayPosts(); } }); } // 狀態篩選 const statusFilter = document.getElementById('statusFilter'); if (statusFilter) { statusFilter.addEventListener('click', (e) => { const btn = e.target.closest('.filter-btn'); if (btn) { statusFilter.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); this.filters.status = btn.dataset.value; this.displayPosts(); } }); } // Modal 背景點擊關閉 const modal = document.getElementById('editPostModal'); if (modal) { modal.addEventListener('click', (e) => { if (e.target === modal) { this.closeEditModal(); } }); } } async loadPosts() { try { const result = await api.getPosts(); this.posts = result.posts || []; this.updateStats(); this.displayPosts(); } catch (error) { console.error('Load posts error:', error); const grid = document.getElementById('postsGrid'); if (grid) { grid.innerHTML = `

載入失敗

${error.message}

`; } } } updateStats() { const total = this.posts.length; const draft = this.posts.filter(p => p.status === 'draft' || p.status === 'published').length; const sent = this.posts.filter(p => p.status === 'sent').length; document.getElementById('statTotal').textContent = total; document.getElementById('statDraft').textContent = draft; document.getElementById('statSent').textContent = sent; } displayPosts() { const grid = document.getElementById('postsGrid'); if (!grid) return; // 篩選貼文 let filteredPosts = this.posts; if (this.filters.productType !== 'all') { filteredPosts = filteredPosts.filter(p => p.product_type === this.filters.productType); } if (this.filters.status !== 'all') { filteredPosts = filteredPosts.filter(p => p.status === this.filters.status); } if (filteredPosts.length === 0) { grid.innerHTML = `

沒有貼文

當發布新版本時,系統會自動建立貼文草稿

`; return; } grid.innerHTML = filteredPosts.map(post => this.createPostCard(post)).join(''); } createPostCard(post) { let productBadge; if (post.product_type === 'revit') { productBadge = 'Revit'; } else if (post.product_type === 'autocad') { productBadge = 'AutoCAD'; } else { productBadge = '公告'; } const statusBadge = this.getStatusBadge(post.status); const createdDate = new Date(post.created_at).toLocaleDateString('zh-TW'); // 如果主旨為空,顯示版本資訊作為標題 let displaySubject; if (post.subject) { displaySubject = post.subject; } else if (post.product_type === 'announcement') { displaySubject = '新公告(待編輯)'; } else if (post.title) { // 如果有版本標題,顯示版本標題 displaySubject = `${post.product_type === 'revit' ? 'Revit' : 'AutoCAD'} v${post.version} - ${post.title}`; } else { displaySubject = `${post.product_type === 'revit' ? 'Revit' : 'AutoCAD'} v${post.version} 版本更新`; } // 如果內容為空,顯示提示訊息 const contentPreview = post.content ? this.truncateText(post.content, 150) : '尚未編輯貼文內容,請點擊編輯按鈕填寫'; // 收件人資訊 const recipientCount = post.selected_emails && post.selected_emails.length > 0 ? post.selected_emails.length : '全部'; // 統一的操作按鈕(簡化流程:所有狀態都可以編輯和發送) const emailLogs = post.email_logs || []; const sendCount = emailLogs.length; const sentInfo = sendCount > 0 ? ` 已發送 ${sendCount} 次 ` : ''; const actionButtons = ` ${sentInfo} `; // 版本顯示 const versionDisplay = post.product_type === 'announcement' ? '' : `v${post.version}`; return `
${productBadge} ${statusBadge}
${versionDisplay}

${this.escapeHtml(displaySubject)}

${this.escapeHtml(contentPreview)}

${actionButtons}
`; } getStatusBadge(status) { const badges = { draft: ' 草稿', published: ' 草稿', sent: ' 已發送' }; return badges[status] || ''; } truncateText(text, maxLength) { if (!text) return ''; if (text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; } escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } closeEditModal() { const modal = document.getElementById('editPostModal'); modal.classList.remove('active'); modal.style.display = 'none'; modal.style.opacity = ''; modal.style.visibility = ''; this.currentEditPost = null; this.selectedEmails = []; } // 發布貼文 async publishPost(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; // 檢查主旨和內容是否已填寫 if (!post.subject || !post.content) { Utils.showError('無法發布', '請先填寫貼文主旨和內容'); this.editPost(postId); // 自動開啟編輯視窗 return; } const confirmed = confirm(`確定要發布「${post.subject}」嗎?\n\n發布後將無法再編輯內容。`); if (!confirmed) return; try { await api.publishPost(postId, false); Utils.showSuccess('貼文已發布'); await this.loadPosts(); } catch (error) { Utils.handleError(error, '發布貼文時'); } } // 發送郵件(簡化流程:可重複發送) async sendEmail(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; // 檢查主旨和內容 if (!post.subject || !post.content) { Utils.showError('無法發送', '請先填寫貼文主旨和內容'); this.editPost(postId); return; } const recipientInfo = post.selected_emails && post.selected_emails.length > 0 ? `${post.selected_emails.length} 位指定用戶` : '所有啟用用戶'; const productName = post.product_type === 'revit' ? 'Revit' : post.product_type === 'autocad' ? 'AutoCAD' : '公告'; const emailLogs = post.email_logs || []; const resendWarning = emailLogs.length > 0 ? `\n⚠️ 此貼文已發送過 ${emailLogs.length} 次` : ''; const confirmed = confirm( `確定要發送郵件通知嗎?\n\n` + `主旨:${post.subject}\n` + `類型:${productName}\n` + `收件人:${recipientInfo}` + `${resendWarning}\n\n` + `發送後無法撤回。` ); if (!confirmed) return; try { // 使用貼文中儲存的收件人列表 const result = await api.sendPostEmailSelective(postId, post.selected_emails); const totalSends = result.total_sends || (emailLogs.length + 1); Utils.showSuccess(`郵件已發送給 ${result.recipient_count || 0} 位用戶(第 ${totalSends} 次發送)`); await this.loadPosts(); } catch (error) { Utils.handleError(error, '發送郵件時'); } } // 查看貼文(已發送的) viewPost(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; // 顯示唯讀 modal this.currentEditPost = null; document.getElementById('editSubject').value = post.subject; document.getElementById('editSubject').disabled = true; document.getElementById('editContent').value = post.content; document.getElementById('editContent').disabled = true; // 修改 modal 標題和按鈕 const modal = document.getElementById('editPostModal'); modal.querySelector('.modal-title').textContent = '查看貼文'; modal.querySelector('.modal-footer').innerHTML = ` `; // 顯示 modal modal.classList.add('active'); modal.style.display = 'flex'; modal.style.opacity = '1'; modal.style.visibility = 'visible'; } closeViewModal() { const modal = document.getElementById('editPostModal'); // 隱藏 modal modal.classList.remove('active'); modal.style.display = 'none'; modal.style.opacity = ''; modal.style.visibility = ''; // 恢復編輯功能 document.getElementById('editSubject').disabled = false; document.getElementById('editContent').disabled = false; modal.querySelector('.modal-title').textContent = '編輯貼文'; modal.querySelector('.modal-footer').innerHTML = ` `; } // 查看郵件發送記錄 viewEmailLogs(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; const emailLogs = post.email_logs || []; if (emailLogs.length === 0) { Utils.showInfo('發送記錄', '此貼文尚未發送過郵件'); return; } // 建立發送記錄 HTML const logsHtml = emailLogs.map((log, index) => { const sentAt = new Date(log.sent_at).toLocaleString('zh-TW'); const recipients = log.recipients_preview || []; const recipientText = recipients.length > 0 ? recipients.join(', ') + (log.recipient_count > recipients.length ? ` 等 ${log.recipient_count} 人` : '') : `${log.recipient_count} 位用戶`; return `
#${emailLogs.length - index} ${sentAt}
發送者: ${log.sent_by || '未知'}
收件人: ${recipientText}
`; }).reverse().join(''); // 使用編輯 Modal 顯示發送記錄 const modal = document.getElementById('editPostModal'); modal.querySelector('.modal-title').textContent = '郵件發送記錄'; modal.querySelector('.modal-body').innerHTML = `
主旨: ${this.escapeHtml(post.subject || '(未設定)')}
${logsHtml}
`; modal.querySelector('.modal-footer').innerHTML = ` `; modal.classList.add('active'); modal.style.display = 'flex'; modal.style.opacity = '1'; modal.style.visibility = 'visible'; } closeEmailLogsModal() { const modal = document.getElementById('editPostModal'); modal.classList.remove('active'); modal.style.display = 'none'; modal.style.opacity = ''; modal.style.visibility = ''; // 恢復原本的 modal 內容 modal.querySelector('.modal-title').textContent = '編輯貼文'; modal.querySelector('.modal-body').innerHTML = `
可使用 {download_link} 變數,系統會自動替換為下載連結
已選擇: 0 位用戶
載入用戶列表...
`; modal.querySelector('.modal-footer').innerHTML = ` `; } // 刪除貼文 async deletePost(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; const displayName = post.subject || `${post.product_type} v${post.version} 版本更新`; const confirmed = confirm(`確定要刪除「${displayName}」嗎?\n\n此操作無法復原。`); if (!confirmed) return; try { await api.deletePost(postId); Utils.showSuccess('貼文已刪除'); await this.loadPosts(); } catch (error) { Utils.handleError(error, '刪除貼文時'); } } async refreshPosts() { const grid = document.getElementById('postsGrid'); if (grid) { grid.innerHTML = `

載入貼文中...

`; } await this.loadPosts(); Utils.showSuccess('已重新整理'); } async refresh() { await this.loadPosts(); } // ==================== 用戶選擇功能 ==================== async loadUsers() { try { const result = await api.getUserEmails(); this.users = result.users || []; return this.users; } catch (error) { console.error('Load users error:', error); this.users = []; return []; } } renderRecipientList() { const container = document.getElementById('recipientList'); if (!container) return; if (this.users.length === 0) { container.innerHTML = `

沒有找到用戶

`; return; } const html = this.users.map(user => { const isChecked = this.selectedEmails.includes(user.email); const statusClass = user.status === 'active' ? 'active' : 'inactive'; return ` `; }).join(''); container.innerHTML = html; this.updateSelectedCount(); } toggleUserSelection(email) { const index = this.selectedEmails.indexOf(email); if (index > -1) { this.selectedEmails.splice(index, 1); } else { this.selectedEmails.push(email); } this.updateSelectedCount(); } selectAllUsers() { this.selectedEmails = this.users.map(u => u.email); this.renderRecipientList(); } deselectAllUsers() { this.selectedEmails = []; this.renderRecipientList(); } updateSelectedCount() { const countEl = document.getElementById('selectedCount'); if (countEl) { const count = this.selectedEmails.length; const total = this.users.length; countEl.textContent = count === 0 ? `已選擇: 全部用戶 (${total} 位)` : `已選擇: ${count} / ${total} 位用戶`; } } // ==================== 新增公告功能 ==================== createNewPost() { try { console.log('createNewPost called'); const modal = document.getElementById('newPostModal'); console.log('Modal element:', modal); if (!modal) { console.error('newPostModal not found!'); Utils.showError('錯誤', '無法開啟新增公告視窗'); return; } document.getElementById('newPostType').value = 'announcement'; document.getElementById('newPostSubject').value = ''; document.getElementById('newPostContent').value = ''; // 顯示 modal(使用 active class 和 inline style 雙重確保) modal.classList.add('active'); modal.style.display = 'flex'; modal.style.opacity = '1'; modal.style.visibility = 'visible'; console.log('Modal should be visible now'); } catch (error) { console.error('createNewPost error:', error); Utils.showError('錯誤', error.message); } } closeNewPostModal() { const modal = document.getElementById('newPostModal'); modal.classList.remove('active'); modal.style.display = 'none'; modal.style.opacity = ''; modal.style.visibility = ''; } async submitNewPost() { const postType = document.getElementById('newPostType').value; const subject = document.getElementById('newPostSubject').value.trim(); const content = document.getElementById('newPostContent').value.trim(); if (!subject || !content) { Utils.showError('驗證錯誤', '請填寫主旨和內容'); return; } try { await api.createManualPost({ post_type: postType, subject, content }); Utils.showSuccess('公告已建立'); this.closeNewPostModal(); await this.loadPosts(); } catch (error) { Utils.handleError(error, '建立公告時'); } } // ==================== 更新編輯功能 ==================== // 編輯貼文(允許任何狀態編輯) async editPost(postId) { const post = this.posts.find(p => p.id === postId); if (!post) return; this.currentEditPost = post; document.getElementById('editSubject').value = post.subject || ''; document.getElementById('editContent').value = post.content || ''; // 載入用戶列表並設定已選擇的收件人 this.selectedEmails = post.selected_emails || []; // 顯示載入中 document.getElementById('recipientList').innerHTML = `
載入用戶列表...
`; // 顯示 modal(使用 active class 和 inline style 雙重確保) const modal = document.getElementById('editPostModal'); modal.classList.add('active'); modal.style.display = 'flex'; modal.style.opacity = '1'; modal.style.visibility = 'visible'; // 載入用戶列表 await this.loadUsers(); // 如果沒有預設選擇,則全選 if (this.selectedEmails.length === 0) { this.selectedEmails = this.users.map(u => u.email); } this.renderRecipientList(); } async savePost() { if (!this.currentEditPost) return; const subject = document.getElementById('editSubject').value.trim(); const content = document.getElementById('editContent').value.trim(); if (!subject || !content) { Utils.showError('驗證錯誤', '請填寫主旨和內容'); return; } try { // 如果全選或沒選,則設為空陣列(表示全部用戶) const emailsToSave = this.selectedEmails.length === this.users.length ? [] : this.selectedEmails; await api.updatePost(this.currentEditPost.id, { subject, content, selected_emails: emailsToSave }); Utils.showSuccess('貼文已更新'); this.closeEditModal(); await this.loadPosts(); } catch (error) { Utils.handleError(error, '更新貼文時'); } } destroy() { this.posts = []; this.users = []; this.currentEditPost = null; this.selectedEmails = []; } } // Create global instance window.postsPage = new PostsPage();