// 采购相关功能
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 += '';
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 });
}