|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
// 采购相关功能
|
|
|
|
// 采购相关功能
|
|
|
|
let editPid = null; // 当前正在编辑的采购ID
|
|
|
|
let editPid = null;
|
|
|
|
|
|
|
|
|
|
|
|
async function loadPurchases() {
|
|
|
|
async function loadPurchases() {
|
|
|
|
if (!Store.currentRoom) return;
|
|
|
|
if (!Store.currentRoom) return;
|
|
|
|
@@ -11,7 +11,7 @@ async function loadPurchases() {
|
|
|
|
|
|
|
|
|
|
|
|
function renderPurchasePanel(items) {
|
|
|
|
function renderPurchasePanel(items) {
|
|
|
|
const container = document.getElementById('purchase-panel-body');
|
|
|
|
const container = document.getElementById('purchase-panel-body');
|
|
|
|
if (items.length === 0) { container.innerHTML = '<p style="text-align:center;color:#888;padding:40px;">暂无采购记录</p>'; return; }
|
|
|
|
if (items.length === 0) { container.innerHTML = '<p style="text-align:center;color:#888;padding:40px;">\u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55</p>'; return; }
|
|
|
|
const groups = {};
|
|
|
|
const groups = {};
|
|
|
|
items.forEach(item => {
|
|
|
|
items.forEach(item => {
|
|
|
|
const month = formatMonth(item.created_at);
|
|
|
|
const month = formatMonth(item.created_at);
|
|
|
|
@@ -22,19 +22,20 @@ function renderPurchasePanel(items) {
|
|
|
|
Object.keys(groups).sort().reverse().forEach(month => {
|
|
|
|
Object.keys(groups).sort().reverse().forEach(month => {
|
|
|
|
const monthItems = groups[month];
|
|
|
|
const monthItems = groups[month];
|
|
|
|
const monthTotal = monthItems.reduce((s, i) => s + (i.amount||0), 0);
|
|
|
|
const monthTotal = monthItems.reduce((s, i) => s + (i.amount||0), 0);
|
|
|
|
html += '<details class="purchase-month-group" open><summary class="purchase-month-header"><span>' + month + '</span><span style="display:flex;align-items:center;gap:6px;">¥' + monthTotal + '<svg class="month-arrow" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></span></summary>';
|
|
|
|
html += '<details class="purchase-month-group" open><summary class="purchase-month-header"><span>' + month + '</span><span style="display:flex;align-items:center;gap:6px;">\uffe5' + monthTotal + '<svg class="month-arrow" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></span></summary>';
|
|
|
|
monthItems.forEach(item => {
|
|
|
|
monthItems.forEach(item => {
|
|
|
|
const timeShort = item.created_at.split(' ')[1]?.substring(0,5) || '';
|
|
|
|
var t = item.created_at.split(' ')[1];
|
|
|
|
const dateShort = item.created_at.split(' ')[0]?.substring(5) || '';
|
|
|
|
var timeShort = t ? t.substring(0,5) : '';
|
|
|
|
html += `
|
|
|
|
var datePart = item.created_at.split(' ')[0];
|
|
|
|
<div class="purchase-item" onclick="openDetail('${item.id}')">
|
|
|
|
var dateShort = datePart ? datePart.substring(5) : '';
|
|
|
|
<div class="item-main"><span class="item-name">${escapeHtml(item.item)}</span><span class="item-amount">${item.amount}</span></div>
|
|
|
|
html += '<div class="purchase-item" onclick="openDetail(\'' + item.id + '\')">' +
|
|
|
|
<div class="item-meta">
|
|
|
|
'<div class="item-main"><span class="item-name">' + escapeHtml(item.item) + '</span><span class="item-amount">\uffe5' + item.amount + '</span></div>' +
|
|
|
|
<span>${dateShort} ${timeShort}</span>
|
|
|
|
'<div class="item-meta">' +
|
|
|
|
<span>x${item.quantity} ${item.invoice_type !== '无票' ? '· ' + item.invoice_type : ''}</span>
|
|
|
|
'<span>' + dateShort + ' ' + timeShort + '</span>' +
|
|
|
|
<span class="status-badge ${item.status==='已完成'?'done':''}">${item.status}</span>
|
|
|
|
'<span>x' + item.quantity + (item.invoice_type !== '\u65e0\u7968' ? ' \u00b7 ' + item.invoice_type : '') + '</span>' +
|
|
|
|
</div>
|
|
|
|
'<span class="status-badge ' + item.status + '">' + item.status + '</span>' +
|
|
|
|
</div>`;
|
|
|
|
'</div>' +
|
|
|
|
|
|
|
|
'</div>';
|
|
|
|
});
|
|
|
|
});
|
|
|
|
html += '</details>';
|
|
|
|
html += '</details>';
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@@ -42,18 +43,16 @@ function renderPurchasePanel(items) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatMonth(dateStr) {
|
|
|
|
function formatMonth(dateStr) {
|
|
|
|
const clean = dateStr.replace(/-/g, '/');
|
|
|
|
var clean = dateStr.replace(/-/g, '/');
|
|
|
|
const parts = clean.split('/');
|
|
|
|
var parts = clean.split('/');
|
|
|
|
if (parts.length >= 2) return parts[0] + '\u5e74' + parseInt(parts[1], 10) + '\u6708';
|
|
|
|
if (parts.length >= 2) return parts[0] + '\u5e74' + parseInt(parts[1], 10) + '\u6708';
|
|
|
|
return dateStr.substring(0,7);
|
|
|
|
return dateStr.substring(0,7);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 可编辑详情弹窗 ============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function openDetail(pid) {
|
|
|
|
async function openDetail(pid) {
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/purchases/' + pid, { headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
var res = await fetch('/api/purchases/' + pid, { headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
const p = await res.json();
|
|
|
|
var p = await res.json();
|
|
|
|
editPid = pid;
|
|
|
|
editPid = pid;
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('edit-item').value = p.item || '';
|
|
|
|
document.getElementById('edit-item').value = p.item || '';
|
|
|
|
@@ -69,62 +68,58 @@ async function openDetail(pid) {
|
|
|
|
document.getElementById('edit-time').value = p.created_at || '';
|
|
|
|
document.getElementById('edit-time').value = p.created_at || '';
|
|
|
|
document.getElementById('edit-remarks').value = p.remarks || '';
|
|
|
|
document.getElementById('edit-remarks').value = p.remarks || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 附件
|
|
|
|
|
|
|
|
renderEditAttachments(p.attachments || []);
|
|
|
|
renderEditAttachments(p.attachments || []);
|
|
|
|
|
|
|
|
|
|
|
|
// 操作历史
|
|
|
|
var historyHtml = (p.history || []).map(function(h) {
|
|
|
|
const historyHtml = (p.history || []).map(h =>
|
|
|
|
return '<li><span class="history-time">' + h.timestamp + '</span> ' + escapeHtml(h.user) + ': ' + escapeHtml(h.action) + '</li>';
|
|
|
|
'<li><span class="history-time">' + h.timestamp + '</span> ' + escapeHtml(h.user) + ': ' + escapeHtml(h.action) + '</li>'
|
|
|
|
}).join('');
|
|
|
|
).join('');
|
|
|
|
document.getElementById('edit-history').innerHTML = historyHtml || '<li style="color:#888;">\u6682\u65e0\u64cd\u4f5c\u8bb0\u5f55</li>';
|
|
|
|
document.getElementById('edit-history').innerHTML = historyHtml || '<li style="color:#888;">暂无操作记录</li>';
|
|
|
|
|
|
|
|
document.getElementById('edit-history-section').open = false;
|
|
|
|
document.getElementById('edit-history-section').open = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 删除按钮
|
|
|
|
|
|
|
|
document.getElementById('delete-purchase-btn').style.display = '';
|
|
|
|
document.getElementById('delete-purchase-btn').style.display = '';
|
|
|
|
document.getElementById('delete-purchase-btn').style.marginLeft = '';
|
|
|
|
document.getElementById('delete-purchase-btn').style.marginLeft = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 折叠所有编辑组
|
|
|
|
document.querySelectorAll('.edit-group-detail').forEach(function(el) { el.classList.add('hidden'); });
|
|
|
|
document.querySelectorAll('.edit-group-detail').forEach(el => el.classList.add('hidden'));
|
|
|
|
document.querySelectorAll('.edit-group-summary').forEach(function(el) { el.classList.remove('hidden'); });
|
|
|
|
document.querySelectorAll('.edit-group-summary').forEach(el => el.classList.remove('hidden'));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('detail-modal').classList.remove('hidden');
|
|
|
|
document.getElementById('detail-modal').classList.remove('hidden');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderEditAttachments(attachments) {
|
|
|
|
function renderEditAttachments(attachments) {
|
|
|
|
const container = document.getElementById('edit-attachments');
|
|
|
|
var container = document.getElementById('edit-attachments');
|
|
|
|
if (!attachments || attachments.length === 0) {
|
|
|
|
if (!attachments || attachments.length === 0) {
|
|
|
|
container.innerHTML = '<span style="color:#888;font-size:14px;">暂无附件</span>';
|
|
|
|
container.innerHTML = '<span style="color:#888;font-size:14px;">\u6682\u65e0\u9644\u4ef6</span>';
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
container.innerHTML = attachments.map(a =>
|
|
|
|
container.innerHTML = attachments.map(function(a) {
|
|
|
|
'<div class="attach-thumb-wrap">' +
|
|
|
|
return '<div class="attach-thumb-wrap">' +
|
|
|
|
'<img class="attach-thumb" src="' + a.file_path + '" ondblclick="onImgThumbDblClick(this)">' +
|
|
|
|
'<img class="attach-thumb" src="' + a.file_path + '" ondblclick="onImgThumbDblClick(this)">' +
|
|
|
|
'<button class="attach-del-btn" onclick="deleteAttachment(event, \'' + encodeURIComponent(a.file_path) + '\')">' +
|
|
|
|
'<button class="attach-del-btn" onclick="deleteAttachment(event, \'' + encodeURIComponent(a.file_path) + '\')">' +
|
|
|
|
'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>' +
|
|
|
|
'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>' +
|
|
|
|
'</button>' +
|
|
|
|
'</button>' +
|
|
|
|
'</div>'
|
|
|
|
'</div>';
|
|
|
|
).join('');
|
|
|
|
}).join('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateAmountSummary() {
|
|
|
|
function updateAmountSummary() {
|
|
|
|
const q = parseFloat(document.getElementById('edit-quantity').value) || 0;
|
|
|
|
var q = parseFloat(document.getElementById('edit-quantity').value) || 0;
|
|
|
|
const up = parseFloat(document.getElementById('edit-unit-price').value) || 0;
|
|
|
|
var up = parseFloat(document.getElementById('edit-unit-price').value) || 0;
|
|
|
|
const f = parseFloat(document.getElementById('edit-freight').value) || 0;
|
|
|
|
var f = parseFloat(document.getElementById('edit-freight').value) || 0;
|
|
|
|
const total = (q * up + f).toFixed(2);
|
|
|
|
var total = (q * up + f).toFixed(2);
|
|
|
|
document.getElementById('amount-summary').textContent = total + ', \u6570\u91cf' + q + ', \u5355\u4ef7' + up + ', \u90ae\u8d39' + f;
|
|
|
|
document.getElementById('amount-summary').textContent = total + ', \u6570\u91cf' + q + ', \u5355\u4ef7' + up + ', \u90ae\u8d39' + f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateStatusSummary() {
|
|
|
|
function updateStatusSummary() {
|
|
|
|
const pm = document.getElementById('edit-payment-method').value;
|
|
|
|
var pm = document.getElementById('edit-payment-method').value;
|
|
|
|
const iv = document.getElementById('edit-invoice-type').value;
|
|
|
|
var iv = document.getElementById('edit-invoice-type').value;
|
|
|
|
const st = document.getElementById('edit-status').value;
|
|
|
|
var st = document.getElementById('edit-status').value;
|
|
|
|
document.getElementById('status-summary').textContent = st + ', ' + pm + ', ' + iv;
|
|
|
|
document.getElementById('status-summary').textContent = st + ', ' + pm + ', ' + iv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleEditGroup(groupId) {
|
|
|
|
function toggleEditGroup(groupId) {
|
|
|
|
const detail = document.getElementById(groupId);
|
|
|
|
var detail = document.getElementById(groupId);
|
|
|
|
const summary = detail.previousElementSibling;
|
|
|
|
var summary = detail.previousElementSibling;
|
|
|
|
const isOpen = !detail.classList.contains('hidden');
|
|
|
|
var isOpen = !detail.classList.contains('hidden');
|
|
|
|
if (isOpen) {
|
|
|
|
if (isOpen) {
|
|
|
|
detail.classList.add('hidden');
|
|
|
|
detail.classList.add('hidden');
|
|
|
|
summary.classList.remove('hidden');
|
|
|
|
summary.classList.remove('hidden');
|
|
|
|
@@ -136,8 +131,8 @@ function toggleEditGroup(groupId) {
|
|
|
|
|
|
|
|
|
|
|
|
async function savePurchase() {
|
|
|
|
async function savePurchase() {
|
|
|
|
if (!editPid) return;
|
|
|
|
if (!editPid) return;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const body = {
|
|
|
|
var body = {
|
|
|
|
item: document.getElementById('edit-item').value,
|
|
|
|
item: document.getElementById('edit-item').value,
|
|
|
|
quantity: parseFloat(document.getElementById('edit-quantity').value) || 0,
|
|
|
|
quantity: parseFloat(document.getElementById('edit-quantity').value) || 0,
|
|
|
|
unit_price: parseFloat(document.getElementById('edit-unit-price').value) || 0,
|
|
|
|
unit_price: parseFloat(document.getElementById('edit-unit-price').value) || 0,
|
|
|
|
@@ -149,12 +144,12 @@ async function savePurchase() {
|
|
|
|
remarks: document.getElementById('edit-remarks').value,
|
|
|
|
remarks: document.getElementById('edit-remarks').value,
|
|
|
|
created_at: document.getElementById('edit-time').value
|
|
|
|
created_at: document.getElementById('edit-time').value
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const res = await fetch('/api/purchases/' + editPid, {
|
|
|
|
var res = await fetch('/api/purchases/' + editPid, {
|
|
|
|
method: 'PUT',
|
|
|
|
method: 'PUT',
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const result = await res.json();
|
|
|
|
var result = await res.json();
|
|
|
|
if (result.success) {
|
|
|
|
if (result.success) {
|
|
|
|
closeDetailModal();
|
|
|
|
closeDetailModal();
|
|
|
|
loadPurchases();
|
|
|
|
loadPurchases();
|
|
|
|
@@ -166,8 +161,8 @@ async function savePurchase() {
|
|
|
|
async function deletePurchaseDetail() {
|
|
|
|
async function deletePurchaseDetail() {
|
|
|
|
if (!editPid) return;
|
|
|
|
if (!editPid) return;
|
|
|
|
if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u6b64\u91c7\u8d2d\u8bb0\u5f55\u5417\uff1f')) return;
|
|
|
|
if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u6b64\u91c7\u8d2d\u8bb0\u5f55\u5417\uff1f')) return;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/purchases/' + editPid, {
|
|
|
|
var res = await fetch('/api/purchases/' + editPid, {
|
|
|
|
method: 'DELETE',
|
|
|
|
method: 'DELETE',
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@@ -175,38 +170,37 @@ async function deletePurchaseDetail() {
|
|
|
|
closeDetailModal();
|
|
|
|
closeDetailModal();
|
|
|
|
loadPurchases();
|
|
|
|
loadPurchases();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
const err = await res.json();
|
|
|
|
var err = await res.json();
|
|
|
|
alert('\u5220\u9664\u5931\u8d25\uff1a' + (err.error || '\u672a\u77e5\u9519\u8bef'));
|
|
|
|
alert('\u5220\u9664\u5931\u8d25\uff1a' + (err.error || '\u672a\u77e5\u9519\u8bef'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteAttachment(event, encodedPath) {
|
|
|
|
async function deleteAttachment(event, encodedPath) {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.stopPropagation();
|
|
|
|
const filePath = decodeURIComponent(encodedPath);
|
|
|
|
var filePath = decodeURIComponent(encodedPath);
|
|
|
|
if (!confirm('\u786e\u5b9a\u5220\u9664\u6b64\u9644\u4ef6\u5417\uff1f')) return;
|
|
|
|
if (!confirm('\u786e\u5b9a\u5220\u9664\u6b64\u9644\u4ef6\u5417\uff1f')) return;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/purchases/' + editPid + '/attachments', {
|
|
|
|
var res = await fetch('/api/purchases/' + editPid + '/attachments', {
|
|
|
|
method: 'DELETE',
|
|
|
|
method: 'DELETE',
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
body: JSON.stringify({ file_path: filePath })
|
|
|
|
body: JSON.stringify({ file_path: filePath })
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if (res.ok) {
|
|
|
|
if (res.ok) openDetail(editPid);
|
|
|
|
openDetail(editPid);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleDetailFileUpload(event) {
|
|
|
|
async function handleDetailFileUpload(event) {
|
|
|
|
const files = event.target.files;
|
|
|
|
var files = event.target.files;
|
|
|
|
if (!files.length) return;
|
|
|
|
if (!files.length) return;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const paths = [];
|
|
|
|
var paths = [];
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
for (let file of files) {
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
let blob = file;
|
|
|
|
var file = files[i];
|
|
|
|
|
|
|
|
var blob = file;
|
|
|
|
if (file.size > 1 * 1024 * 1024) blob = await compressImage(file);
|
|
|
|
if (file.size > 1 * 1024 * 1024) blob = await compressImage(file);
|
|
|
|
const formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg');
|
|
|
|
var 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 });
|
|
|
|
var res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData });
|
|
|
|
const data = await res.json();
|
|
|
|
var data = await res.json();
|
|
|
|
if (data.path) paths.push(data.path);
|
|
|
|
if (data.path) paths.push(data.path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (paths.length) {
|
|
|
|
if (paths.length) {
|
|
|
|
@@ -225,44 +219,41 @@ function closeDetailModal() {
|
|
|
|
editPid = null;
|
|
|
|
editPid = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 采购页面输入区 ============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function handlePurchaseFileSelect(event) {
|
|
|
|
async function handlePurchaseFileSelect(event) {
|
|
|
|
const files = event.target.files;
|
|
|
|
var files = event.target.files;
|
|
|
|
if (!files.length) return;
|
|
|
|
if (!files.length) return;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
for (let file of files) {
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
let blob = file;
|
|
|
|
var file = files[i];
|
|
|
|
|
|
|
|
var blob = file;
|
|
|
|
if (file.size > 1 * 1024 * 1024) blob = await compressImage(file);
|
|
|
|
if (file.size > 1 * 1024 * 1024) blob = await compressImage(file);
|
|
|
|
const formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg');
|
|
|
|
var 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 });
|
|
|
|
var res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData });
|
|
|
|
const data = await res.json();
|
|
|
|
var data = await res.json();
|
|
|
|
if (data.path) Store.pendingUploads.push(data.path);
|
|
|
|
if (data.path) Store.pendingUploads.push(data.path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(e) { alert('\u56fe\u7247\u4e0a\u4f20\u5931\u8d25'); }
|
|
|
|
} catch(e) { alert('\u56fe\u7247\u4e0a\u4f20\u5931\u8d25'); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function sendFromPurchase() {
|
|
|
|
function sendFromPurchase() {
|
|
|
|
const input = document.getElementById('purchase-msg-input');
|
|
|
|
var input = document.getElementById('purchase-msg-input');
|
|
|
|
const text = input.value.trim();
|
|
|
|
var text = input.value.trim();
|
|
|
|
if (!text && Store.pendingUploads.length === 0) return;
|
|
|
|
if (!text && Store.pendingUploads.length === 0) return;
|
|
|
|
Store.pendingPurchaseText = text;
|
|
|
|
Store.pendingPurchaseText = text;
|
|
|
|
input.value = '';
|
|
|
|
input.value = '';
|
|
|
|
openChatFromPurchase();
|
|
|
|
openChatFromPurchase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 汇总 & 群聊管理 ============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function openSummary() {
|
|
|
|
async function openSummary() {
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/summary', { headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
var res = await fetch('/api/summary', { headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
const summary = await res.json();
|
|
|
|
var summary = await res.json();
|
|
|
|
let html = '';
|
|
|
|
var html = '';
|
|
|
|
if (summary.length === 0) html = '<p>\u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55</p>';
|
|
|
|
if (summary.length === 0) html = '<p>\u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55</p>';
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
summary.forEach(s => {
|
|
|
|
summary.forEach(function(s) {
|
|
|
|
html += '<div class="summary-card" onclick="event.stopPropagation(); closeSummaryModal(); openPurchasePage(\'' + s.room_id + '\')" style="margin:8px 0;"><h4>' + escapeHtml(s.room_name) + '</h4><p>' + s.purchase_count + ' \u6761\uff0c\u5408\u8ba1 ' + s.total_amount + '</p></div>';
|
|
|
|
html += '<div class="summary-card" onclick="event.stopPropagation(); closeSummaryModal(); openPurchasePage(\'' + s.room_id + '\')" style="margin:8px 0;"><h4>' + escapeHtml(s.room_name) + '</h4><p>' + s.purchase_count + ' \u6761\uff0c\u5408\u8ba1 \uffe5' + s.total_amount + '</p></div>';
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
document.getElementById('summary-title').textContent = '\u91c7\u8d2d\u6c47\u603b';
|
|
|
|
document.getElementById('summary-title').textContent = '\u91c7\u8d2d\u6c47\u603b';
|
|
|
|
@@ -274,26 +265,26 @@ function closeSummaryModal() { document.getElementById('summary-modal').classLis
|
|
|
|
|
|
|
|
|
|
|
|
function openCreateRoom() {
|
|
|
|
function openCreateRoom() {
|
|
|
|
document.getElementById('create-modal').classList.remove('hidden');
|
|
|
|
document.getElementById('create-modal').classList.remove('hidden');
|
|
|
|
const isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
var isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
document.getElementById('white-list').style.display = isAdmin ? '' : 'none';
|
|
|
|
document.getElementById('white-list').style.display = isAdmin ? '' : 'none';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function closeCreateModal() { document.getElementById('create-modal').classList.add('hidden'); }
|
|
|
|
function closeCreateModal() { document.getElementById('create-modal').classList.add('hidden'); }
|
|
|
|
async function confirmCreateRoom() {
|
|
|
|
async function confirmCreateRoom() {
|
|
|
|
const name = document.getElementById('new-room-name').value.trim();
|
|
|
|
var name = document.getElementById('new-room-name').value.trim();
|
|
|
|
if (!name) return alert('请输入名称');
|
|
|
|
if (!name) return alert('\u8bf7\u8f93\u5165\u540d\u79f0');
|
|
|
|
const isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
var isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
const whiteList = isAdmin ? document.getElementById('white-list').value.trim() : localStorage.getItem('currentUser');
|
|
|
|
var whiteList = isAdmin ? document.getElementById('white-list').value.trim() : localStorage.getItem('currentUser');
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
await fetch('/api/rooms', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, whiteList }) });
|
|
|
|
await fetch('/api/rooms', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, whiteList: whiteList }) });
|
|
|
|
closeCreateModal();
|
|
|
|
closeCreateModal();
|
|
|
|
loadRooms();
|
|
|
|
loadRooms();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openManageRoom(roomId) {
|
|
|
|
function openManageRoom(roomId) {
|
|
|
|
const room = Store.rooms.find(r => r.id === roomId);
|
|
|
|
var room = Store.rooms.find(function(r) { return r.id === roomId; });
|
|
|
|
if (!room) return;
|
|
|
|
if (!room) return;
|
|
|
|
const isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
var isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
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-room-name').value = room.name;
|
|
|
|
document.getElementById('manage-white-list').value = room.white_list || '';
|
|
|
|
document.getElementById('manage-white-list').value = room.white_list || '';
|
|
|
|
document.getElementById('manage-white-list').style.display = isAdmin ? '' : 'none';
|
|
|
|
document.getElementById('manage-white-list').style.display = isAdmin ? '' : 'none';
|
|
|
|
@@ -304,43 +295,41 @@ function openManageRoom(roomId) {
|
|
|
|
function closeManageModal() { document.getElementById('manage-modal').classList.add('hidden'); }
|
|
|
|
function closeManageModal() { document.getElementById('manage-modal').classList.add('hidden'); }
|
|
|
|
|
|
|
|
|
|
|
|
async function saveRoom() {
|
|
|
|
async function saveRoom() {
|
|
|
|
const roomId = document.getElementById('manage-modal').dataset.roomId;
|
|
|
|
var roomId = document.getElementById('manage-modal').dataset.roomId;
|
|
|
|
const name = document.getElementById('manage-room-name').value.trim();
|
|
|
|
var name = document.getElementById('manage-room-name').value.trim();
|
|
|
|
const isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
var isAdmin = localStorage.getItem('isAdmin') === 'true';
|
|
|
|
const whiteList = isAdmin ? document.getElementById('manage-white-list').value.trim() : undefined;
|
|
|
|
var whiteList = isAdmin ? document.getElementById('manage-white-list').value.trim() : undefined;
|
|
|
|
if (!name) return alert('请输入群聊名称');
|
|
|
|
if (!name) return alert('\u8bf7\u8f93\u5165\u7fa4\u804a\u540d\u79f0');
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/rooms/' + roomId, {
|
|
|
|
var res = await fetch('/api/rooms/' + roomId, {
|
|
|
|
method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
|
|
|
body: JSON.stringify({ name, whiteList })
|
|
|
|
body: JSON.stringify({ name: name, whiteList: whiteList })
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if (res.ok) { closeManageModal(); loadRooms(); }
|
|
|
|
if (res.ok) { closeManageModal(); loadRooms(); }
|
|
|
|
else { const err = await res.json(); alert(err.error || '更新失败'); }
|
|
|
|
else { var err = await res.json(); alert(err.error || '\u66f4\u65b0\u5931\u8d25'); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteRoom() {
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
var roomId = document.getElementById('manage-modal').dataset.roomId;
|
|
|
|
const token = Store.getToken();
|
|
|
|
var token = Store.getToken();
|
|
|
|
const res = await fetch('/api/rooms/' + roomId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
var res = await fetch('/api/rooms/' + roomId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
|
|
|
|
if (res.ok) {
|
|
|
|
if (res.ok) {
|
|
|
|
closeManageModal();
|
|
|
|
closeManageModal();
|
|
|
|
loadRooms();
|
|
|
|
loadRooms();
|
|
|
|
if (Store.currentRoom === roomId) showList();
|
|
|
|
if (Store.currentRoom === roomId) showList();
|
|
|
|
} else { const err = await res.json(); alert(err.error || '\u5220\u9664\u5931\u8d25'); }
|
|
|
|
} else { var err = await res.json(); alert(err.error || '\u5220\u9664\u5931\u8d25'); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 滑动手势 ============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function initSwipeGestures() {
|
|
|
|
function initSwipeGestures() {
|
|
|
|
const purchasePage = document.getElementById('purchase-page');
|
|
|
|
var purchasePage = document.getElementById('purchase-page');
|
|
|
|
const chatPage = document.getElementById('chat-page');
|
|
|
|
var chatPage = document.getElementById('chat-page');
|
|
|
|
let startX = 0;
|
|
|
|
var startX = 0;
|
|
|
|
|
|
|
|
|
|
|
|
purchasePage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true });
|
|
|
|
purchasePage.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; }, { passive: true });
|
|
|
|
purchasePage.addEventListener('touchend', (e) => {
|
|
|
|
purchasePage.addEventListener('touchend', function(e) {
|
|
|
|
if (!startX) return;
|
|
|
|
if (!startX) return;
|
|
|
|
const diffX = e.changedTouches[0].clientX - startX;
|
|
|
|
var diffX = e.changedTouches[0].clientX - startX;
|
|
|
|
if (Math.abs(diffX) > 50) {
|
|
|
|
if (Math.abs(diffX) > 50) {
|
|
|
|
if (diffX > 30) showList();
|
|
|
|
if (diffX > 30) showList();
|
|
|
|
else if (diffX < -30) openChatFromPurchase();
|
|
|
|
else if (diffX < -30) openChatFromPurchase();
|
|
|
|
@@ -348,10 +337,10 @@ function initSwipeGestures() {
|
|
|
|
startX = 0;
|
|
|
|
startX = 0;
|
|
|
|
}, { passive: true });
|
|
|
|
}, { passive: true });
|
|
|
|
|
|
|
|
|
|
|
|
chatPage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true });
|
|
|
|
chatPage.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; }, { passive: true });
|
|
|
|
chatPage.addEventListener('touchend', (e) => {
|
|
|
|
chatPage.addEventListener('touchend', function(e) {
|
|
|
|
if (!startX) return;
|
|
|
|
if (!startX) return;
|
|
|
|
const diffX = e.changedTouches[0].clientX - startX;
|
|
|
|
var diffX = e.changedTouches[0].clientX - startX;
|
|
|
|
if (Math.abs(diffX) > 50) backToPurchase();
|
|
|
|
if (Math.abs(diffX) > 50) backToPurchase();
|
|
|
|
startX = 0;
|
|
|
|
startX = 0;
|
|
|
|
}, { passive: true });
|
|
|
|
}, { passive: true });
|
|
|
|
|