Files
Sdf1_login/today_bonds.php
T

212 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/core.php';
if (function_exists('opcache_invalidate')) { @opcache_invalidate(__FILE__); }
if (session_status() === PHP_SESSION_NONE) session_start();
// 今日零时时间戳(北京时间)
$tz = new DateTimeZone('Asia/Shanghai');
$now = new DateTime('now', $tz);
$todayStart = (clone $now)->setTime(0, 0, 0)->getTimestamp();
$todayEnd = $todayStart + 86400;
$todayLabel = $now->format('Y-m-d');
// JSON API 模式:?action=data
$isApi = (getParam('action') === 'data');
function getTodayBondData() {
global $todayStart, $todayEnd;
$spendData = [];
$incomeData = [];
try {
$db = getDB();
// ==========================================
// web_transactions(Web端交易)
// amount < 0 必定为消费(如 admin_deduct)
// amount > 0 则根据类型判断方向
// ==========================================
$spendWebTypes = ['shop_buy', 'shop_cart']; // 金额为正但属于消费的类型
// 消费:amount < 0 取绝对值
$stmt = $db->prepare("SELECT player_name, SUM(ABS(amount)) AS total_spend FROM web_transactions WHERE amount < 0 AND created_at >= :s1 AND created_at < :e1 GROUP BY player_name");
$stmt->bindValue(':s1', $todayStart, SQLITE3_INTEGER);
$stmt->bindValue(':e1', $todayEnd, SQLITE3_INTEGER);
$res = $stmt->execute();
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
$spendData[$row['player_name']] = ($spendData[$row['player_name']] ?? 0) + (int)$row['total_spend'];
}
// 消费:amount > 0 类型为消费类(shop_buy, shop_cart)
$stmt2 = $db->prepare("SELECT player_name, SUM(amount) AS total_spend FROM web_transactions WHERE amount > 0 AND type IN ('shop_buy','shop_cart') AND created_at >= :s2 AND created_at < :e2 GROUP BY player_name");
$stmt2->bindValue(':s2', $todayStart, SQLITE3_INTEGER);
$stmt2->bindValue(':e2', $todayEnd, SQLITE3_INTEGER);
$res2 = $stmt2->execute();
while ($row = $res2->fetchArray(SQLITE3_ASSOC)) {
$spendData[$row['player_name']] = ($spendData[$row['player_name']] ?? 0) + (int)$row['total_spend'];
}
// 收入:amount > 0 且非消费类
$stmt3 = $db->prepare("SELECT player_name, SUM(amount) AS total_income FROM web_transactions WHERE amount > 0 AND type NOT IN ('shop_buy','shop_cart') AND created_at >= :s3 AND created_at < :e3 GROUP BY player_name");
$stmt3->bindValue(':s3', $todayStart, SQLITE3_INTEGER);
$stmt3->bindValue(':e3', $todayEnd, SQLITE3_INTEGER);
$res3 = $stmt3->execute();
while ($row = $res3->fetchArray(SQLITE3_ASSOC)) {
$incomeData[$row['player_name']] = ($incomeData[$row['player_name']] ?? 0) + (int)$row['total_income'];
}
// ==========================================
// game_transactions(游戏内交易)
// 金额始终为正,用 balance_after-before 判断方向
// ==========================================
// 收入:余额增加
$stmt4 = $db->prepare("SELECT player_name, SUM(balance_after - balance_before) AS total_income FROM game_transactions WHERE balance_after > balance_before AND tx_time >= :s4 AND tx_time < :e4 GROUP BY player_name");
$stmt4->bindValue(':s4', $todayStart, SQLITE3_INTEGER);
$stmt4->bindValue(':e4', $todayEnd, SQLITE3_INTEGER);
$res4 = $stmt4->execute();
while ($row = $res4->fetchArray(SQLITE3_ASSOC)) {
$incomeData[$row['player_name']] = ($incomeData[$row['player_name']] ?? 0) + (int)$row['total_income'];
}
// 消费:余额减少
$stmt5 = $db->prepare("SELECT player_name, SUM(balance_before - balance_after) AS total_spend FROM game_transactions WHERE balance_after < balance_before AND tx_time >= :s5 AND tx_time < :e5 GROUP BY player_name");
$stmt5->bindValue(':s5', $todayStart, SQLITE3_INTEGER);
$stmt5->bindValue(':e5', $todayEnd, SQLITE3_INTEGER);
$res5 = $stmt5->execute();
while ($row = $res5->fetchArray(SQLITE3_ASSOC)) {
$spendData[$row['player_name']] = ($spendData[$row['player_name']] ?? 0) + (int)$row['total_spend'];
}
} catch (\Throwable $e) {
debugLog('[today_bonds] 查询异常: ' . $e->getMessage());
}
$allPlayers = array_unique(array_merge(array_keys($spendData), array_keys($incomeData)));
$rows = [];
$totalSpend = 0; $totalIncome = 0;
foreach ($allPlayers as $p) {
$spend = $spendData[$p] ?? 0;
$income = $incomeData[$p] ?? 0;
$net = $income - $spend;
$totalSpend += $spend; $totalIncome += $income;
$rows[] = ['name' => (string)$p, 'spend' => $spend, 'income' => $income, 'net' => $net];
}
usort($rows, fn($a,$b) => $b['net'] - $a['net']);
return [
'rows' => $rows,
'total_spend' => $totalSpend,
'total_income' => $totalIncome,
'net_total' => $totalIncome - $totalSpend,
'player_count' => count($rows),
'date_label' => $GLOBALS['todayLabel'],
];
}
if ($isApi) {
success(getTodayBondData());
exit;
}
$initialState = json_encode(getTodayBondData(), JSON_UNESCAPED_UNICODE);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
<title>SDF1 - 今日债券变动</title>
<style>
:root { --bg:#0d1117; --card:#161b22; --border:#30363d; --text:#e6edf3; --dim:#8b949e; --accent:#58a6ff; --green:#3fb950; --red:#f85149; --yellow:#d29922; --purple:#bc8cff; }
* { margin:0; padding:0; box-sizing:border-box; }
body { background:var(--bg); color:var(--text); font-family:'Segoe UI',system-ui,sans-serif; min-height:100vh; }
.header { background:linear-gradient(135deg,#1a1e2e,#0d1117); border-bottom:1px solid var(--border); padding:14px 24px; display:flex; justify-content:space-between; align-items:center; position:sticky; top:0; z-index:50; }
.header h1 { font-size:18px; color:var(--accent); }
.header .info { font-size:13px; color:var(--dim); }
.container { max-width:1100px; margin:20px auto; padding:0 16px; }
.stat-row { display:flex; gap:14px; margin-bottom:20px; flex-wrap:wrap; }
.stat-card { flex:1; min-width:150px; background:var(--card); border:1px solid var(--border); border-radius:12px; padding:18px; text-align:center; transition:border-color .2s; }
.stat-card:hover { border-color:var(--accent); }
.stat-card .val { font-size:28px; font-weight:700; }
.stat-card .val.green { color:var(--green); }
.stat-card .val.red { color:var(--red); }
.stat-card .val.blue { color:var(--accent); }
.stat-card .val.yellow { color:var(--yellow); }
.stat-card .lbl { font-size:12px; color:var(--dim); margin-top:4px; }
.panel { background:var(--card); border:1px solid var(--border); border-radius:12px; padding:18px; margin-bottom:16px; }
.panel h3 { font-size:15px; color:var(--accent); margin-bottom:12px; display:flex; align-items:center; gap:8px; }
.table-wrap { overflow-x:auto; }
table { width:100%; border-collapse:collapse; font-size:13px; }
th { text-align:left; padding:10px 12px; color:var(--dim); border-bottom:1px solid var(--border); font-weight:600; white-space:nowrap; }
td { padding:10px 12px; border-bottom:1px solid var(--border); }
tr:hover td { background:rgba(88,166,255,0.04); }
.num { font-variant-numeric:tabular-nums; text-align:right; }
.positive { color:var(--green); }
.negative { color:var(--red); }
.zero { color:var(--dim); }
.empty { text-align:center; color:var(--dim); padding:40px; }
.refresh-hint { text-align:center; font-size:12px; color:var(--dim); margin-top:12px; }
#loading { text-align:center; padding:40px; color:var(--dim); }
@media(max-width:640px) {
.stat-row { gap:8px; }
.stat-card { min-width:calc(50% - 4px); padding:12px; }
.stat-card .val { font-size:22px; }
th,td { padding:8px 6px; font-size:12px; }
}
</style>
</head>
<body>
<div class="header">
<h1>📊 今日债券变动</h1>
<div class="info"><span id="dateLabel"></span> · 每30秒刷新</div>
</div>
<div class="container" id="app">
<div id="loading">加载中...</div>
<div id="content" style="display:none">
<div class="stat-row">
<div class="stat-card"><div class="val red" id="statSpend">0</div><div class="lbl">总消费</div></div>
<div class="stat-card"><div class="val green" id="statIncome">0</div><div class="lbl">总收入</div></div>
<div class="stat-card"><div class="val blue" id="statNet">0</div><div class="lbl">净变动</div></div>
<div class="stat-card"><div class="val yellow" id="statPlayers">0</div><div class="lbl">活跃玩家</div></div>
</div>
<div class="panel">
<h3>👤 玩家明细</h3>
<div class="table-wrap">
<table><thead><tr>
<th>玩家</th><th class="num">消费 ↓</th><th class="num">收入 ↓</th><th class="num">净变动</th>
</tr></thead><tbody id="tbody"></tbody></table>
</div>
</div>
<div class="refresh-hint" id="refreshHint"></div>
</div>
</div>
<script>
const state = <?= $initialState ?>;
function fmt(n) { return n.toLocaleString('zh-CN'); }
function render(data) {
document.getElementById('loading').style.display = 'none';
document.getElementById('content').style.display = 'block';
document.getElementById('dateLabel').textContent = data.date_label;
document.getElementById('statSpend').textContent = fmt(data.total_spend);
document.getElementById('statIncome').textContent = fmt(data.total_income);
const netEl = document.getElementById('statNet');
netEl.textContent = (data.net_total >= 0 ? '+' : '') + fmt(data.net_total);
netEl.style.color = data.net_total > 0 ? 'var(--green)' : data.net_total < 0 ? 'var(--red)' : 'var(--dim)';
document.getElementById('statPlayers').textContent = data.player_count;
const tbody = document.getElementById('tbody');
if (data.rows.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="empty">今日暂无数据</td></tr>';
} else {
tbody.innerHTML = data.rows.map(r => {
const netCls = r.net > 0 ? 'positive' : r.net < 0 ? 'negative' : 'zero';
const netSign = r.net >= 0 ? '+' : '';
return `<tr><td>${esc(r.name)}</td><td class="num negative">${fmt(r.spend)}</td><td class="num positive">${fmt(r.income)}</td><td class="num ${netCls}">${netSign}${fmt(r.net)}</td></tr>`;
}).join('');
}
document.getElementById('refreshHint').textContent = '上次更新:' + new Date().toLocaleTimeString('zh-CN', {hour12:false});
}
function esc(s) { const d=document.createElement('div'); d.textContent=s||''; return d.innerHTML; }
render(state);
// 每30秒刷新
setInterval(() => {
fetch('today_bonds.php?action=data&_t=' + Date.now()).then(r=>r.json()).then(d => {
if (d.success) render(d);
}).catch(()=>{});
}, 30000);
</script>
</body>
</html>