第十六批缺陷修复:封禁公示改查java侧表+卡片入内容区自适应高度+后台AGPL页脚
This commit is contained in:
@@ -17,6 +17,18 @@ secGatePage();
|
||||
header('Referrer-Policy: no-referrer');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
// ★ AGPL-3.0 合规:源码地址(使用者可在 开源地址.txt 中改成自己的仓库地址),与 player.php 同款
|
||||
$OSS_REPO_DEFAULT = 'https://git.ypshidifu.cn/youpaishidifu/Sdf1_login/src/branch/php/';
|
||||
$OSS_REPO = $OSS_REPO_DEFAULT;
|
||||
$ossTxt = __DIR__ . '/开源地址.txt';
|
||||
if (is_file($ossTxt)) {
|
||||
foreach (file($ossTxt, FILE_IGNORE_NEW_LINES) as $ossLine) {
|
||||
$ossLine = trim($ossLine);
|
||||
if ($ossLine === '' || $ossLine[0] === '#') continue;
|
||||
if (preg_match('#^https?://#i', $ossLine)) { $OSS_REPO = $ossLine; break; }
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
@@ -33,12 +45,12 @@ header('Pragma: no-cache');
|
||||
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; }
|
||||
.header h1 { font-size:18px; color:var(--accent); }
|
||||
.main { display:flex; min-height:calc(100vh - 52px); }
|
||||
.sidebar { width:200px; background:var(--card); border-right:1px solid var(--border); padding:12px 0; flex-shrink:0; overflow-y:auto; max-height:calc(100vh - 52px); }
|
||||
.main { display:flex; min-height:calc(100vh - 52px - 44px); }
|
||||
.sidebar { width:200px; background:var(--card); border-right:1px solid var(--border); padding:12px 0; flex-shrink:0; overflow-y:auto; max-height:calc(100vh - 52px - 44px); }
|
||||
.si { padding:10px 20px; cursor:pointer; color:var(--dim); transition:all 0.2s; font-size:13px; display:flex; align-items:center; gap:8px; }
|
||||
.si:hover { background:rgba(88,166,255,0.1); color:var(--text); }
|
||||
.si.active { color:var(--accent); border-right:3px solid var(--accent); background:rgba(88,166,255,0.05); }
|
||||
.content { flex:1; padding:20px; overflow-y:auto; max-height:calc(100vh - 52px); }
|
||||
.content { flex:1; padding:20px; overflow-y:auto; max-height:calc(100vh - 52px - 44px); }
|
||||
.card { background:var(--card); border:1px solid var(--border); border-radius:8px; padding:16px; margin-bottom:12px; }
|
||||
.card h2 { font-size:15px; color:var(--accent); margin-bottom:10px; }
|
||||
.stats { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:10px; margin-bottom:16px; }
|
||||
@@ -4718,5 +4730,10 @@ go = function(p) {
|
||||
};
|
||||
</script>
|
||||
<script src="cashier.js?<?php echo filemtime(__FILE__); ?>"></script>
|
||||
<!-- ★ AGPL-3.0 合规页脚:源码地址取自 开源地址.txt(可由使用者替换为自己的仓库) -->
|
||||
<footer style="min-height:44px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:6px;padding:8px 16px;font-size:12px;color:var(--dim);border-top:1px solid var(--border);background:var(--bg);text-align:center">
|
||||
本后台基于 <a href="LICENSE" target="_blank" style="color:var(--accent);text-decoration:none">AGPL-3.0</a> 开源协议发布
|
||||
· 源代码地址:<a href="<?php echo htmlspecialchars($OSS_REPO, ENT_QUOTES, 'UTF-8'); ?>" target="_blank" style="color:var(--accent);text-decoration:none"><?php echo htmlspecialchars($OSS_REPO, ENT_QUOTES, 'UTF-8'); ?></a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+26
-21
@@ -5408,32 +5408,37 @@ function getBanBoard() {
|
||||
try { $db = getDB(); } catch (Throwable $e) { $db = null; }
|
||||
if (!$db) { success(['total' => 0, 'bans' => []], 'db unavailable'); return; }
|
||||
|
||||
// 表不存在 → 空结果
|
||||
$chk = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='web_player_bans'");
|
||||
if (!$chk || !$chk->fetchArray()) { success(['total' => 0, 'bans' => []], 'empty'); return; }
|
||||
|
||||
// ★ 第十六批缺陷修复:禁止用 sqlite_master 探测表是否存在 ——
|
||||
// 那条 SQL 抽不出 web_player_bans(只抽得到 sqlite_master),SdfDbRouter::side()
|
||||
// 会把语句判成 web 侧;而 web_player_bans 在 dbJavaTables() 清单里,实际建在 java 侧,
|
||||
// 于是探测恒为空 → 公示永远返回"暂无"(后台却挂着几十条封禁)。
|
||||
// 改为直接查目标表:SQL 自带表名 → 路由到正确一侧;表缺失由 try/catch 兜底返回空。
|
||||
// 仅公示名字封禁(ban_type='name'),IP 封禁不公开(IP 属敏感信息)
|
||||
$now = time();
|
||||
$res = $db->query("SELECT target, ban_type, reason, expire_time, synced_at
|
||||
FROM web_player_bans
|
||||
WHERE ban_type = 'name'
|
||||
ORDER BY synced_at DESC
|
||||
LIMIT " . $limit);
|
||||
$bans = [];
|
||||
if ($res) {
|
||||
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||
$expire = (int) ($row['expire_time'] ?? 0);
|
||||
// 跳过已过期的临时封禁(expire_time>0 且已过期)
|
||||
if ($expire > 0 && $expire <= $now) continue;
|
||||
$bans[] = [
|
||||
'target' => (string) ($row['target'] ?? ''),
|
||||
'reason' => (string) ($row['reason'] ?? ''),
|
||||
'expire_time'=> $expire, // 0 = 永久
|
||||
'synced_at' => (int) ($row['synced_at'] ?? 0),
|
||||
];
|
||||
try {
|
||||
$res = $db->query("SELECT target, ban_type, reason, expire_time, synced_at
|
||||
FROM web_player_bans
|
||||
WHERE ban_type = 'name'
|
||||
ORDER BY synced_at DESC
|
||||
LIMIT " . $limit);
|
||||
if ($res) {
|
||||
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||
$expire = (int) ($row['expire_time'] ?? 0);
|
||||
// 跳过已过期的临时封禁(expire_time>0 且已过期)
|
||||
if ($expire > 0 && $expire <= $now) continue;
|
||||
$bans[] = [
|
||||
'target' => (string) ($row['target'] ?? ''),
|
||||
'reason' => (string) ($row['reason'] ?? ''),
|
||||
'expire_time'=> $expire, // 0 = 永久
|
||||
'synced_at' => (int) ($row['synced_at'] ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$bans = []; // 表不存在 / 瞬时错误 → 公示为空,不影响接口本身
|
||||
}
|
||||
success(['total' => count($bans), 'bans' => $bans], 'ok');
|
||||
success(['total' => count($bans), 'bans' => $bans], count($bans) ? 'ok' : 'empty');
|
||||
}
|
||||
|
||||
// ===== Java端推送封禁名单到PHP =====
|
||||
|
||||
+69
-29
@@ -274,9 +274,11 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
|
||||
/* ===== 第十六批:封禁常态化公示卡片(从下往上滚动替换) ===== */
|
||||
.ban-roll-wrap {
|
||||
position: relative; height: 320px; overflow: hidden;
|
||||
/* 第十六批缺陷修复:高度由 JS 按实际内容撑开(空态不再占 320px 空白) */
|
||||
position: relative; height: 42px; overflow: hidden;
|
||||
border-radius: 8px; margin-top: 4px;
|
||||
}
|
||||
.ban-roll-page { width: 100%; }
|
||||
.ban-roll-inner {
|
||||
position: absolute; left: 0; right: 0; top: 0;
|
||||
transition: transform 0.7s cubic-bezier(.22,.61,.36,1);
|
||||
@@ -1148,7 +1150,16 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
document.querySelector('.sidebar').style.display = 'block';
|
||||
const c = document.getElementById('content');
|
||||
if (!c) return;
|
||||
if (page === 'dashboard') renderDashboard(c);
|
||||
if (page === 'dashboard') {
|
||||
// renderDashboard 是 async 且会整体重写 content.innerHTML,
|
||||
// 必须等它渲染完再把封禁公示卡插到内容区顶部
|
||||
const rp = renderDashboard(c);
|
||||
if (rp && typeof rp.then === 'function') {
|
||||
rp.then(() => { if (typeof banCardSync === 'function') banCardSync(); }).catch(() => {});
|
||||
} else if (typeof banCardSync === 'function') {
|
||||
banCardSync();
|
||||
}
|
||||
}
|
||||
else if (page === 'shop') renderShop(c);
|
||||
else if (page === 'lands') renderLands(c);
|
||||
else if (page === 'cdk') renderCDK(c);
|
||||
@@ -4503,22 +4514,39 @@ function escHtml(s) { return (s||'').replace(/&/g,'&').replace(/</g,'<').
|
||||
let banBoardRows = []; // 当前生效封禁(从近到远)
|
||||
let banBoardShown = false; // 是否已渲染过(控制动画方向)
|
||||
let banBoardTimer = null;
|
||||
let banBoardLoaded = false; // 是否成功拉到过一次公示数据(未拉到时显示"同步中"而非"暂无")
|
||||
|
||||
// ★ 第十六批缺陷修复:卡片改插进 #content 内部(个人中心内容区第一张卡),
|
||||
// 不再 insertBefore(.main) 横跨整页宽度把侧边栏和正文一起顶下去;
|
||||
// 只在 dashboard 显示——其它页面 content.innerHTML 被整体重写,卡片自然不残留。
|
||||
function ensureBanCard() {
|
||||
if (!document.getElementById(BAN_CARD_ID)) {
|
||||
const card = document.createElement('div');
|
||||
if (currentPage !== 'dashboard') return null;
|
||||
const box = document.getElementById('content');
|
||||
if (!box) return null;
|
||||
let card = document.getElementById(BAN_CARD_ID);
|
||||
if (!card || card.parentNode !== box) {
|
||||
if (card && card.parentNode) card.parentNode.removeChild(card);
|
||||
card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.id = BAN_CARD_ID;
|
||||
card.style.borderColor = 'var(--red)';
|
||||
card.innerHTML = '<h2 style="color:var(--red)">⚠ 封禁常态化公示</h2>' +
|
||||
'<p style="color:var(--dim);font-size:12px;margin-bottom:8px">违规玩家将被公示,杀鸡儆猴,请各位引以为戒</p>' +
|
||||
'<div class="ban-roll-wrap"><div class="ban-roll-inner" id="' + BAN_SLOT_ID + '"></div></div>';
|
||||
const main = document.querySelector('.main');
|
||||
if (main && main.parentNode) main.parentNode.insertBefore(card, main);
|
||||
box.insertBefore(card, box.firstChild);
|
||||
}
|
||||
return document.getElementById(BAN_SLOT_ID);
|
||||
}
|
||||
|
||||
// 页面切换 / 内容被重写后,立刻补位并渲染已有数据(不用干等下一个 15 秒窗口)
|
||||
function banCardSync() {
|
||||
const slot = ensureBanCard();
|
||||
if (!slot) return;
|
||||
if (!slot.children.length) {
|
||||
banRollUpdate(slot, banPageHtml(banBoardRows.slice(0, BAN_PAGE_SIZE)));
|
||||
}
|
||||
}
|
||||
|
||||
function banItemHtml(b) {
|
||||
const name = escHtml(b.target || '');
|
||||
const reason = escHtml(b.reason || '违反服务器规则');
|
||||
@@ -4534,47 +4562,59 @@ function escHtml(s) { return (s||'').replace(/&/g,'&').replace(/</g,'<').
|
||||
}
|
||||
|
||||
function banPageHtml(rows) {
|
||||
if (!banBoardLoaded) return '<div class="ban-roll-item"><span class="breason">⏳ 正在同步公示数据...</span></div>';
|
||||
if (!rows.length) return '<div class="ban-roll-item"><span class="breason" style="color:var(--green)">✓ 当前暂无被封禁玩家</span></div>';
|
||||
return rows.map(banItemHtml).join('');
|
||||
}
|
||||
|
||||
// 从下往上滚动替换信息:先把新页放到下方(translateY(100%)),再滚到 0 位
|
||||
// 从下往上滚动替换:旧页滚出上方的同时新页从下方滚入,容器高度同步过渡
|
||||
// ★ 缺陷修复:按"页"操作而非整块 innerHTML 覆写,高度按内容实测,空态只有一行高
|
||||
function banRollUpdate(slot, html) {
|
||||
const inner = slot;
|
||||
if (!banBoardShown) {
|
||||
// 首次:直接显示
|
||||
inner.innerHTML = html;
|
||||
inner.style.transition = 'none';
|
||||
inner.style.transform = 'translateY(0)';
|
||||
const wrap = slot.parentNode;
|
||||
const page = document.createElement('div');
|
||||
page.className = 'ban-roll-page';
|
||||
page.innerHTML = html;
|
||||
const direct = () => {
|
||||
while (slot.firstChild) slot.removeChild(slot.firstChild);
|
||||
slot.appendChild(page);
|
||||
slot.style.transition = 'none';
|
||||
slot.style.transform = 'translateY(0)';
|
||||
if (wrap) { wrap.style.transition = 'none'; wrap.style.height = page.offsetHeight + 'px'; }
|
||||
banBoardShown = true;
|
||||
return;
|
||||
slot._banBusy = false;
|
||||
};
|
||||
if (!banBoardShown || !slot.children.length || slot._banBusy) { direct(); return; }
|
||||
const oldH = slot.offsetHeight; // 旧页高度
|
||||
slot.appendChild(page);
|
||||
const newH = page.offsetHeight; // 新页高度
|
||||
slot._banBusy = true;
|
||||
void slot.offsetHeight; // 强制回流,让过渡从旧状态起步
|
||||
slot.style.transition = 'transform 0.7s cubic-bezier(.22,.61,.36,1)';
|
||||
slot.style.transform = 'translateY(-' + oldH + 'px)';
|
||||
if (wrap) {
|
||||
wrap.style.transition = 'height 0.7s cubic-bezier(.22,.61,.36,1)';
|
||||
wrap.style.height = newH + 'px';
|
||||
}
|
||||
// 旧页先滚出上方(-100%),同时把新页放在下方再滚入
|
||||
const oldH = inner.offsetHeight;
|
||||
inner.style.transition = 'transform 0.7s cubic-bezier(.22,.61,.36,1)';
|
||||
inner.style.transform = 'translateY(-' + oldH + 'px)';
|
||||
// 滚出结束后换成新页并从下方滚入
|
||||
setTimeout(() => {
|
||||
inner.style.transition = 'none';
|
||||
inner.style.transform = 'translateY(100%)';
|
||||
inner.innerHTML = html;
|
||||
// 强制 reflow 后再滚回 0(新页从下往上出现)
|
||||
void inner.offsetHeight;
|
||||
inner.style.transition = 'transform 0.7s cubic-bezier(.22,.61,.36,1)';
|
||||
inner.style.transform = 'translateY(0)';
|
||||
}, 720);
|
||||
slot.style.transition = 'none';
|
||||
slot.style.transform = 'translateY(0)';
|
||||
while (slot.firstChild && slot.firstChild !== page) slot.removeChild(slot.firstChild);
|
||||
if (wrap) { wrap.style.transition = 'none'; wrap.style.height = page.offsetHeight + 'px'; }
|
||||
slot._banBusy = false;
|
||||
}, 760);
|
||||
}
|
||||
|
||||
async function banBoardLoadAndRoll() {
|
||||
const slot = ensureBanCard();
|
||||
if (!slot) return;
|
||||
try {
|
||||
const res = await fetch(API + 'sync.php?action=get_ban_board&limit=' + BAN_PAGE_SIZE, {cache: 'no-store'});
|
||||
const j = await res.json();
|
||||
if (j && j.success && j.data && Array.isArray(j.data.bans)) {
|
||||
banBoardRows = j.data.bans;
|
||||
banBoardLoaded = true;
|
||||
}
|
||||
} catch (e) { /* 网络抖动沿用上一帧 */ }
|
||||
const slot = ensureBanCard();
|
||||
if (!slot || slot._banBusy) return;
|
||||
banRollUpdate(slot, banPageHtml(banBoardRows.slice(0, BAN_PAGE_SIZE)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user