feat: 成员独立权限管理 + PHP→Java变更同步

This commit is contained in:
ypsdf1
2026-06-23 23:48:19 +08:00
parent 98f9cb6410
commit d12ceaa44a
2 changed files with 688 additions and 6 deletions
+491 -5
View File
@@ -33,8 +33,14 @@ try {
$adminActions = ['list_lands', 'list_shop', 'get_config', 'update_config', 'delete_land', 'update_land_owner', 'delete_shop_item'];
// 玩家端action:需要token
$playerActions = ['my_lands', 'land_detail', 'add_visitor', 'remove_visitor', 'list_visitors', 'land_shop', 'buy_permission'];
// ★ 玩家端权限操作action
$playerPermActions = ['update_visitor_perm', 'get_visitor_perm'];
// ★ 成员独立权限操作action(领地所有者编辑成员权限)
$memberPermActions = ['get_member_perms', 'update_member_perm', 'clear_member_perm'];
// ★ Java端轮询PHP管理员变更
$syncFromPhpActions = ['poll_admin_changes', 'ack_admin_changes'];
if (in_array($action, $syncActions)) {
if (in_array($action, $syncActions) || in_array($action, $syncFromPhpActions)) {
// 同步类:必须有secret
if (!validateSecret($secret)) {
echo json_encode(['success' => false, 'error' => 'invalid secret']);
@@ -56,7 +62,7 @@ try {
echo json_encode(['success' => false, 'error' => '需要管理员认证']);
exit;
}
} elseif (in_array($action, $playerActions)) {
} elseif (in_array($action, $playerActions) || in_array($action, $playerPermActions) || in_array($action, $memberPermActions)) {
// 玩家端:需要token
$token = $_GET['token'] ?? '';
if (empty($token)) {
@@ -181,6 +187,53 @@ try {
handleLandShop($db, $_GET['name'] ?? '');
break;
// ===== 玩家端:获取访客权限 =====
case 'get_visitor_perm':
handleGetVisitorPerm($db, $playerName, $_GET['land'] ?? '');
break;
// ===== 玩家端:更新访客权限 =====
case 'update_visitor_perm':
if ($method !== 'POST') {
echo json_encode(['success' => false, 'error' => 'POST only']);
exit;
}
handleUpdateVisitorPerm($db, $playerName, $_POST);
break;
// ===== 成员独立权限:获取成员权限列表 =====
case 'get_member_perms':
handleGetMemberPerms($db, $playerName, $_GET['land'] ?? '');
break;
// ===== 成员独立权限:更新单个权限 =====
case 'update_member_perm':
if ($method !== 'POST') {
echo json_encode(['success' => false, 'error' => 'POST only']);
exit;
}
handleUpdateMemberPerm($db, $playerName, $_POST);
break;
// ===== 成员独立权限:清除所有自定义权限 =====
case 'clear_member_perm':
if ($method !== 'POST') {
echo json_encode(['success' => false, 'error' => 'POST only']);
exit;
}
handleClearMemberPerm($db, $playerName, $_POST);
break;
// ===== Java端轮询PHP管理员变更 =====
case 'poll_admin_changes':
handlePollAdminChanges($db, $_GET);
break;
// ===== Java端确认已处理变更 =====
case 'ack_admin_changes':
handleAckAdminChanges($db, $_POST);
break;
default:
echo json_encode(['success' => false, 'error' => 'unknown action']);
}
@@ -237,6 +290,29 @@ function initLandTables($db) {
synced_at INTEGER DEFAULT 0,
UNIQUE(land_id, player_name)
)");
// ★ 管理员变更队列表(PHP→Java同步)
$db->exec("CREATE TABLE IF NOT EXISTS web_admin_changes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
change_type TEXT NOT NULL,
target_id TEXT NOT NULL,
target_name TEXT DEFAULT '',
change_data TEXT DEFAULT '',
created_at INTEGER DEFAULT 0,
acknowledged INTEGER DEFAULT 0,
acked_at INTEGER DEFAULT 0
)");
// ★ 领地所有者变更表(记录PHP端修改的所有者变更)
$db->exec("CREATE TABLE IF NOT EXISTS web_land_owner_changes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
land_id INTEGER NOT NULL,
land_name TEXT NOT NULL,
old_owner TEXT DEFAULT '',
new_owner TEXT NOT NULL,
created_at INTEGER DEFAULT 0,
synced INTEGER DEFAULT 0
)");
}
function handleSyncLands($db, $post) {
@@ -367,10 +443,36 @@ function handleUpdateLandOwner($db, $post) {
return;
}
$stmt = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
$stmt->bindValue(':owner', $owner, SQLITE3_TEXT);
// 获取旧所有者
$stmt = $db->prepare("SELECT owner FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
$stmt->execute();
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
$oldOwner = $row ? ($row['owner'] ?? '') : '';
// 更新所有者
$stmt2 = $db->prepare("UPDATE web_area_lands SET owner = :owner WHERE name = :name");
$stmt2->bindValue(':owner', $owner, SQLITE3_TEXT);
$stmt2->bindValue(':name', $name, SQLITE3_TEXT);
$stmt2->execute();
// 记录变更(用于同步到Java)
if ($oldOwner !== $owner) {
$stmt3 = $db->prepare("INSERT INTO web_land_owner_changes (land_name, old_owner, new_owner, created_at) VALUES (:name, :old, :new, :now)");
$stmt3->bindValue(':name', $name, SQLITE3_TEXT);
$stmt3->bindValue(':old', $oldOwner, SQLITE3_TEXT);
$stmt3->bindValue(':new', $owner, SQLITE3_TEXT);
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt3->execute();
// 同时添加到变更队列
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('owner_change', :id, :name, :data, :now)");
$stmt4->bindValue(':id', (int)($post['id'] ?? 0), SQLITE3_INTEGER);
$stmt4->bindValue(':name', $name, SQLITE3_TEXT);
$stmt4->bindValue(':data', json_encode(['old_owner' => $oldOwner, 'new_owner' => $owner]), SQLITE3_TEXT);
$stmt4->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt4->execute();
}
echo json_encode(['success' => true]);
}
@@ -482,6 +584,18 @@ function handleAddVisitor($db, $playerName, $req) {
return;
}
// ★ 校验玩家名格式(至少3字符,只含字母数字下划线)
if (!preg_match('/^[a-zA-Z0-9_]{3,16}$/', $visitor)) {
echo json_encode(['success' => false, 'error' => '无效的玩家名: ' . $visitor]);
return;
}
// ★ 校验玩家是否存在于数据库(login.db或web用户表)
if (!playerExists($db, $visitor)) {
echo json_encode(['success' => false, 'error' => '玩家不存在: ' . $visitor . ',请确认玩家已注册']);
return;
}
// 获取领地
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
@@ -511,6 +625,46 @@ function handleAddVisitor($db, $playerName, $req) {
echo json_encode(['success' => true, 'message' => "已添加访客: $visitor"]);
}
/**
* ★ 检查玩家是否存在于数据库
*/
function playerExists($db, $playerName) {
// 检查web用户表
try {
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM weblogin_tokens WHERE player = :player");
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row && $row['cnt'] > 0) return true;
} catch (\Throwable $e) {
// 表可能不存在,忽略
}
// 检查users表
try {
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM users WHERE player_name = :player");
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row && $row['cnt'] > 0) return true;
} catch (\Throwable $e) {
// 表可能不存在,忽略
}
// 检查tokens表
try {
$stmt = $db->prepare("SELECT COUNT(*) as cnt FROM tokens WHERE player = :player");
$stmt->bindValue(':player', $playerName, SQLITE3_TEXT);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
if ($row && $row['cnt'] > 0) return true;
} catch (\Throwable $e) {
// 表可能不存在,忽略
}
return false;
}
// ==================== 玩家端:移除访客 ====================
function handleRemoveVisitor($db, $playerName, $req) {
@@ -602,6 +756,338 @@ function handleLandShop($db, $landName) {
echo json_encode(['success' => true, 'items' => $items]);
}
// ==================== 玩家端:获取访客权限 ====================
function handleGetVisitorPerm($db, $playerName, $landName) {
if (empty($landName)) {
echo json_encode(['success' => false, 'error' => '缺少领地名称']);
return;
}
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
$result = $stmt->execute();
$land = $result->fetchArray(SQLITE3_ASSOC);
if (!$land) {
echo json_encode(['success' => false, 'error' => '领地不存在']);
return;
}
// 获取该玩家的权限
$stmt2 = $db->prepare("SELECT * FROM web_area_permissions WHERE land_id = :land_id AND player_name = :player");
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt2->bindValue(':player', $playerName, SQLITE3_TEXT);
$result2 = $stmt2->execute();
$perm = $result2->fetchArray(SQLITE3_ASSOC);
echo json_encode(['success' => true, 'permission' => $perm]);
}
// ==================== 玩家端:更新访客权限 ====================
function handleUpdateVisitorPerm($db, $playerName, $post) {
$landName = $post['land'] ?? '';
$visitor = $post['visitor'] ?? '';
$permissions = $post['permissions'] ?? '';
$role = $post['role'] ?? 'visitor';
if (empty($landName) || empty($visitor)) {
echo json_encode(['success' => false, 'error' => '缺少领地名或玩家名']);
return;
}
// 获取领地
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
$result = $stmt->execute();
$land = $result->fetchArray(SQLITE3_ASSOC);
if (!$land) {
echo json_encode(['success' => false, 'error' => '领地不存在']);
return;
}
// 权限检查:只有所有者能更新权限
if ($land['owner'] !== $playerName) {
echo json_encode(['success' => false, 'error' => '只有领地所有者才能更新权限']);
return;
}
// 更新权限
$stmt2 = $db->prepare("INSERT OR REPLACE INTO web_area_permissions
(land_id, land_name, player_name, role, permissions, granted_at, expires_at, synced_at)
VALUES (:land_id, :land_name, :player, :role, :perms, :now, 0, :now)");
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt2->bindValue(':land_name', $landName, SQLITE3_TEXT);
$stmt2->bindValue(':player', $visitor, SQLITE3_TEXT);
$stmt2->bindValue(':role', $role, SQLITE3_TEXT);
$stmt2->bindValue(':perms', $permissions, SQLITE3_TEXT);
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt2->execute();
echo json_encode(['success' => true, 'message' => "已更新 $visitor 的权限"]);
}
// ==================== 成员独立权限操作 ====================
/**
* 29种权限类型定义
*/
$PERM_TYPES = [
'denyMove' => '移动',
'denyBlockPlace' => '放置方块',
'denyBlockBreak' => '破坏方块',
'denyPVP' => 'PVP战斗',
'denyMount' => '骑乘',
'denyEnderPearl' => '末影珍珠',
'denyThrownProjectiles' => '投掷物',
'denyRaid' => '袭击',
'denyBow' => '弓箭',
'denyPotion' => '药水',
'denyFire' => '点火',
'denyFireSpread' => '火焰蔓延',
'denyPickup' => '拾取物品',
'denyDrop' => '丢弃物品',
'denyExplosion' => '爆炸',
'denyFallDamage' => '摔落伤害',
'denyHunger' => '饥饿',
'denyAllDamage' => '所有伤害',
'denyAllEffects' => '所有效果',
'denyItemFrame' => '物品展示框',
'denyRedstoneInteraction' => '红石交互',
'denyDoorInteraction' => '门禁交互',
'denyNoteblockJukebox' => '音符盒/唱片机',
'denyLead' => '拴绳',
'denyCropHarvest' => '收割作物',
'denyWoolShear' => '剪羊毛',
'denyAnimalFeeding' => '投喂动物',
'denyGlowing' => '发光效果',
'denyPeaceMode' => '和平模式'
];
function handleGetMemberPerms($db, $playerName, $landName) {
global $PERM_TYPES;
if (empty($landName)) {
echo json_encode(['success' => false, 'error' => '缺少领地名称']);
return;
}
// 获取领地
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
$result = $stmt->execute();
$land = $result->fetchArray(SQLITE3_ASSOC);
if (!$land) {
echo json_encode(['success' => false, 'error' => '领地不存在']);
return;
}
// 权限检查:只有所有者能编辑成员权限
if ($land['owner'] !== $playerName) {
echo json_encode(['success' => false, 'error' => '只有领地所有者才能编辑成员权限']);
return;
}
// 获取所有成员及其权限
$stmt2 = $db->prepare("SELECT * FROM web_area_permissions WHERE land_id = :land_id ORDER BY player_name");
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$result2 = $stmt2->execute();
$members = [];
while ($row = $result2->fetchArray(SQLITE3_ASSOC)) {
// 解析权限JSON
$permJson = $row['permissions'] ?? '';
$perms = [];
if (!empty($permJson)) {
$decoded = json_decode($permJson, true);
if (is_array($decoded)) {
$perms = $decoded;
}
}
$row['perm_map'] = $perms;
$members[] = $row;
}
echo json_encode([
'success' => true,
'land' => $land,
'members' => $members,
'perm_types' => $PERM_TYPES
]);
}
function handleUpdateMemberPerm($db, $playerName, $post) {
$landName = $post['land'] ?? '';
$targetPlayer = $post['player'] ?? '';
$permKey = $post['perm'] ?? '';
$enabled = isset($post['enabled']) ? (bool)$post['enabled'] : false;
if (empty($landName) || empty($targetPlayer) || empty($permKey)) {
echo json_encode(['success' => false, 'error' => '缺少参数']);
return;
}
// 获取领地
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
$result = $stmt->execute();
$land = $result->fetchArray(SQLITE3_ASSOC);
if (!$land) {
echo json_encode(['success' => false, 'error' => '领地不存在']);
return;
}
// 权限检查
if ($land['owner'] !== $playerName) {
echo json_encode(['success' => false, 'error' => '只有领地所有者才能编辑成员权限']);
return;
}
// 获取当前权限
$stmt2 = $db->prepare("SELECT * FROM web_area_permissions WHERE land_id = :land_id AND player_name = :player");
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
$result2 = $stmt2->execute();
$perm = $result2->fetchArray(SQLITE3_ASSOC);
if (!$perm) {
echo json_encode(['success' => false, 'error' => '该玩家不是领地成员']);
return;
}
// 解析现有权限
$permMap = [];
$permJson = $perm['permissions'] ?? '';
if (!empty($permJson)) {
$decoded = json_decode($permJson, true);
if (is_array($decoded)) {
$permMap = $decoded;
}
}
// 更新权限
if ($enabled) {
$permMap[$permKey] = true;
} else {
unset($permMap[$permKey]);
}
// 写回数据库
$newJson = json_encode($permMap, JSON_UNESCAPED_UNICODE);
$stmt3 = $db->prepare("UPDATE web_area_permissions SET permissions = :perms, synced_at = :now WHERE land_id = :land_id AND player_name = :player");
$stmt3->bindValue(':perms', $newJson, SQLITE3_TEXT);
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt3->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt3->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
$stmt3->execute();
echo json_encode(['success' => true, 'message' => "已更新 $targetPlayer 的 $permKey 权限"]);
}
function handleClearMemberPerm($db, $playerName, $post) {
$landName = $post['land'] ?? '';
$targetPlayer = $post['player'] ?? '';
if (empty($landName) || empty($targetPlayer)) {
echo json_encode(['success' => false, 'error' => '缺少参数']);
return;
}
// 获取领地
$stmt = $db->prepare("SELECT * FROM web_area_lands WHERE name = :name");
$stmt->bindValue(':name', $landName, SQLITE3_TEXT);
$result = $stmt->execute();
$land = $result->fetchArray(SQLITE3_ASSOC);
if (!$land) {
echo json_encode(['success' => false, 'error' => '领地不存在']);
return;
}
// 权限检查
if ($land['owner'] !== $playerName) {
echo json_encode(['success' => false, 'error' => '只有领地所有者才能清除成员权限']);
return;
}
// 获取旧权限
$stmt_old = $db->prepare("SELECT permissions FROM web_area_permissions WHERE land_id = :land_id AND player_name = :player");
$stmt_old->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt_old->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
$result_old = $stmt_old->execute();
$old_perm = $result_old->fetchArray(SQLITE3_ASSOC);
$old_perms = $old_perm ? ($old_perm['permissions'] ?? '') : '';
// 清除权限
$stmt2 = $db->prepare("UPDATE web_area_permissions SET permissions = '', synced_at = :now WHERE land_id = :land_id AND player_name = :player");
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
$stmt2->execute();
// 记录变更
if (!empty($old_perms)) {
$stmt3 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_clear', :id, :name, :data, :now)");
$stmt3->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
$stmt3->bindValue(':name', $targetPlayer, SQLITE3_TEXT);
$stmt3->bindValue(':data', json_encode(['land_name' => $landName, 'old_permissions' => $old_perms]), SQLITE3_TEXT);
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
$stmt3->execute();
}
echo json_encode(['success' => true, 'message' => "已清除 $targetPlayer 的所有自定义权限"]);
}
// ==================== Java端轮询PHP管理员变更 ====================
function handlePollAdminChanges($db, $get) {
$lastId = (int)($get['last_id'] ?? 0);
$limit = min((int)($get['limit'] ?? 50), 100);
// 获取未确认的变更
$stmt = $db->prepare("SELECT * FROM web_admin_changes WHERE id > :last_id AND acknowledged = 0 ORDER BY id ASC LIMIT :limit");
$stmt->bindValue(':last_id', $lastId, SQLITE3_INTEGER);
$stmt->bindValue(':limit', $limit, SQLITE3_INTEGER);
$result = $stmt->execute();
$changes = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$changes[] = $row;
}
echo json_encode(['success' => true, 'changes' => $changes, 'count' => count($changes)]);
}
function handleAckAdminChanges($db, $post) {
$ids = $post['ids'] ?? '';
if (empty($ids)) {
echo json_encode(['success' => false, 'error' => 'missing ids']);
return;
}
$idList = array_filter(array_map('intval', explode(',', $ids)));
if (empty($idList)) {
echo json_encode(['success' => false, 'error' => 'invalid ids']);
return;
}
$now = time();
$placeholders = implode(',', array_fill(0, count($idList), '?'));
$stmt = $db->prepare("UPDATE web_admin_changes SET acknowledged = 1, acked_at = :now WHERE id IN ($placeholders)");
$stmt->bindValue(':now', $now, SQLITE3_INTEGER);
$i = 1;
foreach ($idList as $id) {
$stmt->bindValue($i++, $id, SQLITE3_INTEGER);
}
$stmt->execute();
echo json_encode(['success' => true, 'acked' => count($idList)]);
}
function validateSecret($secret) {
if (empty($secret)) return false;
$validSecrets = [
+197 -1
View File
@@ -2062,7 +2062,7 @@ if ($currentVersion !== $BUILD_VERSION) {
// ==================== 领地管理 ====================
let landsState = { view: 'list', currentLand: null };
let landsState = { view: 'list', currentLand: null, currentMember: null };
async function renderLands(el) {
if (!TOKEN) {
@@ -2080,6 +2080,16 @@ async function renderLands(el) {
return;
}
if (landsState.view === 'member_perms' && landsState.currentLand) {
await renderMemberPerms(el, landsState.currentLand);
return;
}
if (landsState.view === 'member_perm_detail' && landsState.currentLand && landsState.currentMember) {
await renderMemberPermDetail(el, landsState.currentLand, landsState.currentMember);
return;
}
// 加载我的领地列表
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载领地中...</p></div>`;
try {
@@ -2226,6 +2236,7 @@ async function renderLandDetail(el, landName) {
html += `<div style="margin-bottom:16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h3 style="margin:0;color:var(--fg)">👥 访客管理 (${visitors.length})</h3>
<button class="btn" style="font-size:12px;padding:4px 12px" onclick="landsState.view='member_perms';landsState.currentLand='${escHtml(land.name)}';renderLands(document.getElementById('content'))">🎯 成员权限</button>
</div>
<div style="display:flex;gap:8px;margin-bottom:12px">
<input type="text" id="newVisitorName" placeholder="输入玩家名" style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px">
@@ -2254,6 +2265,59 @@ async function renderLandDetail(el, landName) {
}
}
async function renderMemberPerms(el, landName) {
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载成员权限...</p></div>`;
try {
const url = new URL(API + 'land_api.php', location.href);
url.searchParams.set('action', 'get_member_perms');
url.searchParams.set('token', TOKEN);
url.searchParams.set('land', landName);
const res = await fetch(url);
const data = await res.json();
if (!data.success) {
el.innerHTML = `<div class="card"><p style="color:var(--red)">${data.error}</p><button class="btn" onclick="landsState.view='detail';renderLands(document.getElementById('content'))">← 返回</button></div>`;
return;
}
const land = data.land;
const members = data.members || [];
const permTypes = data.perm_types || {};
let html = `<div class="card">
<button class="btn" onclick="landsState.view='detail';landsState.currentLand='${escHtml(landName)}';landsState.currentMember=null;renderLands(document.getElementById('content'))" style="margin-bottom:12px;font-size:13px">← 返回领地详情</button>
<h2 style="margin:0 0 8px;color:var(--fg)">🎯 ${escHtml(landName)} 成员权限</h2>
<p style="color:var(--dim);font-size:13px;margin-bottom:16px">为每个成员设置独立的权限覆盖。启用=覆盖默认值,禁用=使用领地默认值。</p>`;
if (members.length === 0) {
html += `<div style="text-align:center;padding:32px;color:var(--dim)">
<div style="font-size:40px;margin-bottom:12px">👥</div>
<p>暂无成员</p>
<p style="font-size:12px;margin-top:8px">请先在访客管理中添加成员</p>
</div>`;
} else {
html += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px">`;
for (const member of members) {
const permCount = member.perm_map ? Object.keys(member.perm_map).length : 0;
const hasCustom = permCount > 0;
html += `<div onclick="landsState.view='member_perm_detail';landsState.currentMember='${escHtml(member.player_name)}';renderLands(document.getElementById('content'))"
style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:16px;cursor:pointer;transition:all 0.2s"
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<h3 style="margin:0;color:var(--fg);font-size:15px">👤 ${escHtml(member.player_name)}</h3>
${hasCustom ? '<span style="background:var(--accent);color:#fff;padding:2px 8px;border-radius:12px;font-size:11px">★ 自定义</span>' : '<span style="color:var(--dim);font-size:12px">默认权限</span>'}
</div>
<div style="font-size:12px;color:var(--dim)">${permCount} 项自定义权限</div>
</div>`;
}
html += `</div>`;
}
html += `</div>`;
el.innerHTML = html;
} catch (e) {
el.innerHTML = `<div class="card"><p style="color:var(--red)">加载失败: ${e.message}</p></div>`;
}
}
async function addLandVisitor(landName) {
const input = document.getElementById('newVisitorName');
if (!input) return;
@@ -2304,6 +2368,138 @@ function buyLandPermission(landName, itemId) {
glassAlert('请在游戏中使用 /protect shop 购买领地权限', '🎮');
}
async function renderMemberPermDetail(el, landName, targetPlayer) {
el.innerHTML = `<div class="card"><p style="color:var(--dim)">加载权限详情...</p></div>`;
try {
const url = new URL(API + 'land_api.php', location.href);
url.searchParams.set('action', 'get_member_perms');
url.searchParams.set('token', TOKEN);
url.searchParams.set('land', landName);
const res = await fetch(url);
const data = await res.json();
if (!data.success) {
el.innerHTML = `<div class="card"><p style="color:var(--red)">${data.error}</p><button class="btn" onclick="landsState.view='member_perms';renderLands(document.getElementById('content'))">← 返回</button></div>`;
return;
}
const land = data.land;
const permTypes = data.perm_types || {};
const members = data.members || [];
const member = members.find(m => m.player_name === targetPlayer);
if (!member) {
el.innerHTML = `<div class="card"><p style="color:var(--red)">成员不存在</p><button class="btn" onclick="landsState.view='member_perms';renderLands(document.getElementById('content'))">← 返回</button></div>`;
return;
}
const permMap = member.perm_map || {};
const permEntries = Object.entries(permTypes);
let html = `<div class="card">
<button class="btn" onclick="landsState.view='member_perms';landsState.currentMember=null;renderLands(document.getElementById('content'))" style="margin-bottom:12px;font-size:13px">← 返回成员列表</button>
<h2 style="margin:0 0 8px;color:var(--fg)">👤 ${escHtml(targetPlayer)} 权限</h2>
<p style="color:var(--dim);font-size:13px;margin-bottom:16px">在 ${escHtml(landName)} 中的独立权限设置</p>
<div style="display:flex;gap:8px;margin-bottom:16px">
<button class="btn" onclick="clearAllMemberPerms('${escHtml(landName)}','${escHtml(targetPlayer)}')" style="color:var(--red)">🗑️ 清除所有自定义</button>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px">`;
for (const [key, label] of permEntries) {
const isCustom = permMap.hasOwnProperty(key);
const enabled = isCustom ? permMap[key] : false;
const defaultVal = land[key] || false;
html += `<div onclick="toggleMemberPerm('${escHtml(landName)}','${escHtml(targetPlayer)}','${key}',this)"
style="background:${isCustom ? (enabled ? 'rgba(34,197,94,0.1)' : 'rgba(239,68,68,0.1)') : 'var(--bg)'};
border:1px solid ${isCustom ? (enabled ? 'var(--green)' : 'var(--red)') : 'var(--border)'};
border-radius:8px;padding:12px;cursor:pointer;transition:all 0.2s">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
<span style="color:var(--fg);font-size:13px;font-weight:500">${label}</span>
<span style="font-size:11px;color:${isCustom ? (enabled ? 'var(--green)' : 'var(--red)') : 'var(--dim)'}">
${isCustom ? (enabled ? '✓ 启用' : '✗ 禁用') : '默认'}
</span>
</div>
<div style="font-size:11px;color:var(--dim)">
${isCustom ? '★ 自定义' : `领地默认: ${defaultVal ? '禁止' : '允许'}`}
</div>
</div>`;
}
html += `</div></div>`;
el.innerHTML = html;
} catch (e) {
el.innerHTML = `<div class="card"><p style="color:var(--red)">加载失败: ${e.message}</p></div>`;
}
}
async function toggleMemberPerm(landName, targetPlayer, permKey, el) {
try {
const url = new URL(API + 'land_api.php', location.href);
url.searchParams.set('action', 'update_member_perm');
url.searchParams.set('token', TOKEN);
const body = new URLSearchParams();
body.set('land', landName);
body.set('player', targetPlayer);
body.set('perm', permKey);
// 从元素中获取当前状态
const statusSpan = el.querySelector('span:last-child');
const isCustom = statusSpan.textContent.includes('自定义');
const isEnabled = statusSpan.textContent.includes('启用');
if (isCustom) {
// 已有自定义:切换启用/禁用
body.set('enabled', (!isEnabled).toString());
} else {
// 无自定义:设置为启用
body.set('enabled', 'true');
}
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
const data = await res.json();
if (data.success) {
// 刷新页面
await renderMemberPermDetail(document.getElementById('content'), landName, targetPlayer);
} else {
glassAlert(data.error || '操作失败');
}
} catch (e) {
glassAlert('操作失败: ' + e.message);
}
}
async function clearAllMemberPerms(landName, targetPlayer) {
if (!await glassConfirm(`确定要清除 ${targetPlayer} 的所有自定义权限吗?`)) return;
try {
const url = new URL(API + 'land_api.php', location.href);
url.searchParams.set('action', 'clear_member_perm');
url.searchParams.set('token', TOKEN);
const body = new URLSearchParams();
body.set('land', landName);
body.set('player', targetPlayer);
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
const data = await res.json();
if (data.success) {
glassAlert('已清除所有自定义权限');
await renderMemberPermDetail(document.getElementById('content'), landName, targetPlayer);
} else {
glassAlert(data.error || '操作失败');
}
} catch (e) {
glassAlert('操作失败: ' + e.message);
}
}
function escHtml(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
</script>