330 lines
17 KiB
PHP
330 lines
17 KiB
PHP
<?php
|
||
require_once "auth.php";
|
||
require_once '../db/config.php';
|
||
require_once '../includes/currency_helper.php';
|
||
$pdo = db();
|
||
|
||
$user_id = $_GET['user_id'] ?? null;
|
||
if (!$user_id) die("需要用户 ID");
|
||
|
||
// Initial mark as read
|
||
$pdo->prepare("UPDATE messages SET is_read = 1 WHERE user_id = ? AND sender = 'user'")->execute([$user_id]);
|
||
|
||
// 处理消息发送
|
||
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['message'])) {
|
||
$msg = $_POST['message'];
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message, type) VALUES (?, 'admin', ?, 'text')")->execute([$user_id, $msg]);
|
||
exit;
|
||
}
|
||
|
||
// 处理充值/提现操作
|
||
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
|
||
$oid = $_POST['order_id'];
|
||
if ($_POST['action'] == 'match_deposit') {
|
||
$bank = $_POST['bank_name'] ?? '';
|
||
$name = $_POST['account_name'] ?? '';
|
||
$number = $_POST['account_number'] ?? '';
|
||
$remarks = $_POST['remarks'] ?? '';
|
||
$info = "Bank:$bank\nAccount Name:$name\nBank Acc:$number\nRemarks:$remarks";
|
||
$pdo->prepare("UPDATE fiat_orders SET status = 'matched', bank_account_info = ? WHERE id = ?")->execute([$info, $oid]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $info]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "✅ 匹配成功!收款账户已下发,请在转账后上传凭证。clearly"]);
|
||
echo "<script>parent.location.reload();</script>";
|
||
exit;
|
||
} elseif ($_POST['action'] == 'send_withdraw_format') {
|
||
$format = "Bank:\nBank Acc:\nAccount Name:";
|
||
$pdo->prepare("UPDATE fiat_orders SET status = 'matched', bank_account_info = ? WHERE id = ?")->execute([$format, $oid]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $format]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "✅ 请按上述格式回复您的收款详情。"]);
|
||
echo "<script>parent.location.reload();</script>";
|
||
exit;
|
||
} elseif ($_POST['action'] == 'complete') {
|
||
$stmt = $pdo->prepare("SELECT order_type, usdt_amount, currency FROM fiat_orders WHERE id = ?");
|
||
$stmt->execute([$oid]);
|
||
$order = $stmt->fetch();
|
||
|
||
$pdo->beginTransaction();
|
||
try {
|
||
if ($order['order_type'] === 'deposit') {
|
||
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['usdt_amount'], $user_id]);
|
||
$msg = "🎉 您的充值已确认到账!";
|
||
} else {
|
||
$msg = "🎉 您的提现申请已处理完成,请查收!";
|
||
}
|
||
$pdo->prepare("UPDATE fiat_orders SET status = 'completed' WHERE id = ?")->execute([$oid]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $msg]);
|
||
$pdo->commit();
|
||
} catch (Exception $e) {
|
||
$pdo->rollBack();
|
||
die($e->getMessage());
|
||
}
|
||
echo "<script>parent.location.reload();</script>";
|
||
exit;
|
||
} elseif ($_POST['action'] == 'reject') {
|
||
$stmt = $pdo->prepare("SELECT order_type, usdt_amount FROM fiat_orders WHERE id = ?");
|
||
$stmt->execute([$oid]);
|
||
$order = $stmt->fetch();
|
||
|
||
$pdo->beginTransaction();
|
||
try {
|
||
if ($order['order_type'] === 'withdrawal') {
|
||
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['usdt_amount'], $user_id]);
|
||
}
|
||
$pdo->prepare("UPDATE fiat_orders SET status = 'rejected' WHERE id = ?")->execute([$oid]);
|
||
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "❌ 您的申请已被拒绝,如有疑问请咨询客服。"]);
|
||
$pdo->commit();
|
||
} catch (Exception $e) {
|
||
$pdo->rollBack();
|
||
die($e->getMessage());
|
||
}
|
||
echo "<script>parent.location.reload();</script>";
|
||
exit;
|
||
}
|
||
}
|
||
|
||
$user = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||
$user->execute([$user_id]);
|
||
$userData = $user->fetch();
|
||
|
||
$orders = $pdo->prepare("SELECT * FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'matched', 'paid') ORDER BY id DESC");
|
||
$orders->execute([$user_id]);
|
||
$pending_orders = $orders->fetchAll();
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||
<style>
|
||
body { margin: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0B0E11; color: white; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
|
||
.chat-header { padding: 12px 20px; background: #1E2329; border-bottom: 1px solid #2B3139; display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
|
||
.header-top { display: flex; justify-content: space-between; align-items: center; }
|
||
.header-bottom { display: flex; justify-content: space-between; font-size: 12px; color: #848E9C; }
|
||
|
||
#chat-box { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 15px; overflow-y: auto; scroll-behavior: smooth; background: #0b0e11; }
|
||
|
||
.msg { max-width: 80%; padding: 12px 16px; border-radius: 16px; font-size: 13.5px; line-height: 1.6; position: relative; }
|
||
.msg.admin { align-self: flex-end; background: #f0b90b; color: black; border-bottom-right-radius: 4px; }
|
||
.msg.user { align-self: flex-start; background: #2B3139; color: #EAECEF; border-bottom-left-radius: 4px; border: 1px solid #3b424d; }
|
||
.msg-time { font-size: 10px; opacity: 0.5; margin-top: 5px; display: block; text-align: right; }
|
||
|
||
.input-area { padding: 15px 20px; background: #1E2329; border-top: 1px solid #2B3139; display: flex; gap: 12px; align-items: center; }
|
||
input[type="text"] { flex: 1; background: #0B0E11; border: 1px solid #2B3139; color: white; padding: 12px 15px; border-radius: 10px; outline: none; transition: 0.3s; }
|
||
input[type="text"]:focus { border-color: #f0b90b; }
|
||
button { background: #f0b90b; border: none; color: black; padding: 10px 20px; border-radius: 10px; cursor: pointer; font-weight: bold; transition: 0.3s; }
|
||
button:hover { background: #fcd535; }
|
||
|
||
.order-panel { background: #1E2329; border-bottom: 1px solid #2B3139; padding: 15px; max-height: 250px; overflow-y: auto; }
|
||
.order-card { background: #2B3139; border: 1px solid #3b424d; padding: 15px; border-radius: 12px; margin-bottom: 10px; }
|
||
.order-card input { background: #0B0E11; border: 1px solid #3b424d; color: white; padding: 8px; border-radius: 6px; font-size: 12px; margin-bottom: 5px; width: 100%; box-sizing: border-box; }
|
||
|
||
.status-tag { padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: bold; text-transform: uppercase; }
|
||
.status-matching { background: rgba(240, 185, 11, 0.2); color: #f0b90b; }
|
||
.status-matched { background: rgba(0, 192, 135, 0.2); color: #00c087; }
|
||
.status-paid { background: rgba(14, 203, 129, 0.2); color: #0ecb81; }
|
||
|
||
.sending { opacity: 0.7; }
|
||
.sending::after { content: '...'; position: absolute; right: 5px; bottom: 5px; font-size: 10px; }
|
||
|
||
.icon-btn { background: #2b3139; border: 1px solid #3b424d; width: 40px; height: 40px; border-radius: 8px; color: #f0b90b; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
|
||
.icon-btn:hover { background: #3b424d; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="chat-header">
|
||
<div class="header-top">
|
||
<span><i class="fas fa-user-circle"></i> <b><?php echo htmlspecialchars($userData['username'] ?? ''); ?></b> (UID: <?php echo $userData['uid'] ?? ''; ?>)</span>
|
||
<span style="color: #00c087; font-weight: bold;">余额: <?php echo number_format($userData['balance'] ?? 0, 2); ?> USDT</span>
|
||
</div>
|
||
<div class="header-bottom">
|
||
<span><i class="fas fa-network-wired"></i> 实时 IP: <?php echo htmlspecialchars($userData['last_ip'] ?? '未知'); ?></span>
|
||
<span><i class="fas fa-clock"></i> 当前时间: <?php echo date('Y-m-d H:i:s'); ?></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
|
||
<?php if ($pending_orders): ?>
|
||
<div class="order-panel">
|
||
<div style="font-size: 11px; color: #848E9C; margin-bottom: 10px; font-weight: bold;">待处理申请 (ACTIVE REQUESTS)</div>
|
||
<?php foreach($pending_orders as $o):
|
||
$orderTypeDisplay = $o['order_type'] === 'deposit' ? '充值' : '提现';
|
||
$statusDisplay = '';
|
||
if ($o['status'] === 'matching') $statusDisplay = '待受理';
|
||
elseif ($o['status'] === 'matched') $statusDisplay = '已下发';
|
||
elseif ($o['status'] === 'paid') $statusDisplay = '待审核';
|
||
?>
|
||
<div class="order-card">
|
||
<div style="display:flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||
<div>
|
||
<span style="font-weight: 800; color: #f0b90b; font-size: 14px;"><?php echo $orderTypeDisplay; ?>: <?php echo $o['amount']; ?> <?php echo $o['currency']; ?></span>
|
||
<span style="color: #848E9C; font-size: 12px; margin-left: 10px;">≈ <?php echo number_format($o['usdt_amount'], 2); ?> USDT</span>
|
||
</div>
|
||
<span class="status-tag status-<?php echo $o['status']; ?>"><?php echo $statusDisplay; ?></span>
|
||
</div>
|
||
|
||
<?php if($o['status'] == 'matching'): ?>
|
||
<?php if($o['order_type'] == 'deposit'): ?>
|
||
<form method="POST">
|
||
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
|
||
<input type="hidden" name="action" value="match_deposit">
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||
<input type="text" name="bank_name" placeholder="银行名称" required>
|
||
<input type="text" name="account_name" placeholder="收款姓名" required>
|
||
<input type="text" name="account_number" placeholder="收款账号 / IBAN" required style="grid-column: span 2;">
|
||
<input type="text" name="remarks" placeholder="备注说明 (可选)" style="grid-column: span 2;">
|
||
<button type="submit" style="grid-column: span 2; background: #00c087; color: white;">下发收款账户</button>
|
||
</div>
|
||
</form>
|
||
<?php else: ?>
|
||
<form method="POST">
|
||
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
|
||
<input type="hidden" name="action" value="send_withdraw_format">
|
||
<button type="submit" style="width: 100%; background: #4facfe; color: white;">发送提现信息格式模板</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
<?php else: ?>
|
||
<div style="display: flex; gap: 10px;">
|
||
<form method="POST" style="flex: 1;">
|
||
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
|
||
<input type="hidden" name="action" value="complete">
|
||
<button type="submit" style="width: 100%; background: #00c087; color: white;">同意 (Approve)</button>
|
||
</form>
|
||
<form method="POST" style="flex: 1;">
|
||
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
|
||
<input type="hidden" name="action" value="reject">
|
||
<button type="submit" style="width: 100%; background: #f6465d; color: white;">拒绝 (Reject)</button>
|
||
</form>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div id="chat-box"></div>
|
||
</div>
|
||
|
||
<form class="input-area" id="msg-form">
|
||
<button type="button" class="icon-btn" id="upload-btn" onclick="document.getElementById('image-input').click()">
|
||
<i class="fas fa-plus"></i>
|
||
</button>
|
||
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="uploadImage(this)">
|
||
|
||
<input type="text" id="msg-input" placeholder="输入消息..." autocomplete="off">
|
||
<button type="submit"><i class="fas fa-paper-plane"></i></button>
|
||
</form>
|
||
|
||
<audio id="notif-sound" src="https://assets.mixkit.co/active_storage/sfx/2869/2869-preview.mp3" preload="auto"></audio>
|
||
|
||
<script>
|
||
const chatBox = document.getElementById('chat-box');
|
||
const msgInput = document.getElementById('msg-input');
|
||
const uploadBtn = document.getElementById('upload-btn');
|
||
const notifSound = document.getElementById('notif-sound');
|
||
let lastMsgId = 0;
|
||
let lastMessagesHtml = '';
|
||
let isSending = false;
|
||
|
||
async function loadMessages() {
|
||
if (isSending) return;
|
||
try {
|
||
const resp = await fetch('../api/get_messages.php?user_id=<?php echo $user_id; ?>');
|
||
const res = await resp.json();
|
||
if (res.success) {
|
||
let html = '';
|
||
let hasNewUserMsg = false;
|
||
res.data.forEach(m => {
|
||
if (m.id > lastMsgId) {
|
||
if (m.sender === 'user' && lastMsgId > 0) hasNewUserMsg = true;
|
||
lastMsgId = m.id;
|
||
}
|
||
html += `
|
||
<div class="msg ${m.sender}">
|
||
${m.type === 'image' ? `<img src="../${m.message}" style="max-width:100%; border-radius:8px; cursor:pointer;" onclick="window.open(this.src)">` : m.message.replace(/\n/g, '<br>')}
|
||
<span class="msg-time">${new Date(m.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
if (hasNewUserMsg) {
|
||
try { notifSound.play(); } catch(e) {}
|
||
}
|
||
|
||
if (html !== lastMessagesHtml) {
|
||
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 100;
|
||
chatBox.innerHTML = html;
|
||
lastMessagesHtml = html;
|
||
if (isAtBottom) chatBox.scrollTop = chatBox.scrollHeight;
|
||
|
||
// Mark as read
|
||
fetch('../api/get_messages.php?action=mark_read&user_id=<?php echo $user_id; ?>&reader=admin');
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
document.getElementById('msg-form').onsubmit = async (e) => {
|
||
e.preventDefault();
|
||
const msg = msgInput.value.trim();
|
||
if (!msg || isSending) return;
|
||
|
||
msgInput.value = '';
|
||
isSending = true;
|
||
|
||
// Optimistic UI
|
||
const tempMsg = document.createElement('div');
|
||
tempMsg.className = 'msg admin sending';
|
||
tempMsg.innerHTML = `${msg.replace(/\n/g, '<br>')}<span class="msg-time">${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>`;
|
||
chatBox.appendChild(tempMsg);
|
||
chatBox.scrollTop = chatBox.scrollHeight;
|
||
|
||
const formData = new FormData();
|
||
formData.append('message', msg);
|
||
|
||
try {
|
||
await fetch(window.location.href, { method: 'POST', body: formData });
|
||
isSending = false;
|
||
await loadMessages();
|
||
} catch (e) {
|
||
isSending = false;
|
||
tempMsg.style.background = '#f6465d';
|
||
tempMsg.innerHTML += ' (发送失败)';
|
||
}
|
||
};
|
||
|
||
function uploadImage(input) {
|
||
if (!input.files || !input.files[0]) return;
|
||
const formData = new FormData();
|
||
formData.append('image', input.files[0]);
|
||
formData.append('is_admin', '1'); // Optional, to distinguish sender if needed
|
||
|
||
uploadBtn.disabled = true;
|
||
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||
|
||
// We need a separate API or same API for admin image upload
|
||
// api/upload_chat_image.php already handles sending message as 'user'
|
||
// I should probably create a separate one or modify it.
|
||
// Let's modify api/upload_chat_image.php to support admin sender.
|
||
|
||
fetch('../api/upload_chat_image.php?sender=admin&user_id=<?php echo $user_id; ?>', { method: 'POST', body: formData })
|
||
.then(res => res.json())
|
||
.then(res => {
|
||
if (res.success) {
|
||
loadMessages();
|
||
} else {
|
||
alert(res.error || '上传失败');
|
||
}
|
||
})
|
||
.finally(() => {
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.innerHTML = '<i class="fas fa-plus"></i>';
|
||
input.value = '';
|
||
});
|
||
}
|
||
|
||
loadMessages();
|
||
setInterval(loadMessages, 1000);
|
||
</script>
|
||
</body>
|
||
</html>
|