fix: PHP购买用户组流程完善 - 1. 修复duration=0永久验证(从<=0改为<0) - 2. 替换原生alert/confirm为自定义toast玻璃拟态UI - 3. 添加bond记录同步(handleSyncBondRecord) - 4. 前端购买弹窗使用玻璃拟态确认对话框 - 5. 修复可用用户组列表SQL缺失列自动迁移

This commit is contained in:
ypsdf1
2026-07-02 22:41:15 +08:00
parent 60151dc1d0
commit d394bd0ec8
2 changed files with 70 additions and 14 deletions
+48 -4
View File
@@ -103,6 +103,8 @@ try {
$memberPermActions = ['get_member_perms', 'update_member_perm', 'clear_member_perm'];
// ★ Java端轮询PHP管理员变更 + Java回调过户结果
$syncFromPhpActions = ['poll_admin_changes', 'ack_admin_changes', 'transfer_callback', 'owner_change_callback', 'delete_land_callback', 'poll_group_renews', 'renew_group_callback'];
// ★ Java同步扣费记录
$bondActions = ['sync_bond_record'];
if (in_array($action, $syncActions) || in_array($action, $syncFromPhpActions)) {
// 同步类:必须有secret
@@ -406,6 +408,11 @@ try {
handleGetPlayerGroups($db, $_GET['player'] ?? '');
break;
// ===== Java端:同步扣费记录 =====
case 'sync_bond_record':
handleSyncBondRecord($db, $_GET + $_POST);
break;
// ===== Java端:轮询PHP发起的用户组续费请求 =====
case 'poll_group_renews':
handlePollGroupRenews($db);
@@ -1966,10 +1973,11 @@ function handleAddGroupMember($db, $data) {
echo json_encode(['success' => false, 'error' => "玩家名格式不正确,仅支持英文字母、数字和下划线(3-16位)"]);
return;
}
// ★ 校验玩家是否存在(异步验证:写入pending表,Java定时器拉取验证)
// ★ 校验玩家是否存在
$playerValid = validatePlayerViaJava($db, $player, 'add_group_member', ['group' => $group]);
if ($playerValid === null) {
echo json_encode(['success' => true, 'pending' => true, 'message' => "玩家 {$player} 的验证请求已提交,系统将在1-2分钟内自动完成验证"]);
// 异步验证中 → 稍后重试(不直接写DB,等Java验证)
echo json_encode(['success' => true, 'pending' => true, 'message' => "玩家 {$player} 的验证请求已提交,系统正在验证,请稍候再试"]);
return;
}
if (!$playerValid) {
@@ -2201,10 +2209,11 @@ function handleBuyGroup($db, $player, $data) {
echo json_encode(['success' => false, 'error' => '该用户组不开放付费加入']);
return;
}
if ((int)$cfg['duration_minutes'] <= 0) {
echo json_encode(['success' => false, 'error' => '该用户组配置异常(有效时长未设置)']);
if ((int)$cfg['duration_minutes'] < 0) {
echo json_encode(['success' => false, 'error' => '该用户组配置异常(有效时长为负数)']);
return;
}
// ★ duration_minutes=0 表示永久,视为合法
if (!isset($cfg['join_price'])) {
echo json_encode(['success' => false, 'error' => '数据库缺少 join_price 列,请重新同步']);
return;
@@ -2264,6 +2273,41 @@ function handleBuyGroup($db, $player, $data) {
/**
* 玩家端:续费用户组(写入web_admin_changes,Java轮询拉取执行)
*/
function handleSyncBondRecord($db, $data) {
$player = $data['player'] ?? '';
$amount = (int)($data['amount'] ?? 0);
$type = $data['type'] ?? '';
$by = $data['by'] ?? 'system';
$desc = $data['desc'] ?? '';
if (empty($player) || $amount <= 0) {
return;
}
// 写入web_bond_records表
$db->exec("CREATE TABLE IF NOT EXISTS web_bond_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_name TEXT NOT NULL,
amount INTEGER DEFAULT 0,
record_type TEXT DEFAULT '',
recorded_by TEXT DEFAULT 'system',
description TEXT DEFAULT '',
created_at INTEGER DEFAULT 0
)");
$stmt = $db->prepare("INSERT INTO web_bond_records (player_name, amount, record_type, recorded_by, description, created_at)
VALUES (:player, :amount, :type, :by, :desc, :time)");
$stmt->bindValue(':player', $player, SQLITE3_TEXT);
$stmt->bindValue(':amount', $amount, SQLITE3_INTEGER);
$stmt->bindValue(':type', $type, SQLITE3_TEXT);
$stmt->bindValue(':by', $by, SQLITE3_TEXT);
$stmt->bindValue(':desc', $desc, SQLITE3_TEXT);
$stmt->bindValue(':time', time(), SQLITE3_INTEGER);
$stmt->execute();
error_log("[BondRecord] 同步扣费记录: player=$player, amount=$amount, type=$type, desc=$desc");
}
function handleRenewGroup($db, $player, $data) {
$group = $data['group'] ?? $data['group_name'] ?? '';
if (empty($player) || empty($group)) {
+22 -10
View File
@@ -141,11 +141,14 @@ if ($currentVersion !== $BUILD_VERSION) {
.modal .actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.toast {
position: fixed; top: 20px; right: 20px; padding: 12px 20px;
border-radius: 8px; font-size: 14px; z-index: 200;
border-radius: 12px; font-size: 14px; z-index: 200;
animation: slideIn 0.3s ease;
backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid rgba(88,166,255,0.2);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
.toast.success { background: var(--green); color: #fff; }
.toast.error { background: var(--red); color: #fff; }
.toast.success { background: rgba(63,185,80,0.85); color: #fff; }
.toast.error { background: rgba(248,81,73,0.85); color: #fff; }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
/* ★ 游客模式C位登录按钮 */
.guest-center-login {
@@ -1102,6 +1105,15 @@ if ($currentVersion !== $BUILD_VERSION) {
return await res.json();
}
function showToast(msg, type = 'info') {
const t = document.createElement('div');
t.className = 'toast ' + type;
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(100%)'; }, 2500);
setTimeout(() => { if (t.parentNode) t.parentNode.removeChild(t); }, 3000);
}
// 通知Java插件立即同步数据
async function notifyJavaSync() {
try {
@@ -1542,19 +1554,19 @@ if ($currentVersion !== $BUILD_VERSION) {
async function doBuyGroup(groupName) {
const g = window._availGroupsMap?.[groupName];
const price = g ? g.join_price + ' 张债券' : '(价格未知)';
const dur = g ? (g.duration_minutes >= 1440 ? Math.floor(g.duration_minutes/1440) + '天' : Math.floor(g.duration_minutes/60) + '小时') : '未知';
const dur = g ? (g.duration_minutes == 0 ? '永久' : (g.duration_minutes >= 1440 ? Math.floor(g.duration_minutes/1440) + '天' : Math.floor(g.duration_minutes/60) + '小时')) : '未知';
if (!confirm(`确认付费加入用户组「${g ? (g.display_name || groupName) : groupName}」?\n价格: ${price}\n有效期: ${dur}\n将从债券余额扣费。`)) return;
try {
const res = await api('land_api.php', {action: 'buy_group', group: groupName, player: currentPlayer, token: TOKEN});
if (res.success) {
alert(res.message || '加入成功');
showToast(res.message || '加入成功', 'success');
loadMyGroups();
loadAvailableGroups();
} else {
alert(res.error || '加入失败');
showToast(res.error || '加入失败', 'error');
}
} catch(e) {
alert('请求失败: ' + e.message);
showToast('请求失败: ' + e.message, 'error');
}
}
@@ -1566,13 +1578,13 @@ if ($currentVersion !== $BUILD_VERSION) {
try {
const res = await api('land_api.php', {action: 'renew_group', group: groupName, player: currentPlayer, token: TOKEN});
if (res.success) {
alert(res.message || '续费请求已提交');
showToast(res.message || '续费请求已提交', 'success');
loadMyGroups();
} else {
alert(res.error || '续费失败');
showToast(res.error || '续费失败', 'error');
}
} catch(e) {
alert('请求失败: ' + e.message);
showToast('请求失败: ' + e.message, 'error');
}
}