diff --git a/server/public/css/style.css b/server/public/css/style.css index db79353..ea5e9ed 100644 --- a/server/public/css/style.css +++ b/server/public/css/style.css @@ -112,9 +112,9 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- .detail-modal { max-height: 90vh; overflow-y: auto; padding: 0; } .detail-modal input, .detail-modal select, .detail-modal textarea { margin: 0; } .detail-header { background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); color: #fff; padding: 8px 12px 8px 16px; display: flex; align-items: center; gap: 8px; position: sticky; top: 0; z-index: 1; } -.detail-header .icon-btn { flex-shrink: 0; margin-left: auto; } +.detail-header .icon-btn { padding:0; margin-right:0; flex-shrink: 0; margin-left: auto; } .detail-header .icon-btn svg { stroke: #fff; } -.detail-title-input { flex: 1; font-size: 18px; font-weight: 600; color: #fff; background: transparent; border: none; outline: none; padding: 4px 0; min-width: 0; } +.modal .detail-title-input { flex: 1; font-size: 18px; font-weight: 600; color: #fff; background: transparent; border: none; outline: none; padding: 4px 0; min-width: 0; } .detail-title-input::placeholder { color: rgba(255,255,255,0.6); } .detail-title-input:focus { border: none; outline: none; } .edit-form { padding: 12px 16px 16px 16px; } @@ -127,6 +127,13 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- .amount-row { font-weight: 600; } .amount-row label { color: #333; } .edit-amount { flex: 1; font-size: 17px; color: #e53e3e; font-weight: 700; } +.edit-group { margin-bottom: 8px; } +.edit-group-summary { display: flex; align-items: center; gap: 8px; padding: 7px 0; cursor: pointer; border-bottom: 1px solid #f0f0f0; } +.edit-group-summary label { min-width: 48px; font-size: 13px; color: #777; text-align: right; flex-shrink: 0; } +.edit-group-summary span { flex: 1; font-size: 14px; color: #333; } +.group-arrow { width: 14px; height: 14px; stroke: #aaa; fill: none; stroke-width: 2; flex-shrink: 0; } +.edit-group-detail { padding: 4px 0; } +.edit-group-detail .edit-row { margin-bottom: 4px; } .edit-section { margin-top: 8px; border-top: 1px solid #eee; padding-top: 8px; } .edit-section-header { font-size: 13px; font-weight: 600; color: #555; cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 2px 0; } .edit-section-header svg { width: 16px; height: 16px; stroke: #888; fill: none; stroke-width: 2; } @@ -137,7 +144,7 @@ details[open] .history-summary::after { content: '收起'; } .attach-gallery { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; } .attach-thumb-wrap { position: relative; display: inline-block; } .attach-thumb { width: 56px; height: 56px; object-fit: cover; border-radius: 6px; border: 1px solid #eee; } -.attach-del-btn { position: absolute; top: -4px; right: -4px; width: 18px; height: 18px; border-radius: 50%; background: rgba(0,0,0,0.5); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; } +.attach-gallery .attach-del-btn { position: absolute; top: -4px; right: -4px; width: 18px; height: 18px; border-radius: 50%; background: rgba(0,0,0,0.5); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; } .attach-del-btn svg { width: 10px; height: 10px; stroke: #fff; fill: none; stroke-width: 2.5; } .attach-add-btn { cursor: pointer; display: flex; align-items: center; } .attach-add-btn svg { width: 18px; height: 18px; stroke: #3b82f6; fill: none; stroke-width: 2; } diff --git a/server/public/index.html b/server/public/index.html index 155fd47..3257048 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -151,36 +151,49 @@
-
- + +
+
+ + 0 + +
+
-
- -
-
- -
-
- 0 -
-
- - -
-
- - -
-
- - + + +
+
+ + 待付款 + +
+
+
@@ -235,12 +248,6 @@ if (e.target.id === 'msg-input' && e.key === 'Enter' && !e.ctrlKey) { e.preventDefault(); sendMessage(); } if (e.target.id === 'purchase-msg-input' && e.key === 'Enter' && !e.ctrlKey) { e.preventDefault(); sendFromPurchase(); } }); - // 实时计算金额 - document.addEventListener('input', function(e) { - if (['edit-quantity','edit-unit-price','edit-freight'].includes(e.target.id)) { - updateEditAmount(); - } - }); init(); diff --git a/server/public/js/purchases.js b/server/public/js/purchases.js index 0a66e39..1d34675 100644 --- a/server/public/js/purchases.js +++ b/server/public/js/purchases.js @@ -31,7 +31,7 @@ function renderPurchasePanel(items) {
${escapeHtml(item.item)}${item.amount}
${dateShort} ${timeShort} - ${item.quantity} ${item.invoice_type !== '无票' ? '· ' + item.invoice_type : ''} + ${item.quantity} ${item.invoice_type !== '无票' ? '\u00b7 ' + item.invoice_type : ''} ${item.status}
`; @@ -44,7 +44,7 @@ function renderPurchasePanel(items) { function formatMonth(dateStr) { const clean = dateStr.replace(/-/g, '/'); const parts = clean.split('/'); - if (parts.length >= 2) return parts[0] + '年' + parseInt(parts[1], 10) + '月'; + if (parts.length >= 2) return parts[0] + '\u5e74' + parseInt(parts[1], 10) + '\u6708'; return dateStr.substring(0,7); } @@ -60,10 +60,11 @@ async function openDetail(pid) { 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; - updateEditAmount(); - document.getElementById('edit-payment-method').value = p.payment_method || '未指定'; - document.getElementById('edit-invoice-type').value = p.invoice_type || '无票'; - document.getElementById('edit-status').value = p.status || '待付款'; + 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 || ''; @@ -89,6 +90,9 @@ async function openDetail(pid) { delBtn.style.marginLeft = '0'; } + // 折叠所有编辑组 + document.querySelectorAll('.edit-group-detail').forEach(el => el.classList.add('hidden')); + document.getElementById('detail-modal').classList.remove('hidden'); } @@ -108,11 +112,32 @@ function renderEditAttachments(attachments) { ).join(''); } -function updateEditAmount() { +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; - document.getElementById('edit-amount').textContent = (q * up + f).toFixed(2); + 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() { @@ -140,13 +165,13 @@ async function savePurchase() { closeDetailModal(); loadPurchases(); } else { - alert('保存失败:' + (result.error || '未知错误')); + alert('\u4fdd\u5b58\u5931\u8d25\uff1a' + (result.error || '\u672a\u77e5\u9519\u8bef')); } } async function deletePurchaseDetail() { if (!editPid) return; - if (!confirm('确定要删除此采购记录吗?')) 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', @@ -157,14 +182,14 @@ async function deletePurchaseDetail() { loadPurchases(); } else { const err = await res.json(); - alert('删除失败:' + (err.error || '未知错误')); + 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('确定删除此附件吗?')) return; + 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', @@ -172,7 +197,6 @@ async function deleteAttachment(event, encodedPath) { body: JSON.stringify({ file_path: filePath }) }); if (res.ok) { - // 重新加载详情 openDetail(editPid); } } @@ -199,7 +223,7 @@ async function handleDetailFileUpload(event) { }); openDetail(editPid); } - } catch(e) { alert('上传失败,请重试'); } + } catch(e) { alert('\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5'); } } function closeDetailModal() { @@ -222,14 +246,13 @@ async function handlePurchaseFileSelect(event) { const data = await res.json(); if (data.path) Store.pendingUploads.push(data.path); } - } catch(e) { alert('图片上传失败'); } + } 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(); @@ -242,13 +265,13 @@ async function openSummary() { const res = await fetch('/api/summary', { headers: { 'Authorization': 'Bearer ' + token } }); const summary = await res.json(); let html = ''; - if (summary.length === 0) html = '

暂无采购记录

'; + if (summary.length === 0) html = '

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

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

' + escapeHtml(s.room_name) + '

' + s.purchase_count + ' 条,合计 ' + s.total_amount + '

'; + html += '

' + escapeHtml(s.room_name) + '

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

'; }); } - document.getElementById('summary-title').textContent = '采购汇总'; + document.getElementById('summary-title').textContent = '\u91c7\u8d2d\u6c47\u603b'; document.getElementById('summary-content').innerHTML = html; document.getElementById('summary-modal').classList.remove('hidden'); } @@ -259,7 +282,7 @@ function openCreateRoom() { document.getElementById('create-modal').classList.re 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('请输入名称'); + if (!name) return alert('\u8bf7\u8f93\u5165\u540d\u79f0'); const whiteList = document.getElementById('white-list').value.trim(); const token = Store.getToken(); await fetch('/api/rooms', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, whiteList }) }); @@ -270,7 +293,7 @@ async function confirmCreateRoom() { function openManageRoom(roomId) { const room = Store.rooms.find(r => r.id === roomId); if (!room) return; - document.getElementById('manage-title').textContent = '编辑群聊'; + document.getElementById('manage-title').textContent = '\u7f16\u8f91\u7fa4\u804a'; document.getElementById('manage-room-name').value = room.name; document.getElementById('manage-white-list').value = room.white_list || ''; document.getElementById('manage-modal').dataset.roomId = roomId; @@ -283,18 +306,18 @@ async function saveRoom() { const roomId = document.getElementById('manage-modal').dataset.roomId; const name = document.getElementById('manage-room-name').value.trim(); const whiteList = document.getElementById('manage-white-list').value.trim(); - if (!name) return alert('请输入群聊名称'); + if (!name) return alert('\u8bf7\u8f93\u5165\u7fa4\u804a\u540d\u79f0'); 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 || '更新失败'); } + else { const err = await res.json(); alert(err.error || '\u66f4\u65b0\u5931\u8d25'); } } async function deleteRoom() { - if (!confirm('确定要删除该群聊吗?所有消息和采购数据将被永久删除。')) return; + 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 } }); @@ -302,7 +325,7 @@ async function deleteRoom() { closeManageModal(); loadRooms(); if (Store.currentRoom === roomId) showList(); - } else { const err = await res.json(); alert(err.error || '删除失败'); } + } else { const err = await res.json(); alert(err.error || '\u5220\u9664\u5931\u8d25'); } } // ============ 滑动手势 ============ @@ -312,7 +335,6 @@ function initSwipeGestures() { 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; @@ -324,7 +346,6 @@ function initSwipeGestures() { startX = 0; }, { passive: true }); - // 聊天页:左右滑回采购页 chatPage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true }); chatPage.addEventListener('touchend', (e) => { if (!startX) return;