Autosave: 20260220-063425

This commit is contained in:
Flatlogic Bot 2026-02-20 06:34:26 +00:00
parent 3955cd1acf
commit abd41c080c
17 changed files with 267 additions and 157 deletions

View File

@ -70,6 +70,13 @@ ob_start();
font-size: 14px;
line-height: 1.5;
position: relative;
display: flex;
flex-direction: column;
}
.msg-time {
font-size: 10px;
opacity: 0.7;
margin-top: 4px;
}
.msg-admin {
align-self: flex-end;
@ -77,12 +84,19 @@ ob_start();
color: #fff;
border-bottom-right-radius: 2px;
}
.msg-admin .msg-time {
text-align: right;
color: #e0e0e0;
}
.msg-user {
align-self: flex-start;
background: #f0f0f0;
color: #333;
border-bottom-left-radius: 2px;
}
.msg-user .msg-time {
color: #888;
}
.chat-input-area {
padding: 15px;
border-top: 1px solid #eee;
@ -123,8 +137,12 @@ ob_start();
<div class="chat-header" id="chat-header" style="display: none;">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="m-0 fw-bold" id="header-name">用户名称</h6>
<div class="small" id="header-meta"><span class="text-muted">IP:</span> <span class="text-primary fw-bold" id="info-ip-header">---</span></div>
<h6 class="m-0 fw-bold"><span id="header-name">用户名称</span> <small class="text-muted fw-normal">(UID: <span id="header-uid">---</span>)</small></h6>
<div class="small" id="header-meta">
<span class="text-muted">实时 IP:</span> <span class="text-primary fw-bold" id="info-ip-header">---</span>
<span class="mx-2 text-muted">|</span>
<span class="text-muted">用户时间:</span> <span class="text-dark fw-bold" id="info-user-time">---</span>
</div>
</div>
<div>
<span class="status-online"></span>
@ -185,19 +203,24 @@ async function refreshUsers() {
users.forEach(u => {
const username = u.username || '匿名用户';
const uid = u.uid || '---';
const ip = u.ip_address;
const ip = u.ip_address || '---';
const remark = u.remark || '';
const userTime = u.user_time || '---';
const lastTime = u.created_at ? new Date(u.created_at.replace(/-/g, "/")) : new Date();
if (search && !username.toLowerCase().includes(search) && !ip.includes(search) && !uid.toString().includes(search)) {
return;
}
const isActive = (selectedIp === ip && selectedUser == u.user_id);
if (isActive) {
document.getElementById('info-user-time').innerText = userTime;
}
html += `
<div class="user-card ${isActive ? 'active' : ''}" onclick="openChat('${u.user_id}', '${ip}', '${username}', '${uid}', '${remark.replace(/'/g, "\\'")}')">
<div class="user-card ${isActive ? 'active' : ''}" onclick="openChat('${u.user_id}', '${ip}', '${username}', '${uid}', '${remark.replace(/'/g, "\\'")}', '${userTime}')">
<div class="d-flex justify-content-between mb-1">
<span class="fw-bold small text-truncate" style="max-width: 150px;">${username}</span>
<span class="text-muted" style="font-size: 10px;">${new Date(u.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
<span class="text-muted" style="font-size: 10px;">${lastTime.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit'})}</span>
</div>
${remark ? `<div class="small text-danger text-truncate mb-1" style="font-size: 11px;">[备注: ${remark}]</div>` : ''}
<div class="small text-truncate text-muted mb-1" style="font-size: 12px;">${u.message}</div>
@ -211,11 +234,13 @@ async function refreshUsers() {
list.innerHTML = html;
}
function openChat(userId, ip, name, uid, remark) {
function openChat(userId, ip, name, uid, remark, userTime) {
selectedUser = userId;
selectedIp = ip;
document.getElementById('header-name').innerText = name;
document.getElementById('header-uid').innerText = uid;
document.getElementById('info-ip-header').innerText = ip;
document.getElementById('info-user-time').innerText = userTime;
document.getElementById('chat-header').style.display = 'block';
document.getElementById('input-area').style.display = 'block';
document.getElementById('remark-area').style.display = 'block';
@ -230,27 +255,45 @@ function openChat(userId, ip, name, uid, remark) {
}
async function fetchMessages() {
if (!selectedIp) return;
if (!selectedIp && !selectedUser) return;
const r = await fetch(`/api/chat.php?action=get_messages&user_id=${selectedUser}&ip=${selectedIp}`);
const msgs = await r.json();
const area = document.getElementById('messages-area');
const filtered = msgs.filter(m => m.ip_address === selectedIp && (m.user_id == selectedUser || m.user_id == 0));
// For registered users, we show all their messages. For guests, we filter by IP.
const filtered = msgs.filter(m => {
if (selectedUser != 0) {
return m.user_id == selectedUser;
} else {
return m.ip_address === selectedIp && m.user_id == 0;
}
});
if (filtered.length > lastMsgId) {
area.innerHTML = '';
if (filtered.length > 0) {
const isAtBottom = area.scrollTop + area.clientHeight >= area.scrollHeight - 50;
let html = '';
filtered.forEach(m => {
const div = document.createElement('div');
div.className = `msg ${m.sender === 'admin' ? 'msg-admin' : 'msg-user'}`;
div.innerHTML = m.message;
area.appendChild(div);
const msgDate = new Date(m.created_at.replace(/-/g, "/"));
const timeStr = msgDate.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
html += `
<div class="msg ${m.sender === 'admin' ? 'msg-admin' : 'msg-user'}">
<div class="msg-content">${m.message}</div>
<div class="msg-time">${timeStr}</div>
</div>
`;
});
area.scrollTop = area.scrollHeight;
if (area.innerHTML !== html) {
area.innerHTML = html;
if (isAtBottom || lastMsgId === 0) {
area.scrollTop = area.scrollHeight;
}
}
lastMsgId = filtered.length;
if (filtered.length > 0) {
document.getElementById('info-time').innerText = new Date(filtered[filtered.length-1].created_at).toLocaleString();
}
const lastMsg = filtered[filtered.length - 1];
document.getElementById('info-time').innerText = new Date(lastMsg.created_at.replace(/-/g, "/")).toLocaleString('zh-CN');
}
}
@ -313,8 +356,8 @@ document.getElementById('save-remark-btn').addEventListener('click', async () =>
document.getElementById('user-search').addEventListener('input', refreshUsers);
setInterval(refreshUsers, 500);
setInterval(fetchMessages, 500);
setInterval(refreshUsers, 300);
setInterval(fetchMessages, 300);
refreshUsers();
</script>

View File

@ -83,71 +83,79 @@ if ($user_id) {
<div class="alert alert-success mb-4">操作成功!</div>
<?php endif; ?>
<div class="row g-4">
<?php foreach ($users as $u): ?>
<div class="col-md-12">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<span>UID: <code><?= $u['uid'] ?></code> | 用户: <strong><?= htmlspecialchars($u['username']) ?></strong></span>
<span>
<?php if ($u['kyc_status'] == 1): ?>
<span class="badge bg-warning">待审核</span>
<?php elseif ($u['kyc_status'] == 2): ?>
<span class="badge bg-success">已通过</span>
<?php elseif ($u['kyc_status'] == 3): ?>
<span class="badge bg-danger">已拒绝</span>
<?php else: ?>
<span class="badge bg-secondary">未提交</span>
<?php endif; ?>
</span>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<p class="mb-1 text-muted small">真实姓名</p>
<h6><?= htmlspecialchars($u['kyc_name'] ?? '未填写') ?></h6>
<p class="mb-1 text-muted small mt-3">身份证号</p>
<h6><?= htmlspecialchars($u['kyc_id_number'] ?? '未填写') ?></h6>
<?php if ($u['kyc_status'] == 3 && $u['kyc_rejection_reason']): ?>
<div class="alert alert-danger mt-3 small">
拒绝理由: <?= htmlspecialchars($u['kyc_rejection_reason']) ?>
</div>
<div class="table-container shadow-sm border-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="text-nowrap">用户ID</th>
<th class="text-nowrap">姓名</th>
<th class="text-nowrap">证件号</th>
<th class="text-nowrap">国家</th>
<th class="text-nowrap">正面照片</th>
<th class="text-nowrap">反面照片</th>
<th class="text-nowrap">手持照片</th>
<th class="text-nowrap">初级验证状态</th>
<th class="text-nowrap">高级验证状态</th>
<th class="text-nowrap">操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td><code><?= $u['uid'] ?></code></td>
<td><?= htmlspecialchars($u['kyc_name'] ?? '---') ?></td>
<td><?= htmlspecialchars($u['kyc_id_number'] ?? '---') ?></td>
<td><span class="text-muted small">Global</span></td>
<td>
<img src="<?= $u['kyc_photo_front'] ? (strpos($u['kyc_photo_front'], 'http') === 0 ? $u['kyc_photo_front'] : '/' . ltrim($u['kyc_photo_front'], '/')) : 'https://via.placeholder.com/100x70?text=No+Photo' ?>"
class="rounded border cursor-zoom-in" style="width: 60px; height: 40px; object-fit: cover;"
onclick="viewPhoto(this.src)">
</td>
<td>
<img src="<?= $u['kyc_photo_back'] ? (strpos($u['kyc_photo_back'], 'http') === 0 ? $u['kyc_photo_back'] : '/' . ltrim($u['kyc_photo_back'], '/')) : 'https://via.placeholder.com/100x70?text=No+Photo' ?>"
class="rounded border cursor-zoom-in" style="width: 60px; height: 40px; object-fit: cover;"
onclick="viewPhoto(this.src)">
</td>
<td>
<img src="<?= $u['kyc_photo_handheld'] ? (strpos($u['kyc_photo_handheld'], 'http') === 0 ? $u['kyc_photo_handheld'] : '/' . ltrim($u['kyc_photo_handheld'], '/')) : 'https://via.placeholder.com/100x70?text=No+Photo' ?>"
class="rounded border cursor-zoom-in" style="width: 60px; height: 40px; object-fit: cover;"
onclick="viewPhoto(this.src)">
</td>
<td>
<?php if ($u['kyc_status'] == 1): ?>
<span class="badge bg-warning">待审核</span>
<?php elseif ($u['kyc_status'] == 2): ?>
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3">已审核</span>
<?php elseif ($u['kyc_status'] == 3): ?>
<span class="badge bg-danger">已拒绝</span>
<?php else: ?>
<span class="badge bg-secondary">未提交</span>
<?php endif; ?>
</div>
<div class="col-md-8">
<div class="row g-2">
<div class="col-md-4">
<p class="small text-center mb-1">正面照</p>
<img src="<?= $u['kyc_photo_front'] ? (strpos($u['kyc_photo_front'], 'http') === 0 ? $u['kyc_photo_front'] : '/' . ltrim($u['kyc_photo_front'], '/')) : 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded cursor-zoom-in" onclick="viewPhoto(this.src)">
</div>
<div class="col-md-4">
<p class="small text-center mb-1">反面照</p>
<img src="<?= $u['kyc_photo_back'] ? (strpos($u['kyc_photo_back'], 'http') === 0 ? $u['kyc_photo_back'] : '/' . ltrim($u['kyc_photo_back'], '/')) : 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded cursor-zoom-in" onclick="viewPhoto(this.src)">
</div>
<div class="col-md-4">
<p class="small text-center mb-1">手持照</p>
<img src="<?= $u['kyc_photo_handheld'] ? (strpos($u['kyc_photo_handheld'], 'http') === 0 ? $u['kyc_photo_handheld'] : '/' . ltrim($u['kyc_photo_handheld'], '/')) : 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded cursor-zoom-in" onclick="viewPhoto(this.src)">
</div>
</td>
<td>
<span class="text-muted small">---</span>
</td>
<td>
<div class="d-flex gap-2">
<?php if ($u['kyc_status'] == 1): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="user_id" value="<?= $u['id'] ?>">
<input type="hidden" name="action" value="approve">
<button type="submit" class="btn btn-sm btn-success" onclick="return confirm('确定通过审核吗?')">通过</button>
</form>
<button class="btn btn-sm btn-danger" onclick="showRejectModal(<?= $u['id'] ?>)">拒绝</button>
<?php else: ?>
<span class="text-muted small">已处理</span>
<?php endif; ?>
</div>
</div>
</div>
<?php if ($u['kyc_status'] == 1): ?>
<div class="mt-4 pt-3 border-top d-flex gap-2">
<form method="POST" class="d-inline">
<input type="hidden" name="user_id" value="<?= $u['id'] ?>">
<input type="hidden" name="action" value="approve">
<button type="submit" class="btn btn-success" onclick="return confirm('确定通过审核吗?')">通过认证</button>
</form>
<button class="btn btn-danger" onclick="showRejectModal(<?= $u['id'] ?>)">拒绝申请</button>
</div>
<?php endif; ?>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
</div>
<?php if (empty($users)): ?>
<div class="col-12">

View File

@ -242,7 +242,10 @@ function renderAdminPage($content, $title = '后台管理') {
</div>
<div class="admin-header">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center gap-2">
<button onclick="location.reload()" class="btn btn-sm btn-light border">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
<h5 class="mb-0"><?= $title ?></h5>
</div>
<div class="d-flex align-items-center gap-3">

View File

@ -52,8 +52,8 @@ if ($action === 'place_order') {
->execute([$amount, $user_id]);
// Insert order
$stmt = $db->prepare("INSERT INTO binary_orders (user_id, symbol, direction, amount, duration, entry_price, profit_rate, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', NOW())");
$stmt->execute([$user_id, $symbol, $direction, $amount, $duration, $entry_price, $profit_rate]);
$stmt = $db->prepare("INSERT INTO binary_orders (user_id, symbol, direction, amount, duration, entry_price, profit_rate, status, created_at, ip_address) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', NOW(), ?)");
$stmt->execute([$user_id, $symbol, $direction, $amount, $duration, $entry_price, $profit_rate, getRealIP()]);
$order_id = $db->lastInsertId();
$db->commit();
@ -124,11 +124,11 @@ if ($action === 'settle_order') {
$db->prepare("UPDATE user_balances SET available = available + ? WHERE user_id = ? AND symbol = 'USDT'")
->execute([$win_amount, $user_id]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'binary_win', ?, 'USDT', 'completed')")
->execute([$user_id, $win_amount]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'binary_win', ?, 'USDT', 'completed', ?)")
->execute([$user_id, $win_amount, getRealIP()]);
} else {
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'binary_loss', ?, 'USDT', 'completed')")
->execute([$user_id, $order['amount']]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'binary_loss', ?, 'USDT', 'completed', ?)")
->execute([$user_id, $order['amount'], getRealIP()]);
}
$db->commit();

View File

@ -106,35 +106,55 @@ if ($action === 'admin_send') {
if ($action === 'ping') {
$user_id = $_SESSION['user_id'] ?? 0;
$ip = getRealIP();
$stmt = db()->prepare("INSERT INTO chat_visitors (user_id, ip_address) VALUES (?, ?) ON DUPLICATE KEY UPDATE last_ping = CURRENT_TIMESTAMP");
$stmt->execute([$user_id, $ip]);
$user_time = $_GET['user_time'] ?? '';
$stmt = db()->prepare("INSERT INTO chat_visitors (user_id, ip_address, user_time) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE last_ping = CURRENT_TIMESTAMP, user_time = ?");
$stmt->execute([$user_id, $ip, $user_time, $user_time]);
echo json_encode(['success' => true]);
exit;
}
if ($action === 'admin_get_all') {
// Get distinct users/IPs who have messaged or pinged
// Combine messages and visitors to show even those who haven't messaged yet
// Get distinct users (prefer user_id, fallback to IP if user_id is 0)
// Registered users are grouped by user_id. Anonymous users are grouped by ip_address.
$stmt = db()->query("
SELECT
combined.user_id,
combined.ip_address,
combined.group_user_id as user_id,
combined.display_ip as ip_address,
COALESCE(m.message, '用户已进入聊天室') as message,
COALESCE(m.created_at, combined.last_activity) as created_at,
u.username,
u.uid,
r.remark
r.remark,
combined.user_time
FROM (
SELECT user_id, ip_address, MAX(created_at) as last_activity FROM messages GROUP BY user_id, ip_address
SELECT
user_id as group_user_id,
CASE WHEN user_id = 0 THEN ip_address ELSE '0' END as group_id,
MAX(ip_address) as display_ip,
MAX(created_at) as last_activity,
NULL as user_time
FROM messages
GROUP BY group_user_id, group_id
UNION
SELECT user_id, ip_address, last_ping as last_activity FROM chat_visitors
SELECT
user_id as group_user_id,
CASE WHEN user_id = 0 THEN ip_address ELSE '0' END as group_id,
MAX(ip_address) as display_ip,
MAX(last_ping) as last_activity,
MAX(user_time) as user_time
FROM chat_visitors
GROUP BY group_user_id, group_id
) combined
LEFT JOIN (
SELECT user_id, ip_address, message, created_at FROM messages WHERE id IN (SELECT MAX(id) FROM messages GROUP BY user_id, ip_address)
) m ON (combined.user_id = m.user_id AND combined.ip_address = m.ip_address)
LEFT JOIN users u ON combined.user_id = u.id
LEFT JOIN chat_remarks r ON (combined.user_id = r.user_id AND combined.ip_address = r.ip_address)
WHERE combined.last_activity > DATE_SUB(NOW(), INTERVAL 24 HOUR)
SELECT id, user_id, ip_address, message, created_at FROM messages
WHERE id IN (
SELECT MAX(id) FROM messages GROUP BY user_id, (CASE WHEN user_id = 0 THEN ip_address ELSE '0' END)
)
) m ON (combined.group_user_id = m.user_id AND (combined.group_user_id != 0 OR combined.display_ip = m.ip_address))
LEFT JOIN users u ON (combined.group_user_id = u.id AND combined.group_user_id != 0)
LEFT JOIN chat_remarks r ON (combined.group_user_id = r.user_id AND (combined.group_user_id != 0 OR combined.display_ip = r.ip_address))
WHERE combined.last_activity > DATE_SUB(NOW(), INTERVAL 48 HOUR)
GROUP BY combined.group_user_id, combined.display_ip
ORDER BY created_at DESC
");
echo json_encode($stmt->fetchAll());

View File

@ -46,12 +46,12 @@ if ($action === 'place_order') {
->execute([$margin, $user_id]);
// Insert order
$stmt = $db->prepare("INSERT INTO contract_orders (user_id, symbol, type, direction, leverage, amount, entry_price, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'open')");
$stmt->execute([$user_id, $symbol, $type, $direction, $leverage, $amount, $entry_price]);
$stmt = $db->prepare("INSERT INTO contract_orders (user_id, symbol, type, direction, leverage, amount, entry_price, status, ip_address) VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?)");
$stmt->execute([$user_id, $symbol, $type, $direction, $leverage, $amount, $entry_price, getRealIP()]);
// Record transaction (Margin lock)
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'contract_margin', ?, 'USDT', 'completed')")
->execute([$user_id, $margin]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'contract_margin', ?, 'USDT', 'completed', ?)")
->execute([$user_id, $margin, getRealIP()]);
$db->commit();
echo json_encode(['success' => true]);
@ -109,8 +109,8 @@ if ($action === 'place_order') {
->execute([$total_return, $user_id]);
// Record transaction
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'contract_settle', ?, 'USDT', 'completed')")
->execute([$user_id, $total_return]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'contract_settle', ?, 'USDT', 'completed', ?)")
->execute([$user_id, $total_return, getRealIP()]);
}
$db->commit();

View File

@ -166,8 +166,8 @@ if ($action === 'recharge') {
exit;
}
$stmt = $db->prepare("INSERT INTO finance_requests (user_id, type, amount, symbol, payment_method, tx_hash, fiat_amount, fiat_currency, status) VALUES (?, 'recharge', ?, ?, ?, ?, ?, ?, 'pending')");
$stmt->execute([$user_id, $amount, $symbol, $method, $tx_hash, $fiat_amount, $fiat_currency]);
$stmt = $db->prepare("INSERT INTO finance_requests (user_id, type, amount, symbol, payment_method, tx_hash, fiat_amount, fiat_currency, status, ip_address) VALUES (?, 'recharge', ?, ?, ?, ?, ?, ?, 'pending', ?)");
$stmt->execute([$user_id, $amount, $symbol, $method, $tx_hash, $fiat_amount, $fiat_currency, getRealIP()]);
echo json_encode(['success' => true]);
exit;
@ -201,12 +201,12 @@ if ($action === 'withdraw') {
->execute([$amount, $user_id, $symbol]);
// Record request
$stmt = $db->prepare("INSERT INTO finance_requests (user_id, type, amount, symbol, payment_details, fiat_amount, fiat_currency, status) VALUES (?, 'withdrawal', ?, ?, ?, ?, ?, 'pending')");
$stmt->execute([$user_id, $amount, $symbol, $address, $fiat_amount, $fiat_currency]);
$stmt = $db->prepare("INSERT INTO finance_requests (user_id, type, amount, symbol, payment_details, fiat_amount, fiat_currency, status, ip_address) VALUES (?, 'withdrawal', ?, ?, ?, ?, ?, 'pending', ?)");
$stmt->execute([$user_id, $amount, $symbol, $address, $fiat_amount, $fiat_currency, getRealIP()]);
// Add to transactions as pending
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'withdrawal', ?, ?, 'pending')")
->execute([$user_id, $amount, $symbol]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'withdrawal', ?, ?, 'pending', ?)")
->execute([$user_id, $amount, $symbol, getRealIP()]);
$db->commit();
echo json_encode(['success' => true]);

View File

@ -53,12 +53,12 @@ try {
$endDate = date('Y-m-d', strtotime("+$period days"));
if ($period == 0) $endDate = '2099-12-31';
$stmt = $db->prepare("INSERT INTO staking_records (user_id, plan_name, amount, symbol, daily_profit, period, status, start_date, end_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$userId, $poolName, $amount, $symbol, $dailyProfit, $period, 'running', $startDate, $endDate]);
$stmt = $db->prepare("INSERT INTO staking_records (user_id, plan_name, amount, symbol, daily_profit, period, status, start_date, end_date, ip_address) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$userId, $poolName, $amount, $symbol, $dailyProfit, $period, 'running', $startDate, $endDate, getRealIP()]);
// Add transaction record
$stmt = $db->prepare("INSERT INTO transactions (user_id, symbol, type, amount, status) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$userId, $symbol, 'mining', $amount, 'completed']);
$stmt = $db->prepare("INSERT INTO transactions (user_id, symbol, type, amount, status, ip_address) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$userId, $symbol, 'mining', $amount, 'completed', getRealIP()]);
$db->commit();
echo json_encode(['success' => true]);

View File

@ -44,27 +44,14 @@ try {
// Insert order (auto-fill for spot if price matches)
// For this simple implementation, we mark as filled immediately
$stmt = $db->prepare("INSERT INTO spot_orders (user_id, symbol, side, price, amount, filled, status) VALUES (?, ?, ?, ?, ?, ?, 'filled')");
$stmt->execute([$user_id, $symbol, $side, $price, $amount, $amount]);
$stmt = $db->prepare("INSERT INTO spot_orders (user_id, symbol, side, price, amount, filled, status, ip_address) VALUES (?, ?, ?, ?, ?, ?, 'filled', ?)");
$stmt->execute([$user_id, $symbol, $side, $price, $amount, $amount, getRealIP()]);
// Add target balance
$target_symbol = ($side === 'buy') ? $symbol : 'USDT';
$target_amount = ($side === 'buy') ? $amount : ($price * $amount);
// Check if target balance exists, if not create
$stmt = $db->prepare("SELECT id FROM user_balances WHERE user_id = ? AND symbol = ?");
$stmt->execute([$user_id, $target_symbol]);
if (!$stmt->fetch()) {
$db->prepare("INSERT INTO user_balances (user_id, symbol, available) VALUES (?, ?, 0)")
->execute([$user_id, $target_symbol]);
}
$db->prepare("UPDATE user_balances SET available = available + ? WHERE user_id = ? AND symbol = ?")
->execute([$target_amount, $user_id, $target_symbol]);
// ...
// Record transaction
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, ?, ?, ?, 'completed')")
->execute([$user_id, 'spot_trade', $total_cost, $pay_symbol]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, ?, ?, ?, 'completed', ?)")
->execute([$user_id, 'spot_trade', $total_cost, $pay_symbol, getRealIP()]);
$db->commit();
echo json_encode(['success' => true]);

View File

@ -78,7 +78,7 @@ $from_price = getPrice($from_coin);
$to_price = getPrice($to_coin);
if ($from_price === null || $to_price === null) {
echo json_encode(["success" => false, "error" => "Failed to fetch exchange rate (Provider issue)"]);
echo json_encode(["success" => false, "error" => __("rate_fetch_failed")]);
exit;
}
@ -111,12 +111,12 @@ try {
->execute([$target_amount, $user_id, $to_coin]);
// Record exchange
$stmt = $db->prepare("INSERT INTO exchange_records (user_id, from_symbol, to_symbol, from_amount, to_amount, rate) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $from_coin, $to_coin, $amount, $target_amount, $rate]);
$stmt = $db->prepare("INSERT INTO exchange_records (user_id, from_symbol, to_symbol, from_amount, to_amount, rate, ip_address) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $from_coin, $to_coin, $amount, $target_amount, $rate, getRealIP()]);
// Record transaction
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'swap', ?, ?, 'completed')")
->execute([$user_id, $amount, $from_coin]);
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status, ip_address) VALUES (?, 'swap', ?, ?, 'completed', ?)")
->execute([$user_id, $amount, $from_coin, getRealIP()]);
$db->commit();
echo json_encode(["success" => true, "target_amount" => $target_amount]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -40,7 +40,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($reg_type === 'email') {
$email = $account;
$username = explode('@', $account)[0] . mt_rand(100, 999);
$username = explode('@', $account)[0];
} else {
$email = $username . '@user.byro'; // Fallback
}

View File

@ -184,9 +184,16 @@ $service_link = getSetting('service_link');
<div id="cs-box" class="bg-surface border border-secondary shadow-lg d-none" style="position: fixed; bottom: 100px; right: 30px; width: 350px; height: 500px; border-radius: 20px; z-index: 9999; display: flex; flex-direction: column; overflow: hidden; background: #161a1e;">
<div class="bg-primary p-3 d-flex justify-content-between align-items-center text-white">
<div class="d-flex align-items-center gap-2">
<div class="bg-white rounded-circle" style="width: 10px; height: 10px;"></div>
<span class="fw-bold"><?= __('online_support') ?></span>
<div class="d-flex flex-column">
<div class="d-flex align-items-center gap-2">
<div class="bg-white rounded-circle" style="width: 10px; height: 10px;"></div>
<span class="fw-bold"><?= __('online_support') ?></span>
</div>
<?php if (isset($_SESSION['uid'])): ?>
<div style="font-size: 10px; opacity: 0.8;">
IP: <?= getRealIP() ?>
</div>
<?php endif; ?>
</div>
<button class="btn btn-sm text-white border-0" id="cs-close"><i class="bi bi-x-lg"></i></button>
</div>
@ -245,7 +252,9 @@ csToggle.addEventListener('click', () => {
csBox.classList.toggle('d-none');
scrollToBottom();
if (!csBox.classList.contains('d-none')) {
fetch('/api/chat.php?action=ping');
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
fetch('/api/chat.php?action=ping&user_time=' + encodeURIComponent(timeStr));
}
});
@ -277,12 +286,22 @@ csForm.addEventListener('submit', async (e) => {
}
});
function appendMessage(sender, text) {
function appendMessage(sender, text, time = null) {
const div = document.createElement('div');
div.className = `mb-2 d-flex ${sender === 'user' ? 'justify-content-end' : 'justify-content-start'}`;
div.className = `mb-3 d-flex flex-column ${sender === 'user' ? 'align-items-end' : 'align-items-start'}`;
let dateObj;
if (time) {
dateObj = new Date(time.replace(/-/g, "/"));
} else {
dateObj = new Date();
}
const timeStr = dateObj.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
div.innerHTML = `
<div class="p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary'}" style="max-width: 80%; color: #ffffff !important;">
<div class="p-2 px-3 rounded-4 small ${sender === 'user' ? 'bg-primary text-white' : 'bg-dark text-white border border-secondary'}" style="max-width: 80%; color: #ffffff !important; word-break: break-all; position: relative; padding-bottom: 20px !important;">
${text}
<div style="font-size: 9px; opacity: 0.6; position: absolute; bottom: 4px; ${sender === 'user' ? 'right: 10px;' : 'left: 10px;'}">${timeStr}</div>
</div>
`;
csMessages.appendChild(div);
@ -291,22 +310,37 @@ function appendMessage(sender, text) {
// Polling for new messages
let lastMsgId = 0;
let lastPingTime = 0;
async function pollMessages() {
if (csBox.classList.contains('d-none')) return;
// Ping every 10 seconds to update user time
const now = Date.now();
if (now - lastPingTime > 10000) {
const timeStr = new Date().toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit', second: '2-digit'});
fetch('/api/chat.php?action=ping&user_time=' + encodeURIComponent(timeStr));
lastPingTime = now;
}
try {
const resp = await fetch('/api/chat.php?action=get_messages');
const data = await resp.json();
if (data && data.length > 0) {
let hasNew = false;
data.forEach(m => {
if (parseInt(m.id) > lastMsgId) {
appendMessage(m.sender, m.message);
// Check if this is an image upload temp message and remove it if needed
// Actually, simple append is fine.
appendMessage(m.sender, m.message, m.created_at);
lastMsgId = parseInt(m.id);
hasNew = true;
}
});
if (hasNew) scrollToBottom();
}
} catch (err) {}
}
setInterval(pollMessages, 500);
setInterval(pollMessages, 300);
</script>
<style>

View File

@ -522,6 +522,11 @@ $translations = [
'backend_settings' => '后台设置',
'personal_settings' => '个人设置',
'agents' => '代理管理',
'processing' => '处理中...',
'swap_processing_desc' => '请稍候,正在为您完成兑换',
'swap_success_desc' => '您的兑换已成功完成!',
'unknown_error' => '发生未知错误',
'rate_fetch_failed' => '获取汇率失败(服务商问题)',
],
'en' => [
'home' => 'Home',
@ -1023,6 +1028,11 @@ $translations = [
'mxn_name' => 'MXN',
'php_name' => 'PHP',
'idr_name' => 'IDR',
'processing' => 'Processing...',
'swap_processing_desc' => 'Please wait while we complete your exchange',
'swap_success_desc' => 'Your exchange has been completed successfully!',
'unknown_error' => 'Unknown error occurred',
'rate_fetch_failed' => 'Failed to fetch exchange rate (Provider issue)',
],
];

View File

@ -194,6 +194,7 @@ async function executeSwap() {
title: '<?= __("enter_amount") ?>',
background: '#161a1e',
color: '#fff',
confirmButtonText: '<?= __("confirm") ?>',
confirmButtonColor: '#0062ff',
customClass: {
popup: 'rounded-4 border border-secondary shadow-lg'
@ -208,6 +209,7 @@ async function executeSwap() {
title: '<?= __("insufficient_balance") ?>',
background: '#161a1e',
color: '#fff',
confirmButtonText: '<?= __("confirm") ?>',
confirmButtonColor: '#0062ff',
customClass: {
popup: 'rounded-4 border border-secondary shadow-lg'
@ -241,8 +243,8 @@ async function executeSwap() {
if (!confirmResult.isConfirmed) return;
Swal.fire({
title: 'Processing...',
html: 'Please wait while we complete your exchange',
title: '<?= __("processing") ?>',
html: '<?= __("swap_processing_desc") ?>',
allowOutsideClick: false,
background: '#161a1e',
color: '#fff',
@ -256,7 +258,7 @@ async function executeSwap() {
swapBtn.disabled = true;
const originalText = swapBtn.innerText;
swapBtn.innerText = "Processing...";
swapBtn.innerText = '<?= __("processing") ?>';
const formData = new FormData();
formData.append("from_coin", fromCoin);
@ -274,9 +276,10 @@ async function executeSwap() {
await Swal.fire({
icon: 'success',
title: '<?= __("success") ?>',
text: 'Your exchange has been completed successfully!',
text: '<?= __("swap_success_desc") ?>',
background: '#161a1e',
color: '#fff',
confirmButtonText: '<?= __("confirm") ?>',
confirmButtonColor: '#0062ff',
customClass: {
popup: 'rounded-4 border border-secondary shadow-lg'
@ -287,9 +290,10 @@ async function executeSwap() {
Swal.fire({
icon: 'error',
title: '<?= __("failed") ?>',
text: result.error || 'Unknown error occurred',
text: result.error || '<?= __("unknown_error") ?>',
background: '#161a1e',
color: '#fff',
confirmButtonText: '<?= __("confirm") ?>',
confirmButtonColor: '#0062ff',
customClass: {
popup: 'rounded-4 border border-secondary shadow-lg'
@ -304,6 +308,7 @@ async function executeSwap() {
title: '<?= __("request_failed") ?>',
background: '#161a1e',
color: '#fff',
confirmButtonText: '<?= __("confirm") ?>',
confirmButtonColor: '#0062ff',
customClass: {
popup: 'rounded-4 border border-secondary shadow-lg'