Autosave: 20260322-133028

This commit is contained in:
Flatlogic Bot 2026-03-22 13:30:28 +00:00
parent e5fca24bdc
commit 18ea545595
14 changed files with 111 additions and 173 deletions

View File

@ -132,6 +132,14 @@ try {
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 != "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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,4 +1,5 @@
<?php
date_default_timezone_set("Asia/Shanghai");
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
@ -237,13 +238,11 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
border-color: var(--primary);
}
/* 提示框样式 */
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 2000; }
.custom-toast { background: white; border-radius: 12px; box-shadow: var(--shadow-lg); padding: 16px 24px; border: 1px solid var(--border-color); margin-bottom: 10px; display: flex; align-items: center; gap: 12px; }
.custom-toast.error { border-left: 4px solid #ef4444; }
.custom-toast.success { border-left: 4px solid #22c55e; }
/* Modal Custom Style */
.modal-custom {
position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 3000;
display: none; align-items: center; justify-content: center;
@ -254,6 +253,14 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
box-shadow: var(--shadow-lg); text-align: center;
}
.highlight-area {
background: #f0f7ff;
border: 2px dashed #3b82f6;
border-radius: var(--radius-lg);
padding: 1.5rem;
margin-bottom: 2rem;
}
@media (max-width: 992px) {
.main-content { margin-left: 0; padding: 1.5rem; }
.search-grid { grid-template-columns: 1fr; gap: 1rem; }
@ -267,7 +274,6 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
<div class="toast-container" id="toastContainer"></div>
<!-- Custom Modal -->
<div class="modal-custom" id="confirmModal">
<div class="modal-content-custom">
<h5 class="fw-bold mb-3" id="confirmTitle">确认操作</h5>
@ -303,31 +309,45 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
</div>
</div>
<!-- Active Tasks -->
<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 class="text-end pe-4">操作</th>
</tr>
</thead>
<tbody id="activeTasksBody"></tbody>
</table>
</div>
</div>
<div class="action-card">
<!-- 最新获取号码 -->
<div class="highlight-area" id="latestOrderArea" style="display: none;">
<div class="d-flex align-items-center mb-3">
<div class="bg-success text-white rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
<i class="fas fa-check fa-xs"></i>
</div>
<h6 class="fw-bold mb-0 text-success">最新获取号码</h6>
</div>
<div id="latestOrderContent" class="row align-items-center text-center">
<!-- Data populated here -->
</div>
</div>
<!-- Active Tasks -->
<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 class="text-end pe-4">操作</th>
</tr>
</thead>
<tbody id="activeTasksBody"></tbody>
</table>
</div>
</div>
<div class="search-grid">
<div class="custom-dropdown" id="countryContainer">
<label class="form-label small fw-bold text-muted mb-2 px-1">第1步选择国家/地区</label>
@ -378,7 +398,6 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
</div>
</div>
<!-- Success Modal -->
<div class="modal fade" id="smsModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-5 overflow-hidden">
@ -405,14 +424,13 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
let currentCountry = null;
let currentService = null;
let activePolls = {};
let activeTimers = {};
let searchTimeout = null;
document.addEventListener('DOMContentLoaded', () => {
loadCountries();
renderServices(popularServices);
loadActiveOrders();
setInterval(loadActiveOrders, 30000);
setInterval(loadActiveOrders, 10000);
document.addEventListener('click', (e) => {
if (!e.target.closest('.custom-dropdown')) hideAllDropdowns();
@ -445,14 +463,8 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
if (data.code === 0) {
allCountries = Array.isArray(data.data) ? data.data : [];
renderCountries();
} else if (data.code === 401) {
window.location.href = 'index.php';
} else {
listContainer.innerHTML = `<div class="p-4 text-center text-danger small">加载失败: ${data.msg || '未知API错误'}</div>`;
}
} catch (e) {
listContainer.innerHTML = '<div class="p-4 text-center text-danger small">网络连接超时,请刷新页面</div>';
}
} catch (e) {}
}
function toggleDropdown(id, event) {
@ -476,25 +488,15 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
if (!container) return;
container.innerHTML = '';
if (!Array.isArray(allCountries) || allCountries.length === 0) {
container.innerHTML = '<div class="p-3 text-center text-muted small">暂无可用国家数据</div>';
return;
}
const filtered = filter ? allCountries.filter(c =>
(c.name_zh && c.name_zh.includes(filter)) ||
(c.name_en && c.name_en.toLowerCase().includes(filter.toLowerCase()))
) : allCountries;
if (filtered.length === 0) {
container.innerHTML = '<div class="p-3 text-center text-muted small">未找到匹配的国家</div>';
return;
}
filtered.slice(0, 100).forEach(c => {
const div = document.createElement('div');
div.className = 'list-item';
div.innerHTML = `<div><span class="fw-bold">${c.name_zh || '未知'}</span><span class="text-muted ms-2 small">${c.name_en || ''}</span></div><i class="fas fa-chevron-right small opacity-25"></i>`;
div.innerHTML = `<div><span class="fw-bold">${c.name_zh || '未知'}</span></div><i class="fas fa-chevron-right small opacity-25"></i>`;
div.onclick = (e) => { e.stopPropagation(); selectCountry(c); };
container.appendChild(div);
});
@ -504,16 +506,10 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
const container = document.getElementById('servicesList');
if (!container) return;
container.innerHTML = '';
if (!Array.isArray(services) || services.length === 0) {
container.innerHTML = '<div class="p-3 text-center text-muted small">暂无搜索结果</div>';
return;
}
services.forEach(s => {
const div = document.createElement('div');
div.className = 'list-item';
div.innerHTML = `<span class="fw-bold">${s.name}</span><i class="fas fa-star small text-warning opacity-75"></i>`;
div.innerHTML = `<span class="fw-bold">${s.name}</span>`;
div.onclick = (e) => { e.stopPropagation(); selectService(s); };
container.appendChild(div);
});
@ -523,7 +519,6 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
function handleServiceInput() {
const q = document.getElementById('serviceSearch').value;
const listContainer = document.getElementById('servicesList');
if (searchTimeout) clearTimeout(searchTimeout);
if (!q) { renderServices(popularServices); return; }
@ -534,20 +529,12 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
if (data.code === 0) {
const unique = [];
const map = new Map();
const services = Array.isArray(data.data) ? data.data : [];
services.forEach(i => {
if(i.service_name && !map.has(i.service_name)){
map.set(i.service_name, true);
unique.push({name: i.service_name});
}
(data.data || []).forEach(i => {
if(i.service_name && !map.has(i.service_name)){ map.set(i.service_name, true); unique.push({name: i.service_name}); }
});
renderServices(unique);
} else if (data.code === 401) {
window.location.href = 'index.php';
}
} catch (e) {
console.error("Search error", e);
}
} catch (e) {}
}, 400);
}
@ -555,8 +542,7 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
currentCountry = c;
const l = document.getElementById('countryLabel');
l.textContent = c.name_zh;
l.classList.remove('placeholder');
l.classList.add('val');
l.classList.remove('placeholder'); l.classList.add('val');
hideAllDropdowns();
loadQuotation();
}
@ -565,15 +551,14 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
currentService = s;
const l = document.getElementById('serviceLabel');
l.textContent = s.name;
l.classList.remove('placeholder');
l.classList.add('val');
l.classList.remove('placeholder'); l.classList.add('val');
hideAllDropdowns();
loadQuotation();
}
async function loadQuotation() {
const body = document.getElementById('quotationBody');
body.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary" style="width: 2rem; height: 2rem;"></div><div class="mt-2 text-muted small">正在调取实时行情...</div></div>';
body.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary"></div></div>';
const cP = currentCountry ? encodeURIComponent(currentCountry.name_zh) : '';
const sP = currentService ? encodeURIComponent(currentService.name) : '';
@ -583,66 +568,31 @@ $notice_text = $settings['notice_text'] ?? '欢迎使用全球接码平台!';
const data = await res.json();
if (data.code === 0) {
body.innerHTML = '';
const services = Array.isArray(data.data) ? data.data : [];
if (!services.length) {
body.innerHTML = '<div class="p-5 text-center text-muted"><i class="fas fa-exclamation-circle fa-2x mb-3 opacity-25"></i><div>该地区暂无此服务,请尝试其他国家或项目</div></div>';
return;
}
services.forEach(s => {
(data.data || []).forEach(s => {
const item = document.createElement('div');
item.className = 'quotation-item';
const isPop = popularServices.some(ps => ps.name === s.service_name);
item.innerHTML = `
<div class="flex-grow-1">
<div class="d-flex align-items-center gap-2 mb-1">
<span class="fw-bold fs-5">${s.service_name}</span>
${isPop ? '<span class="badge bg-primary bg-opacity-10 text-primary small" style="font-size: 10px; padding: 4px 8px;">热门</span>' : ''}
</div>
<div class="small text-muted"><i class="fas fa-globe-asia me-1 opacity-50"></i> ${s.country_name_zh || (currentCountry ? currentCountry.name_zh : '全球')}</div>
</div>
<div class="text-end me-5">
<div class="small text-muted fw-bold" style="font-size: 10px; letter-spacing: 0.5px;">价格</div>
<div class="fw-bold text-dark fs-5">$${s.cost}</div>
</div>
<div>
<button class="btn-get" onclick="getNumber('${s.service_id}', '${s.service_name}', ${s.cost}, this)">获取号码</button>
<div class="fw-bold fs-5">${s.service_name}</div>
<div class="small text-muted">${s.country_name_zh}</div>
</div>
<div class="text-end me-5"><div class="fw-bold text-dark fs-5">$${s.cost}</div></div>
<div><button class="btn-get" onclick="getNumber('${s.service_id}', '${s.service_name}', ${s.cost}, this)">获取号码</button></div>
`;
body.appendChild(item);
});
document.getElementById('lastUpdated').textContent = '更新时间: ' + new Date().toLocaleTimeString();
} else if (data.code === 401) {
window.location.href = 'index.php';
} else {
body.innerHTML = `<div class="p-5 text-center text-danger">加载行情失败: ${data.msg || '未知接口错误'}</div>`;
}
} catch (e) { body.innerHTML = '<div class="p-5 text-center text-danger">行情数据连接失败,请检查网络</div>'; }
} catch (e) {}
}
async function getNumber(sid, sname, price, btn) {
showConfirm('购买确认', `确认扣费 $${price} 购买 ${sname} 号码?`, async () => {
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i>';
try {
const cname = currentCountry ? currentCountry.name_zh : '全球';
const res = await fetch(`${apiHandler}?action=get_number&service_id=${sid}&service_name=${encodeURIComponent(sname)}&country_name=${encodeURIComponent(cname)}&price=${price}`);
const res = await fetch(`${apiHandler}?action=get_number&service_id=${sid}&service_name=${encodeURIComponent(sname)}&country_name=${encodeURIComponent(currentCountry.name_zh)}&price=${price}`);
const data = await res.json();
if (data.code === 0) {
showToast('号码获取成功!');
loadActiveOrders(); updateBalance(); window.scrollTo({top: 0, behavior: 'smooth'});
} else if (data.code === 401) {
window.location.href = 'index.php';
} else {
showToast(data.msg || '库存不足或接口超时', 'error');
}
} catch (e) {
showToast('获取号码失败,请重试', 'error');
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
}
if (data.code === 0) { showToast('获取成功!'); loadActiveOrders(); updateBalance(); }
else { showToast(data.msg || '获取失败', 'error'); }
} catch (e) { showToast('接口异常', 'error'); }
});
}
@ -660,90 +610,68 @@ $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');
Object.values(activeTimers).forEach(t => clearInterval(t));
activeTimers = {};
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 exp = new Date(o.expire_at.replace(/-/g, "/")).getTime();
let tl = Math.floor((exp - new Date().getTime())/1000);
const row = document.createElement('tr');
row.innerHTML = `
<td class="ps-4 py-4">
<div class="fw-bold text-dark">${o.service_name}</div>
<div class="small text-muted">${o.country_name}</div>
</td>
<td class="fw-bold text-primary fs-5" style="letter-spacing: 1px;">${o.number}</td>
<td id="sms-${o.request_id}">
${o.status === 'received' ? `<span class="sms-badge">${o.sms_content}</span>` : `
<div class="d-flex align-items-center gap-3 text-primary">
<div class="spinner-grow spinner-grow-sm" style="animation-duration: 1.5s;"></div>
<span class="fw-bold small" style="letter-spacing: 0.5px;">等待短信...</span>
</div>`}
</td>
<td><span class="badge bg-light text-dark border p-2 px-3 fw-bold" id="timer-${o.request_id}">${formatTime(tl)}</span></td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-outline-danger fw-bold px-3 py-2 rounded-3" 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') { startPolling(o.request_id); startTimer(o.request_id, tl); }
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) {}
}
function startPolling(rid) {
if (activePolls[rid]) return;
activePolls[rid] = setInterval(async () => {
try {
const res = await fetch(`${apiHandler}?action=check_sms&request_id=${rid}`);
const data = await res.json();
if (data.code === 0 && (data.msg === 'success' || data.sms_code)) {
const el = document.getElementById(`sms-${rid}`);
if (el) el.innerHTML = `<span class="sms-badge">${data.sms_code}</span>`;
clearInterval(activePolls[rid]); delete activePolls[rid]; showSmsModal(data.sms_code);
} else if (data.code === 400 || (data.code !== 0 && data.code !== 500)) {
clearInterval(activePolls[rid]); delete activePolls[rid]; loadActiveOrders();
clearInterval(activePolls[rid]); delete activePolls[rid]; showSmsModal(data.sms_code); loadActiveOrders();
}
} catch (e) {}
}, 5000);
}
function startTimer(id, s) {
activeTimers[id] = setInterval(() => {
s--;
const el = document.getElementById('timer-' + id);
if (s <= 0) { clearInterval(activeTimers[id]); loadActiveOrders(); }
else if (el) el.textContent = formatTime(s);
}, 1000);
}
async function releaseNumber(id) {
showConfirm('释放确认', '确定取消并释放此号码吗?', async () => {
try {
const res = await fetch(`${apiHandler}?action=release_number&request_id=${id}`);
const data = await res.json();
if (data.code === 0) { showToast('号码已取消!'); loadActiveOrders(); updateBalance(); } else { showToast(data.msg, 'error'); }
} catch (e) { showToast('连接服务器失败', 'error'); }
});
}
function formatTime(s) {
if (s <= 0) return "00:00";
const m = Math.floor(s/60), sec = s%60;
return `${m.toString().padStart(2,'0')}:${sec.toString().padStart(2,'0')}`;
try {
const res = await fetch(`${apiHandler}?action=release_number&request_id=${id}`);
loadActiveOrders(); updateBalance();
} catch (e) {}
}
function showSmsModal(code) {
document.getElementById('modalSmsCode').textContent = code;
new bootstrap.Modal(document.getElementById('smsModal')).show();
if(navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code).catch(e => {});
}
}
</script>
</body>

0
dashboard.php.new Normal file
View File

View File

@ -1,5 +1,6 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
date_default_timezone_set("Asia/Shanghai");
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_38320');
define('DB_USER', 'app_38320');
@ -14,4 +15,4 @@ function db() {
]);
}
return $pdo;
}
}

View File

@ -8,6 +8,7 @@ require_once __DIR__ . '/db/config.php';
$pdo = db();
date_default_timezone_set("Asia/Shanghai");
// Handle Cancel Request
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'cancel' && isset($_POST['order_id'])) {
$order_id = $_POST['order_id'];

View File

@ -256,7 +256,7 @@ $user = $stmt->fetch();
const row = document.createElement("div");
row.className = `message-row ${msg.sender === "user" ? "me" : "them"}`;
const time = new Date(msg.created_at.replace(" ", "T")).toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
const time = new Date(msg.created_at.replace(" ", "T") + "Z").toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
row.innerHTML = `
<div class="message-bubble">