Files
gamer/public/js/app.js
T

309 lines
11 KiB
JavaScript
Raw Normal View History

2026-03-12 11:23:10 +08:00
const API = '';
let token = localStorage.getItem('token');
let username = localStorage.getItem('username');
let currentType = 'income';
let currentCategory = '接单';
let currentYear = new Date().getFullYear();
let currentMonth = new Date().getMonth() + 1;
let currentNav = 'stats';
function headers() {
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
}
function formatLocalTime(isoStr) {
if (!isoStr) return '';
const d = new Date(isoStr);
const h = String(d.getHours()).padStart(2, '0');
const m = String(d.getMinutes()).padStart(2, '0');
return h + ':' + m;
}
function formatLocalDate(input) {
if (!input) return '未知日期';
const d = input instanceof Date ? input : new Date(input);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + day;
}
async function loadTotalStats() {
try {
const res = await fetch(API + '/api/stats/total', { headers: headers() });
if (!res.ok) return;
const s = await res.json();
document.getElementById('totalTakeCount').textContent = s.take_count;
document.getElementById('totalDispatchCount').textContent = s.dispatch_count;
document.getElementById('totalNetIncome').textContent = '' + s.net_income.toFixed(2);
} catch (e) { console.error(e); }
}
function renderHorizontalChart(containerId, rows, clickHandler, fromPage) {
const container = document.getElementById(containerId);
if (rows.length === 0) {
container.innerHTML = '<div class="ranking-empty">暂无数据</div>';
return;
}
const max = Math.max(...rows.map(r => r.count), 1);
container.innerHTML = rows.map((r, i) => {
const pct = Math.max((r.count / max) * 100, 2);
const onclick = clickHandler ? ` onclick="${clickHandler}('${r.name.replace(/'/g, "\\'")}', '${fromPage}')"` : '';
return `<div class="hbar-row"${onclick}>
<div class="hbar-index ${i < 3 ? 'top' : ''}">${i + 1}</div>
<div class="hbar-label" title="${r.name}">${r.name}</div>
<div class="hbar-track"><div class="hbar-fill" style="width:${pct}%"></div></div>
<div class="hbar-value">${r.count}单</div>
</div>`;
}).join('');
}
async function openTakeRanking() {
showPage('takeRankingPage');
try {
const res = await fetch(API + '/api/stats/take-ranking', { headers: headers() });
if (!res.ok) return;
const data = await res.json();
const rows = data.rows || [];
// 显示统计卡片
const summaryContainer = document.getElementById('takeRankingSummary');
summaryContainer.innerHTML = `
<div class="item">
<div class="label">接单总数</div>
<div class="val">${data.total_take_count || 0}</div>
</div>
<div class="item">
<div class="label">接单总收入</div>
<div class="val green">${(data.total_take_income || 0).toFixed(2)}</div>
</div>
<div class="item">
<div class="label">红包收入</div>
<div class="val purple">${(data.total_redEnvelope_income || 0).toFixed(2)}</div>
</div>
`;
renderHorizontalChart('takeRankingChart', rows, 'openBossDetail', 'takeRankingPage');
} catch (e) { console.error(e); }
}
async function openDispatchRanking() {
showPage('dispatchRankingPage');
try {
const res = await fetch(API + '/api/stats/dispatch-ranking', { headers: headers() });
if (!res.ok) return;
const data = await res.json();
const rows = data.rows || [];
// 计算总计
const totalDispatch = data.total_dispatch_count !== undefined ? data.total_dispatch_count : (rows.reduce((sum, r) => sum + (r.dispatch_count || 0), 0));
const totalPartnerIncome = rows.reduce((sum, r) => sum + (r.partner_income || 0), 0);
const totalRebateIncome = rows.reduce((sum, r) => sum + (r.rebate_income || 0), 0);
const totalMilkTeaIncome = data.total_milkTea_income || 0;
// 显示4个卡片
const summaryContainer = document.getElementById('dispatchRankingSummary');
summaryContainer.innerHTML = `
<div class="item">
<div class="label">派单总数</div>
<div class="val">${totalDispatch}</div>
</div>
<div class="item">
<div class="label">陪玩总收入</div>
<div class="val green">${totalPartnerIncome.toFixed(2)}</div>
</div>
<div class="item">
<div class="label">返点总收入</div>
<div class="val orange">${totalRebateIncome.toFixed(2)}</div>
</div>
<div class="item">
<div class="label">奶茶收入</div>
<div class="val purple">${totalMilkTeaIncome.toFixed(2)}</div>
</div>
`;
// 显示排行列表
const max = Math.max(...rows.map(r => r.dispatch_count || 0), 1);
const container = document.getElementById('dispatchRankingChart');
container.innerHTML = rows.map((r, i) => {
const pct = Math.max(((r.dispatch_count || 0) / max) * 100, 2);
return `<div class="hbar-row" onclick="openPartnerDetail('${r.name.replace(/'/g, "\\'")}', 'dispatchRankingPage')">
<div class="hbar-index ${i < 3 ? 'top' : ''}">${i + 1}</div>
<div class="hbar-label" title="${r.name}">${r.name}</div>
<div class="hbar-track"><div class="hbar-fill" style="width:${pct}%"></div></div>
<div class="hbar-value">${r.dispatch_count}单</div>
</div>`;
}).join('');
} catch (e) { console.error(e); }
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2000);
}
function showPage(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
window.scrollTo(0, 0);
if (pageId === 'homePage') {
updateMonthDisplay();
loadStats();
document.getElementById('profileName').textContent = username;
document.getElementById('avatarText').textContent = username.charAt(0).toUpperCase();
switchNav(currentNav);
}
if (pageId === 'recordPage') {
currentType = 'income';
currentCategory = '接单';
document.querySelectorAll('.type-btn').forEach((b, i) => b.classList.toggle('active', i === 0));
document.getElementById('incomeCats').style.display = 'grid';
document.getElementById('expenseCats').style.display = 'none';
selectCategory(document.querySelector('#incomeCats .category-item'), '接单');
}
if (pageId === 'rankingPage') {
const startDate = `${currentYear}-${String(currentMonth).padStart(2, '0')}-01`;
const now = new Date();
const endDay = (currentYear === now.getFullYear() && currentMonth === now.getMonth() + 1) ? formatLocalDate(now) : `${currentYear}-${String(currentMonth).padStart(2, '0')}-28`;
document.getElementById('rankStartDate').value = startDate;
document.getElementById('rankEndDate').value = endDay;
currentSort = 'total_income';
document.querySelectorAll('.sort-btn').forEach((b, i) => b.classList.toggle('active', i === 0));
loadRankingDetail();
}
if (pageId === 'orderListPage') {
document.getElementById('orderListTitle').textContent = `${currentYear}${currentMonth}月接单列表`;
orderPage = 1; orderHasMore = true;
document.getElementById('orderList').innerHTML = '';
loadOrders();
}
if (pageId === 'otherIncomeListPage') {
document.getElementById('otherIncomeListTitle').textContent = `${currentYear}${currentMonth}月红包及其他收入`;
otherIncomePage = 1; otherIncomeHasMore = true;
document.getElementById('otherIncomeList').innerHTML = '';
loadOtherIncome();
}
if (pageId === 'dispatchListPage') {
document.getElementById('dispatchListTitle').textContent = `${currentYear}${currentMonth}月派单列表`;
dispatchPage = 1; dispatchHasMore = true;
document.getElementById('dispatchList').innerHTML = '';
loadDispatches();
}
if (pageId === 'redEnvelopeListPage') {
redEnvelopePage = 1; redEnvelopeHasMore = true;
document.getElementById('redEnvelopeList').innerHTML = '';
loadRedEnvelope();
}
if (pageId === 'milkTeaListPage') {
milkTeaPage = 1; milkTeaHasMore = true;
document.getElementById('milkTeaList').innerHTML = '';
loadMilkTea();
}
}
function switchNav(nav) {
currentNav = nav;
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.getElementById('navStats').classList.toggle('active', nav === 'stats');
document.getElementById('navMine').classList.toggle('active', nav === 'mine');
if (nav === 'stats') {
document.getElementById('statsPanel').classList.add('active');
} else {
document.getElementById('minePanel').classList.add('active');
loadTotalStats();
}
}
function updateMonthDisplay() {
document.getElementById('monthDisplay').textContent = `${currentYear}${currentMonth}月`;
}
// 头像连续点击5次显示隐藏按钮
let avatarTapCount = 0;
let avatarTapTimer = null;
function onAvatarTap() {
avatarTapCount++;
clearTimeout(avatarTapTimer);
avatarTapTimer = setTimeout(() => { avatarTapCount = 0; }, 1500);
if (avatarTapCount >= 5) {
avatarTapCount = 0;
const el = document.getElementById('secretBtns');
const visible = el.style.display !== 'none';
el.style.display = visible ? 'none' : 'block';
showToast(visible ? '已隐藏' : '已解锁');
}
}
// 下拉刷新
(function() {
let startY = 0, pulling = false, triggered = false;
const threshold = 60;
function getPanel() { return document.getElementById('statsPanel'); }
function getIndicator() { return document.getElementById('pullIndicator'); }
document.addEventListener('touchstart', function(e) {
const panel = getPanel();
if (!panel || !panel.classList.contains('active')) return;
if (currentNav !== 'stats') return;
if (window.scrollY > 0) return;
startY = e.touches[0].clientY;
pulling = true;
triggered = false;
}, { passive: true });
document.addEventListener('touchmove', function(e) {
if (!pulling) return;
const panel = getPanel();
if (!panel || !panel.classList.contains('active')) { pulling = false; return; }
if (window.scrollY > 0) { pulling = false; return; }
const dy = e.touches[0].clientY - startY;
if (dy < 0) return;
const indicator = getIndicator();
const arrow = document.getElementById('pullArrow');
const text = document.getElementById('pullText');
if (dy > 10) {
indicator.classList.add('visible');
if (dy >= threshold) {
arrow.classList.add('flipped');
text.textContent = '松手刷新';
triggered = true;
} else {
arrow.classList.remove('flipped');
text.textContent = '下拉刷新';
triggered = false;
}
}
}, { passive: true });
document.addEventListener('touchend', function() {
if (!pulling) return;
pulling = false;
const indicator = getIndicator();
const arrow = document.getElementById('pullArrow');
const text = document.getElementById('pullText');
if (triggered) {
text.textContent = '刷新中...';
arrow.classList.remove('flipped');
indicator.classList.add('refreshing');
loadStats().then(() => {
setTimeout(() => {
indicator.classList.remove('visible', 'refreshing');
showToast('已刷新');
}, 500);
});
} else {
indicator.classList.remove('visible');
}
}, { passive: true });
})();
document.addEventListener('DOMContentLoaded', function() {
if (token && username) {
showPage('homePage');
}
renderForm();
});