diff --git a/public/js/order-list.js b/public/js/order-list.js index fc6452f..4209ce7 100644 --- a/public/js/order-list.js +++ b/public/js/order-list.js @@ -1,20 +1,38 @@ -let orderPage = 1; -let orderHasMore = true; -let orderLoading = false; +// 全局状态变量(供 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; + +// 通用分页状态管理 +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 } +}; + +// 通用列表加载函数 +async function loadList(listId, append) { + const config = listConfig[listId]; + if (!config) return; + + if (config.getLoading() || (!append && !config.getHasMore())) return; + config.setLoading(true); + document.getElementById(listId.replace('List', 'Loading')).style.display = 'block'; -async function loadOrders(append) { - if (orderLoading || (!append && !orderHasMore)) return; - orderLoading = true; - document.getElementById('orderLoading').style.display = 'block'; try { - const res = await fetch(API + '/api/records/orders?page=' + orderPage + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); + 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('orderList'); + const container = document.getElementById(listId); if (!append) container.innerHTML = ''; + if (data.records.length === 0) { - if (orderPage === 1) container.innerHTML = '
暂无接单记录
'; - orderHasMore = false; + if (config.getPage() === 1) container.innerHTML = '
' + config.emptyMsg + '
'; + config.setHasMore(false); } else { const grouped = {}; data.records.forEach(r => { @@ -22,272 +40,88 @@ async function loadOrders(append) { if (!grouped[date]) grouped[date] = []; grouped[date].push(r); }); + let html = ''; for (const [date, records] of Object.entries(grouped)) { - html += `
-
${date}
`; + html += '
' + date + '
'; records.forEach(r => { - const time = formatLocalTime(r.created_at); - html += `
-
- ${r.boss || '-'} - ${(r.amount || 0).toFixed(2)} -
-
- ${r.quantity || 0}单 × ${(r.unit_price || 0).toFixed(2)} - ${time}${r.partner ? ' · ' + r.partner : ''} -
-
`; + html += config.renderItem(r); }); html += '
'; } container.insertAdjacentHTML('beforeend', html); - orderHasMore = data.records.length >= 20; - orderPage++; + config.setHasMore(data.records.length >= 20); + config.setPage(config.getPage() + 1); } } catch (e) { console.error(e); } - orderLoading = false; - document.getElementById('orderLoading').style.display = 'none'; + + config.setLoading(false); + document.getElementById(listId.replace('List', 'Loading')).style.display = 'none'; +} + +// 通用渲染函数 +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); + return '
' + + '
' + (r.boss || '-') + '' + (r.amount || 0).toFixed(2) + '
' + + '
' + (r.quantity || 0) + '单 × ' + ((r.rebate || r.amount / r.quantity) || 0).toFixed(2) + '(单价' + (r.unit_price || 0).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 + '
'; } // 滚动加载 window.addEventListener('scroll', () => { - if (document.getElementById('orderListPage').classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - if (orderHasMore && !orderLoading) loadOrders(true); - } - } - if (document.getElementById('dispatchListPage').classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - if (dispatchHasMore && !dispatchLoading) loadDispatches(true); - } - } - if (document.getElementById('otherIncomeListPage').classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - if (otherIncomeHasMore && !otherIncomeLoading) loadOtherIncome(true); - } - } - if (document.getElementById('redEnvelopeListPage').classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - if (redEnvelopeHasMore && !redEnvelopeLoading) loadRedEnvelope(true); - } - } - if (document.getElementById('milkTeaListPage').classList.contains('active')) { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) { - if (milkTeaHasMore && !milkTeaLoading) loadMilkTea(true); + 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; } } }); -// 红包及其他收入列表 -let otherIncomePage = 1; -let otherIncomeHasMore = true; -let otherIncomeLoading = false; - -async function loadOtherIncome(append) { - if (otherIncomeLoading || (!append && !otherIncomeHasMore)) return; - otherIncomeLoading = true; - document.getElementById('otherIncomeLoading').style.display = 'block'; - try { - const res = await fetch(API + '/api/records/other-income?page=' + otherIncomePage + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); - if (!res.ok) return; - const data = await res.json(); - const container = document.getElementById('otherIncomeList'); - if (!append) container.innerHTML = ''; - if (data.records.length === 0) { - if (otherIncomePage === 1) container.innerHTML = '
暂无红包及其他收入记录
'; - otherIncomeHasMore = 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 => { - const time = formatLocalTime(r.created_at); - const displayCategory = r.category === '红包收入' ? '红包' : (r.category || '-'); - html += `
-
- ${displayCategory}${r.boss ? ' · ' + r.boss : ''} - ${(r.amount || 0).toFixed(2)} -
-
- ${r.source || ''} - ${time} -
-
`; - }); - html += '
'; - } - container.insertAdjacentHTML('beforeend', html); - otherIncomeHasMore = data.records.length >= 20; - otherIncomePage++; - } - } catch (e) { console.error(e); } - otherIncomeLoading = false; - document.getElementById('otherIncomeLoading').style.display = 'none'; -} - -// 派单列表 -let dispatchPage = 1; -let dispatchHasMore = true; -let dispatchLoading = false; - -async function loadDispatches(append) { - if (dispatchLoading || (!append && !dispatchHasMore)) return; - dispatchLoading = true; - document.getElementById('dispatchLoading').style.display = 'block'; - try { - const res = await fetch(API + '/api/records/dispatches?page=' + dispatchPage + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); - if (!res.ok) return; - const data = await res.json(); - const container = document.getElementById('dispatchList'); - if (!append) container.innerHTML = ''; - if (data.records.length === 0) { - if (dispatchPage === 1) container.innerHTML = '
暂无派单记录
'; - dispatchHasMore = 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 => { - const time = formatLocalTime(r.created_at); - html += `
-
- ${r.boss || '-'} - ${(r.amount || 0).toFixed(2)} -
-
- ${r.quantity || 0}单 × ${(r.rebate || r.amount / r.quantity).toFixed(2)}(单价${(r.unit_price || 0).toFixed(2)}) - ${time}${r.partner ? ' · ' + r.partner : ''} -
-
`; - }); - html += '
'; - } - container.insertAdjacentHTML('beforeend', html); - dispatchHasMore = data.records.length >= 20; - dispatchPage++; - } - } catch (e) { console.error(e); } - dispatchLoading = false; - document.getElementById('dispatchLoading').style.display = 'none'; -} - -// 红包收入列表 -let redEnvelopePage = 1; -let redEnvelopeHasMore = true; -let redEnvelopeLoading = false; - -async function loadRedEnvelope(append) { - if (redEnvelopeLoading || (!append && !redEnvelopeHasMore)) return; - redEnvelopeLoading = true; - document.getElementById('redEnvelopeLoading').style.display = 'block'; - try { - const res = await fetch(API + '/api/records/red-envelope?page=' + redEnvelopePage + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); - if (!res.ok) return; - const data = await res.json(); - const container = document.getElementById('redEnvelopeList'); - if (!append) container.innerHTML = ''; - if (data.records.length === 0) { - if (redEnvelopePage === 1) container.innerHTML = '
暂无红包收入记录
'; - redEnvelopeHasMore = 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 => { - const time = formatLocalTime(r.created_at); - html += `
-
- 红包 - ${(r.amount || 0).toFixed(2)} -
-
- ${r.source || ''} - ${time} -
-
`; - }); - html += '
'; - } - container.insertAdjacentHTML('beforeend', html); - redEnvelopeHasMore = data.records.length >= 20; - redEnvelopePage++; - } - } catch (e) { console.error(e); } - redEnvelopeLoading = false; - document.getElementById('redEnvelopeLoading').style.display = 'none'; -} - -// 奶茶收入列表 -let milkTeaPage = 1; -let milkTeaHasMore = true; -let milkTeaLoading = false; - -async function loadMilkTea(append) { - if (milkTeaLoading || (!append && !milkTeaHasMore)) return; - milkTeaLoading = true; - document.getElementById('milkTeaLoading').style.display = 'block'; - try { - const res = await fetch(API + '/api/records/milk-tea?page=' + milkTeaPage + '&limit=20&year=' + currentYear + '&month=' + currentMonth, { headers: headers() }); - if (!res.ok) return; - const data = await res.json(); - const container = document.getElementById('milkTeaList'); - if (!append) container.innerHTML = ''; - if (data.records.length === 0) { - if (milkTeaPage === 1) container.innerHTML = '
暂无奶茶收入记录
'; - milkTeaHasMore = 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 => { - const time = formatLocalTime(r.created_at); - html += `
-
- 奶茶 - ${(r.amount || 0).toFixed(2)} -
-
- ${r.source || ''} - ${time} -
-
`; - }); - html += '
'; - } - container.insertAdjacentHTML('beforeend', html); - milkTeaHasMore = data.records.length >= 20; - milkTeaPage++; - } - } catch (e) { console.error(e); } - milkTeaLoading = false; - document.getElementById('milkTeaLoading').style.display = 'none'; -} +// 兼容旧接口 +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/server/routes/records.js b/server/routes/records.js index c28fae4..7f5a8b4 100644 --- a/server/routes/records.js +++ b/server/routes/records.js @@ -15,89 +15,80 @@ router.post('/', (req, res) => { res.json({ id: result.lastInsertRowid }); }); -// 接单列表 -router.get('/orders', (req, res) => { - const { page = 1, limit = 20 } = req.query; +// 通用分页查询函数 +function queryPagedRecords(userId, year, month, options) { + const { page = 1, limit = 20 } = options; const offset = (Number(page) - 1) * Number(limit); - const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); + const { startDate, endDate } = getMonthRange(year, month); + + let whereClause = `user_id = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?`; + const params = [userId, startDate, endDate]; + + if (options.category) { + whereClause += ` AND category = ?`; + params.push(options.category); + } + if (options.type) { + whereClause += ` AND type = ?`; + params.push(options.type); + } + if (options.excludeCategories && options.excludeCategories.length > 0) { + whereClause += ` AND category NOT IN (${options.excludeCategories.map(() => '?').join(',')})`; + params.push(...options.excludeCategories); + } const total = db.prepare( - `SELECT COUNT(*) as count FROM records WHERE user_id = ? AND category = '接单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?` - ).get(req.user.id, startDate, endDate).count; + `SELECT COUNT(*) as count FROM records WHERE ${whereClause}` + ).get(...params).count; const records = db.prepare( - `SELECT * FROM records WHERE user_id = ? AND category = '接单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? ORDER BY created_at DESC LIMIT ? OFFSET ?` - ).all(req.user.id, startDate, endDate, Number(limit), offset); + `SELECT * FROM records WHERE ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?` + ).all(...params, Number(limit), offset); - res.json({ records, total, page: Number(page), limit: Number(limit) }); + return { records, total, page: Number(page), limit: Number(limit) }; +} + +// 接单列表 +router.get('/orders', (req, res) => { + const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { + category: '接单' + }); + res.json(result); }); // 红包及其他收入列表 router.get('/other-income', (req, res) => { - const { page = 1, limit = 20 } = req.query; - const offset = (Number(page) - 1) * Number(limit); - const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); - - const total = db.prepare( - `SELECT COUNT(*) as count FROM records WHERE user_id = ? AND type = 'income' AND category != '接单' AND category != '派单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?` - ).get(req.user.id, startDate, endDate).count; - - const records = db.prepare( - `SELECT * FROM records WHERE user_id = ? AND type = 'income' AND category != '接单' AND category != '派单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? ORDER BY created_at DESC LIMIT ? OFFSET ?` - ).all(req.user.id, startDate, endDate, Number(limit), offset); - - res.json({ records, total, page: Number(page), limit: Number(limit) }); + const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { + type: 'income', + excludeCategories: ['接单', '派单'] + }); + res.json(result); }); // 红包收入列表 router.get('/red-envelope', (req, res) => { - const { page = 1, limit = 20 } = req.query; - const offset = (Number(page) - 1) * Number(limit); - const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); - - const total = db.prepare( - `SELECT COUNT(*) as count FROM records WHERE user_id = ? AND type = 'income' AND category = '红包收入' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?` - ).get(req.user.id, startDate, endDate).count; - - const records = db.prepare( - `SELECT * FROM records WHERE user_id = ? AND type = 'income' AND category = '红包收入' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? ORDER BY created_at DESC LIMIT ? OFFSET ?` - ).all(req.user.id, startDate, endDate, Number(limit), offset); - - res.json({ records, total, page: Number(page), limit: Number(limit) }); + const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { + type: 'income', + category: '红包收入' + }); + res.json(result); }); // 奶茶收入列表 router.get('/milk-tea', (req, res) => { - const { page = 1, limit = 20 } = req.query; - const offset = (Number(page) - 1) * Number(limit); - const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); - - const total = db.prepare( - `SELECT COUNT(*) as count FROM records WHERE user_id = ? AND type = 'income' AND category = '奶茶' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?` - ).get(req.user.id, startDate, endDate).count; - - const records = db.prepare( - `SELECT * FROM records WHERE user_id = ? AND type = 'income' AND category = '奶茶' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? ORDER BY created_at DESC LIMIT ? OFFSET ?` - ).all(req.user.id, startDate, endDate, Number(limit), offset); - - res.json({ records, total, page: Number(page), limit: Number(limit) }); + const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { + type: 'income', + category: '奶茶' + }); + res.json(result); }); // 派单列表 router.get('/dispatches', (req, res) => { - const { page = 1, limit = 20 } = req.query; - const offset = (Number(page) - 1) * Number(limit); - const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); - - const total = db.prepare( - `SELECT COUNT(*) as count FROM records WHERE user_id = ? AND category = '派单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?` - ).get(req.user.id, startDate, endDate).count; - - const records = db.prepare( - `SELECT * FROM records WHERE user_id = ? AND category = '派单' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? ORDER BY created_at DESC LIMIT ? OFFSET ?` - ).all(req.user.id, startDate, endDate, Number(limit), offset); - - res.json({ records, total, page: Number(page), limit: Number(limit) }); + const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, { + category: '派单' + }); + res.json(result); }); // 获取记录列表 diff --git a/server/routes/stats.js b/server/routes/stats.js index 74fe008..da02d33 100644 --- a/server/routes/stats.js +++ b/server/routes/stats.js @@ -35,22 +35,28 @@ router.get('/monthly', (req, res) => { // 月收入/支出记录列表 router.get('/monthly-records', (req, res) => { - const { type } = req.query; + const { type, page = 1, limit = 20 } = req.query; if (!type || !['income', 'expense'].includes(type)) return res.status(400).json({ error: '缺少类型参数' }); const { startDate, endDate } = getMonthRange(req.query.year, req.query.month); + const offset = (Number(page) - 1) * Number(limit); const summary = db.prepare(` SELECT COALESCE(SUM(amount), 0) as total FROM records WHERE user_id = ? AND type = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? `).get(req.user.id, type, startDate, endDate); + const total = db.prepare(` + SELECT COUNT(*) as count FROM records + WHERE user_id = ? AND type = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? + `).get(req.user.id, type, startDate, endDate).count; + const records = db.prepare(` SELECT * FROM records WHERE user_id = ? AND type = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? - ORDER BY created_at DESC - `).all(req.user.id, type, startDate, endDate); + ORDER BY created_at DESC LIMIT ? OFFSET ? + `).all(req.user.id, type, startDate, endDate, Number(limit), offset); - res.json({ total: summary.total, records }); + res.json({ total: summary.total, records, page: Number(page), limit: Number(limit), totalCount: total }); }); // 年度净收入按年统计 @@ -236,7 +242,9 @@ router.get('/total', (req, res) => { // 老板交易记录 router.get('/boss-records', (req, res) => { const boss = req.query.boss; + const { page = 1, limit = 20 } = req.query; if (!boss) return res.status(400).json({ error: '缺少老板参数' }); + const offset = (Number(page) - 1) * Number(limit); // 我的收入:只统计 type='income' 的记录(接单、派单返点、红包收入等) // 使用 COALESCE(boss, source) 兼容旧数据(之前的红包收入只有 source 字段) @@ -256,21 +264,30 @@ router.get('/boss-records', (req, res) => { const boss_total_expense = expenseSummary.dispatch_expense + expenseSummary.order_expense + expenseSummary.redEnvelope_expense; + const total = db.prepare(` + SELECT COUNT(*) as count FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? + `).get(req.user.id, boss).count; + const records = db.prepare(` - SELECT * FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? ORDER BY created_at DESC - `).all(req.user.id, boss); + SELECT * FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? ORDER BY created_at DESC LIMIT ? OFFSET ? + `).all(req.user.id, boss, Number(limit), offset); res.json({ my_income: myIncomeSummary.total, boss_total_expense: boss_total_expense, - records + records, + page: Number(page), + limit: Number(limit), + totalCount: total }); }); // 陪玩交易记录 router.get('/partner-records', (req, res) => { const partner = req.query.partner; + const { page = 1, limit = 20 } = req.query; if (!partner) return res.status(400).json({ error: '缺少陪玩参数' }); + const offset = (Number(page) - 1) * Number(limit); const dispatchStats = db.prepare(` SELECT @@ -285,20 +302,31 @@ router.get('/partner-records', (req, res) => { FROM records WHERE user_id = ? AND type = 'income' AND category != '派单' AND category != '接单' AND source = ? `).get(req.user.id, partner); + const total = db.prepare(` + SELECT COUNT(*) as count FROM records + WHERE user_id = ? AND ( + (category = '派单' AND partner = ?) OR + (type = 'income' AND category != '派单' AND category != '接单' AND source = ?) + ) + `).get(req.user.id, partner, partner).count; + const records = db.prepare(` SELECT * FROM records WHERE user_id = ? AND ( (category = '派单' AND partner = ?) OR (type = 'income' AND category != '派单' AND category != '接单' AND source = ?) - ) ORDER BY created_at DESC - `).all(req.user.id, partner, partner); + ) ORDER BY created_at DESC LIMIT ? OFFSET ? + `).all(req.user.id, partner, partner, Number(limit), offset); res.json({ dispatch_count: dispatchStats.dispatch_count, spread_income: dispatchStats.spread_income, rebate_income: dispatchStats.rebate_income, other_income: otherIncome.total, - records + records, + page: Number(page), + limit: Number(limit), + totalCount: total }); }); @@ -329,8 +357,9 @@ router.get('/daily-income', (req, res) => { // 某天收入记录详情 router.get('/daily-detail', (req, res) => { - const { date } = req.query; + const { date, page = 1, limit = 20 } = req.query; if (!date) return res.status(400).json({ error: '缺少日期参数' }); + const offset = (Number(page) - 1) * Number(limit); const nextDay = new Date(date); nextDay.setDate(nextDay.getDate() + 1); const endDate = formatLocalDate(nextDay); @@ -340,12 +369,16 @@ router.get('/daily-detail', (req, res) => { FROM records WHERE user_id = ? AND type = 'income' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? `).get(req.user.id, date, endDate); + const total = db.prepare(` + SELECT COUNT(*) as count FROM records WHERE user_id = ? AND type = 'income' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? + `).get(req.user.id, date, endDate).count; + const records = db.prepare(` SELECT * FROM records WHERE user_id = ? AND type = 'income' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ? - ORDER BY created_at DESC - `).all(req.user.id, date, endDate); + ORDER BY created_at DESC LIMIT ? OFFSET ? + `).all(req.user.id, date, endDate, Number(limit), offset); - res.json({ total_income: summary.total_income, records }); + res.json({ total_income: summary.total_income, records, page: Number(page), limit: Number(limit), totalCount: total }); }); // 老板收入排行 - 首页预览