2026-03-25 22:12:11 +08:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// 通用列表管理 - 一次性加载
|
|
|
|
|
|
// ============================================
|
2026-03-25 10:25:37 +08:00
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
// 当前活动的列表状态
|
|
|
|
|
|
let currentListId = null;
|
2026-03-18 09:53:48 +08:00
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
// 通用列表配置
|
2026-03-18 09:53:48 +08:00
|
|
|
|
const listConfig = {
|
2026-03-25 22:12:11 +08:00
|
|
|
|
orderList: { endpoint: '/api/records/orders', emptyMsg: '暂无接单记录', renderItem: renderOrderItem },
|
|
|
|
|
|
otherIncomeList: { endpoint: '/api/records/other-income', emptyMsg: '暂无红包及其他收入记录', renderItem: renderOtherIncomeItem },
|
|
|
|
|
|
dispatchList: { endpoint: '/api/records/dispatches', emptyMsg: '暂无派单记录', renderItem: renderDispatchItem },
|
|
|
|
|
|
redEnvelopeList: { endpoint: '/api/records/red-envelope', emptyMsg: '暂无红包收入记录', renderItem: renderRedEnvelopeItem },
|
|
|
|
|
|
milkTeaList: { endpoint: '/api/records/milk-tea', emptyMsg: '暂无奶茶收入记录', renderItem: renderMilkTeaItem }
|
2026-03-18 09:53:48 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
// 通用列表加载状态(按 listId 存储)
|
|
|
|
|
|
const listState = {
|
|
|
|
|
|
orderList: { loading: false, records: [] },
|
|
|
|
|
|
otherIncomeList: { loading: false, records: [] },
|
|
|
|
|
|
dispatchList: { loading: false, records: [] },
|
|
|
|
|
|
redEnvelopeList: { loading: false, records: [] },
|
|
|
|
|
|
milkTeaList: { loading: false, records: [] }
|
|
|
|
|
|
};
|
2026-03-18 09:53:48 +08:00
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
// 初始化列表 - 清空状态
|
|
|
|
|
|
function initList(listId) {
|
|
|
|
|
|
const state = listState[listId];
|
|
|
|
|
|
if (!state) return;
|
2026-03-12 11:23:10 +08:00
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
state.loading = false;
|
|
|
|
|
|
state.records = [];
|
2026-03-18 09:53:48 +08:00
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
const container = document.getElementById(listId);
|
|
|
|
|
|
if (container) {
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
}
|
2026-03-18 09:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 22:12:11 +08:00
|
|
|
|
// 加载列表数据
|
|
|
|
|
|
async function loadListData(listId) {
|
|
|
|
|
|
const config = listConfig[listId];
|
|
|
|
|
|
const state = listState[listId];
|
|
|
|
|
|
if (!config || !state) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 防止重复加载
|
|
|
|
|
|
if (state.loading) {
|
|
|
|
|
|
console.log('[loadListData] already loading, skip');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
state.loading = true;
|
|
|
|
|
|
showLoading(listId, true);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`[loadListData] fetching ${listId}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 一次性加载全部数据,不做分页
|
|
|
|
|
|
const res = await fetch(API + config.endpoint + '?page=1&limit=1000&year=' + currentYear + '&month=' + currentMonth, { headers: headers() });
|
|
|
|
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
console.error(`[loadListData] fetch failed: ${res.status}`);
|
|
|
|
|
|
state.loading = false;
|
|
|
|
|
|
showLoading(listId, false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
console.log(`[loadListData] got ${data.records?.length || 0} records`);
|
|
|
|
|
|
|
|
|
|
|
|
state.records = data.records || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染
|
|
|
|
|
|
renderList(listId);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`[loadListData] ${listId} done, total records=${state.records.length}`);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(`[loadListData] error:`, e);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
state.loading = false;
|
|
|
|
|
|
showLoading(listId, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示/隐藏加载指示器
|
|
|
|
|
|
function showLoading(listId, show) {
|
|
|
|
|
|
const loadingId = listId.replace('List', 'Loading');
|
|
|
|
|
|
const loadingEl = document.getElementById(loadingId);
|
|
|
|
|
|
if (loadingEl) {
|
|
|
|
|
|
loadingEl.style.display = show ? 'block' : 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染列表
|
|
|
|
|
|
function renderList(listId) {
|
|
|
|
|
|
const config = listConfig[listId];
|
|
|
|
|
|
const state = listState[listId];
|
|
|
|
|
|
if (!config || !state) return;
|
|
|
|
|
|
|
|
|
|
|
|
const container = document.getElementById(listId);
|
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (state.records.length === 0) {
|
|
|
|
|
|
container.innerHTML = '<div class="ranking-empty">' + config.emptyMsg + '</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按日期分组
|
|
|
|
|
|
const grouped = {};
|
|
|
|
|
|
state.records.forEach(r => {
|
|
|
|
|
|
const date = formatLocalDate(r.created_at);
|
|
|
|
|
|
if (!grouped[date]) grouped[date] = [];
|
|
|
|
|
|
grouped[date].push(r);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 生成 HTML
|
|
|
|
|
|
let html = '';
|
|
|
|
|
|
for (const [date, records] of Object.entries(grouped)) {
|
|
|
|
|
|
html += '<div class="order-date-group" data-date="' + date + '"><div class="order-date-label">' + date + '</div>';
|
|
|
|
|
|
records.forEach(r => {
|
|
|
|
|
|
html += config.renderItem(r);
|
|
|
|
|
|
});
|
|
|
|
|
|
html += '</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = html;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 列表滚动(已简化为一次性加载)
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
function setupInfiniteScroll(listId) {
|
|
|
|
|
|
// 不再需要无限滚动,一次性加载全部数据
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 兼容接口
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async function loadOrders() { return loadListData('orderList'); }
|
|
|
|
|
|
async function loadOtherIncome() { return loadListData('otherIncomeList'); }
|
|
|
|
|
|
async function loadDispatches() { return loadListData('dispatchList'); }
|
|
|
|
|
|
async function loadRedEnvelope() { return loadListData('redEnvelopeList'); }
|
|
|
|
|
|
async function loadMilkTea() { return loadListData('milkTeaList'); }
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 渲染函数
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2026-03-18 09:53:48 +08:00
|
|
|
|
function renderOrderItem(r) {
|
|
|
|
|
|
const time = formatLocalTime(r.created_at);
|
|
|
|
|
|
return '<div class="order-item" onclick=\'openEditModal(' + JSON.stringify(r).replace(/'/g, "'") + ')\'>' +
|
|
|
|
|
|
'<div class="order-item-top"><span class="order-item-boss">' + (r.boss || '-') + '</span><span class="order-item-amount">' + (r.amount || 0).toFixed(2) + '</span></div>' +
|
|
|
|
|
|
'<div class="order-item-bottom"><span>' + (r.quantity || 0) + '单 × ' + (r.unit_price || 0).toFixed(2) + '</span><span>' + time + (r.partner ? ' · ' + r.partner : '') + '</span></div></div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderOtherIncomeItem(r) {
|
|
|
|
|
|
const time = formatLocalTime(r.created_at);
|
|
|
|
|
|
const displayCategory = r.category === '红包收入' ? '红包' : (r.category || '-');
|
|
|
|
|
|
return '<div class="order-item" onclick=\'openEditModal(' + JSON.stringify(r).replace(/'/g, "'") + ')\'>' +
|
|
|
|
|
|
'<div class="order-item-top"><span class="order-item-boss">' + displayCategory + (r.boss ? ' · ' + r.boss : '') + '</span><span class="order-item-amount">' + (r.amount || 0).toFixed(2) + '</span></div>' +
|
|
|
|
|
|
'<div class="order-item-bottom"><span>' + (r.source || '') + '</span><span>' + time + '</span></div></div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderDispatchItem(r) {
|
|
|
|
|
|
const time = formatLocalTime(r.created_at);
|
2026-03-18 09:58:33 +08:00
|
|
|
|
const unitPrice = r.unit_price || (r.quantity ? r.amount / r.quantity : 0);
|
2026-03-18 09:53:48 +08:00
|
|
|
|
return '<div class="order-item" onclick=\'openEditModal(' + JSON.stringify(r).replace(/'/g, "'") + ')\'>' +
|
|
|
|
|
|
'<div class="order-item-top"><span class="order-item-boss">' + (r.boss || '-') + '</span><span class="order-item-amount">' + (r.amount || 0).toFixed(2) + '</span></div>' +
|
2026-03-18 09:58:33 +08:00
|
|
|
|
'<div class="order-item-bottom"><span>' + (r.quantity || 0) + '单 × ' + (r.rebate || 0).toFixed(2) + '(单价' + unitPrice.toFixed(2) + ')</span><span>' + time + (r.partner ? ' · ' + r.partner : '') + '</span></div></div>';
|
2026-03-18 09:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderRedEnvelopeItem(r) {
|
|
|
|
|
|
const time = formatLocalTime(r.created_at);
|
|
|
|
|
|
return '<div class="order-item" onclick=\'openEditModal(' + JSON.stringify(r).replace(/'/g, "'") + ')\'>' +
|
|
|
|
|
|
'<div class="order-item-top"><span class="order-item-boss">红包</span><span class="order-item-amount">' + (r.amount || 0).toFixed(2) + '</span></div>' +
|
|
|
|
|
|
'<div class="order-item-bottom"><span>' + (r.source || '') + '</span><span>' + time + '</span></div></div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderMilkTeaItem(r) {
|
|
|
|
|
|
const time = formatLocalTime(r.created_at);
|
|
|
|
|
|
return '<div class="order-item" onclick=\'openEditModal(' + JSON.stringify(r).replace(/'/g, "'") + ')\'>' +
|
|
|
|
|
|
'<div class="order-item-top"><span class="order-item-boss">奶茶</span><span class="order-item-amount">' + (r.amount || 0).toFixed(2) + '</span></div>' +
|
|
|
|
|
|
'<div class="order-item-bottom"><span>' + (r.source || '') + '</span><span>' + time + '</span></div></div>';
|
2026-03-12 11:23:10 +08:00
|
|
|
|
}
|