Revert to version 18ea545
141
ajax_handler.php
@ -1,9 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/api/LocalLubanApi.php';
|
||||
|
||||
@ -71,7 +67,7 @@ try {
|
||||
}
|
||||
echo json_encode(['code' => 0, 'data' => $data], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
echo json_encode($res ?: ['code' => 500, 'msg' => '获取项目列表 失败'], JSON_UNESCAPED_UNICODE);
|
||||
echo json_encode($res ?: ['code' => 500, 'msg' => '获取项目列表失败'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -79,7 +75,7 @@ try {
|
||||
$service_id = $_GET['service_id'] ?? '';
|
||||
$country_name = $_GET['country_name'] ?? '未知国家';
|
||||
$service_name = $_GET['service_name'] ?? '未知项目';
|
||||
$price = round((float)($_GET["price"] ?? 0), 2);
|
||||
$price = (float)($_GET['price'] ?? 0);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT balance FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
@ -107,30 +103,6 @@ try {
|
||||
echo json_encode($res ?: ['code' => 500, 'msg' => 'API获取号码失败'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'release_number':
|
||||
$request_id = $_GET['request_id'] ?? '';
|
||||
$stmt = $pdo->prepare("SELECT * FROM sms_orders WHERE request_id = ? AND user_id = ? AND status = 'pending'");
|
||||
$stmt->execute([$request_id, $_SESSION['user_id']]);
|
||||
$order = $stmt->fetch();
|
||||
if ($order) {
|
||||
$api->releaseNumber($request_id);
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE sms_orders SET status = 'canceled' WHERE request_id = ?");
|
||||
$stmt->execute([$request_id]);
|
||||
$stmt = $pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
|
||||
$stmt->execute([$order['cost'], $_SESSION['user_id']]);
|
||||
$pdo->commit();
|
||||
echo json_encode(['code' => 0, 'msg' => '成功']);
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['code' => 500, 'msg' => '错误']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['code' => 400, 'msg' => '无效操作']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'check_sms':
|
||||
$request_id = $_GET['request_id'] ?? '';
|
||||
@ -142,11 +114,118 @@ try {
|
||||
echo json_encode($res ?: ['code' => 500, 'msg' => 'API Error'], JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case 'create_recharge':
|
||||
$amount = (float)($_POST['amount'] ?? 0);
|
||||
if ($amount < 10) { echo json_encode(['code' => 400, 'msg' => '最低充值金额为 10 USDT']); break; }
|
||||
$final_amount = floor($amount) + (rand(1, 99) / 100);
|
||||
$stmt = $pdo->prepare("INSERT INTO recharges (user_id, amount, txid, status) VALUES (?, ?, 'Manual/Auto', 'pending')");
|
||||
$stmt->execute([$_SESSION['user_id'], $final_amount]);
|
||||
echo json_encode(['code' => 0, 'recharge_id' => $pdo->lastInsertId(), 'amount' => $final_amount]);
|
||||
break;
|
||||
|
||||
case 'check_recharge_status':
|
||||
$recharge_id = $_GET['recharge_id'] ?? '';
|
||||
$stmt = $pdo->prepare("SELECT * FROM recharges WHERE id = ? AND user_id = ?");
|
||||
$stmt->execute([$recharge_id, $_SESSION['user_id']]);
|
||||
$recharge = $stmt->fetch();
|
||||
if (!$recharge) { echo json_encode(['code' => 404, 'msg' => '未找到充值订单']); break; }
|
||||
if ($recharge['status'] === 'completed') { echo json_encode(['code' => 0, 'status' => 'completed']); break; }
|
||||
echo json_encode(['code' => 0, 'status' => 'pending']);
|
||||
break;
|
||||
case "get_active_orders":
|
||||
$stmt = $pdo->prepare("SELECT * FROM sms_orders WHERE user_id = ? AND status IN ('pending', 'received') ORDER BY created_at DESC");
|
||||
$stmt = $pdo->prepare("SELECT * FROM sms_orders WHERE user_id = ? AND status != "canceled" ORDER BY created_at DESC");
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
echo json_encode(["code" => 0, "data" => $stmt->fetchAll(PDO::FETCH_ASSOC)], JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
$stmt->execute([$_SESSION["user_id"]]);
|
||||
echo json_encode(["code" => 0, "data" => $stmt->fetchAll(PDO::FETCH_ASSOC)], JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case "upload_image":
|
||||
$file = $_FILES["image"] ?? null;
|
||||
if ($file) {
|
||||
$ext = pathinfo($file["name"], PATHINFO_EXTENSION);
|
||||
$name = "uploads/" . bin2hex(random_bytes(8)) . "." . $ext;
|
||||
move_uploaded_file($file["tmp_name"], __DIR__ . "/" . $name);
|
||||
echo json_encode(["code" => 0, "url" => $name]);
|
||||
} else {
|
||||
echo json_encode(["code" => 400, "msg" => "上传失败"]);
|
||||
}
|
||||
break;
|
||||
case 'send_message':
|
||||
$message = trim($_POST['message'] ?? '');
|
||||
$target_user_id = $_POST['user_id'] ?? $_SESSION['user_id'];
|
||||
if (!$message) { echo json_encode(['code' => 400, 'msg' => '消息内容不能为空']); break; }
|
||||
|
||||
$stmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$role = $stmt->fetchColumn();
|
||||
$sender = ($role === 'admin') ? 'admin' : 'user';
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO support_messages (user_id, sender, message, `is_read`) VALUES (?, ?, ?, 0)");
|
||||
$stmt->execute([$target_user_id, $sender, $message]);
|
||||
echo json_encode(['code' => 0, 'msg' => '已发送']);
|
||||
break;
|
||||
|
||||
case 'get_messages':
|
||||
$target_user_id = $_GET['user_id'] ?? $_SESSION['user_id'];
|
||||
$stmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$isAdmin = ($stmt->fetchColumn() === 'admin');
|
||||
|
||||
if (!$isAdmin && (int)$target_user_id !== (int)$_SESSION['user_id']) {
|
||||
echo json_encode(['code' => 403, 'msg' => 'Forbidden']); break;
|
||||
}
|
||||
|
||||
if ($isAdmin && (int)$target_user_id !== (int)$_SESSION['user_id']) {
|
||||
$pdo->prepare("UPDATE support_messages SET `is_read` = 1 WHERE user_id = ? AND sender = 'user'")->execute([$target_user_id]);
|
||||
} else if (!$isAdmin) {
|
||||
$pdo->prepare("UPDATE support_messages SET `is_read` = 1 WHERE user_id = ? AND sender = 'admin'")->execute([$_SESSION['user_id']]);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM support_messages WHERE user_id = ? ORDER BY id ASC");
|
||||
$stmt->execute([$target_user_id]);
|
||||
echo json_encode(['code' => 0, 'data' => $stmt->fetchAll()]);
|
||||
break;
|
||||
|
||||
case 'get_chat_users':
|
||||
$stmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
if ($stmt->fetchColumn() !== 'admin') { echo json_encode(['code' => 403, 'msg' => 'Forbidden']); break; }
|
||||
|
||||
// Optimized query to get last message reliably
|
||||
$stmt = $pdo->query("
|
||||
SELECT u.id, u.username, m.message as last_message, m.created_at as last_time,
|
||||
(SELECT COUNT(*) FROM support_messages WHERE user_id = u.id AND sender = 'user' AND `is_read` = 0) as unread_count
|
||||
FROM users u
|
||||
JOIN (SELECT user_id, MAX(id) as max_id FROM support_messages GROUP BY user_id) last_msg_idx ON u.id = last_msg_idx.user_id
|
||||
JOIN support_messages m ON m.id = last_msg_idx.max_id
|
||||
ORDER BY m.id DESC
|
||||
");
|
||||
echo json_encode(['code' => 0, 'data' => $stmt->fetchAll()]);
|
||||
break;
|
||||
|
||||
case 'check_new_messages':
|
||||
$stmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$role = $stmt->fetchColumn();
|
||||
if ($role === 'admin') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT m.*, u.username
|
||||
FROM support_messages m
|
||||
JOIN users u ON m.user_id = u.id
|
||||
WHERE m.sender = 'user' AND m.`is_read` = 0
|
||||
ORDER BY m.id DESC LIMIT 1
|
||||
");
|
||||
$last_unread = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$total_unread = $pdo->query("SELECT COUNT(*) FROM support_messages WHERE sender = 'user' AND `is_read` = 0")->fetchColumn();
|
||||
echo json_encode(['code' => 0, 'unread_total' => $total_unread, 'last_user' => $last_unread['username'] ?? '']);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM support_messages WHERE user_id = ? AND sender = 'admin' AND `is_read` = 0");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
echo json_encode(['code' => 0, 'unread_total' => $stmt->fetchColumn()]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['code' => 404, 'msg' => '未知请求']);
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@ -327,13 +327,19 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
<div class="active-tasks-area" id="activeTasksSection" style="display: none;">
|
||||
<div class="active-tasks-header">
|
||||
<span class="fw-bold text-primary"><i class="fas fa-satellite-dish me-2"></i> 活跃任务</span>
|
||||
<button class="btn btn-link btn-sm text-decoration-none fw-bold" onclick="loadActiveOrders()" style="color: #64748b;">
|
||||
<i class="fas fa-sync-alt me-1"></i> 刷新
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="text-muted small">
|
||||
<th class="ps-4">项目/地区</th>
|
||||
<th>号码/剩余时间</th>
|
||||
<th>号码</th>
|
||||
<th>短信内容</th>
|
||||
<th>状态</th>
|
||||
<th>剩余时间</th>
|
||||
<th class="text-end pe-4">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -376,6 +382,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 px-1">
|
||||
<h6 class="fw-bold mb-0" style="color: #475569;">实时行情列表</h6>
|
||||
<span class="badge bg-light text-muted fw-normal" id="lastUpdated">已就绪</span>
|
||||
</div>
|
||||
|
||||
<div class="quotation-wrapper border rounded-4 overflow-hidden" style="border-color: #e2e8f0 !important;">
|
||||
@ -424,6 +431,10 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
renderServices(popularServices);
|
||||
loadActiveOrders();
|
||||
setInterval(loadActiveOrders, 10000);
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.custom-dropdown')) hideAllDropdowns();
|
||||
});
|
||||
});
|
||||
|
||||
function showToast(msg, type = 'success') {
|
||||
@ -460,7 +471,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
if (event) event.stopPropagation();
|
||||
const d = document.getElementById(id);
|
||||
const isShow = d.classList.contains('show');
|
||||
document.querySelectorAll('.dropdown-menu-custom').forEach(dd => dd.classList.remove('show'));
|
||||
hideAllDropdowns();
|
||||
if (!isShow) {
|
||||
d.classList.add('show');
|
||||
const input = d.querySelector('input');
|
||||
@ -468,6 +479,10 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllDropdowns() {
|
||||
document.querySelectorAll('.dropdown-menu-custom').forEach(d => d.classList.remove('show'));
|
||||
}
|
||||
|
||||
function renderCountries(filter = '') {
|
||||
const container = document.getElementById('countriesList');
|
||||
if (!container) return;
|
||||
@ -528,7 +543,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
const l = document.getElementById('countryLabel');
|
||||
l.textContent = c.name_zh;
|
||||
l.classList.remove('placeholder'); l.classList.add('val');
|
||||
document.querySelectorAll('.dropdown-menu-custom').forEach(dd => dd.classList.remove('show'));
|
||||
hideAllDropdowns();
|
||||
loadQuotation();
|
||||
}
|
||||
|
||||
@ -537,7 +552,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
const l = document.getElementById('serviceLabel');
|
||||
l.textContent = s.name;
|
||||
l.classList.remove('placeholder'); l.classList.add('val');
|
||||
document.querySelectorAll('.dropdown-menu-custom').forEach(dd => dd.classList.remove('show'));
|
||||
hideAllDropdowns();
|
||||
loadQuotation();
|
||||
}
|
||||
|
||||
@ -595,35 +610,43 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
const data = await res.json();
|
||||
const body = document.getElementById('activeTasksBody');
|
||||
const section = document.getElementById('activeTasksSection');
|
||||
const latestArea = document.getElementById('latestOrderArea');
|
||||
const latestContent = document.getElementById('latestOrderContent');
|
||||
|
||||
if (data.code === 0 && Array.isArray(data.data) && data.data.length > 0) {
|
||||
section.style.display = 'block';
|
||||
body.innerHTML = '';
|
||||
|
||||
const receivedList = data.data.filter(o => o.status === 'received');
|
||||
if (receivedList.length > 0) {
|
||||
const latest = receivedList.sort((a,b) => new Date(b.created_at) - new Date(a.created_at))[0];
|
||||
const now = new Date();
|
||||
const rAt = new Date(latest.created_at.replace(/-/g, "/").replace(" ", "T") + "Z");
|
||||
if ((now - rAt) / 1000 < 300) {
|
||||
latestArea.style.display = 'block';
|
||||
latestContent.innerHTML = `
|
||||
<div class="col-md-3">项目: ${latest.service_name}</div>
|
||||
<div class="col-md-3">号码: <span class="fw-bold text-primary">${latest.number}</span></div>
|
||||
<div class="col-md-4">短信: <span class="text-dark fw-bold">${latest.sms_content}</span></div>
|
||||
<div class="col-md-2"><button class="btn btn-sm btn-danger" onclick="releaseNumber('${latest.request_id}')">取消</button></div>
|
||||
`;
|
||||
} else { latestArea.style.display = 'none'; }
|
||||
} else { latestArea.style.display = 'none'; }
|
||||
|
||||
data.data.forEach(o => {
|
||||
const row = document.createElement('tr');
|
||||
const expireAt = new Date(o.expire_at.replace(/-/g, "/").replace(" ", "T"));
|
||||
const now = new Date();
|
||||
const diffMs = expireAt - now;
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
const diffSeconds = Math.floor((diffMs % 60000) / 1000);
|
||||
|
||||
if (diffMs < 0) {
|
||||
// Do not auto-release here, let the status update or user handle it
|
||||
continue;
|
||||
}
|
||||
|
||||
const timeRemaining = `${diffMinutes}分${diffSeconds}秒`;
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="ps-4">${o.service_name} / ${o.country_name}</td>
|
||||
<td>${o.number || ''} <span class="text-muted ms-2">${timeRemaining}</span></td>
|
||||
<td class="text-end pe-4"><button class="btn btn-sm btn-outline-danger fw-bold" onclick="releaseNumber('${o.request_id}')">取消</button></td>
|
||||
<td>${o.service_name} / ${o.country_name}</td>
|
||||
<td class="fw-bold">${o.number}</td>
|
||||
<td>${o.status === 'received' ? o.sms_content : '等待中...'}</td>
|
||||
<td>${o.status}</td>
|
||||
<td>${o.expire_at}</td>
|
||||
<td class="text-end"><button class="btn btn-sm btn-outline-danger" onclick="releaseNumber('${o.request_id}')">释放</button></td>
|
||||
`;
|
||||
body.appendChild(row);
|
||||
if (o.status !== 'received' && !activePolls[o.request_id]) startPolling(o.request_id);
|
||||
});
|
||||
} else { section.style.display = 'none'; }
|
||||
} else { section.style.display = 'none'; latestArea.style.display = 'none'; }
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@ -632,7 +655,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
try {
|
||||
const res = await fetch(`${apiHandler}?action=check_sms&request_id=${rid}`);
|
||||
const data = await res.json();
|
||||
if (data.code === 0 && data.sms_code) {
|
||||
if (data.code === 0 && (data.msg === 'success' || data.sms_code)) {
|
||||
clearInterval(activePolls[rid]); delete activePolls[rid]; showSmsModal(data.sms_code); loadActiveOrders();
|
||||
}
|
||||
} catch (e) {}
|
||||
@ -642,8 +665,6 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
|
||||
async function releaseNumber(id) {
|
||||
try {
|
||||
const res = await fetch(`${apiHandler}?action=release_number&request_id=${id}`);
|
||||
const data = await res.json();
|
||||
if (data.code === 0) { showToast("已取消"); } else { showToast(data.msg || "取消失败", "error"); }
|
||||
loadActiveOrders(); updateBalance();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||