Initial commit: gamer project
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
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();
|
||||
console.log('take-ranking data:', data);
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user