// ============================================ // 通用列表管理 - 无限滚动加载 // ============================================ const config = { 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 } }; const PAGE_SIZE = 20; const listState = {}; function initList(listId) { listState[listId] = { loading: false, records: [], page: 1, hasMore: true, totalCount: 0 }; const container = document.getElementById(listId); if (container) container.innerHTML = ''; } async function loadListData(listId) { const cfg = config[listId]; const state = listState[listId]; if (!cfg || !state) return; if (state.loading || !state.hasMore) return; state.loading = true; showLoading(listId, true); try { const res = await fetch( API + cfg.endpoint + '?page=' + state.page + '&limit=' + PAGE_SIZE + '&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(); const newRecords = data.records || []; const total = data.total || 0; state.records = state.records.concat(newRecords); state.totalCount = total; state.hasMore = state.records.length < total; state.page++; renderList(listId); setupScrollObserver(listId); } 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'; if (!show && listState[listId]) { const state = listState[listId]; if (state.hasMore) { loadingEl.textContent = '下拉加载更多...'; } else if (state.records.length > 0) { loadingEl.textContent = '已加载全部'; } else { loadingEl.textContent = ''; } } } } function renderList(listId) { const cfg = config[listId]; const state = listState[listId]; if (!cfg || !state) return; const container = document.getElementById(listId); if (!container) return; if (state.records.length === 0) { container.innerHTML = '
' + cfg.emptyMsg + '
'; return; } const grouped = {}; state.records.forEach(r => { const date = formatLocalDate(r.created_at); if (!grouped[date]) grouped[date] = []; grouped[date].push(r); }); container.innerHTML = ''; for (const [date, records] of Object.entries(grouped)) { const group = document.createElement('div'); group.className = 'order-date-group'; group.setAttribute('data-date', date); const label = document.createElement('div'); label.className = 'order-date-label'; label.textContent = date; group.appendChild(label); records.forEach(r => { const item = document.createElement('div'); item.innerHTML = cfg.renderItem(r); group.appendChild(item.firstChild); }); container.appendChild(group); } } function setupScrollObserver(listId) { const state = listState[listId]; if (!state || !state.hasMore) return; const container = document.getElementById(listId); if (!container) return; // 移除旧的 sentinel const oldSentinel = container.querySelector('.scroll-sentinel'); if (oldSentinel) oldSentinel.remove(); // 创建新的 sentinel const sentinel = document.createElement('div'); sentinel.className = 'scroll-sentinel'; sentinel.style.height = '1px'; container.appendChild(sentinel); const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting && !state.loading && state.hasMore) { loadListData(listId); } }, { rootMargin: '100px' }); observer.observe(sentinel); // 存储 observer 以便清理 state.observer = observer; } // ============================================ // 兼容接口 // ============================================ 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'); } // ============================================ // 渲染函数 // ============================================ function renderOrderItem(r) { const time = formatLocalTime(r.created_at); return '
' + '
' + (r.boss || '-') + '' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.quantity || 0) + '单 × ' + (r.unit_price || 0).toFixed(2) + '' + time + (r.partner ? ' · ' + r.partner : '') + '
'; } function renderOtherIncomeItem(r) { const time = formatLocalTime(r.created_at); const displayCategory = r.category === '红包收入' ? '红包' : (r.category || '-'); return '
' + '
' + displayCategory + (r.boss ? ' · ' + r.boss : '') + '' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.source || '') + '' + time + '
'; } function renderDispatchItem(r) { const time = formatLocalTime(r.created_at); const unitPrice = r.unit_price || (r.quantity ? r.amount / r.quantity : 0); return '
' + '
' + (r.boss || '-') + '' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.quantity || 0) + '单 × ' + (r.rebate || 0).toFixed(2) + '(单价' + unitPrice.toFixed(2) + ')' + time + (r.partner ? ' · ' + r.partner : '') + '
'; } function renderRedEnvelopeItem(r) { const time = formatLocalTime(r.created_at); return '
' + '
红包' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.source || '') + '' + time + '
'; } function renderMilkTeaItem(r) { const time = formatLocalTime(r.created_at); return '
' + '
奶茶' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.source || '') + '' + time + '
'; }