From 9126348d1b13ca7217c08207e98cf5f290885f39 Mon Sep 17 00:00:00 2001 From: lizhilun Date: Wed, 25 Mar 2026 22:12:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=92=8C=E7=BC=93=E5=AD=98=E7=9B=B8=E5=85=B3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端: 移除无限滚动,改为一次性加载全部数据 - 前端: monthly-records接口添加limit=1000参数 - 前端: 修复edit-modal.js中loadListData调用参数 - 后端: 修复stats.js中getCached函数Promise处理 - 后端: 修复records.js各接口正确传递limit参数 - 后端: 修复partner-records SQL参数数量问题 - 统一版本号强制浏览器刷新缓存 Co-Authored-By: Claude Opus 4.6 --- public/index.html | 4 +- public/js/app.js | 25 ++--- public/js/edit-modal.js | 15 +-- public/js/order-list.js | 235 +++++++++++++++++++++++---------------- public/js/record-form.js | 5 +- public/js/stats.js | 2 +- server/routes/records.js | 20 +++- server/routes/stats.js | 4 +- 8 files changed, 181 insertions(+), 129 deletions(-) diff --git a/public/index.html b/public/index.html index 0b5846f..bcb292c 100644 --- a/public/index.html +++ b/public/index.html @@ -460,9 +460,9 @@ - + - + diff --git a/public/js/app.js b/public/js/app.js index 04ed447..6360819 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -175,31 +175,26 @@ function showPage(pageId) { } if (pageId === 'orderListPage') { document.getElementById('orderListTitle').textContent = `${currentYear}年${currentMonth}月接单列表`; - orderPage = 1; orderHasMore = true; - document.getElementById('orderList').innerHTML = ''; - loadOrders(); + initList('orderList'); + loadListData('orderList'); } if (pageId === 'otherIncomeListPage') { document.getElementById('otherIncomeListTitle').textContent = `${currentYear}年${currentMonth}月红包及其他收入`; - otherIncomePage = 1; otherIncomeHasMore = true; - document.getElementById('otherIncomeList').innerHTML = ''; - loadOtherIncome(); + initList('otherIncomeList'); + loadListData('otherIncomeList'); } if (pageId === 'dispatchListPage') { document.getElementById('dispatchListTitle').textContent = `${currentYear}年${currentMonth}月派单列表`; - dispatchPage = 1; dispatchHasMore = true; - document.getElementById('dispatchList').innerHTML = ''; - loadDispatches(); + initList('dispatchList'); + loadListData('dispatchList'); } if (pageId === 'redEnvelopeListPage') { - redEnvelopePage = 1; redEnvelopeHasMore = true; - document.getElementById('redEnvelopeList').innerHTML = ''; - loadRedEnvelope(); + initList('redEnvelopeList'); + loadListData('redEnvelopeList'); } if (pageId === 'milkTeaListPage') { - milkTeaPage = 1; milkTeaHasMore = true; - document.getElementById('milkTeaList').innerHTML = ''; - loadMilkTea(); + initList('milkTeaList'); + loadListData('milkTeaList'); } } diff --git a/public/js/edit-modal.js b/public/js/edit-modal.js index 8dd28d0..efce8f6 100644 --- a/public/js/edit-modal.js +++ b/public/js/edit-modal.js @@ -74,17 +74,14 @@ function closeEditModal() { function refreshCurrentList() { const cat = document.getElementById('editCategory').value; if (cat === '派单') { - dispatchPage = 1; dispatchHasMore = true; - document.getElementById('dispatchList').innerHTML = ''; - loadDispatches(); + initList('dispatchList'); + loadListData('dispatchList'); } else if (cat === '接单') { - orderPage = 1; orderHasMore = true; - document.getElementById('orderList').innerHTML = ''; - loadOrders(); + initList('orderList'); + loadListData('orderList'); } else { - otherIncomePage = 1; otherIncomeHasMore = true; - document.getElementById('otherIncomeList').innerHTML = ''; - loadOtherIncome(); + initList('otherIncomeList'); + loadListData('otherIncomeList'); } // 如果在日详情页也刷新 if (document.getElementById('dailyDetailPage').classList.contains('active') && currentDailyDate) { diff --git a/public/js/order-list.js b/public/js/order-list.js index 21bab66..504735d 100644 --- a/public/js/order-list.js +++ b/public/js/order-list.js @@ -1,74 +1,155 @@ -// 防抖工具函数 -function debounce(fn, delay) { - let timeoutId; - return function(...args) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => fn.apply(this, args), delay); - }; -} +// ============================================ +// 通用列表管理 - 一次性加载 +// ============================================ -// 全局状态变量(供 app.js 引用) -var orderPage = 1, orderHasMore = true, orderLoading = false; -var otherIncomePage = 1, otherIncomeHasMore = true, otherIncomeLoading = false; -var dispatchPage = 1, dispatchHasMore = true, dispatchLoading = false; -var redEnvelopePage = 1, redEnvelopeHasMore = true, redEnvelopeLoading = false; -var milkTeaPage = 1, milkTeaHasMore = true, milkTeaLoading = false; +// 当前活动的列表状态 +let currentListId = null; -// 通用分页状态管理 +// 通用列表配置 const listConfig = { - orderList: { getPage: () => orderPage, setPage: (v) => orderPage = v, getHasMore: () => orderHasMore, setHasMore: (v) => orderHasMore = v, getLoading: () => orderLoading, setLoading: (v) => orderLoading = v, emptyMsg: '暂无接单记录', endpoint: '/api/records/orders', renderItem: renderOrderItem }, - otherIncomeList: { getPage: () => otherIncomePage, setPage: (v) => otherIncomePage = v, getHasMore: () => otherIncomeHasMore, setHasMore: (v) => otherIncomeHasMore = v, getLoading: () => otherIncomeLoading, setLoading: (v) => otherIncomeLoading = v, emptyMsg: '暂无红包及其他收入记录', endpoint: '/api/records/other-income', renderItem: renderOtherIncomeItem }, - dispatchList: { getPage: () => dispatchPage, setPage: (v) => dispatchPage = v, getHasMore: () => dispatchHasMore, setHasMore: (v) => dispatchHasMore = v, getLoading: () => dispatchLoading, setLoading: (v) => dispatchLoading = v, emptyMsg: '暂无派单记录', endpoint: '/api/records/dispatches', renderItem: renderDispatchItem }, - redEnvelopeList: { getPage: () => redEnvelopePage, setPage: (v) => redEnvelopePage = v, getHasMore: () => redEnvelopeHasMore, setHasMore: (v) => redEnvelopeHasMore = v, getLoading: () => redEnvelopeLoading, setLoading: (v) => redEnvelopeLoading = v, emptyMsg: '暂无红包收入记录', endpoint: '/api/records/red-envelope', renderItem: renderRedEnvelopeItem }, - milkTeaList: { getPage: () => milkTeaPage, setPage: (v) => milkTeaPage = v, getHasMore: () => milkTeaHasMore, setHasMore: (v) => milkTeaHasMore = v, getLoading: () => milkTeaLoading, setLoading: (v) => milkTeaLoading = v, emptyMsg: '暂无奶茶收入记录', endpoint: '/api/records/milk-tea', renderItem: renderMilkTeaItem } + 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 } }; -// 通用列表加载函数 -async function loadList(listId, append) { - const config = listConfig[listId]; - if (!config) return; +// 通用列表加载状态(按 listId 存储) +const listState = { + orderList: { loading: false, records: [] }, + otherIncomeList: { loading: false, records: [] }, + dispatchList: { loading: false, records: [] }, + redEnvelopeList: { loading: false, records: [] }, + milkTeaList: { loading: false, records: [] } +}; - if (config.getLoading() || (!append && !config.getHasMore())) return; - config.setLoading(true); - document.getElementById(listId.replace('List', 'Loading')).style.display = 'block'; +// 初始化列表 - 清空状态 +function initList(listId) { + const state = listState[listId]; + if (!state) return; - try { - const res = await fetch(API + config.endpoint + '?page=' + config.getPage() + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); - if (!res.ok) return; - const data = await res.json(); - const container = document.getElementById(listId); - if (!append) container.innerHTML = ''; + state.loading = false; + state.records = []; - if (data.records.length === 0) { - if (config.getPage() === 1) container.innerHTML = '
' + config.emptyMsg + '
'; - config.setHasMore(false); - } else { - const grouped = {}; - data.records.forEach(r => { - const date = formatLocalDate(r.created_at); - if (!grouped[date]) grouped[date] = []; - grouped[date].push(r); - }); - - let html = ''; - for (const [date, records] of Object.entries(grouped)) { - html += '
' + date + '
'; - records.forEach(r => { - html += config.renderItem(r); - }); - html += '
'; - } - container.insertAdjacentHTML('beforeend', html); - config.setHasMore(data.records.length >= 20); - config.setPage(config.getPage() + 1); - } - } catch (e) { console.error(e); } - - config.setLoading(false); - document.getElementById(listId.replace('List', 'Loading')).style.display = 'none'; + const container = document.getElementById(listId); + if (container) { + container.innerHTML = ''; + } } -// 通用渲染函数 +// 加载列表数据 +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 = '
' + config.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 = ''; + + // 生成 HTML + let html = ''; + for (const [date, records] of Object.entries(grouped)) { + html += '
' + date + '
'; + records.forEach(r => { + html += config.renderItem(r); + }); + html += '
'; + } + + 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'); } + +// ============================================ +// 渲染函数 +// ============================================ + function renderOrderItem(r) { const time = formatLocalTime(r.created_at); return '
' + @@ -105,35 +186,3 @@ function renderMilkTeaItem(r) { '
奶茶' + (r.amount || 0).toFixed(2) + '
' + '
' + (r.source || '') + '' + time + '
'; } - -// 滚动加载(带防抖) -const handleListScroll = debounce(() => { - const pageListMap = [ - { page: 'orderListPage', list: 'orderList' }, - { page: 'dispatchListPage', list: 'dispatchList' }, - { page: 'otherIncomeListPage', list: 'otherIncomeList' }, - { page: 'redEnvelopeListPage', list: 'redEnvelopeList' }, - { page: 'milkTeaListPage', list: 'milkTeaList' } - ]; - - for (const m of pageListMap) { - if (document.getElementById(m.page).classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - const config = listConfig[m.list]; - if (config && config.getHasMore() && !config.getLoading()) { - loadList(m.list, true); - } - } - break; - } - } -}, 100); - -window.addEventListener('scroll', handleListScroll); - -// 兼容旧接口 -async function loadOrders(append) { return loadList('orderList', append); } -async function loadOtherIncome(append) { return loadList('otherIncomeList', append); } -async function loadDispatches(append) { return loadList('dispatchList', append); } -async function loadRedEnvelope(append) { return loadList('redEnvelopeList', append); } -async function loadMilkTea(append) { return loadList('milkTeaList', append); } diff --git a/public/js/record-form.js b/public/js/record-form.js index f118391..5e36e73 100644 --- a/public/js/record-form.js +++ b/public/js/record-form.js @@ -123,11 +123,13 @@ async function submitRecord() { const partner = document.getElementById('fPartner')?.value.trim() || ''; if (currentCategory === '派单') { const rebate = parseFloat(document.getElementById('fRebate').value); - if (!quantity || !unit_price || !rebate || !boss) { showToast('请填写完整信息'); return; } + const partner = document.getElementById('fPartner')?.value.trim() || ''; + if (!quantity || !unit_price || !rebate || !boss || !partner) { showToast('请填写完整信息(包括负责打单的陪玩)'); return; } body.quantity = quantity; body.unit_price = unit_price; body.rebate = rebate; body.amount = quantity * rebate; + body.partner = partner; } else { if (!quantity || !unit_price || !boss) { showToast('请填写完整信息'); return; } body.quantity = quantity; @@ -135,7 +137,6 @@ async function submitRecord() { body.amount = quantity * unit_price; } body.boss = boss; - body.partner = partner; } else if (currentCategory === '红包收入' || currentCategory === '奶茶') { const amount = parseFloat(document.getElementById('fAmount').value); const source = document.getElementById('fSource').value.trim(); diff --git a/public/js/stats.js b/public/js/stats.js index 6584fca..b3f2043 100644 --- a/public/js/stats.js +++ b/public/js/stats.js @@ -214,7 +214,7 @@ async function loadMonthlyDetail(type) { } try { - const res = await fetch(API + '/api/stats/monthly-records?type=' + type + '&year=' + monthlyDetailYear + '&month=' + monthlyDetailMonth, { headers: headers() }); + const res = await fetch(API + '/api/stats/monthly-records?type=' + type + '&year=' + monthlyDetailYear + '&month=' + monthlyDetailMonth + '&limit=1000', { headers: headers() }); if (!res.ok) return; const data = await res.json(); const color = type === 'income' ? '#27ae60' : '#e74c3c'; diff --git a/server/routes/records.js b/server/routes/records.js index 7f5a8b4..0be5abb 100644 --- a/server/routes/records.js +++ b/server/routes/records.js @@ -51,7 +51,9 @@ function queryPagedRecords(userId, year, month, options) { // 接单列表 router.get('/orders', (req, res) => { const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { - category: '接单' + category: '接单', + page: req.query.page, + limit: req.query.limit }); res.json(result); }); @@ -60,7 +62,9 @@ router.get('/orders', (req, res) => { router.get('/other-income', (req, res) => { const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { type: 'income', - excludeCategories: ['接单', '派单'] + excludeCategories: ['接单', '派单'], + page: req.query.page, + limit: req.query.limit }); res.json(result); }); @@ -69,7 +73,9 @@ router.get('/other-income', (req, res) => { router.get('/red-envelope', (req, res) => { const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { type: 'income', - category: '红包收入' + category: '红包收入', + page: req.query.page, + limit: req.query.limit }); res.json(result); }); @@ -78,7 +84,9 @@ router.get('/red-envelope', (req, res) => { router.get('/milk-tea', (req, res) => { const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { type: 'income', - category: '奶茶' + category: '奶茶', + page: req.query.page, + limit: req.query.limit }); res.json(result); }); @@ -86,7 +94,9 @@ router.get('/milk-tea', (req, res) => { // 派单列表 router.get('/dispatches', (req, res) => { const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { - category: '派单' + category: '派单', + page: req.query.page, + limit: req.query.limit }); res.json(result); }); diff --git a/server/routes/stats.js b/server/routes/stats.js index 3a8b8ab..2e9a33d 100644 --- a/server/routes/stats.js +++ b/server/routes/stats.js @@ -22,12 +22,12 @@ function getCached(key, fetchFn) { const firstKey = statsCache.keys().next().value; statsCache.delete(firstKey); } - return fetchFn().then(data => { + return Promise.resolve(fetchFn()).then(data => { statsCache.set(key, { data, ts: Date.now() }); return data; }).catch(err => { console.error('Cache fetch error:', err.message); - return fetchFn(); // fallback to direct fetch + throw err; }); }