修复: 权限管理多项问题
1. 权限字段补齐: PHP $PERM_TYPES对齐Java全部32个权限键 - 新增denyEntityInteract(实体交互)、denyMobAttack(攻击生物) - 修正denyPeaceMode→peaceMode(键名与Java对齐) - 数据迁移: 自动重命名DB中已有的denyPeaceMode为peaceMode 2. 权限编辑二态化: 成员权限只有允许/禁止,不存在"领地默认" - 移除前端"默认"状态,改为允许(green)/禁止(red)二态切换 - 后端: 禁用时存false而非删除键,确保Java端正确识别 3. 角色切换修复: 移除visitor选项(访客=未授权玩家) - 下拉菜单只保留admin/member - 确认对话框文案对应调整 4. 同步删除逻辑: handleSyncPermissions新增清理机制 - 删除incoming列表中不存在的旧记录(120秒宽限期) - synced_at=NULL的记录不参与删除(防误删PHP本地创建) - role CASE简化为role=excluded.role(信任Java为角色权威源) 5. 部署: json_encode加JSON_UNESCAPED_UNICODE防双重编码
This commit is contained in:
+81
-33
@@ -36,28 +36,31 @@ function convertEffectsForFrontend($row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ★ 29种权限类型定义(必须在try之前,否则handleGetMemberPerms调用时变量未定义)
|
// ★ 29种权限类型定义(必须在try之前,否则handleGetMemberPerms调用时变量未定义)
|
||||||
|
// ★ 与Java AreaProtection.getLandDefaultDeny 完全对齐(32个per-player权限键)
|
||||||
$PERM_TYPES = [
|
$PERM_TYPES = [
|
||||||
'denyMove' => '移动',
|
'denyMove' => '移动',
|
||||||
'denyBlockPlace' => '放置方块',
|
'denyBlockPlace' => '放置方块',
|
||||||
'denyBlockBreak' => '破坏方块',
|
'denyBlockBreak' => '破坏方块',
|
||||||
'denyContainer' => '容器管理',
|
'denyContainer' => '容器管理',
|
||||||
'denyPVP' => 'PVP战斗',
|
'denyPVP' => 'PVP战斗',
|
||||||
'denyMount' => '骑乘',
|
|
||||||
'denyEnderPearl' => '末影珍珠',
|
|
||||||
'denyThrownProjectiles' => '投掷物',
|
|
||||||
'denyRaid' => '袭击',
|
|
||||||
'denyBow' => '弓箭',
|
|
||||||
'denyPotion' => '药水',
|
|
||||||
'denyFire' => '点火',
|
|
||||||
'denyFireSpread' => '火焰蔓延',
|
|
||||||
'denyPickup' => '拾取物品',
|
|
||||||
'denyDrop' => '丢弃物品',
|
|
||||||
'denyExplosion' => '爆炸',
|
|
||||||
'denyFallDamage' => '摔落伤害',
|
'denyFallDamage' => '摔落伤害',
|
||||||
'denyHunger' => '饥饿',
|
'denyHunger' => '饥饿',
|
||||||
'denyAllDamage' => '所有伤害',
|
'denyAllDamage' => '所有伤害',
|
||||||
|
'denyDrop' => '丢弃物品',
|
||||||
|
'denyMount' => '骑乘',
|
||||||
|
'denyEnderPearl' => '末影珍珠',
|
||||||
|
'denyBow' => '弓箭',
|
||||||
|
'denyPotion' => '药水',
|
||||||
|
'denyExplosion' => '爆炸',
|
||||||
|
'denyRaid' => '袭击',
|
||||||
|
'denyFireSpread' => '火焰蔓延',
|
||||||
'denyAllEffects' => '所有效果',
|
'denyAllEffects' => '所有效果',
|
||||||
'denyItemFrame' => '物品展示框',
|
'denyItemFrame' => '物品展示框',
|
||||||
|
'denyEntityInteract' => '实体交互',
|
||||||
|
'denyPickup' => '拾取物品',
|
||||||
|
'denyFire' => '点火',
|
||||||
|
'denyThrownProjectiles' => '投掷物',
|
||||||
|
'denyGlowing' => '发光效果',
|
||||||
'denyRedstoneInteraction' => '红石交互',
|
'denyRedstoneInteraction' => '红石交互',
|
||||||
'denyDoorInteraction' => '门禁交互',
|
'denyDoorInteraction' => '门禁交互',
|
||||||
'denyNoteblockJukebox' => '音符盒/唱片机',
|
'denyNoteblockJukebox' => '音符盒/唱片机',
|
||||||
@@ -65,8 +68,8 @@ $PERM_TYPES = [
|
|||||||
'denyCropHarvest' => '收割作物',
|
'denyCropHarvest' => '收割作物',
|
||||||
'denyWoolShear' => '剪羊毛',
|
'denyWoolShear' => '剪羊毛',
|
||||||
'denyAnimalFeeding' => '投喂动物',
|
'denyAnimalFeeding' => '投喂动物',
|
||||||
'denyGlowing' => '发光效果',
|
'denyMobAttack' => '攻击生物',
|
||||||
'denyPeaceMode' => '和平模式'
|
'peaceMode' => '和平模式',
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -567,6 +570,22 @@ function initLandTables($db) {
|
|||||||
try { $db->exec("ALTER TABLE web_area_lands ADD COLUMN $col $type"); } catch (\Throwable $e) {}
|
try { $db->exec("ALTER TABLE web_area_lands ADD COLUMN $col $type"); } catch (\Throwable $e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 数据迁移:修复旧键名 denyPeaceMode → peaceMode(对齐Java端key)
|
||||||
|
try {
|
||||||
|
$rows = $db->query("SELECT id, permissions FROM web_area_permissions WHERE permissions LIKE '%denyPeaceMode%'");
|
||||||
|
while ($row = $rows->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$decoded = json_decode($row['permissions'], true);
|
||||||
|
if (is_array($decoded) && isset($decoded['denyPeaceMode'])) {
|
||||||
|
$decoded['peaceMode'] = $decoded['denyPeaceMode'];
|
||||||
|
unset($decoded['denyPeaceMode']);
|
||||||
|
$stmtFix = $db->prepare("UPDATE web_area_permissions SET permissions = :perms WHERE id = :id");
|
||||||
|
$stmtFix->bindValue(':perms', json_encode($decoded, JSON_UNESCAPED_UNICODE), SQLITE3_TEXT);
|
||||||
|
$stmtFix->bindValue(':id', (int)$row['id'], SQLITE3_INTEGER);
|
||||||
|
$stmtFix->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) { /* 表可能还不存在 */ }
|
||||||
|
|
||||||
$db->exec("CREATE TABLE IF NOT EXISTS web_area_shop (
|
$db->exec("CREATE TABLE IF NOT EXISTS web_area_shop (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
land_id INTEGER NOT NULL,
|
land_id INTEGER NOT NULL,
|
||||||
@@ -1034,12 +1053,9 @@ function handleSyncPermissions($db, $post) {
|
|||||||
granted_at = excluded.granted_at,
|
granted_at = excluded.granted_at,
|
||||||
expires_at = excluded.expires_at,
|
expires_at = excluded.expires_at,
|
||||||
synced_at = excluded.synced_at,
|
synced_at = excluded.synced_at,
|
||||||
role = CASE
|
role = excluded.role");
|
||||||
WHEN excluded.role IN ('admin', 'member') THEN excluded.role
|
|
||||||
WHEN excluded.role = 'visitor' AND web_area_permissions.role IN ('admin', 'member') THEN web_area_permissions.role
|
|
||||||
ELSE excluded.role
|
|
||||||
END");
|
|
||||||
|
|
||||||
|
$syncedKeys = [];
|
||||||
foreach ($perms as $p) {
|
foreach ($perms as $p) {
|
||||||
$stmt->bindValue(':land_id', (int)($p['land_id'] ?? 0), SQLITE3_INTEGER);
|
$stmt->bindValue(':land_id', (int)($p['land_id'] ?? 0), SQLITE3_INTEGER);
|
||||||
$stmt->bindValue(':land_name', $p['land_name'] ?? '', SQLITE3_TEXT);
|
$stmt->bindValue(':land_name', $p['land_name'] ?? '', SQLITE3_TEXT);
|
||||||
@@ -1050,9 +1066,40 @@ function handleSyncPermissions($db, $post) {
|
|||||||
$stmt->bindValue(':expires', (int)($p['expires_at'] ?? 0), SQLITE3_INTEGER);
|
$stmt->bindValue(':expires', (int)($p['expires_at'] ?? 0), SQLITE3_INTEGER);
|
||||||
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
$syncedKeys[] = ((int)($p['land_id'] ?? 0)) . ':' . ($p['player_name'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'count' => count($perms)]);
|
// ★ 删除PHP中有但Java同步列表中不存在的旧记录(120秒宽限期防误删)
|
||||||
|
// NULL synced_at 的记录视为"PHP本地创建,从未被Java同步过",不删
|
||||||
|
$deleted = 0;
|
||||||
|
if (!empty($syncedKeys)) {
|
||||||
|
$stmtAll = $db->prepare("SELECT id, land_id, player_name, synced_at FROM web_area_permissions");
|
||||||
|
$allRows = $stmtAll->execute()->fetchArray(SQLITE3_ASSOC);
|
||||||
|
$toDelete = [];
|
||||||
|
while ($allRows) {
|
||||||
|
$key = $allRows['land_id'] . ':' . $allRows['player_name'];
|
||||||
|
if (!in_array($key, $syncedKeys)) {
|
||||||
|
$syncedAt = $allRows['synced_at'] ?? null;
|
||||||
|
// NULL synced_at = PHP本地创建,从未被Java同步过 → 不删
|
||||||
|
// synced_at非NULL但超过120秒 → 确认Java已不包含此记录 → 删
|
||||||
|
if ($syncedAt !== null && ($now - (int)$syncedAt) > 120) {
|
||||||
|
$toDelete[] = (int)$allRows['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$allRows = $stmtAll->execute()->fetchArray(SQLITE3_ASSOC);
|
||||||
|
}
|
||||||
|
if (!empty($toDelete)) {
|
||||||
|
$delPlaceholders = implode(',', array_fill(0, count($toDelete), '?'));
|
||||||
|
$stmtDel = $db->prepare("DELETE FROM web_area_permissions WHERE id IN ($delPlaceholders)");
|
||||||
|
foreach ($toDelete as $idx => $id) {
|
||||||
|
$stmtDel->bindValue($idx + 1, $id, SQLITE3_INTEGER);
|
||||||
|
}
|
||||||
|
$stmtDel->execute();
|
||||||
|
$deleted = count($toDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'count' => count($perms), 'deleted' => $deleted]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 玩家端:我拥有的领地 ====================
|
// ==================== 玩家端:我拥有的领地 ====================
|
||||||
@@ -1157,7 +1204,7 @@ function handleAddVisitor($db, $playerName, $req) {
|
|||||||
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at, status) VALUES ('add_visitor', :id, :name, :data, :now, 'pending')");
|
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at, status) VALUES ('add_visitor', :id, :name, :data, :now, 'pending')");
|
||||||
$stmt4->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
$stmt4->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
$stmt4->bindValue(':name', $landName, SQLITE3_TEXT);
|
$stmt4->bindValue(':name', $landName, SQLITE3_TEXT);
|
||||||
$stmt4->bindValue(':data', json_encode(['player' => $visitor, 'land_name' => $landName, 'role' => 'visitor', 'source' => 'admin_panel'], JSON_UNESCAPED_UNICODE), SQLITE3_TEXT);
|
$stmt4->bindValue(':data', json_encode(['player' => $visitor, 'land_name' => $landName, 'role' => 'member', 'source' => 'admin_panel'], JSON_UNESCAPED_UNICODE), SQLITE3_TEXT);
|
||||||
$stmt4->bindValue(':now', time(), SQLITE3_INTEGER);
|
$stmt4->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||||
$stmt4->execute();
|
$stmt4->execute();
|
||||||
|
|
||||||
@@ -1603,7 +1650,7 @@ function handleChangeVisitorRole($db, $playerName, $post) {
|
|||||||
$stmtIns->bindValue(':now', time(), SQLITE3_INTEGER);
|
$stmtIns->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||||
$stmtIns->execute();
|
$stmtIns->execute();
|
||||||
// ★ 新成员走 add_visitor 流程,确保 Java 端加入白名单
|
// ★ 新成员走 add_visitor 流程,确保 Java 端加入白名单
|
||||||
$changeData = json_encode(['player' => $visitor, 'role' => $newRole, 'land_name' => $landName]);
|
$changeData = json_encode(['player' => $visitor, 'role' => $newRole, 'land_name' => $landName], JSON_UNESCAPED_UNICODE);
|
||||||
$stmtC = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('add_visitor', :id, :name, :data, :now)");
|
$stmtC = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('add_visitor', :id, :name, :data, :now)");
|
||||||
$stmtC->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
$stmtC->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
$stmtC->bindValue(':name', $landName, SQLITE3_TEXT);
|
$stmtC->bindValue(':name', $landName, SQLITE3_TEXT);
|
||||||
@@ -1626,7 +1673,7 @@ function handleChangeVisitorRole($db, $playerName, $post) {
|
|||||||
$now = time();
|
$now = time();
|
||||||
$oldPerms = $permRow ? ($permRow['permissions'] ?? '') : '';
|
$oldPerms = $permRow ? ($permRow['permissions'] ?? '') : '';
|
||||||
error_log("[领地权限][切换角色] 已写入变更队列 role=$newRole oldPermsLen=" . strlen($oldPerms));
|
error_log("[领地权限][切换角色] 已写入变更队列 role=$newRole oldPermsLen=" . strlen($oldPerms));
|
||||||
$changeData = json_encode(['player' => $visitor, 'role' => $newRole, 'land_name' => $landName, 'permissions' => $oldPerms]);
|
$changeData = json_encode(['player' => $visitor, 'role' => $newRole, 'land_name' => $landName, 'permissions' => $oldPerms], JSON_UNESCAPED_UNICODE);
|
||||||
$stmt3 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_change', :id, :name, :data, :now)");
|
$stmt3 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_change', :id, :name, :data, :now)");
|
||||||
$stmt3->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
$stmt3->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
$stmt3->bindValue(':name', $landName, SQLITE3_TEXT);
|
$stmt3->bindValue(':name', $landName, SQLITE3_TEXT);
|
||||||
@@ -1791,7 +1838,7 @@ function handleUpdateVisitorPerm($db, $playerName, $post) {
|
|||||||
$stmt2->execute();
|
$stmt2->execute();
|
||||||
|
|
||||||
// ★ 写入web_admin_changes以便Java同步(带 role,确保Java端不会把访客误写为 member)
|
// ★ 写入web_admin_changes以便Java同步(带 role,确保Java端不会把访客误写为 member)
|
||||||
$changeData = json_encode(['land_name' => $landName, 'permissions' => $permissions, 'role' => $role]);
|
$changeData = json_encode(['land_name' => $landName, 'permissions' => $permissions, 'role' => $role], JSON_UNESCAPED_UNICODE);
|
||||||
error_log("[领地权限][更新访客权限] 放行写入: land=$landName visitor=$visitor role=$role permsLen=" . strlen($permissions) . " changeData=$changeData");
|
error_log("[领地权限][更新访客权限] 放行写入: land=$landName visitor=$visitor role=$role permsLen=" . strlen($permissions) . " changeData=$changeData");
|
||||||
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_change', :id, :name, :data, :now)");
|
$stmt4 = $db->prepare("INSERT INTO web_admin_changes (change_type, target_id, target_name, change_data, created_at) VALUES ('perm_change', :id, :name, :data, :now)");
|
||||||
$stmt4->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
$stmt4->bindValue(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
@@ -1862,7 +1909,7 @@ function handleUpdateMemberPerm($db, $playerName, $post) {
|
|||||||
$landName = $post['land'] ?? '';
|
$landName = $post['land'] ?? '';
|
||||||
$targetPlayer = $post['player'] ?? '';
|
$targetPlayer = $post['player'] ?? '';
|
||||||
$permKey = $post['perm'] ?? '';
|
$permKey = $post['perm'] ?? '';
|
||||||
$enabled = isset($post['enabled']) ? (bool)$post['enabled'] : false;
|
$enabled = filter_var($post['enabled'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
if (empty($landName) || empty($targetPlayer) || empty($permKey)) {
|
if (empty($landName) || empty($targetPlayer) || empty($permKey)) {
|
||||||
echo json_encode(['success' => false, 'error' => '缺少参数']);
|
echo json_encode(['success' => false, 'error' => '缺少参数']);
|
||||||
@@ -1908,12 +1955,8 @@ function handleUpdateMemberPerm($db, $playerName, $post) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新权限
|
// 更新权限(成员权限只有两种状态:启用true / 禁用false,不存在"默认")
|
||||||
if ($enabled) {
|
$permMap[$permKey] = (bool)$enabled;
|
||||||
$permMap[$permKey] = true;
|
|
||||||
} else {
|
|
||||||
unset($permMap[$permKey]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写回数据库(不改变角色,只改权限)
|
// 写回数据库(不改变角色,只改权限)
|
||||||
$newJson = json_encode($permMap, JSON_UNESCAPED_UNICODE);
|
$newJson = json_encode($permMap, JSON_UNESCAPED_UNICODE);
|
||||||
@@ -1975,8 +2018,13 @@ function handleClearMemberPerm($db, $playerName, $post) {
|
|||||||
$old_perm = $result_old->fetchArray(SQLITE3_ASSOC);
|
$old_perm = $result_old->fetchArray(SQLITE3_ASSOC);
|
||||||
$old_perms = $old_perm ? ($old_perm['permissions'] ?? '') : '';
|
$old_perms = $old_perm ? ($old_perm['permissions'] ?? '') : '';
|
||||||
|
|
||||||
// 清除权限
|
// 清除权限(全部设为禁用 false,而非清空回到"默认")
|
||||||
$stmt2 = $db->prepare("UPDATE web_area_permissions SET permissions = '', synced_at = :now WHERE land_id = :land_id AND player_name = :player");
|
global $PERM_TYPES;
|
||||||
|
$resetPerms = [];
|
||||||
|
foreach (array_keys($PERM_TYPES) as $k) { $resetPerms[$k] = false; }
|
||||||
|
$newJson = json_encode($resetPerms, JSON_UNESCAPED_UNICODE);
|
||||||
|
$stmt2 = $db->prepare("UPDATE web_area_permissions SET permissions = :perms, synced_at = :now WHERE land_id = :land_id AND player_name = :player");
|
||||||
|
$stmt2->bindValue(':perms', $newJson, SQLITE3_TEXT);
|
||||||
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
|
$stmt2->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||||
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
$stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
$stmt2->bindValue(':player', $targetPlayer, SQLITE3_TEXT);
|
||||||
@@ -1987,7 +2035,7 @@ function handleClearMemberPerm($db, $playerName, $post) {
|
|||||||
$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 = $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(':id', (int)$land['id'], SQLITE3_INTEGER);
|
||||||
$stmt3->bindValue(':name', $targetPlayer, SQLITE3_TEXT);
|
$stmt3->bindValue(':name', $targetPlayer, SQLITE3_TEXT);
|
||||||
$stmt3->bindValue(':data', json_encode(['land_name' => $landName, 'old_permissions' => $old_perms]), SQLITE3_TEXT);
|
$stmt3->bindValue(':data', json_encode(['land_name' => $landName, 'old_permissions' => $old_perms], JSON_UNESCAPED_UNICODE), SQLITE3_TEXT);
|
||||||
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
|
$stmt3->bindValue(':now', time(), SQLITE3_INTEGER);
|
||||||
$stmt3->execute();
|
$stmt3->execute();
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-41
@@ -2852,16 +2852,15 @@ async function renderLandDetail(el, landName) {
|
|||||||
html += `<table class="table"><thead><tr><th>玩家名</th><th>角色</th><th>授权时间</th><th>操作</th></tr></thead><tbody>`;
|
html += `<table class="table"><thead><tr><th>玩家名</th><th>角色</th><th>授权时间</th><th>操作</th></tr></thead><tbody>`;
|
||||||
for (const v of visitors) {
|
for (const v of visitors) {
|
||||||
const grantDate = v.granted_at ? new Date(v.granted_at * 1000).toLocaleString('zh-CN') : '未知';
|
const grantDate = v.granted_at ? new Date(v.granted_at * 1000).toLocaleString('zh-CN') : '未知';
|
||||||
const isAdmin = v.role === 'admin';
|
const roleMap = {admin: {label:'👑 管理员', color:'var(--accent)', bg:'rgba(99,102,241,0.15)'}, member: {label:'🔧 成员', color:'#f59e0b', bg:'rgba(245,158,11,0.15)'}, visitor: {label:'👤 访客', color:'var(--dim)', bg:'rgba(107,114,128,0.1)'}};
|
||||||
const roleColor = isAdmin ? 'var(--accent)' : 'var(--dim)';
|
const r = roleMap[v.role] || roleMap.member;
|
||||||
const roleBg = isAdmin ? 'rgba(99,102,241,0.15)' : 'rgba(107,114,128,0.1)';
|
|
||||||
const nextRoleLabel = isAdmin ? '设为访客' : '设为管理员';
|
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td><strong>${escHtml(v.player_name)}</strong> <button class="btn" style="font-size:10px;padding:1px 6px;color:var(--accent);border-color:var(--accent)" onclick="event.stopPropagation();landsState.view='member_perm_detail';landsState.currentLand='${escHtml(land.name)}';landsState.currentMember='${escHtml(v.player_name)}';renderLands(document.getElementById('content'))">权限</button></td>
|
<td><strong>${escHtml(v.player_name)}</strong> <button class="btn" style="font-size:10px;padding:1px 6px;color:var(--accent);border-color:var(--accent)" onclick="event.stopPropagation();landsState.view='member_perm_detail';landsState.currentLand='${escHtml(land.name)}';landsState.currentMember='${escHtml(v.player_name)}';renderLands(document.getElementById('content'))">权限</button></td>
|
||||||
<td><span onclick="changeVisitorRole('${escHtml(land.name)}','${escHtml(v.player_name)}','${escHtml(v.role)}')"
|
<td><select onchange="if(this.value &&this.value!=='${v.role}')changeVisitorRole('${escHtml(land.name)}','${escHtml(v.player_name)}',this.value);this.value='${v.role}'"
|
||||||
style="cursor:pointer;padding:2px 8px;border-radius:12px;font-size:12px;background:${roleBg};color:${roleColor};border:1px solid ${roleColor};transition:all 0.2s"
|
style="cursor:pointer;padding:2px 6px;border-radius:12px;font-size:12px;background:${r.bg};color:${r.color};border:1px solid ${r.color};transition:all 0.2s;appearance:auto;-webkit-appearance:auto">
|
||||||
onmouseover="this.style.opacity='0.7'" onmouseout="this.style.opacity='1'"
|
<option value="admin" ${v.role==='admin'?'selected':''}>👑 管理员</option>
|
||||||
title="点击${nextRoleLabel}">${isAdmin ? '👑 管理员' : '👤 访客'}</span></td>
|
<option value="member" ${v.role==='member'?'selected':''}>🔧 成员</option>
|
||||||
|
</select></td>
|
||||||
<td style="font-size:12px;color:var(--dim)">${grantDate}</td>
|
<td style="font-size:12px;color:var(--dim)">${grantDate}</td>
|
||||||
<td><button class="btn" style="color:var(--red);font-size:11px;padding:3px 8px" onclick="removeLandVisitor('${escHtml(land.name)}','${escHtml(v.player_name)}')">移除</button></td>
|
<td><button class="btn" style="color:var(--red);font-size:11px;padding:3px 8px" onclick="removeLandVisitor('${escHtml(land.name)}','${escHtml(v.player_name)}')">移除</button></td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@@ -2973,6 +2972,7 @@ async function renderLandDetail(el, landName) {
|
|||||||
['deny_fluid', '流体放置', false],
|
['deny_fluid', '流体放置', false],
|
||||||
['allow_visitor_teleport', '允许传送', true],
|
['allow_visitor_teleport', '允许传送', true],
|
||||||
['is_public_building', '公共建筑设施', true],
|
['is_public_building', '公共建筑设施', true],
|
||||||
|
['peace_mode', '和平模式(禁生物)', false],
|
||||||
];
|
];
|
||||||
|
|
||||||
html += `<div class="card" style="margin-top:12px">
|
html += `<div class="card" style="margin-top:12px">
|
||||||
@@ -3118,14 +3118,10 @@ async function addLandVisitor(landName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeVisitorRole(landName, player, currentRole) {
|
async function changeVisitorRole(landName, player, newRole) {
|
||||||
// 三角色循环: visitor → admin → member → visitor
|
const roleLabels = {admin: '管理员', member: '成员'};
|
||||||
const roleOrder = ['visitor', 'admin', 'member'];
|
const roleLabel = roleLabels[newRole] || newRole;
|
||||||
const roleLabels = {visitor: '访客', admin: '管理员', member: '成员'};
|
if (!await glassConfirm(`确定将 ${player} 设为${roleLabel}吗?${newRole === 'admin' ? '\n管理员将拥有领地管理权限!' : ''}${newRole === 'member' ? '\n成员将降级为普通成员。' : ''}`)) return;
|
||||||
const nextIdx = (roleOrder.indexOf(currentRole) + 1) % roleOrder.length;
|
|
||||||
const newRole = roleOrder[nextIdx];
|
|
||||||
const roleLabel = roleLabels[newRole];
|
|
||||||
if (!await glassConfirm(`确定将 ${player} 设为${roleLabel}吗?${newRole === 'admin' ? '\n管理员将拥有领地管理权限!' : ''}${newRole === 'member' ? '\n成员将拥有独立权限控制。' : ''}`)) return;
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(API + 'land_api.php', location.href);
|
const url = new URL(API + 'land_api.php', location.href);
|
||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
@@ -3491,23 +3487,18 @@ async function renderMemberPermDetail(el, landName, targetPlayer) {
|
|||||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px">`;
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px">`;
|
||||||
|
|
||||||
for (const [key, label] of permEntries) {
|
for (const [key, label] of permEntries) {
|
||||||
const isCustom = permMap.hasOwnProperty(key);
|
const denied = permMap[key] === true; // true = deny类权限激活=禁止
|
||||||
const enabled = isCustom ? permMap[key] : false;
|
|
||||||
const defaultVal = land[key] || false;
|
|
||||||
|
|
||||||
html += `<div onclick="toggleMemberPerm('${escHtml(landName)}','${escHtml(targetPlayer)}','${key}',this)"
|
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)'};
|
style="background:${denied ? 'rgba(239,68,68,0.1)' : 'rgba(34,197,94,0.1)'};
|
||||||
border:1px solid ${isCustom ? (enabled ? 'var(--green)' : 'var(--red)') : 'var(--border)'};
|
border:1px solid ${denied ? 'var(--red)' : 'var(--green)'};
|
||||||
border-radius:8px;padding:12px;cursor:pointer;transition:all 0.2s">
|
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">
|
<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="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)'}">
|
<span style="font-size:11px;color:${denied ? 'var(--red)' : 'var(--green)'}">
|
||||||
${isCustom ? (enabled ? '✓ 启用' : '✗ 禁用') : '默认'}
|
${denied ? '✗ 禁止' : '✓ 允许'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;color:var(--dim)">
|
|
||||||
${isCustom ? '★ 自定义' : `领地默认: ${defaultVal ? '禁止' : '允许'}`}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
@@ -3528,23 +3519,12 @@ async function toggleMemberPerm(landName, targetPlayer, permKey, el) {
|
|||||||
body.set('player', targetPlayer);
|
body.set('player', targetPlayer);
|
||||||
body.set('perm', permKey);
|
body.set('perm', permKey);
|
||||||
|
|
||||||
// 从元素中获取当前状态
|
// 点击"允许"→切换为禁止(enabled=true);点击"禁止"→切换为允许(enabled=false)
|
||||||
// 状态文字在 flex div 的最后一个 span 里(✓ 启用 / ✗ 禁用 / 默认)
|
|
||||||
const spans = el.querySelectorAll('span');
|
const spans = el.querySelectorAll('span');
|
||||||
const statusSpan = spans[spans.length - 1]; // 第二个 span 是状态
|
const statusSpan = spans[spans.length - 1];
|
||||||
const statusText = statusSpan ? statusSpan.textContent : '';
|
const statusText = statusSpan ? statusSpan.textContent : '';
|
||||||
const isEnabled = statusText.includes('启用');
|
const newDenied = statusText.includes('允许'); // 当前允许→新值禁止(true);当前禁止→新值允许(false)
|
||||||
// "★ 自定义" 在第二个 div 里
|
body.set('enabled', newDenied.toString());
|
||||||
const divs = el.querySelectorAll('div');
|
|
||||||
const isCustom = divs.length > 1 && divs[divs.length - 1].textContent.includes('自定义');
|
|
||||||
|
|
||||||
if (isCustom) {
|
|
||||||
// 已有自定义:切换启用/禁用
|
|
||||||
body.set('enabled', (!isEnabled).toString());
|
|
||||||
} else {
|
|
||||||
// 无自定义:设置为启用
|
|
||||||
body.set('enabled', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
Reference in New Issue
Block a user