Files
gamer/server/routes/stats.js
T

478 lines
21 KiB
JavaScript
Raw Normal View History

2026-03-12 11:23:10 +08:00
const express = require('express');
const db = require('../db');
const { getMonthRange } = require('../utils/helpers');
const router = express.Router();
// 格式化本地日期为 YYYY-MM-DD
function formatLocalDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
// 本月统计
router.get('/monthly', (req, res) => {
const { startDate, endDate } = getMonthRange(req.query.year, req.query.month);
const stats = db.prepare(`
SELECT
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_order_count,
COALESCE(SUM(CASE WHEN category = '接单' THEN amount ELSE 0 END), 0) as take_order_income,
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_order_count,
COALESCE(SUM(CASE WHEN category = '派单' THEN amount ELSE 0 END), 0) as dispatch_order_income,
COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_income,
COALESCE(SUM(CASE WHEN category = '红包收入' THEN 1 ELSE 0 END), 0) as redEnvelope_count,
COALESCE(SUM(CASE WHEN category = '奶茶' THEN amount ELSE 0 END), 0) as milkTea_income,
COALESCE(SUM(CASE WHEN category = '奶茶' THEN 1 ELSE 0 END), 0) as milkTea_count,
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_expense
FROM records
WHERE user_id = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
`).get(req.user.id, startDate, endDate);
res.json(stats);
});
// 月收入/支出记录列表
router.get('/monthly-records', (req, res) => {
const { type, page = 1, limit = 20 } = req.query;
2026-03-12 11:23:10 +08:00
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);
2026-03-12 11:23:10 +08:00
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;
2026-03-12 11:23:10 +08:00
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 LIMIT ? OFFSET ?
`).all(req.user.id, type, startDate, endDate, Number(limit), offset);
2026-03-12 11:23:10 +08:00
res.json({ total: summary.total, records, page: Number(page), limit: Number(limit), totalCount: total });
2026-03-12 11:23:10 +08:00
});
// 年度净收入按年统计
router.get('/yearly-net-income', (req, res) => {
// 获取所有年份数据
const yearlyStats = db.prepare(`
SELECT
strftime('%Y', created_at) as year,
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_expense
FROM records
WHERE user_id = ?
GROUP BY year ORDER BY year DESC
`).all(req.user.id);
// 获取所有年份的每月数据
const monthlyByYear = db.prepare(`
SELECT
strftime('%Y', created_at) as year,
strftime('%m', created_at) as month,
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_expense
FROM records
WHERE user_id = ?
GROUP BY year, month ORDER BY year DESC, month
`).all(req.user.id);
// 按年份分组每月数据
const monthlyData = {};
monthlyByYear.forEach(m => {
if (!monthlyData[m.year]) monthlyData[m.year] = [];
monthlyData[m.year].push({
month: m.month,
total_income: m.total_income,
total_expense: m.total_expense
});
});
const grandTotal = db.prepare(`
SELECT
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_expense
FROM records WHERE user_id = ?
`).get(req.user.id);
res.json({ yearlyStats, monthlyData, grandTotal });
});
// 陪玩派单榜预览
router.get('/partner-ranking/preview', (req, res) => {
const { startDate, endDate } = getMonthRange(req.query.year, req.query.month);
const rows = db.prepare(`
SELECT partner, COALESCE(SUM(quantity), 0) as dispatch_count, COALESCE(SUM(amount), 0) as dispatch_income
FROM records
WHERE user_id = ? AND category = '派单' AND partner IS NOT NULL AND partner != ''
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY partner ORDER BY dispatch_count DESC LIMIT 10
`).all(req.user.id, startDate, endDate);
res.json(rows);
});
// 陪玩派单榜详情
router.get('/partner-ranking', (req, res) => {
const { startDate, endDate } = req.query;
let start, end;
if (startDate && endDate) {
start = startDate;
end = endDate;
} else {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
start = `${year}-${month}-01`;
const endMonth = now.getMonth() + 2 > 12 ? 1 : now.getMonth() + 2;
const endYear = endMonth === 1 ? year + 1 : year;
end = `${endYear}-${String(endMonth).padStart(2, '0')}-01`;
}
const rows = db.prepare(`
SELECT partner, COALESCE(SUM(quantity), 0) as dispatch_count, COALESCE(SUM(quantity * COALESCE(unit_price, 0)), 0) as dispatch_income, COALESCE(SUM(amount), 0) as rebate_income
FROM records
WHERE user_id = ? AND category = '派单' AND partner IS NOT NULL AND partner != ''
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY partner ORDER BY dispatch_count DESC
`).all(req.user.id, start, end);
// 计算每个陪玩的陪玩总收入:(单价-返点)*单数 = 单价*单数 - 返点
rows.forEach(row => {
row.partner_income = (row.dispatch_income || 0) - (row.rebate_income || 0);
});
const summary = db.prepare(`
SELECT
COALESCE(SUM(quantity), 0) as total_dispatch_count,
COALESCE(SUM(quantity * COALESCE(unit_price, 0)), 0) as total_dispatch_income,
COALESCE(SUM(amount), 0) as total_rebate_income,
COALESCE(SUM(quantity * COALESCE(unit_price, 0)), 0) - COALESCE(SUM(amount), 0) as total_partner_income
FROM records
WHERE user_id = ? AND category = '派单' AND partner IS NOT NULL AND partner != ''
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
`).get(req.user.id, start, end);
// 获取奶茶收入
const milkTeaStats = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total_milkTea_income
FROM records
WHERE user_id = ? AND type = 'income' AND category = '奶茶'
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
`).get(req.user.id, start, end);
summary.total_milkTea_income = milkTeaStats.total_milkTea_income;
res.json({ rows, summary });
});
// 总接单数按老板排行
router.get('/take-ranking', (req, res) => {
const rows = db.prepare(`
SELECT boss as name, COALESCE(SUM(quantity), 0) as count, COALESCE(SUM(amount), 0) as income
FROM records WHERE user_id = ? AND category = '接单' AND boss IS NOT NULL AND boss != ''
GROUP BY boss ORDER BY count DESC
`).all(req.user.id);
// 获取接单总收入和红包收入
const stats = db.prepare(`
SELECT
COALESCE(SUM(quantity), 0) as total_take_count,
COALESCE(SUM(amount), 0) as total_take_income
FROM records WHERE user_id = ? AND category = '接单'
`).get(req.user.id);
const redEnvelopeStats = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total_redEnvelope_income
FROM records WHERE user_id = ? AND type = 'income' AND category = '红包收入'
`).get(req.user.id);
const result = {
rows: rows,
total_take_count: stats.total_take_count,
total_take_income: stats.total_take_income,
total_redEnvelope_income: redEnvelopeStats.total_redEnvelope_income
};
res.json(result);
});
// 总派单数按陪玩排行
router.get('/dispatch-ranking', (req, res) => {
const rows = db.prepare(`
SELECT
partner as name,
COALESCE(SUM(quantity), 0) as dispatch_count,
COALESCE(SUM(quantity * COALESCE(unit_price, 0)), 0) as dispatch_income,
COALESCE(SUM(amount), 0) as rebate_income
FROM records WHERE user_id = ? AND category = '派单' AND partner IS NOT NULL AND partner != ''
GROUP BY partner ORDER BY dispatch_count DESC
`).all(req.user.id);
// 计算每个陪玩的陪玩总收入:(单价-返点)*单数 = 单价*单数 - 返点
rows.forEach(row => {
row.partner_income = (row.dispatch_income || 0) - (row.rebate_income || 0);
});
// 获取派单总数和奶茶收入
const dispatchTotal = db.prepare(`
SELECT COALESCE(SUM(quantity), 0) as total_dispatch_count
FROM records WHERE user_id = ? AND category = '派单'
`).get(req.user.id);
const milkTeaStats = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total_milkTea_income
FROM records WHERE user_id = ? AND type = 'income' AND category = '奶茶'
`).get(req.user.id);
const result = {
rows: rows,
total_dispatch_count: dispatchTotal.total_dispatch_count,
total_milkTea_income: milkTeaStats.total_milkTea_income
};
res.json(result);
});
// 用户总统计
router.get('/total', (req, res) => {
const stats = db.prepare(`
SELECT
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_count,
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_count,
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_expense
FROM records WHERE user_id = ?
`).get(req.user.id);
stats.net_income = stats.total_income - stats.total_expense;
res.json(stats);
});
// 老板交易记录
router.get('/boss-records', (req, res) => {
const boss = req.query.boss;
const { page = 1, limit = 20, year, month } = req.query;
2026-03-12 11:23:10 +08:00
if (!boss) return res.status(400).json({ error: '缺少老板参数' });
const offset = (Number(page) - 1) * Number(limit);
2026-03-12 11:23:10 +08:00
// 日期过滤条件
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];
}
2026-03-12 11:23:10 +08:00
const myIncomeSummary = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total
FROM records WHERE user_id = ? AND COALESCE(boss, source) = ? AND type = 'income'${dateFilter}
`).get(req.user.id, boss, ...dateParams);
2026-03-12 11:23:10 +08:00
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,
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);
2026-03-12 11:23:10 +08:00
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) = ?${dateFilter}
`).get(req.user.id, boss, ...dateParams).count;
2026-03-12 11:23:10 +08:00
const records = db.prepare(`
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);
2026-03-12 11:23:10 +08:00
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),
totalCount: total
2026-03-12 11:23:10 +08:00
});
});
// 陪玩交易记录
router.get('/partner-records', (req, res) => {
const partner = req.query.partner;
const { page = 1, limit = 20, year, month } = req.query;
2026-03-12 11:23:10 +08:00
if (!partner) return res.status(400).json({ error: '缺少陪玩参数' });
const offset = (Number(page) - 1) * Number(limit);
2026-03-12 11:23:10 +08:00
// 日期过滤条件
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];
}
2026-03-12 11:23:10 +08:00
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 = ?${dateFilter}
`).get(req.user.id, partner, ...dateParams);
2026-03-12 11:23:10 +08:00
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 = ?${dateFilter}
`).get(req.user.id, partner, ...dateParams);
2026-03-12 11:23:10 +08:00
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 = ?)
)${dateFilter}
`).get(req.user.id, partner, partner, ...dateParams).count;
2026-03-12 11:23:10 +08:00
const records = db.prepare(`
SELECT * FROM records
WHERE user_id = ? AND (
(category = '派单' AND partner = ?) OR
(type = 'income' AND category != '派单' AND category != '接单' AND source = ?)
)${dateFilter} ORDER BY created_at DESC LIMIT ? OFFSET ?
`).all(req.user.id, partner, partner, ...dateParams, Number(limit), offset);
2026-03-12 11:23:10 +08:00
res.json({
dispatch_count: dispatchStats.dispatch_count,
spread_income: dispatchStats.spread_income,
rebate_income: dispatchStats.rebate_income,
other_income: otherIncome.total,
records,
page: Number(page),
limit: Number(limit),
totalCount: total
2026-03-12 11:23:10 +08:00
});
});
// 最近7天每日收入
router.get('/daily-income', (req, res) => {
const today = new Date();
const days = [];
for (let i = 6; i >= 0; i--) {
const d = new Date(today);
d.setDate(d.getDate() - i);
days.push(formatLocalDate(d));
}
const startDate = days[0];
const endDay = new Date(today);
endDay.setDate(endDay.getDate() + 1);
const endDate = formatLocalDate(endDay);
const rows = db.prepare(`
SELECT DATE(created_at, 'localtime') as date, COALESCE(SUM(amount), 0) as income
FROM records WHERE user_id = ? AND type = 'income' AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY DATE(created_at, 'localtime')
`).all(req.user.id, startDate, endDate);
const map = {};
rows.forEach(r => { map[r.date] = r.income; });
res.json(days.map(d => ({ date: d, income: map[d] || 0 })));
});
// 某天收入记录详情
router.get('/daily-detail', (req, res) => {
const { date, page = 1, limit = 20 } = req.query;
2026-03-12 11:23:10 +08:00
if (!date) return res.status(400).json({ error: '缺少日期参数' });
const offset = (Number(page) - 1) * Number(limit);
2026-03-12 11:23:10 +08:00
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
const endDate = formatLocalDate(nextDay);
const summary = db.prepare(`
SELECT COALESCE(SUM(amount), 0) as total_income
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;
2026-03-12 11:23:10 +08:00
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 LIMIT ? OFFSET ?
`).all(req.user.id, date, endDate, Number(limit), offset);
2026-03-12 11:23:10 +08:00
res.json({ total_income: summary.total_income, records, page: Number(page), limit: Number(limit), totalCount: total });
2026-03-12 11:23:10 +08:00
});
// 老板收入排行 - 首页预览
router.get('/boss-ranking/preview', (req, res) => {
const { startDate, endDate } = getMonthRange(req.query.year, req.query.month);
const rows = db.prepare(`
SELECT COALESCE(boss, source) as boss, SUM(amount) as total_income,
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_count,
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_count,
COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_income
FROM records
WHERE user_id = ? AND type = 'income' AND (boss IS NOT NULL AND boss != '' OR source IS NOT NULL AND source != '')
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY COALESCE(boss, source) ORDER BY total_income DESC LIMIT 10
`).all(req.user.id, startDate, endDate);
res.json(rows);
});
// 老板收入排行 - 详情页(支持筛选)
router.get('/boss-ranking', (req, res) => {
const { startDate, endDate, sortBy = 'total_income' } = req.query;
let start, end;
if (startDate && endDate) {
start = startDate;
end = endDate;
} else {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
start = `${year}-${month}-01`;
const endMonth = now.getMonth() + 2 > 12 ? 1 : now.getMonth() + 2;
const endYear = endMonth === 1 ? year + 1 : year;
end = `${endYear}-${String(endMonth).padStart(2, '0')}-01`;
}
const allowedSorts = ['total_income', 'take_count', 'dispatch_count'];
const orderCol = allowedSorts.includes(sortBy) ? sortBy : 'total_income';
if (sortBy === 'dispatch_count') {
const rows = db.prepare(`
SELECT COALESCE(boss, source) as boss,
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_count,
COALESCE(SUM(CASE WHEN category = '派单' THEN amount ELSE 0 END), 0) as dispatch_income,
SUM(amount) as total_income,
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_count,
COALESCE(SUM(CASE WHEN category = '接单' THEN amount ELSE 0 END), 0) as take_income,
COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_income
FROM records
WHERE user_id = ? AND type = 'income' AND (boss IS NOT NULL AND boss != '' OR source IS NOT NULL AND source != '')
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY COALESCE(boss, source) ORDER BY dispatch_count DESC
`).all(req.user.id, start, end);
return res.json(rows.filter(r => r.dispatch_count > 0));
}
const rows = db.prepare(`
SELECT COALESCE(boss, source) as boss, SUM(amount) as total_income,
COALESCE(SUM(CASE WHEN category = '接单' THEN quantity ELSE 0 END), 0) as take_count,
COALESCE(SUM(CASE WHEN category = '接单' THEN amount ELSE 0 END), 0) as take_income,
COALESCE(SUM(CASE WHEN category = '派单' THEN quantity ELSE 0 END), 0) as dispatch_count,
COALESCE(SUM(CASE WHEN category = '派单' THEN amount ELSE 0 END), 0) as dispatch_income,
COALESCE(SUM(CASE WHEN category = '红包收入' THEN amount ELSE 0 END), 0) as redEnvelope_income
FROM records
WHERE user_id = ? AND type = 'income' AND (boss IS NOT NULL AND boss != '' OR source IS NOT NULL AND source != '')
AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?
GROUP BY COALESCE(boss, source) ORDER BY ${orderCol} DESC
`).all(req.user.id, start, end);
if (sortBy === 'take_count') {
return res.json(rows.filter(r => r.take_count > 0));
}
res.json(rows);
});
module.exports = router;