diff --git a/public/css/style.css b/public/css/style.css
index b258b9a..0624cc5 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -159,7 +159,7 @@ body {
font-size: 15px;
font-weight: 600;
color: #333;
- margin-bottom: 16px;
+ margin-bottom: 0;
}
.weekly-chart-bars {
display: flex;
@@ -215,6 +215,21 @@ body {
.daily-nav-date {
font-size: 15px; font-weight: 600; color: #333;
}
+.scope-toggle {
+ margin-left: auto;
+ padding: 4px 12px;
+ border-radius: 12px;
+ border: 1px solid #667eea;
+ background: #fff;
+ color: #667eea;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+.scope-toggle.active {
+ background: #667eea;
+ color: #fff;
+}
.daily-summary {
background: #fff;
border-radius: 12px;
diff --git a/public/index.html b/public/index.html
index 5ee2b31..0b5846f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -284,9 +284,16 @@
+
+
+ 2026年3月
+
+ 加载更多
@@ -294,9 +301,16 @@
+
+
+ 2026年3月
+
+ 加载更多
diff --git a/public/js/stats.js b/public/js/stats.js
index 8dec6d5..85dfee9 100644
--- a/public/js/stats.js
+++ b/public/js/stats.js
@@ -56,13 +56,14 @@ function renderWeeklyChart(days) {
return;
}
const max = Math.max(...days.map(d => d.income), 1);
+ const barMaxHeight = 110; // 柱状图最大可用像素高度
container.innerHTML = days.map(d => {
- const pct = Math.max((d.income / max) * 100, 3);
+ const height = Math.max((d.income / max) * barMaxHeight, 4);
const label = d.date.slice(5).replace('-', '/');
const amount = d.income > 0 ? '' + d.income.toFixed(0) : '0';
return ``;
}).join('');
@@ -284,46 +285,145 @@ function renderBossRankingPreview(rows) {
let currentBossName = '';
let bossDetailFrom = 'homePage';
+let bossDetailYear = 0;
+let bossDetailMonth = 0;
+let bossDetailScopeAll = false;
+let bossDetailPage = 1;
+let bossDetailLimit = 20;
+let bossDetailTotalCount = 0;
+let bossDetailAllRecords = [];
+let bossDetailLoading = false;
+let bossDetailScrollHandler = null;
function openBossDetail(boss, from) {
currentBossName = boss;
bossDetailFrom = from || 'homePage';
+ bossDetailYear = currentYear;
+ bossDetailMonth = currentMonth;
+ // 从排行榜进入时默认显示全部
+ bossDetailScopeAll = (from === 'rankingPage' || from === 'takeRankingPage');
+ bossDetailPage = 1;
+ bossDetailLimit = bossDetailScopeAll ? 500 : 20;
+ bossDetailTotalCount = 0;
+ bossDetailAllRecords = [];
+ updateBossDetailNav();
loadBossDetail();
}
function bossDetailGoBack() {
+ if (bossDetailScrollHandler) {
+ window.removeEventListener('scroll', bossDetailScrollHandler);
+ bossDetailScrollHandler = null;
+ }
showPage(bossDetailFrom);
}
-async function loadBossDetail() {
- document.getElementById('bossDetailTitle').textContent = currentBossName + ' 交易记录';
- document.getElementById('bossDetailSummary').innerHTML = '加载中...
';
- document.getElementById('bossDetailList').innerHTML = '';
- showPage('bossDetailPage');
+function updateBossDetailNav() {
+ const scopeEl = document.getElementById('bossDetailScope');
+ const allBtn = document.getElementById('bossDetailAllBtn');
+ if (bossDetailScopeAll) {
+ scopeEl.textContent = '全部';
+ allBtn.textContent = '当月';
+ allBtn.classList.add('active');
+ } else {
+ scopeEl.textContent = `${bossDetailYear}年${bossDetailMonth}月`;
+ allBtn.textContent = '全部';
+ allBtn.classList.remove('active');
+ }
+}
+
+function switchBossDetailScope(offset) {
+ if (bossDetailScopeAll) return;
+ bossDetailMonth += offset;
+ if (bossDetailMonth > 12) { bossDetailMonth = 1; bossDetailYear++; }
+ if (bossDetailMonth < 1) { bossDetailMonth = 12; bossDetailYear--; }
+ bossDetailPage = 1;
+ bossDetailAllRecords = [];
+ updateBossDetailNav();
+ loadBossDetail();
+}
+
+function toggleBossDetailAll() {
+ bossDetailScopeAll = !bossDetailScopeAll;
+ bossDetailPage = 1;
+ bossDetailAllRecords = [];
+ if (bossDetailScopeAll) {
+ bossDetailLimit = 500;
+ } else {
+ bossDetailLimit = 20;
+ bossDetailYear = currentYear;
+ bossDetailMonth = currentMonth;
+ }
+ updateBossDetailNav();
+ loadBossDetail();
+}
+
+function loadMoreBossDetail() {
+ bossDetailPage++;
+ loadBossDetail(true);
+}
+
+async function loadBossDetail(append) {
+ if (bossDetailLoading) return;
+ bossDetailLoading = true;
+
+ if (!append) {
+ document.getElementById('bossDetailTitle').textContent = currentBossName + ' 交易记录';
+ document.getElementById('bossDetailSummary').innerHTML = '加载中...
';
+ document.getElementById('bossDetailList').innerHTML = '';
+ showPage('bossDetailPage');
+ }
+
+ let url = API + '/api/stats/boss-records?boss=' + encodeURIComponent(currentBossName) + '&page=' + bossDetailPage + '&limit=' + bossDetailLimit;
+ if (!bossDetailScopeAll) {
+ url += '&year=' + bossDetailYear + '&month=' + bossDetailMonth;
+ }
try {
- const res = await fetch(API + '/api/stats/boss-records?boss=' + encodeURIComponent(currentBossName), { headers: headers() });
- if (!res.ok) return;
+ const res = await fetch(url, { headers: headers() });
+ if (!res.ok) { bossDetailLoading = false; return; }
const data = await res.json();
- document.getElementById('bossDetailSummary').innerHTML = `
-
-
-
${data.boss_total_expense.toFixed(2)}
-
老板总支出
+
+ if (!append) {
+ document.getElementById('bossDetailSummary').innerHTML = `
+
+
+
${data.boss_total_expense.toFixed(2)}
+
老板总支出
+
+
+
${data.my_income.toFixed(2)}
+
我的总收入
+
+
+
${data.take_count || 0}
+
接单数
+
+
+
${data.dispatch_count || 0}
+
派单数
+
-
-
${data.my_income.toFixed(2)}
-
我的总收入
-
-
- `;
+ `;
+ bossDetailAllRecords = data.records || [];
+ setupBossDetailScroll();
+ } else {
+ bossDetailAllRecords = bossDetailAllRecords.concat(data.records || []);
+ }
+ bossDetailTotalCount = data.totalCount || 0;
+
const container = document.getElementById('bossDetailList');
- if (data.records.length === 0) {
+ const moreBtn = document.getElementById('bossDetailMore');
+
+ if (bossDetailAllRecords.length === 0) {
container.innerHTML = '
暂无交易记录
';
+ if (moreBtn) moreBtn.style.display = 'none';
+ bossDetailLoading = false;
return;
}
+
const grouped = {};
- data.records.forEach(r => {
+ bossDetailAllRecords.forEach(r => {
const d = new Date(r.created_at);
const key = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
if (!grouped[key]) grouped[key] = { records: [], total: 0 };
@@ -350,7 +450,42 @@ async function loadBossDetail() {
html += '
';
}
container.innerHTML = html;
- } catch (e) { console.error(e); }
+
+ // Show/hide load more indicator
+ if (moreBtn) {
+ if (bossDetailAllRecords.length < bossDetailTotalCount) {
+ moreBtn.style.display = 'block';
+ moreBtn.textContent = '加载中... (' + bossDetailAllRecords.length + '/' + bossDetailTotalCount + ')';
+ } else {
+ moreBtn.style.display = 'none';
+ }
+ }
+ bossDetailLoading = false;
+ } catch (e) {
+ bossDetailLoading = false;
+ console.error(e);
+ }
+}
+
+function setupBossDetailScroll() {
+ if (bossDetailScrollHandler) {
+ window.removeEventListener('scroll', bossDetailScrollHandler);
+ }
+ bossDetailScrollHandler = function() {
+ if (bossDetailLoading) return;
+ if (bossDetailScopeAll && bossDetailAllRecords.length >= bossDetailTotalCount) {
+ window.removeEventListener('scroll', bossDetailScrollHandler);
+ return;
+ }
+ const scrollY = window.scrollY;
+ const windowHeight = window.innerHeight;
+ const docHeight = document.documentElement.scrollHeight;
+ if (scrollY + windowHeight >= docHeight - 200) {
+ bossDetailPage++;
+ loadBossDetail(true);
+ }
+ };
+ window.addEventListener('scroll', bossDetailScrollHandler);
}
async function loadPartnerRankingPreview() {
@@ -443,54 +578,140 @@ async function loadPartnerRankingDetail() {
let currentPartnerName = '';
let partnerDetailFrom = 'homePage';
+let partnerDetailYear = 0;
+let partnerDetailMonth = 0;
+let partnerDetailScopeAll = false;
+let partnerDetailPage = 1;
+let partnerDetailLimit = 20;
+let partnerDetailTotalCount = 0;
+let partnerDetailAllRecords = [];
+let partnerDetailLoading = false;
+let partnerDetailScrollHandler = null;
function openPartnerDetail(partner, from) {
currentPartnerName = partner;
partnerDetailFrom = from || 'homePage';
+ partnerDetailYear = currentYear;
+ partnerDetailMonth = currentMonth;
+ // 从排行榜进入时默认显示全部
+ partnerDetailScopeAll = (from === 'dispatchRankingPage' || from === 'partnerRankingPage');
+ partnerDetailPage = 1;
+ partnerDetailLimit = partnerDetailScopeAll ? 500 : 20;
+ partnerDetailTotalCount = 0;
+ partnerDetailAllRecords = [];
+ updatePartnerDetailNav();
loadPartnerDetail();
}
function partnerDetailGoBack() {
+ if (partnerDetailScrollHandler) {
+ window.removeEventListener('scroll', partnerDetailScrollHandler);
+ partnerDetailScrollHandler = null;
+ }
showPage(partnerDetailFrom);
}
-async function loadPartnerDetail() {
- document.getElementById('partnerDetailTitle').textContent = currentPartnerName + ' 交易记录';
- document.getElementById('partnerDetailSummary').innerHTML = '加载中...
';
- document.getElementById('partnerDetailList').innerHTML = '';
- showPage('partnerDetailPage');
+function updatePartnerDetailNav() {
+ const scopeEl = document.getElementById('partnerDetailScope');
+ const allBtn = document.getElementById('partnerDetailAllBtn');
+ if (partnerDetailScopeAll) {
+ scopeEl.textContent = '全部';
+ allBtn.textContent = '当月';
+ allBtn.classList.add('active');
+ } else {
+ scopeEl.textContent = `${partnerDetailYear}年${partnerDetailMonth}月`;
+ allBtn.textContent = '全部';
+ allBtn.classList.remove('active');
+ }
+}
+
+function switchPartnerDetailScope(offset) {
+ if (partnerDetailScopeAll) return;
+ partnerDetailMonth += offset;
+ if (partnerDetailMonth > 12) { partnerDetailMonth = 1; partnerDetailYear++; }
+ if (partnerDetailMonth < 1) { partnerDetailMonth = 12; partnerDetailYear--; }
+ partnerDetailPage = 1;
+ partnerDetailAllRecords = [];
+ updatePartnerDetailNav();
+ loadPartnerDetail();
+}
+
+function togglePartnerDetailAll() {
+ partnerDetailScopeAll = !partnerDetailScopeAll;
+ partnerDetailPage = 1;
+ partnerDetailAllRecords = [];
+ if (partnerDetailScopeAll) {
+ partnerDetailLimit = 500;
+ } else {
+ partnerDetailLimit = 20;
+ partnerDetailYear = currentYear;
+ partnerDetailMonth = currentMonth;
+ }
+ updatePartnerDetailNav();
+ loadPartnerDetail();
+}
+
+async function loadPartnerDetail(append) {
+ if (partnerDetailLoading) return;
+ partnerDetailLoading = true;
+
+ if (!append) {
+ document.getElementById('partnerDetailTitle').textContent = currentPartnerName + ' 交易记录';
+ document.getElementById('partnerDetailSummary').innerHTML = '加载中...
';
+ document.getElementById('partnerDetailList').innerHTML = '';
+ showPage('partnerDetailPage');
+ }
+
+ let url = API + '/api/stats/partner-records?partner=' + encodeURIComponent(currentPartnerName) + '&page=' + partnerDetailPage + '&limit=' + partnerDetailLimit;
+ if (!partnerDetailScopeAll) {
+ url += '&year=' + partnerDetailYear + '&month=' + partnerDetailMonth;
+ }
try {
- const res = await fetch(API + '/api/stats/partner-records?partner=' + encodeURIComponent(currentPartnerName), { headers: headers() });
- if (!res.ok) return;
+ const res = await fetch(url, { headers: headers() });
+ if (!res.ok) { partnerDetailLoading = false; return; }
const data = await res.json();
- document.getElementById('partnerDetailSummary').innerHTML = `
-
-
-
${data.dispatch_count}
-
总派单数
+
+ if (!append) {
+ document.getElementById('partnerDetailSummary').innerHTML = `
+
+
+
${data.dispatch_count}
+
总派单数
+
+
+
${data.spread_income.toFixed(2)}
+
陪玩总收入
+
+
+
${data.rebate_income.toFixed(2)}
+
总返点收入
+
+
+
${data.other_income.toFixed(2)}
+
红包及其他
+
-
-
${data.spread_income.toFixed(2)}
-
陪玩总收入
-
-
-
${data.rebate_income.toFixed(2)}
-
总返点收入
-
-
-
${data.other_income.toFixed(2)}
-
红包及其他
-
-
- `;
+ `;
+ partnerDetailAllRecords = data.records || [];
+ setupPartnerDetailScroll();
+ } else {
+ partnerDetailAllRecords = partnerDetailAllRecords.concat(data.records || []);
+ }
+ partnerDetailTotalCount = data.totalCount || 0;
+
const container = document.getElementById('partnerDetailList');
- if (data.records.length === 0) {
+ const moreBtn = document.getElementById('partnerDetailMore');
+
+ if (partnerDetailAllRecords.length === 0) {
container.innerHTML = '
暂无交易记录
';
+ if (moreBtn) moreBtn.style.display = 'none';
+ partnerDetailLoading = false;
return;
}
+
const grouped = {};
- data.records.forEach(r => {
+ partnerDetailAllRecords.forEach(r => {
const d = new Date(r.created_at);
const key = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
if (!grouped[key]) grouped[key] = { records: [], total: 0 };
@@ -517,5 +738,39 @@ async function loadPartnerDetail() {
html += '
';
}
container.innerHTML = html;
- } catch (e) { console.error(e); }
+
+ if (moreBtn) {
+ if (partnerDetailAllRecords.length < partnerDetailTotalCount) {
+ moreBtn.style.display = 'block';
+ moreBtn.textContent = '加载中... (' + partnerDetailAllRecords.length + '/' + partnerDetailTotalCount + ')';
+ } else {
+ moreBtn.style.display = 'none';
+ }
+ }
+ partnerDetailLoading = false;
+ } catch (e) {
+ partnerDetailLoading = false;
+ console.error(e);
+ }
+}
+
+function setupPartnerDetailScroll() {
+ if (partnerDetailScrollHandler) {
+ window.removeEventListener('scroll', partnerDetailScrollHandler);
+ }
+ partnerDetailScrollHandler = function() {
+ if (partnerDetailLoading) return;
+ if (partnerDetailScopeAll && partnerDetailAllRecords.length >= partnerDetailTotalCount) {
+ window.removeEventListener('scroll', partnerDetailScrollHandler);
+ return;
+ }
+ const scrollY = window.scrollY;
+ const windowHeight = window.innerHeight;
+ const docHeight = document.documentElement.scrollHeight;
+ if (scrollY + windowHeight >= docHeight - 200) {
+ partnerDetailPage++;
+ loadPartnerDetail(true);
+ }
+ };
+ window.addEventListener('scroll', partnerDetailScrollHandler);
}
diff --git a/server/routes/stats.js b/server/routes/stats.js
index da02d33..96ce653 100644
--- a/server/routes/stats.js
+++ b/server/routes/stats.js
@@ -242,39 +242,50 @@ router.get('/total', (req, res) => {
// 老板交易记录
router.get('/boss-records', (req, res) => {
const boss = req.query.boss;
- const { page = 1, limit = 20 } = req.query;
+ const { page = 1, limit = 20, year, month } = req.query;
if (!boss) return res.status(400).json({ error: '缺少老板参数' });
const offset = (Number(page) - 1) * Number(limit);
- // 我的收入:只统计 type='income' 的记录(接单、派单返点、红包收入等)
- // 使用 COALESCE(boss, source) 兼容旧数据(之前的红包收入只有 source 字段)
+ // 日期过滤条件
+ let dateFilter = '';
+ let dateParams = [];
+ if (year && month) {
+ const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
+ const endDate = `${year}-${String(month).padStart(2, '0')}-31`;
+ dateFilter = ` AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') <= ?`;
+ dateParams = [startDate, endDate];
+ }
+
const myIncomeSummary = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total
- FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? AND type = 'income'
- `).get(req.user.id, boss);
+ FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? AND type = 'income'${dateFilter}
+ `).get(req.user.id, boss, ...dateParams);
- // 老板支出:派单支出 + 接单支出 + 红包收入(老板发的红包也算老板支出)
const expenseSummary = db.prepare(`
SELECT
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity * COALESCE(unit_price, 0) ELSE 0 END), 0) as dispatch_expense,
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity * COALESCE(unit_price, 0) ELSE 0 END), 0) as order_expense,
- COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_expense
- FROM records WHERE user_id = ? AND COALESCE(boss, source) = ?
- `).get(req.user.id, boss);
+ COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_expense,
+ COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_count,
+ COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_count
+ FROM records WHERE user_id = ? AND COALESCE(boss, source) = ?${dateFilter}
+ `).get(req.user.id, boss, ...dateParams);
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;
+ SELECT COUNT(*) as count FROM records WHERE user_id = ? AND COALESCE(boss, source) = ?${dateFilter}
+ `).get(req.user.id, boss, ...dateParams).count;
const records = db.prepare(`
- 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);
+ SELECT * FROM records WHERE user_id = ? AND COALESCE(boss, source) = ?${dateFilter} ORDER BY created_at DESC LIMIT ? OFFSET ?
+ `).all(req.user.id, boss, ...dateParams, Number(limit), offset);
res.json({
my_income: myIncomeSummary.total,
boss_total_expense: boss_total_expense,
+ take_count: expenseSummary.take_count,
+ dispatch_count: expenseSummary.dispatch_count,
records,
page: Number(page),
limit: Number(limit),
@@ -285,38 +296,48 @@ router.get('/boss-records', (req, res) => {
// 陪玩交易记录
router.get('/partner-records', (req, res) => {
const partner = req.query.partner;
- const { page = 1, limit = 20 } = req.query;
+ const { page = 1, limit = 20, year, month } = req.query;
if (!partner) return res.status(400).json({ error: '缺少陪玩参数' });
const offset = (Number(page) - 1) * Number(limit);
+ // 日期过滤条件
+ let dateFilter = '';
+ let dateParams = [];
+ if (year && month) {
+ const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
+ const endDate = `${year}-${String(month).padStart(2, '0')}-31`;
+ dateFilter = ` AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') <= ?`;
+ dateParams = [startDate, endDate];
+ }
+
const dispatchStats = db.prepare(`
SELECT
COALESCE(SUM(quantity), 0) as dispatch_count,
COALESCE(SUM(quantity * (COALESCE(unit_price, 0) - COALESCE(rebate, amount * 1.0 / CASE WHEN quantity > 0 THEN quantity ELSE 1 END))), 0) as spread_income,
COALESCE(SUM(amount), 0) as rebate_income
- FROM records WHERE user_id = ? AND category = '派单' AND partner = ?
- `).get(req.user.id, partner);
+ FROM records WHERE user_id = ? AND category = '派单' AND partner = ?${dateFilter}
+ `).get(req.user.id, partner, ...dateParams);
const otherIncome = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total
- FROM records WHERE user_id = ? AND type = 'income' AND category != '派单' AND category != '接单' AND source = ?
- `).get(req.user.id, partner);
+ FROM records WHERE user_id = ? AND type = 'income' AND category != '派单' AND category != '接单' AND source = ?${dateFilter}
+ `).get(req.user.id, partner, ...dateParams);
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;
+ )${dateFilter}
+ `).get(req.user.id, partner, partner, ...dateParams).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 LIMIT ? OFFSET ?
- `).all(req.user.id, partner, partner, Number(limit), offset);
+ )${dateFilter} ORDER BY created_at DESC LIMIT ? OFFSET ?
+ `).all(req.user.id, partner, partner, ...dateParams, Number(limit), offset);
res.json({
dispatch_count: dispatchStats.dispatch_count,