// 采购相关功能 let editPid = null; // 当前正在编辑的采购ID async function loadPurchases() { if (!Store.currentRoom) return; const token = Store.getToken(); const res = await fetch('/api/rooms/' + Store.currentRoom + '/purchases', { headers: { 'Authorization': 'Bearer ' + token } }); const items = await res.json(); renderPurchasePanel(items); } function renderPurchasePanel(items) { const container = document.getElementById('purchase-panel-body'); if (items.length === 0) { container.innerHTML = '

暂无采购记录

'; return; } const groups = {}; items.forEach(item => { const month = formatMonth(item.created_at); if (!groups[month]) groups[month] = []; groups[month].push(item); }); let html = ''; Object.keys(groups).sort().reverse().forEach(month => { const monthItems = groups[month]; const monthTotal = monthItems.reduce((s, i) => s + (i.amount||0), 0); html += '
' + month + '¥' + monthTotal + ''; monthItems.forEach(item => { const timeShort = item.created_at.split(' ')[1]?.substring(0,5) || ''; const dateShort = item.created_at.split(' ')[0]?.substring(5) || ''; html += `
${escapeHtml(item.item)}${item.amount}
${dateShort} ${timeShort} x${item.quantity} ${item.invoice_type !== '无票' ? '· ' + item.invoice_type : ''} ${item.status}
`; }); html += '
'; }); container.innerHTML = html; } function formatMonth(dateStr) { const clean = dateStr.replace(/-/g, '/'); const parts = clean.split('/'); if (parts.length >= 2) return parts[0] + '\u5e74' + parseInt(parts[1], 10) + '\u6708'; return dateStr.substring(0,7); } // ============ 可编辑详情弹窗 ============ async function openDetail(pid) { const token = Store.getToken(); const res = await fetch('/api/purchases/' + pid, { headers: { 'Authorization': 'Bearer ' + token } }); const p = await res.json(); editPid = pid; document.getElementById('edit-item').value = p.item || ''; document.getElementById('edit-quantity').value = p.quantity || 0; document.getElementById('edit-unit-price').value = p.unit_price || 0; document.getElementById('edit-freight').value = p.freight || 0; updateAmountSummary(); document.getElementById('edit-payment-method').value = p.payment_method || '\u672a\u6307\u5b9a'; document.getElementById('edit-invoice-type').value = p.invoice_type || '\u65e0\u7968'; document.getElementById('edit-status').value = p.status || '\u5f85\u4ed8\u6b3e'; updateStatusSummary(); document.getElementById('edit-applicant').value = p.applicant || ''; document.getElementById('edit-time').value = p.created_at || ''; document.getElementById('edit-remarks').value = p.remarks || ''; // 附件 renderEditAttachments(p.attachments || []); // 操作历史 const historyHtml = (p.history || []).map(h => '
  • ' + h.timestamp + ' ' + escapeHtml(h.user) + ': ' + escapeHtml(h.action) + '
  • ' ).join(''); document.getElementById('edit-history').innerHTML = historyHtml || '
  • 暂无操作记录
  • '; document.getElementById('edit-history-section').open = false; // 删除按钮 document.getElementById('delete-purchase-btn').style.display = ''; document.getElementById('delete-purchase-btn').style.marginLeft = ''; // 折叠所有编辑组 document.querySelectorAll('.edit-group-detail').forEach(el => el.classList.add('hidden')); document.querySelectorAll('.edit-group-summary').forEach(el => el.classList.remove('hidden')); document.getElementById('detail-modal').classList.remove('hidden'); } function renderEditAttachments(attachments) { const container = document.getElementById('edit-attachments'); if (!attachments || attachments.length === 0) { container.innerHTML = '暂无附件'; return; } container.innerHTML = attachments.map(a => '
    ' + '' + '' + '
    ' ).join(''); } function updateAmountSummary() { const q = parseFloat(document.getElementById('edit-quantity').value) || 0; const up = parseFloat(document.getElementById('edit-unit-price').value) || 0; const f = parseFloat(document.getElementById('edit-freight').value) || 0; const total = (q * up + f).toFixed(2); document.getElementById('amount-summary').textContent = total + ', \u6570\u91cf' + q + ', \u5355\u4ef7' + up + ', \u90ae\u8d39' + f; } function updateStatusSummary() { const pm = document.getElementById('edit-payment-method').value; const iv = document.getElementById('edit-invoice-type').value; const st = document.getElementById('edit-status').value; document.getElementById('status-summary').textContent = st + ', ' + pm + ', ' + iv; } function toggleEditGroup(groupId) { const detail = document.getElementById(groupId); const summary = detail.previousElementSibling; const isOpen = !detail.classList.contains('hidden'); if (isOpen) { detail.classList.add('hidden'); summary.classList.remove('hidden'); } else { detail.classList.remove('hidden'); summary.classList.add('hidden'); } } async function savePurchase() { if (!editPid) return; const token = Store.getToken(); const body = { item: document.getElementById('edit-item').value, quantity: parseFloat(document.getElementById('edit-quantity').value) || 0, unit_price: parseFloat(document.getElementById('edit-unit-price').value) || 0, freight: parseFloat(document.getElementById('edit-freight').value) || 0, payment_method: document.getElementById('edit-payment-method').value, invoice_type: document.getElementById('edit-invoice-type').value, status: document.getElementById('edit-status').value, applicant: document.getElementById('edit-applicant').value, remarks: document.getElementById('edit-remarks').value, created_at: document.getElementById('edit-time').value }; const res = await fetch('/api/purchases/' + editPid, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const result = await res.json(); if (result.success) { closeDetailModal(); loadPurchases(); } else { alert('\u4fdd\u5b58\u5931\u8d25\uff1a' + (result.error || '\u672a\u77e5\u9519\u8bef')); } } async function deletePurchaseDetail() { if (!editPid) return; if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u6b64\u91c7\u8d2d\u8bb0\u5f55\u5417\uff1f')) return; const token = Store.getToken(); const res = await fetch('/api/purchases/' + editPid, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (res.ok) { closeDetailModal(); loadPurchases(); } else { const err = await res.json(); alert('\u5220\u9664\u5931\u8d25\uff1a' + (err.error || '\u672a\u77e5\u9519\u8bef')); } } async function deleteAttachment(event, encodedPath) { event.stopPropagation(); const filePath = decodeURIComponent(encodedPath); if (!confirm('\u786e\u5b9a\u5220\u9664\u6b64\u9644\u4ef6\u5417\uff1f')) return; const token = Store.getToken(); const res = await fetch('/api/purchases/' + editPid + '/attachments', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ file_path: filePath }) }); if (res.ok) { openDetail(editPid); } } async function handleDetailFileUpload(event) { const files = event.target.files; if (!files.length) return; const token = Store.getToken(); const paths = []; try { for (let file of files) { let blob = file; if (file.size > 1 * 1024 * 1024) blob = await compressImage(file); const formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg'); const res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData }); const data = await res.json(); if (data.path) paths.push(data.path); } if (paths.length) { await fetch('/api/purchases/' + editPid + '/attachments', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ file_paths: paths }) }); openDetail(editPid); } } catch(e) { alert('\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5'); } } function closeDetailModal() { document.getElementById('detail-modal').classList.add('hidden'); editPid = null; } // ============ 采购页面输入区 ============ async function handlePurchaseFileSelect(event) { const files = event.target.files; if (!files.length) return; const token = Store.getToken(); try { for (let file of files) { let blob = file; if (file.size > 1 * 1024 * 1024) blob = await compressImage(file); const formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg'); const res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData }); const data = await res.json(); if (data.path) Store.pendingUploads.push(data.path); } } catch(e) { alert('\u56fe\u7247\u4e0a\u4f20\u5931\u8d25'); } } function sendFromPurchase() { const input = document.getElementById('purchase-msg-input'); const text = input.value.trim(); if (!text && Store.pendingUploads.length === 0) return; Store.pendingPurchaseText = text; input.value = ''; openChatFromPurchase(); } // ============ 汇总 & 群聊管理 ============ async function openSummary() { const token = Store.getToken(); const res = await fetch('/api/summary', { headers: { 'Authorization': 'Bearer ' + token } }); const summary = await res.json(); let html = ''; if (summary.length === 0) html = '

    \u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55

    '; else { summary.forEach(s => { html += '

    ' + escapeHtml(s.room_name) + '

    ' + s.purchase_count + ' \u6761\uff0c\u5408\u8ba1 ' + s.total_amount + '

    '; }); } document.getElementById('summary-title').textContent = '\u91c7\u8d2d\u6c47\u603b'; document.getElementById('summary-content').innerHTML = html; document.getElementById('summary-modal').classList.remove('hidden'); } function closeSummaryModal() { document.getElementById('summary-modal').classList.add('hidden'); } function openCreateRoom() { document.getElementById('create-modal').classList.remove('hidden'); const isAdmin = localStorage.getItem('isAdmin') === 'true'; document.getElementById('white-list').style.display = isAdmin ? '' : 'none'; } function closeCreateModal() { document.getElementById('create-modal').classList.add('hidden'); } async function confirmCreateRoom() { const name = document.getElementById('new-room-name').value.trim(); if (!name) return alert('请输入名称'); const isAdmin = localStorage.getItem('isAdmin') === 'true'; const whiteList = isAdmin ? document.getElementById('white-list').value.trim() : localStorage.getItem('currentUser'); const token = Store.getToken(); await fetch('/api/rooms', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, whiteList }) }); closeCreateModal(); loadRooms(); } function openManageRoom(roomId) { const room = Store.rooms.find(r => r.id === roomId); if (!room) return; const isAdmin = localStorage.getItem('isAdmin') === 'true'; document.getElementById('manage-title').textContent = '编辑群聊'; document.getElementById('manage-room-name').value = room.name; document.getElementById('manage-white-list').value = room.white_list || ''; document.getElementById('manage-white-list').style.display = isAdmin ? '' : 'none'; document.getElementById('manage-modal').dataset.roomId = roomId; document.getElementById('manage-modal').classList.remove('hidden'); } function closeManageModal() { document.getElementById('manage-modal').classList.add('hidden'); } async function saveRoom() { const roomId = document.getElementById('manage-modal').dataset.roomId; const name = document.getElementById('manage-room-name').value.trim(); const isAdmin = localStorage.getItem('isAdmin') === 'true'; const whiteList = isAdmin ? document.getElementById('manage-white-list').value.trim() : undefined; if (!name) return alert('请输入群聊名称'); const token = Store.getToken(); const res = await fetch('/api/rooms/' + roomId, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, whiteList }) }); if (res.ok) { closeManageModal(); loadRooms(); } else { const err = await res.json(); alert(err.error || '更新失败'); } } async function deleteRoom() { if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u8be5\u7fa4\u804a\u5417\uff1f\u6240\u6709\u6d88\u606f\u548c\u91c7\u8d2d\u6570\u636e\u5c06\u88ab\u6c38\u4e45\u5220\u9664\u3002')) return; const roomId = document.getElementById('manage-modal').dataset.roomId; const token = Store.getToken(); const res = await fetch('/api/rooms/' + roomId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (res.ok) { closeManageModal(); loadRooms(); if (Store.currentRoom === roomId) showList(); } else { const err = await res.json(); alert(err.error || '\u5220\u9664\u5931\u8d25'); } } // ============ 滑动手势 ============ function initSwipeGestures() { const purchasePage = document.getElementById('purchase-page'); const chatPage = document.getElementById('chat-page'); let startX = 0; purchasePage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true }); purchasePage.addEventListener('touchend', (e) => { if (!startX) return; const diffX = e.changedTouches[0].clientX - startX; if (Math.abs(diffX) > 50) { if (diffX > 30) showList(); else if (diffX < -30) openChatFromPurchase(); } startX = 0; }, { passive: true }); chatPage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true }); chatPage.addEventListener('touchend', (e) => { if (!startX) return; const diffX = e.changedTouches[0].clientX - startX; if (Math.abs(diffX) > 50) backToPurchase(); startX = 0; }, { passive: true }); }