From d568a2ce5de82dcce3d10400d974ccc82a365a19 Mon Sep 17 00:00:00 2001 From: ypsdf1 Date: Sun, 28 Jun 2026 10:48:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20PHP=E6=95=88=E6=9E=9C=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8+=E7=AE=A1=E7=90=86=E5=91=98=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E5=88=87=E6=8D=A2+JSON=E7=BC=96=E7=A0=81=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - player.php: 新增glassSelect()毛玻璃选择器弹窗(29种MC药水效果) - player.php: addClearEffect/addGiveEffect从手动输入改为效果选择器 - player.php: 访客表格角色列改为可点击标签,点击切换管理员/访客 - land_api.php: 新增change_visitor_role API + handleChangeVisitorRole() - land_api.php: handleListUserGroups添加JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES --- api/land_api.php | 67 ++++++++++++++++- player.php | 188 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 229 insertions(+), 26 deletions(-) diff --git a/api/land_api.php b/api/land_api.php index 1e17b40..c67d116 100644 --- a/api/land_api.php +++ b/api/land_api.php @@ -98,7 +98,7 @@ try { // ★ 玩家端领地字段更新(效果管理、开关等) $playerFieldActions = ['update_land_field']; // ★ 玩家端权限操作action - $playerPermActions = ['update_visitor_perm', 'get_visitor_perm']; + $playerPermActions = ['update_visitor_perm', 'get_visitor_perm', 'change_visitor_role']; // ★ 成员独立权限操作action(领地所有者编辑成员权限) $memberPermActions = ['get_member_perms', 'update_member_perm', 'clear_member_perm']; // ★ Java端轮询PHP管理员变更 @@ -265,6 +265,15 @@ try { handleUpdateVisitorPerm($db, $playerName, $_POST); break; + // ===== 玩家端:切换访客角色(admin/visitor)===== + case 'change_visitor_role': + if ($method !== 'POST') { + echo json_encode(['success' => false, 'error' => 'POST only']); + exit; + } + handleChangeVisitorRole($db, $playerName, $_POST); + break; + // ===== 成员独立权限:获取成员权限列表 ===== case 'get_member_perms': handleGetMemberPerms($db, $playerName, $_GET['land'] ?? ''); @@ -1068,6 +1077,60 @@ function handleRemoveVisitor($db, $playerName, $req) { echo json_encode(['success' => true, 'message' => "已移除访客: $visitor"]); } +// ==================== 玩家端:切换访客角色 ==================== + +function handleChangeVisitorRole($db, $playerName, $post) { + $landName = $post['name'] ?? $post['land'] ?? ''; + $visitor = $post['visitor'] ?? $post['player'] ?? ''; + $newRole = $post['role'] ?? ''; + + if (empty($landName) || empty($visitor) || empty($newRole)) { + echo json_encode(['success' => false, 'error' => '缺少参数']); + return; + } + + if (!in_array($newRole, ['admin', 'visitor'])) { + echo json_encode(['success' => false, 'error' => '无效角色: ' . $newRole]); + 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("UPDATE web_area_permissions SET role = :role WHERE land_id = :land_id AND player_name = :player"); + $stmt2->bindValue(':role', $newRole, SQLITE3_TEXT); + $stmt2->bindValue(':land_id', (int)$land['id'], SQLITE3_INTEGER); + $stmt2->bindValue(':player', $visitor, SQLITE3_TEXT); + $stmt2->execute(); + + // 写入变更队列通知Java同步 + $now = time(); + $changeData = json_encode(['player' => $visitor, 'role' => $newRole, 'land' => $landName]); + $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(':name', $landName, SQLITE3_TEXT); + $stmt3->bindValue(':data', $changeData, SQLITE3_TEXT); + $stmt3->bindValue(':now', $now, SQLITE3_INTEGER); + $stmt3->execute(); + + $roleLabel = $newRole === 'admin' ? '管理员' : '访客'; + echo json_encode(['success' => true, 'message' => "已将 $visitor 设为$roleLabel"]); +} + // ==================== 玩家端:访客列表 ==================== function handleListVisitors($db, $landName) { @@ -1460,7 +1523,7 @@ function handleListUserGroups($db) { while ($row = $rs->fetchArray(SQLITE3_ASSOC)) { $groups[] = $row; } - echo json_encode(['success' => true, 'groups' => $groups]); + echo json_encode(['success' => true, 'groups' => $groups], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } function handleGetUserGroup($db, $name) { diff --git a/player.php b/player.php index 9a7bcf0..1537e6b 100644 --- a/player.php +++ b/player.php @@ -1501,6 +1501,42 @@ if ($currentVersion !== $BUILD_VERSION) { }); } + // ★ 毛玻璃选择器弹窗(替代文本输入,提供可选项列表) + function glassSelect(label, options, hint = '') { + // options: [{value, label, desc?}] 或 string[](自动转为{value,label}) + return new Promise(resolve => { + _glassAlertResolve = resolve; + _isGlassPrompt = false; + const icon = document.getElementById('glassAlertIcon'); + icon.textContent = '📋'; + const msgEl = document.getElementById('glassAlertMsg'); + msgEl.style.display = 'none'; + const labelEl = document.getElementById('glassAlertLabel'); + labelEl.textContent = label; + labelEl.style.display = ''; + const input = document.getElementById('glassAlertInput'); + input.style.display = 'none'; + const hintEl = document.getElementById('glassAlertHint'); + if (hint) { hintEl.textContent = '💡 ' + hint; hintEl.style.display = ''; } + else { hintEl.style.display = 'none'; } + // 构建选项列表 + const normalized = options.map(o => typeof o === 'string' ? {value: o, label: o} : o); + let listHtml = '
'; + for (const opt of normalized) { + listHtml += `
+
${opt.label}
+ ${opt.desc ? `
${opt.desc}
` : ''} +
`; + } + listHtml += '
'; + document.getElementById('glassAlertBtns').innerHTML = listHtml + ''; + document.getElementById('glassAlertOverlay').classList.add('show'); + }); + } + // ===== 主题切换 ===== function toggleLightTheme() { document.body.classList.toggle('light-theme'); @@ -2317,9 +2353,16 @@ async function renderLandDetail(el, landName) { html += ``; for (const v of visitors) { const grantDate = v.granted_at ? new Date(v.granted_at * 1000).toLocaleString('zh-CN') : '未知'; + const isAdmin = v.role === 'admin'; + const roleColor = isAdmin ? 'var(--accent)' : 'var(--dim)'; + const roleBg = isAdmin ? 'rgba(99,102,241,0.15)' : 'rgba(107,114,128,0.1)'; + const nextRoleLabel = isAdmin ? '设为访客' : '设为管理员'; html += ` - + `; @@ -2480,6 +2523,31 @@ async function addLandVisitor(landName) { } } +async function changeVisitorRole(landName, player, currentRole) { + const newRole = currentRole === 'admin' ? 'visitor' : 'admin'; + const roleLabel = newRole === 'admin' ? '管理员' : '访客'; + if (!await glassConfirm(`确定将 ${player} 设为${roleLabel}吗?${newRole === 'admin' ? '\n管理员将拥有领地管理权限!' : ''}`)) return; + try { + const url = new URL(API + 'land_api.php', location.href); + const body = new URLSearchParams(); + body.set('action', 'change_visitor_role'); + body.set('token', TOKEN); + body.set('name', landName); + body.set('visitor', player); + body.set('role', newRole); + 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) { + landsState.currentLand = landName; + await renderLandDetail(document.getElementById('content'), landName); + } else { + glassAlert(data.error || '操作失败'); + } + } catch (e) { + glassAlert('操作失败: ' + e.message); + } +} + async function removeLandVisitor(landName, player) { if (!await glassConfirm(`确定要移除访客 ${player} 吗?`)) return; try { @@ -2542,13 +2610,41 @@ async function toggleLandField(landName, field) { } async function addClearEffect(landName) { - const eff = await glassPrompt('输入要清除的效果英文名', '', '如: SLOWNESS, WEAKNESS, POISON'); - if (!eff || eff === false) return; - const trimmed = eff.trim().toUpperCase(); - if (!trimmed) return; + // MC药水效果列表(用户选择而非手动输入) + const EFFECTS = [ + {value:'SPEED',label:'🏃 速度',desc:'SPEED - 提升移动速度'}, + {value:'SLOWNESS',label:'🐌 缓慢',desc:'SLOWNESS - 降低移动速度'}, + {value:'HASTE',label:'⛏️ 急迫',desc:'HASTE - 提升挖掘和攻击速度'}, + {value:'MINING_FATIGUE',label:'🔧 疲劳',desc:'MINING_FATIGUE - 降低挖掘速度'}, + {value:'STRENGTH',label:'💪 力量',desc:'STRENGTH - 提升近战伤害'}, + {value:'JUMP_BOOST',label:'🦘 跳跃提升',desc:'JUMP_BOOST - 提升跳跃高度'}, + {value:'NAUSEA',label:'🤢 反胃',desc:'NAUSEA - 视角扭曲'}, + {value:'REGENERATION',label:'❤️ 再生',desc:'REGENERATION - 缓慢恢复生命'}, + {value:'RESISTANCE',label:'🛡️ 抗性提升',desc:'RESISTANCE - 减少受到的伤害'}, + {value:'FIRE_RESISTANCE',label:'🔥 火焰抗性',desc:'FIRE_RESISTANCE - 免疫火焰伤害'}, + {value:'WATER_BREATHING',label:'🐠 水下呼吸',desc:'WATER_BREATHING - 水下不消耗氧气'}, + {value:'INVISIBILITY',label:'👻 隐身',desc:'INVISIBILITY - 对其他玩家隐身'}, + {value:'BLINDNESS',label:' blinded 失明',desc:'BLINDNESS - 屏幕全黑,无法看到远处'}, + {value:'NIGHT_VISION',label:'🦉 夜视',desc:'NIGHT_VISION - 黑暗中看清一切'}, + {value:'WEAKNESS',label:'😵 虚弱',desc:'WEAKNESS - 降低近战伤害'}, + {value:'POISON',label:'☠️ 中毒',desc:'POISON - 持续失去生命'}, + {value:'WITHER',label:'💀 凋零',desc:'WITHER - 持续失去生命(更严重)'}, + {value:'HEALTH_BOOST',label:'💖 生命提升',desc:'HEALTH_BOOST - 增加最大生命值'}, + {value:'ABSORPTION',label:'🥇 吸收',desc:'ABSORPTION - 提供额外黄色生命值'}, + {value:'SATURATION',label:'🍖 饱和',desc:'SATURATION - 恢复饥饿值'}, + {value:'GLOWING',label:'✨ 发光',desc:'GLOWING - 对所有人高亮显示'}, + {value:'LEVITATION',label:'🎈 漂浮',desc:'LEVITATION - 向上飘浮'}, + {value:'SLOW_FALLING',label:'🪂 缓降',desc:'SLOW_FALLING - 下落速度减慢'}, + {value:'LUCK',label:'🍀 幸运',desc:'LUCK - 提升战利品品质'}, + {value:'UNLUCK',label:'🤕 厄运',desc:'UNLUCK - 降低战利品品质'}, + {value:'DOLPHINS_GRACE',label:'🐬 海豚的恩惠',desc:'DOLPHINS_GRACE - 水下加速游泳'}, + {value:'CONDUIT_POWER',label:'🔷 潮涌能量',desc:'CONDUIT_POWER - 水下夜视+呼吸+速掘'}, + {value:'BAD_OMEN',label:'💀 不祥之兆',desc:'BAD_OMEN - 进入村庄触发袭击'}, + {value:'HERO_OF_THE_VILLAGE',label:'🏆 英雄',desc:'HERO_OF_THE_VILLAGE - 村民打折'} + ]; + // 获取当前已有的清除效果,过滤掉已选的 try { - const url = new URL(API + 'land_api.php', location.href); const detailUrl = new URL(API + 'land_api.php', location.href); detailUrl.searchParams.set('action', 'land_detail'); detailUrl.searchParams.set('token', TOKEN); @@ -2557,9 +2653,18 @@ async function addClearEffect(landName) { const detailData = await detailRes.json(); if (!detailData.success) { glassAlert(detailData.error); return; } const current = detailData.land.clear_effects ? JSON.parse(detailData.land.clear_effects) : []; - if (current.includes(trimmed)) { glassAlert('该效果已在清除列表中'); return; } - current.push(trimmed); + // 过滤掉已添加的效果 + const available = EFFECTS.filter(e => !current.includes(e.value)); + if (available.length === 0) { glassAlert('所有效果都已添加'); return; } + + const eff = await glassSelect('选择要清除的效果', available, '点击选择效果,将在领地内自动清除该效果'); + if (!eff || eff === false) return; + + if (current.includes(eff)) { glassAlert('该效果已在清除列表中'); return; } + current.push(eff); + + const url = new URL(API + 'land_api.php', location.href); const body = new URLSearchParams(); body.set('name', landName); body.set('field', 'clear_effects'); @@ -2578,23 +2683,40 @@ async function addClearEffect(landName) { } async function addGiveEffect(landName) { - const eff = await glassPrompt('输入效果英文名', '', '如: SPEED, STRENGTH, REGENERATION'); - if (!eff || eff === false) return; - const trimmed = eff.trim().toUpperCase(); - if (!trimmed) return; - - const levelStr = await glassPrompt('等级 (1-255)', '1', '默认1级,管理员最高255级'); - if (levelStr === null || levelStr === false) return; - const level = parseInt(levelStr) || 1; - - const durStr = await glassPrompt('时长(秒)', '300', '默认300秒'); - if (durStr === null || durStr === false) return; - const duration = parseInt(durStr) || 300; - - const entry = JSON.stringify([trimmed, Math.min(Math.max(level,1),255), Math.min(Math.max(duration,1),3600)]); + // MC药水效果列表 + const EFFECTS = [ + {value:'SPEED',label:'🏃 速度',desc:'SPEED - 提升移动速度'}, + {value:'SLOWNESS',label:'🐌 缓慢',desc:'SLOWNESS - 降低移动速度'}, + {value:'HASTE',label:'⛏️ 急迫',desc:'HASTE - 提升挖掘和攻击速度'}, + {value:'MINING_FATIGUE',label:'🔧 疲劳',desc:'MINING_FATIGUE - 降低挖掘速度'}, + {value:'STRENGTH',label:'💪 力量',desc:'STRENGTH - 提升近战伤害'}, + {value:'JUMP_BOOST',label:'🦘 跳跃提升',desc:'JUMP_BOOST - 提升跳跃高度'}, + {value:'NAUSEA',label:'🤢 反胃',desc:'NAUSEA - 视角扭曲'}, + {value:'REGENERATION',label:'❤️ 再生',desc:'REGENERATION - 缓慢恢复生命'}, + {value:'RESISTANCE',label:'🛡️ 抗性提升',desc:'RESISTANCE - 减少受到的伤害'}, + {value:'FIRE_RESISTANCE',label:'🔥 火焰抗性',desc:'FIRE_RESISTANCE - 免疫火焰伤害'}, + {value:'WATER_BREATHING',label:'🐠 水下呼吸',desc:'WATER_BREATHING - 水下不消耗氧气'}, + {value:'INVISIBILITY',label:'👻 隐身',desc:'INVISIBILITY - 对其他玩家隐身'}, + {value:'BLINDNESS',label:'🌑 失明',desc:'BLINDNESS - 屏幕全黑,无法看到远处'}, + {value:'NIGHT_VISION',label:'🦉 夜视',desc:'NIGHT_VISION - 黑暗中看清一切'}, + {value:'WEAKNESS',label:'😵 虚弱',desc:'WEAKNESS - 降低近战伤害'}, + {value:'POISON',label:'☠️ 中毒',desc:'POISON - 持续失去生命'}, + {value:'WITHER',label:'💀 凋零',desc:'WITHER - 持续失去生命(更严重)'}, + {value:'HEALTH_BOOST',label:'💖 生命提升',desc:'HEALTH_BOOST - 增加最大生命值'}, + {value:'ABSORPTION',label:'🥇 吸收',desc:'ABSORPTION - 提供额外黄色生命值'}, + {value:'SATURATION',label:'🍖 饱和',desc:'SATURATION - 恢复饥饿值'}, + {value:'GLOWING',label:'✨ 发光',desc:'GLOWING - 对所有人高亮显示'}, + {value:'LEVITATION',label:'🎈 漂浮',desc:'LEVITATION - 向上飘浮'}, + {value:'SLOW_FALLING',label:'🪂 缓降',desc:'SLOW_FALLING - 下落速度减慢'}, + {value:'LUCK',label:'🍀 幸运',desc:'LUCK - 提升战利品品质'}, + {value:'UNLUCK',label:'🤕 厄运',desc:'UNLUCK - 降低战利品品质'}, + {value:'DOLPHINS_GRACE',label:'🐬 海豚的恩惠',desc:'DOLPHINS_GRACE - 水下加速游泳'}, + {value:'CONDUIT_POWER',label:'🔷 潮涌能量',desc:'CONDUIT_POWER - 水下夜视+呼吸+速掘'}, + {value:'BAD_OMEN',label:'💀 不祥之兆',desc:'BAD_OMEN - 进入村庄触发袭击'}, + {value:'HERO_OF_THE_VILLAGE',label:'🏆 英雄',desc:'HERO_OF_THE_VILLAGE - 村民打折'} + ]; try { - const url = new URL(API + 'land_api.php', location.href); const detailUrl = new URL(API + 'land_api.php', location.href); detailUrl.searchParams.set('action', 'land_detail'); detailUrl.searchParams.set('token', TOKEN); @@ -2603,8 +2725,26 @@ async function addGiveEffect(landName) { const detailData = await detailRes.json(); if (!detailData.success) { glassAlert(detailData.error); return; } const current = detailData.land.give_effects ? JSON.parse(detailData.land.give_effects) : []; - current.push([trimmed, Math.min(Math.max(level,1),255), Math.min(Math.max(duration,1),3600)]); + const existingNames = current.map(e => Array.isArray(e) ? e[0] : e); + // 过滤掉已添加的效果 + const available = EFFECTS.filter(e => !existingNames.includes(e.value)); + if (available.length === 0) { glassAlert('所有效果都已添加'); return; } + + const eff = await glassSelect('选择要添加的增益效果', available, '点击选择效果'); + if (!eff || eff === false) return; + + const levelStr = await glassPrompt('等级 (1-255)', '1', '默认1级,管理员最高255级'); + if (levelStr === null || levelStr === false) return; + const level = parseInt(levelStr) || 1; + + const durStr = await glassPrompt('时长(秒)', '300', '默认300秒,最长3600秒'); + if (durStr === null || durStr === false) return; + const duration = parseInt(durStr) || 300; + + current.push([eff, Math.min(Math.max(level,1),255), Math.min(Math.max(duration,1),3600)]); + + const url = new URL(API + 'land_api.php', location.href); const body = new URLSearchParams(); body.set('name', landName); body.set('field', 'give_effects');
玩家名角色授权时间操作
${escHtml(v.player_name)} ${escHtml(v.role)}${isAdmin ? '👑 管理员' : '👤 访客'} ${grantDate}