feat: 游客模式毛玻璃登录弹窗 + C位登录按钮 + 60秒定时弹窗
- admin.php: 修复go()缺失reset_requests路由(黑屏bug) - player.php: 毛玻璃登录弹窗(backdrop-filter:blur) - player.php: 游客模式C位大按钮(脉冲动画) - player.php: 60秒定时弹窗(未登录自动弹出) - sync.php: 添加领地同步接口(sync_lands/sync_land_shop) - land_api.php: 领地管理API(新建)
This commit is contained in:
@@ -92,6 +92,7 @@
|
||||
<div class="si" data-p="active" onclick="go('active')">⏱️ 活跃用户</div>
|
||||
<div class="si" data-p="reset_requests" onclick="go('reset_requests')">🔑 密码重置审核</div>
|
||||
<div class="si" data-p="tickets" onclick="go('tickets')">📋 工单管理</div>
|
||||
<div class="si" data-p="lands" onclick="go('lands')">🏡 领地管理</div></div>
|
||||
</div>
|
||||
<div class="content" id="C"></div>
|
||||
</div>
|
||||
@@ -152,7 +153,9 @@ function go(p) {
|
||||
else if (p==='users') loadUsers(c);
|
||||
else if (p==='online') loadOnlinePlayers(c);
|
||||
else if (p==='active') loadActivePlayers(c);
|
||||
else if (p==='reset_requests') loadResetRequests(c);
|
||||
else if (p==='tickets') loadTickets(c);
|
||||
else if (p==='lands') loadLands(c);
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
@@ -1716,27 +1719,40 @@ async function doLogout() {
|
||||
}
|
||||
|
||||
// ===== 模态框输入(替代prompt)=====
|
||||
function showModal(title, message, defaultValue) {
|
||||
function showModal(title, message, defaultValue, customHtml) {
|
||||
return new Promise((resolve) => {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;justify-content:center;align-items:center;z-index:1000';
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:24px;width:400px;max-width:90%">
|
||||
<h3 style="margin-bottom:12px">${title}</h3>
|
||||
<p style="color:var(--dim);font-size:13px;margin-bottom:12px">${message}</p>
|
||||
<input type="text" id="modalInput" value="${defaultValue}" style="width:100%;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px;box-sizing:border-box">
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
|
||||
<button class="btn" id="modalCancel">取消</button>
|
||||
<button class="btn btn-blue" id="modalConfirm">确认</button>
|
||||
</div>
|
||||
</div>`;
|
||||
if (customHtml) {
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:24px;width:400px;max-width:90%">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
||||
<h3 style="margin:0">${title}</h3>
|
||||
<span class="modal-close" style="cursor:pointer;font-size:18px;color:var(--dim)" onclick="this.closest('div[style*=fixed]').remove()">✕</span>
|
||||
</div>
|
||||
${customHtml}
|
||||
</div>`;
|
||||
} else {
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:24px;width:400px;max-width:90%">
|
||||
<h3 style="margin-bottom:12px">${title}</h3>
|
||||
<p style="color:var(--dim);font-size:13px;margin-bottom:12px">${message}</p>
|
||||
<input type="text" id="modalInput" value="${defaultValue}" style="width:100%;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:14px;box-sizing:border-box">
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
|
||||
<button class="btn" id="modalCancel">取消</button>
|
||||
<button class="btn btn-blue" id="modalConfirm">确认</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
document.body.appendChild(overlay);
|
||||
const input = document.getElementById('modalInput');
|
||||
input.focus();
|
||||
input.select();
|
||||
input.addEventListener('keydown', e => { if (e.key === 'Enter') { resolve(input.value); overlay.remove(); } });
|
||||
document.getElementById('modalCancel').onclick = () => { overlay.remove(); resolve(null); };
|
||||
document.getElementById('modalConfirm').onclick = () => { resolve(input.value); overlay.remove(); };
|
||||
if (!customHtml) {
|
||||
const input = document.getElementById('modalInput');
|
||||
input.focus();
|
||||
input.select();
|
||||
input.addEventListener('keydown', e => { if (e.key === 'Enter') { resolve(input.value); overlay.remove(); } });
|
||||
document.getElementById('modalCancel').onclick = () => { overlay.remove(); resolve(null); };
|
||||
document.getElementById('modalConfirm').onclick = () => { resolve(input.value); overlay.remove(); };
|
||||
}
|
||||
overlay.onclick = (e) => { if (e.target === overlay) { overlay.remove(); resolve(null); } };
|
||||
});
|
||||
}
|
||||
@@ -2225,6 +2241,183 @@ function adminRenderMd(text) {
|
||||
return s;
|
||||
} catch(e) { console.error('[MD_RENDER] Error:', e); return escAdmHtml(text).replace(/\n/g, '<br>'); }
|
||||
}
|
||||
|
||||
// ==================== 领地管理 ====================
|
||||
async function loadLands(el) {
|
||||
el.innerHTML = '<div style="text-align:center;padding:40px;color:var(--dim)">加载中...</div>';
|
||||
try {
|
||||
const [landsRes, shopRes, configRes] = await Promise.all([
|
||||
fetch('api/land_api.php?action=list_lands').then(r => r.json()),
|
||||
fetch('api/land_api.php?action=list_shop').then(r => r.json()),
|
||||
fetch('api/land_api.php?action=get_config').then(r => r.json())
|
||||
]);
|
||||
|
||||
let html = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;flex-wrap:wrap;gap:8px">';
|
||||
html += '<h2 style="margin:0;color:var(--fg)">🏡 领地管理</h2>';
|
||||
html += '<div style="display:flex;gap:8px">';
|
||||
html += `<button onclick="showLandConfig()" style="padding:6px 12px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px">⚙️ 配置</button>`;
|
||||
html += `<button onclick="loadLands(document.getElementById('C'))" style="padding:6px 12px;background:var(--card);color:var(--dim);border:1px solid var(--border);border-radius:4px;cursor:pointer;font-size:12px">🔄 刷新</button>`;
|
||||
html += '</div></div>';
|
||||
|
||||
// 配置信息
|
||||
const cfg = configRes.config || {};
|
||||
html += `<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-bottom:20px">`;
|
||||
const lands = landsRes.lands || [];
|
||||
html += `<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px;text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:var(--accent)">${lands.length}</div>
|
||||
<div style="font-size:12px;color:var(--dim);margin-top:4px">总领地数</div>
|
||||
</div>`;
|
||||
const shopItems = (shopRes.items || []).filter(i => i.status === 'active' && !i.buyer);
|
||||
html += `<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px;text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#4caf50">${shopItems.length}</div>
|
||||
<div style="font-size:12px;color:var(--dim);margin-top:4px">在售权限</div>
|
||||
</div>`;
|
||||
const pricePerSqM = cfg.land_price_per_sqm || '10';
|
||||
const maxLands = cfg.max_lands || '5';
|
||||
html += `<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px;text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#ff9800">${pricePerSqM}</div>
|
||||
<div style="font-size:12px;color:var(--dim);margin-top:4px">每平米价格(债券)</div>
|
||||
</div>`;
|
||||
html += `<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px;text-align:center">
|
||||
<div style="font-size:24px;font-weight:bold;color:#e91e63">${maxLands}</div>
|
||||
<div style="font-size:12px;color:var(--dim);margin-top:4px">最大领地数/人</div>
|
||||
</div>`;
|
||||
html += '</div>';
|
||||
|
||||
// 领地列表
|
||||
html += '<h3 style="color:var(--fg);margin-bottom:8px">📍 领地列表</h3>';
|
||||
if (lands.length === 0) {
|
||||
html += '<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--dim)">暂无领地数据<br><span style="font-size:11px">Java端会自动同步领地数据到此</span></div>';
|
||||
} else {
|
||||
html += '<div style="overflow-x:auto"><table class="table"><tr>';
|
||||
html += '<th>ID</th><th>名称</th><th>所有者</th><th>世界</th><th>坐标</th><th>面积</th><th>操作</th>';
|
||||
html += '</tr>';
|
||||
for (const l of lands) {
|
||||
const size = l.area_size || Math.abs((l.x2-l.x1+1)*(l.z2-l.z1+1));
|
||||
html += `<tr>
|
||||
<td>${l.id}</td>
|
||||
<td><strong>${escAdmHtml(l.name)}</strong></td>
|
||||
<td>${escAdmHtml(l.owner || '无')}</td>
|
||||
<td>${escAdmHtml(l.world)}</td>
|
||||
<td style="font-size:11px">${l.x1},${l.z1} → ${l.x2},${l.z2}</td>
|
||||
<td>${size} 格²</td>
|
||||
<td>
|
||||
<button onclick="changeLandOwner('${escAdmHtml(l.name)}','${escAdmHtml(l.owner)}')" style="padding:2px 8px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">改主</button>
|
||||
<button onclick="deleteLand('${escAdmHtml(l.name)}')" style="padding:2px 8px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">删除</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += '</table></div>';
|
||||
}
|
||||
|
||||
// 权限商店
|
||||
html += '<h3 style="color:var(--fg);margin:20px 0 8px">🛒 权限商店</h3>';
|
||||
const allShop = shopRes.items || [];
|
||||
if (allShop.length === 0) {
|
||||
html += '<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--dim)">暂无权限商品</div>';
|
||||
} else {
|
||||
html += '<div style="overflow-x:auto"><table class="table"><tr>';
|
||||
html += '<th>ID</th><th>领地</th><th>卖家</th><th>价格</th><th>时长</th><th>状态</th><th>买家</th><th>操作</th>';
|
||||
html += '</tr>';
|
||||
for (const s of allShop) {
|
||||
const dur = s.duration >= 86400 ? Math.floor(s.duration/86400)+'天' : s.duration >= 3600 ? Math.floor(s.duration/3600)+'小时' : Math.floor(s.duration/60)+'分钟';
|
||||
const statusColor = s.status === 'active' ? '#4caf50' : s.status === 'sold' ? '#ff9800' : '#f44336';
|
||||
html += `<tr>
|
||||
<td>${s.id}</td>
|
||||
<td>${escAdmHtml(s.land_name)}</td>
|
||||
<td>${escAdmHtml(s.seller)}</td>
|
||||
<td style="color:#ff9800">${s.price}💰</td>
|
||||
<td>${dur}</td>
|
||||
<td style="color:${statusColor}">${s.status === 'active' ? '在售' : s.status === 'sold' ? '已售' : '已下架'}</td>
|
||||
<td>${s.buyer ? escAdmHtml(s.buyer) : '-'}</td>
|
||||
<td>
|
||||
${s.status === 'active' ? `<button onclick="deleteShopItem(${s.id})" style="padding:2px 8px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">下架</button>` : ''}
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += '</table></div>';
|
||||
}
|
||||
|
||||
el.innerHTML = html;
|
||||
} catch(e) {
|
||||
console.error('[LANDS] Error:', e);
|
||||
el.innerHTML = `<div style="background:var(--card);border:1px solid var(--border);border-radius:8px;padding:24px;text-align:center;color:var(--dim)">加载失败: ${escAdmHtml(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function showLandConfig() {
|
||||
const html = `<div style="padding:16px">
|
||||
<h3 style="margin:0 0 12px;color:var(--fg)">⚙️ 领地配置</h3>
|
||||
<div style="margin-bottom:12px">
|
||||
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">每平米价格(债券)</label>
|
||||
<input id="cfgPrice" type="number" value="10" min="1" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
|
||||
</div>
|
||||
<div style="margin-bottom:12px">
|
||||
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">每人最大领地数</label>
|
||||
<input id="cfgMaxLands" type="number" value="5" min="1" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
|
||||
</div>
|
||||
<button onclick="saveLandConfig()" style="width:100%;padding:8px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer">保存</button>
|
||||
</div>`;
|
||||
showModal('领地配置', '', null, html);
|
||||
}
|
||||
|
||||
async function saveLandConfig() {
|
||||
const price = document.getElementById('cfgPrice')?.value || '10';
|
||||
const maxLands = document.getElementById('cfgMaxLands')?.value || '5';
|
||||
try {
|
||||
await fetch('api/land_api.php?action=update_config', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'key=land_price_per_sqm&value=' + encodeURIComponent(price)
|
||||
});
|
||||
await fetch('api/land_api.php?action=update_config', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'key=max_lands&value=' + encodeURIComponent(maxLands)
|
||||
});
|
||||
document.querySelector('.modal-close')?.click();
|
||||
loadLands(document.getElementById('C'));
|
||||
} catch(e) {
|
||||
alert('保存失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function changeLandOwner(name, currentOwner) {
|
||||
const newOwner = prompt('领地 [' + name + '] 当前所有者: ' + (currentOwner||'无') + '\n输入新所有者:', currentOwner);
|
||||
if (newOwner === null || newOwner === currentOwner) return;
|
||||
fetch('api/land_api.php?action=update_land_owner', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'name=' + encodeURIComponent(name) + '&owner=' + encodeURIComponent(newOwner)
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (d.success) loadLands(document.getElementById('C'));
|
||||
else alert('失败: ' + (d.error||''));
|
||||
});
|
||||
}
|
||||
|
||||
function deleteLand(name) {
|
||||
if (!confirm('确定删除领地 [' + name + '] ?')) return;
|
||||
fetch('api/land_api.php?action=delete_land', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'name=' + encodeURIComponent(name)
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (d.success) loadLands(document.getElementById('C'));
|
||||
else alert('失败: ' + (d.error||''));
|
||||
});
|
||||
}
|
||||
|
||||
function deleteShopItem(id) {
|
||||
if (!confirm('确定下架商品 #' + id + ' ?')) return;
|
||||
fetch('api/land_api.php?action=delete_shop_item', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'id=' + id
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (d.success) loadLands(document.getElementById('C'));
|
||||
else alert('失败: ' + (d.error||''));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
/**
|
||||
* 领地系统API - 接收Java端领地数据 + 管理面板
|
||||
* POST: Java端推送领地数据
|
||||
* GET: admin面板读取领地数据
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
try {
|
||||
// ★ 加载core.php(与sync.php相同的加载方式)
|
||||
$coreFile = dirname(__DIR__) . '/core.php';
|
||||
if (!file_exists($coreFile)) {
|
||||
$coreFile = dirname(__DIR__) . '/plugin/core.php';
|
||||
}
|
||||
require_once $coreFile;
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
$secret = $_GET['secret'] ?? $_POST['secret'] ?? '';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// ===== 验证 =====
|
||||
if ($method === 'POST') {
|
||||
if (!validateSecret($secret)) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid secret']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// 建表
|
||||
initLandTables($db);
|
||||
|
||||
switch ($action) {
|
||||
// ===== Java端推送领地数据 =====
|
||||
case 'sync_lands':
|
||||
handleSyncLands($db, $_POST);
|
||||
break;
|
||||
|
||||
// ===== Java端推送权限商店数据 =====
|
||||
case 'sync_shop':
|
||||
handleSyncShop($db, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:获取所有领地 =====
|
||||
case 'list_lands':
|
||||
if ($method !== 'GET') {
|
||||
echo json_encode(['success' => false, 'error' => 'GET only']);
|
||||
exit;
|
||||
}
|
||||
handleListLands($db);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:获取权限商店 =====
|
||||
case 'list_shop':
|
||||
if ($method !== 'GET') {
|
||||
echo json_encode(['success' => false, 'error' => 'GET only']);
|
||||
exit;
|
||||
}
|
||||
handleListShop($db);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:获取配置 =====
|
||||
case 'get_config':
|
||||
handleGetConfig($db);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:更新配置 =====
|
||||
case 'update_config':
|
||||
handleUpdateConfig($db, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:删除领地 =====
|
||||
case 'delete_land':
|
||||
handleDeleteLand($db, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:更新领地所有者 =====
|
||||
case 'update_land_owner':
|
||||
handleUpdateLandOwner($db, $_POST);
|
||||
break;
|
||||
|
||||
// ===== 管理面板:删除权限商品 =====
|
||||
case 'delete_shop_item':
|
||||
handleDeleteShopItem($db, $_POST);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['success' => false, 'error' => 'unknown action']);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
// ==================== 函数 ====================
|
||||
|
||||
function initLandTables($db) {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_lands (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
owner TEXT DEFAULT '',
|
||||
world TEXT DEFAULT '',
|
||||
x1 INTEGER DEFAULT 0, z1 INTEGER DEFAULT 0,
|
||||
x2 INTEGER DEFAULT 0, z2 INTEGER DEFAULT 0,
|
||||
y_min INTEGER DEFAULT 0, y_max INTEGER DEFAULT 255,
|
||||
area_size INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_shop (
|
||||
id INTEGER PRIMARY KEY,
|
||||
land_id INTEGER NOT NULL,
|
||||
land_name TEXT DEFAULT '',
|
||||
seller TEXT NOT NULL,
|
||||
permission TEXT DEFAULT 'visitor',
|
||||
price INTEGER DEFAULT 0,
|
||||
duration INTEGER DEFAULT 86400,
|
||||
status TEXT DEFAULT 'active',
|
||||
buyer TEXT DEFAULT '',
|
||||
bought_at INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT DEFAULT ''
|
||||
)");
|
||||
}
|
||||
|
||||
function handleSyncLands($db, $post) {
|
||||
$lands = json_decode($post['lands'] ?? '[]', true);
|
||||
if (!is_array($lands)) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid lands data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_area_lands
|
||||
(id, name, owner, world, x1, z1, x2, z2, y_min, y_max, area_size, created_at, synced_at)
|
||||
VALUES (:id, :name, :owner, :world, :x1, :z1, :x2, :z2, :ymin, :ymax, :size, :created, :synced)");
|
||||
|
||||
foreach ($lands as $land) {
|
||||
$stmt->bindValue(':id', (int)($land['id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':name', $land['name'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':owner', $land['owner'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':world', $land['world'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':x1', (int)($land['x1'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':z1', (int)($land['z1'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':x2', (int)($land['x2'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':z2', (int)($land['z2'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':ymin', (int)($land['y_min'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':ymax', (int)($land['y_max'] ?? 255), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':size', (int)($land['area_size'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':created', (int)($land['created_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'count' => count($lands)]);
|
||||
}
|
||||
|
||||
function handleSyncShop($db, $post) {
|
||||
$items = json_decode($post['items'] ?? '[]', true);
|
||||
if (!is_array($items)) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid shop data']);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_area_shop
|
||||
(id, land_id, land_name, seller, permission, price, duration, status, buyer, bought_at, created_at, synced_at)
|
||||
VALUES (:id, :land_id, :land_name, :seller, :perm, :price, :dur, :status, :buyer, :bought, :created, :synced)");
|
||||
|
||||
foreach ($items as $item) {
|
||||
$stmt->bindValue(':id', (int)($item['id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':land_id', (int)($item['land_id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':land_name', $item['land_name'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':seller', $item['seller'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':perm', $item['permission'] ?? 'visitor', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':price', (int)($item['price'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':dur', (int)($item['duration'] ?? 86400), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':status', $item['status'] ?? 'active', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':buyer', $item['buyer'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':bought', (int)($item['bought_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':created', (int)($item['created_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'count' => count($items)]);
|
||||
}
|
||||
|
||||
function handleListLands($db) {
|
||||
$result = $db->query("SELECT * FROM web_area_lands ORDER BY synced_at DESC");
|
||||
$lands = [];
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$lands[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'lands' => $lands]);
|
||||
}
|
||||
|
||||
function handleListShop($db) {
|
||||
$result = $db->query("SELECT * FROM web_area_shop ORDER BY created_at DESC LIMIT 100");
|
||||
$items = [];
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$items[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'items' => $items]);
|
||||
}
|
||||
|
||||
function handleGetConfig($db) {
|
||||
$config = [];
|
||||
$result = $db->query("SELECT * FROM web_area_config");
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$config[$row['key']] = $row['value'];
|
||||
}
|
||||
echo json_encode(['success' => true, 'config' => $config]);
|
||||
}
|
||||
|
||||
function handleUpdateConfig($db, $post) {
|
||||
$key = $post['key'] ?? '';
|
||||
$value = $post['value'] ?? '';
|
||||
if (empty($key)) {
|
||||
echo json_encode(['success' => false, 'error' => 'missing key']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_area_config (key, value) VALUES (:key, :val)");
|
||||
$stmt->bindValue(':key', $key, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':val', $value, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
|
||||
function handleDeleteLand($db, $post) {
|
||||
$name = $post['name'] ?? '';
|
||||
if (empty($name)) {
|
||||
echo json_encode(['success' => false, 'error' => 'missing name']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM web_area_lands WHERE name = :name");
|
||||
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
|
||||
function handleUpdateLandOwner($db, $post) {
|
||||
$name = $post['name'] ?? '';
|
||||
$owner = $post['owner'] ?? '';
|
||||
if (empty($name)) {
|
||||
echo json_encode(['success' => false, 'error' => 'missing name']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
|
||||
$stmt->bindValue(':owner', $owner, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
|
||||
function handleDeleteShopItem($db, $post) {
|
||||
$id = (int)($post['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'invalid id']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE web_area_shop SET status = 'removed' WHERE id = :id");
|
||||
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
|
||||
function validateSecret($secret) {
|
||||
if (empty($secret)) return false;
|
||||
$validSecrets = [
|
||||
'sdf1_web_comm_2026_ypshidifu'
|
||||
];
|
||||
return in_array($secret, $validSecrets);
|
||||
}
|
||||
+106
@@ -299,6 +299,12 @@ switch ($action) {
|
||||
case 'push_cdk_validate_result':
|
||||
pushCdkValidateResult();
|
||||
break;
|
||||
case 'sync_lands':
|
||||
syncLands();
|
||||
break;
|
||||
case 'sync_land_shop':
|
||||
syncLandShop();
|
||||
break;
|
||||
default:
|
||||
error('未知操作: ' . $action);
|
||||
}
|
||||
@@ -422,6 +428,106 @@ function syncServiceProviders() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 领地数据同步 =====
|
||||
function syncLands() {
|
||||
$secret = getParam('secret');
|
||||
if ($secret !== SECRET_KEY) error('密钥验证失败', 403);
|
||||
|
||||
$landsRaw = getParam('lands');
|
||||
if (!$landsRaw) error('缺少lands参数');
|
||||
$lands = is_array($landsRaw) ? $landsRaw : json_decode($landsRaw, true);
|
||||
if (!is_array($lands)) error('lands格式无效');
|
||||
|
||||
$db = getDB();
|
||||
// 建表
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_lands (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
owner TEXT DEFAULT '',
|
||||
world TEXT DEFAULT '',
|
||||
x1 INTEGER DEFAULT 0, z1 INTEGER DEFAULT 0,
|
||||
x2 INTEGER DEFAULT 0, z2 INTEGER DEFAULT 0,
|
||||
y_min INTEGER DEFAULT 0, y_max INTEGER DEFAULT 255,
|
||||
area_size INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
$now = time();
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_area_lands
|
||||
(id, name, owner, world, x1, z1, x2, z2, y_min, y_max, area_size, created_at, synced_at)
|
||||
VALUES (:id, :name, :owner, :world, :x1, :z1, :x2, :z2, :ymin, :ymax, :size, :created, :synced)");
|
||||
$count = 0;
|
||||
foreach ($lands as $land) {
|
||||
$stmt->bindValue(':id', (int)($land['id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':name', $land['name'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':owner', $land['owner'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':world', $land['world'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':x1', (int)($land['x1'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':z1', (int)($land['z1'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':x2', (int)($land['x2'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':z2', (int)($land['z2'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':ymin', (int)($land['y_min'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':ymax', (int)($land['y_max'] ?? 255), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':size', (int)($land['area_size'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':created', (int)($land['created_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
$count++;
|
||||
}
|
||||
success("领地同步成功: {$count}个");
|
||||
}
|
||||
|
||||
// ===== 领地权限商店同步 =====
|
||||
function syncLandShop() {
|
||||
$secret = getParam('secret');
|
||||
if ($secret !== SECRET_KEY) error('密钥验证失败', 403);
|
||||
|
||||
$itemsRaw = getParam('items');
|
||||
if (!$itemsRaw) error('缺少items参数');
|
||||
$items = is_array($itemsRaw) ? $itemsRaw : json_decode($itemsRaw, true);
|
||||
if (!is_array($items)) error('items格式无效');
|
||||
|
||||
$db = getDB();
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_shop (
|
||||
id INTEGER PRIMARY KEY,
|
||||
land_id INTEGER NOT NULL,
|
||||
land_name TEXT DEFAULT '',
|
||||
seller TEXT NOT NULL,
|
||||
permission TEXT DEFAULT 'visitor',
|
||||
price INTEGER DEFAULT 0,
|
||||
duration INTEGER DEFAULT 86400,
|
||||
status TEXT DEFAULT 'active',
|
||||
buyer TEXT DEFAULT '',
|
||||
bought_at INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
synced_at INTEGER DEFAULT 0
|
||||
)");
|
||||
|
||||
$now = time();
|
||||
$stmt = $db->prepare("INSERT OR REPLACE INTO web_area_shop
|
||||
(id, land_id, land_name, seller, permission, price, duration, status, buyer, bought_at, created_at, synced_at)
|
||||
VALUES (:id, :land_id, :land_name, :seller, :perm, :price, :dur, :status, :buyer, :bought, :created, :synced)");
|
||||
$count = 0;
|
||||
foreach ($items as $item) {
|
||||
$stmt->bindValue(':id', (int)($item['id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':land_id', (int)($item['land_id'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':land_name', $item['land_name'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':seller', $item['seller'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':perm', $item['permission'] ?? 'visitor', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':price', (int)($item['price'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':dur', (int)($item['duration'] ?? 86400), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':status', $item['status'] ?? 'active', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':buyer', $item['buyer'] ?? '', SQLITE3_TEXT);
|
||||
$stmt->bindValue(':bought', (int)($item['bought_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':created', (int)($item['created_at'] ?? 0), SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
||||
$stmt->execute();
|
||||
$count++;
|
||||
}
|
||||
success("权限商店同步成功: {$count}个");
|
||||
}
|
||||
|
||||
// ===== 插件推送债券余额 =====
|
||||
function syncBonds() {
|
||||
// ★ 支持token或SECRET_KEY认证
|
||||
|
||||
+211
@@ -147,6 +147,81 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
.toast.success { background: var(--green); color: #fff; }
|
||||
.toast.error { background: var(--red); color: #fff; }
|
||||
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||||
/* ★ 游客模式C位登录按钮 */
|
||||
.guest-center-login {
|
||||
display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
z-index: 50; pointer-events: none;
|
||||
}
|
||||
.guest-center-login.show { display: flex; justify-content: center; align-items: center; }
|
||||
.guest-center-login .glow-btn {
|
||||
pointer-events: auto;
|
||||
background: linear-gradient(135deg, var(--accent), #79c0ff);
|
||||
color: #fff; border: none; border-radius: 16px;
|
||||
padding: 20px 48px; font-size: 18px; font-weight: 700;
|
||||
cursor: pointer; box-shadow: 0 8px 32px rgba(88,166,255,0.4);
|
||||
animation: glowPulse 2s ease-in-out infinite;
|
||||
transition: all 0.3s; text-decoration: none;
|
||||
}
|
||||
.guest-center-login .glow-btn:hover { transform: scale(1.08); box-shadow: 0 12px 48px rgba(88,166,255,0.6); }
|
||||
@keyframes glowPulse {
|
||||
0%,100% { box-shadow: 0 8px 32px rgba(88,166,255,0.4); }
|
||||
50% { box-shadow: 0 8px 48px rgba(88,166,255,0.7); }
|
||||
}
|
||||
/* ★ 毛玻璃登录弹窗 */
|
||||
.glass-login-overlay {
|
||||
display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.35);
|
||||
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
||||
z-index: 2000; justify-content: center; align-items: center;
|
||||
animation: glassFadeIn 0.3s ease;
|
||||
}
|
||||
.glass-login-overlay.show { display: flex; }
|
||||
.glass-card {
|
||||
background: rgba(22,27,34,0.88);
|
||||
backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
|
||||
border: 1px solid rgba(88,166,255,0.25);
|
||||
border-radius: 20px; padding: 36px 32px; width: 400px; max-width: 90%;
|
||||
box-shadow: 0 16px 64px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
|
||||
animation: glassSlideUp 0.35s ease;
|
||||
position: relative;
|
||||
}
|
||||
.glass-card .x-btn {
|
||||
position: absolute; top: 12px; right: 16px; background: none; border: none;
|
||||
color: var(--dim); font-size: 22px; cursor: pointer; padding: 4px 8px;
|
||||
border-radius: 6px; transition: all 0.2s; line-height: 1;
|
||||
}
|
||||
.glass-card .x-btn:hover { color: var(--text); background: rgba(255,255,255,0.1); }
|
||||
.glass-card .logo { text-align: center; font-size: 48px; margin-bottom: 16px; }
|
||||
.glass-card h2 { text-align: center; color: var(--text); margin-bottom: 6px; font-size: 20px; }
|
||||
.glass-card .subtitle { text-align: center; color: var(--dim); font-size: 13px; margin-bottom: 24px; }
|
||||
.glass-card input[type=text], .glass-card input[type=password] {
|
||||
width: 100%; padding: 12px 16px; background: rgba(13,17,23,0.6);
|
||||
border: 1px solid rgba(88,166,255,0.2); border-radius: 10px;
|
||||
color: var(--text); font-size: 14px; outline: none; margin-bottom: 12px;
|
||||
transition: border-color 0.2s; box-sizing: border-box;
|
||||
}
|
||||
.glass-card input:focus { border-color: var(--accent); }
|
||||
.glass-card .login-btn {
|
||||
width: 100%; padding: 14px; background: linear-gradient(135deg, var(--accent), #79c0ff);
|
||||
color: #fff; border: none; border-radius: 10px; font-size: 15px;
|
||||
font-weight: 700; cursor: pointer; transition: all 0.2s; margin-top: 4px;
|
||||
}
|
||||
.glass-card .login-btn:hover { opacity: 0.9; transform: translateY(-1px); }
|
||||
.glass-card .login-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
||||
.glass-card .hint { text-align: center; color: var(--dim); font-size: 12px; margin-top: 16px; }
|
||||
.glass-card .hint code {
|
||||
background: rgba(88,166,255,0.1); padding: 1px 5px; border-radius: 4px;
|
||||
color: var(--accent); font-size: 11px;
|
||||
}
|
||||
.glass-card .status-msg {
|
||||
text-align: center; padding: 8px; border-radius: 8px; font-size: 13px;
|
||||
margin-bottom: 12px; display: none;
|
||||
}
|
||||
@keyframes glassFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes glassSlideUp {
|
||||
from { opacity: 0; transform: translateY(30px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.sidebar { display: none; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
@@ -177,6 +252,26 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ★ 游客模式C位登录按钮 -->
|
||||
<div class="guest-center-login" id="guestCenterLogin">
|
||||
<button class="glow-btn" onclick="openGlassLogin()">🔑 立即登录</button>
|
||||
</div>
|
||||
|
||||
<!-- ★ 毛玻璃登录弹窗 -->
|
||||
<div class="glass-login-overlay" id="glassLoginOverlay" onclick="closeGlassLogin(event)">
|
||||
<div class="glass-card" onclick="event.stopPropagation()">
|
||||
<button class="x-btn" onclick="closeGlassLogin()">×</button>
|
||||
<div class="logo">🔐</div>
|
||||
<h2>登录到SDF1</h2>
|
||||
<p class="subtitle">使用游戏内密码登录,无需在游戏里敲指令</p>
|
||||
<div class="status-msg" id="glassLoginStatus"></div>
|
||||
<input type="text" id="glassPlayerName" placeholder="玩家名" maxlength="16" autocomplete="username">
|
||||
<input type="password" id="glassPassword" placeholder="游戏内密码" maxlength="32" autocomplete="current-password">
|
||||
<button class="login-btn" id="glassLoginBtn" onclick="doGlassLogin()">登 录</button>
|
||||
<p class="hint">也可在游戏中输入 <code>/sdf1_login weblogin</code> 获取链接</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="modalOverlay" onclick="closeModal()">
|
||||
<div class="modal" onclick="event.stopPropagation()">
|
||||
<h3 id="modalTitle">确认</h3>
|
||||
@@ -317,7 +412,9 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
currentPlayer = '';
|
||||
document.getElementById('playerInfo').textContent = '游客模式';
|
||||
document.getElementById('previewBadge').style.display = 'inline';
|
||||
document.querySelector('.guest-center-login').classList.add('show');
|
||||
showLoginPrompt();
|
||||
startGuestLoginTimer();
|
||||
if (callback) callback({ok: false, message: data.message});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -329,7 +426,9 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
currentPlayer = '';
|
||||
document.getElementById('playerInfo').textContent = '游客模式';
|
||||
document.getElementById('previewBadge').style.display = 'inline';
|
||||
document.querySelector('.guest-center-login').classList.add('show');
|
||||
showLoginPrompt();
|
||||
startGuestLoginTimer();
|
||||
if (callback) callback({ok: false, message: e.message});
|
||||
}
|
||||
}
|
||||
@@ -753,7 +852,119 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
document.getElementById('playerInfo').textContent = '游客模式';
|
||||
document.getElementById('previewBadge').style.display = 'inline';
|
||||
document.querySelector('.sidebar').style.display = 'block';
|
||||
document.querySelector('.guest-center-login').classList.add('show');
|
||||
switchPage('dashboard');
|
||||
// ★ 启动60秒定时弹窗
|
||||
startGuestLoginTimer();
|
||||
}
|
||||
|
||||
// ==================== 毛玻璃登录弹窗 ====================
|
||||
let guestLoginTimer = null;
|
||||
let glassLoginDismissed = false;
|
||||
|
||||
function openGlassLogin() {
|
||||
const overlay = document.getElementById('glassLoginOverlay');
|
||||
overlay.classList.add('show');
|
||||
glassLoginDismissed = false;
|
||||
// 停止定时弹窗
|
||||
if (guestLoginTimer) { clearInterval(guestLoginTimer); guestLoginTimer = null; }
|
||||
setTimeout(() => {
|
||||
const nameInput = document.getElementById('glassPlayerName');
|
||||
const savedPlayer = localStorage.getItem('sdf1_player') || currentPlayer || '';
|
||||
if (nameInput) { nameInput.value = savedPlayer; nameInput.focus(); }
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function closeGlassLogin(e) {
|
||||
if (e && e.target !== document.getElementById('glassLoginOverlay')) return;
|
||||
document.getElementById('glassLoginOverlay').classList.remove('show');
|
||||
glassLoginDismissed = true;
|
||||
glassLoginReset();
|
||||
// 重新启动定时弹窗
|
||||
if (IS_PREVIEW) startGuestLoginTimer();
|
||||
}
|
||||
|
||||
function glassLoginReset() {
|
||||
const btn = document.getElementById('glassLoginBtn');
|
||||
const st = document.getElementById('glassLoginStatus');
|
||||
const pwd = document.getElementById('glassPassword');
|
||||
if (btn) { btn.disabled = false; btn.textContent = '登 录'; }
|
||||
if (st) { st.style.display = 'none'; st.textContent = ''; }
|
||||
if (pwd) pwd.value = '';
|
||||
}
|
||||
|
||||
function setGlassStatus(msg, type) {
|
||||
const st = document.getElementById('glassLoginStatus');
|
||||
if (!st) return;
|
||||
const colors = { loading: 'var(--accent)', success: 'var(--green)', error: 'var(--red)' };
|
||||
const bgs = { loading: 'rgba(88,166,255,0.12)', success: 'rgba(63,185,80,0.12)', error: 'rgba(248,81,73,0.12)' };
|
||||
st.style.display = 'block';
|
||||
st.style.background = bgs[type] || bgs.loading;
|
||||
st.style.border = '1px solid ' + (colors[type] || colors.loading);
|
||||
st.style.borderRadius = '8px';
|
||||
st.style.color = colors[type] || 'var(--text)';
|
||||
st.innerHTML = type === 'loading'
|
||||
? '<span style="display:inline-block;width:14px;height:14px;border:2px solid var(--dim);border-top-color:var(--accent);border-radius:50%;animation:spin 0.8s linear infinite;vertical-align:middle;margin-right:6px"></span> ' + msg
|
||||
: msg;
|
||||
}
|
||||
|
||||
async function doGlassLogin() {
|
||||
const player = document.getElementById('glassPlayerName').value.trim();
|
||||
const password = document.getElementById('glassPassword').value;
|
||||
const btn = document.getElementById('glassLoginBtn');
|
||||
if (!player) { setGlassStatus('请输入玩家名', 'error'); return; }
|
||||
if (!password) { setGlassStatus('请输入密码', 'error'); return; }
|
||||
btn.disabled = true; btn.textContent = '登录中...';
|
||||
setGlassStatus('正在提交登录请求...', 'loading');
|
||||
try {
|
||||
const reqRes = await fetch(API + 'sync.php?action=web_login_request', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({player, password})
|
||||
});
|
||||
const reqData = await reqRes.json();
|
||||
if (!reqData.success) { setGlassStatus(reqData.message || '提交失败', 'error'); btn.disabled = false; btn.textContent = '登 录'; return; }
|
||||
const requestId = reqData.data.request_id;
|
||||
setGlassStatus('等待游戏服务器验证...', 'loading');
|
||||
let attempts = 0;
|
||||
const pollInterval = setInterval(async () => {
|
||||
attempts++;
|
||||
if (attempts > 60) { clearInterval(pollInterval); setGlassStatus('验证超时,请稍后重试', 'error'); btn.disabled = false; btn.textContent = '登 录'; return; }
|
||||
try {
|
||||
const pollRes = await fetch(API + 'sync.php?action=check_web_login_result&player=' + encodeURIComponent(player) + '&request_id=' + requestId);
|
||||
const pollData = await pollRes.json();
|
||||
if (pollData.success && pollData.data) {
|
||||
const result = pollData.data;
|
||||
if (result.status === 'success') {
|
||||
clearInterval(pollInterval);
|
||||
setGlassStatus('登录成功!正在跳转...', 'success');
|
||||
const token = result.token;
|
||||
localStorage.setItem('sdf1_token', token);
|
||||
localStorage.setItem('sdf1_player', player);
|
||||
setTimeout(() => { window.location.href = 'player.php?login=' + encodeURIComponent(player) + '&token=' + encodeURIComponent(token); }, 600);
|
||||
} else if (result.status === 'failed') {
|
||||
clearInterval(pollInterval);
|
||||
setGlassStatus(result.message || '密码错误', 'error');
|
||||
btn.disabled = false; btn.textContent = '登 录';
|
||||
}
|
||||
}
|
||||
} catch (e) { /* 继续轮询 */ }
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
setGlassStatus('连接失败: ' + e.message, 'error');
|
||||
btn.disabled = false; btn.textContent = '登 录';
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 60秒定时弹窗(游客模式专用)
|
||||
function startGuestLoginTimer() {
|
||||
if (guestLoginTimer) clearInterval(guestLoginTimer);
|
||||
guestLoginTimer = setInterval(() => {
|
||||
if (!IS_PREVIEW) { clearInterval(guestLoginTimer); guestLoginTimer = null; return; }
|
||||
if (!glassLoginDismissed) {
|
||||
openGlassLogin();
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
function switchPage(page) {
|
||||
|
||||
Reference in New Issue
Block a user