Autosave: 20260218-062147

This commit is contained in:
Flatlogic Bot 2026-02-18 06:21:48 +00:00
parent b573ba93a2
commit e78b6820eb
27 changed files with 1075 additions and 179 deletions

View File

@ -138,17 +138,21 @@ ob_start();
<label class="form-label">权限设置</label>
<div class="d-flex flex-wrap gap-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="manage_users" id="p_users">
<input class="form-check-input" type="checkbox" name="permissions[]" value="manage_users" id="p_users" checked>
<label class="form-check-label" for="p_users">管理用户</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="view_orders" id="p_orders">
<input class="form-check-input" type="checkbox" name="permissions[]" value="view_orders" id="p_orders" checked>
<label class="form-check-label" for="p_orders">查看订单</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="audit_finance" id="p_finance">
<input class="form-check-input" type="checkbox" name="permissions[]" value="audit_finance" id="p_finance" checked>
<label class="form-check-label" for="p_finance">财务审核</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions[]" value="manage_kyc" id="p_kyc">
<label class="form-check-label" for="p_kyc">实名审核</label>
</div>
</div>
</div>
</div>
@ -194,6 +198,10 @@ ob_start();
<input class="form-check-input p-checkbox" type="checkbox" name="permissions[]" value="audit_finance" id="ep_finance">
<label class="form-check-label" for="ep_finance">财务审核</label>
</div>
<div class="form-check">
<input class="form-check-input p-checkbox" type="checkbox" name="permissions[]" value="manage_kyc" id="ep_kyc">
<label class="form-check-label" for="ep_kyc">实名审核</label>
</div>
</div>
</div>
</div>

View File

@ -16,9 +16,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$settings = [
'email_verification_enabled' => $_POST['email_verification_enabled'] ?? '0',
'site_logo' => $_POST['site_logo'] ?? '',
'usdt_recharge_address' => $_POST['usdt_recharge_address'] ?? '',
'usdt_protocol' => $_POST['usdt_protocol'] ?? 'TRC20',
'usdt_trc20_address' => $_POST['usdt_trc20_address'] ?? '',
'usdt_erc20_address' => $_POST['usdt_erc20_address'] ?? '',
'usdt_bep20_address' => $_POST['usdt_bep20_address'] ?? '',
'service_link' => $_POST['service_link'] ?? '',
'site_name' => $_POST['site_name'] ?? 'Byro',
];
foreach ($settings as $key => $val) {
@ -31,6 +33,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ext = pathinfo($_FILES['logo_file']['name'], PATHINFO_EXTENSION);
$filename = 'logo_' . time() . '.' . $ext;
$target = __DIR__ . '/../assets/images/' . $filename;
if (!is_dir(__DIR__ . '/../assets/images/')) {
mkdir(__DIR__ . '/../assets/images/', 0777, true);
}
if (move_uploaded_file($_FILES['logo_file']['tmp_name'], $target)) {
$logo_path = '/assets/images/' . $filename;
$stmt = db()->prepare("INSERT INTO system_settings (setting_key, setting_value) VALUES ('site_logo', ?) ON DUPLICATE KEY UPDATE setting_value = ?");
@ -43,9 +48,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email_verify = getLocalSetting('email_verification_enabled', '0');
$site_logo = getLocalSetting('site_logo', '');
$usdt_address = getLocalSetting('usdt_recharge_address', '');
$usdt_protocol = getLocalSetting('usdt_protocol', 'TRC20');
$trc20_addr = getLocalSetting('usdt_trc20_address', '');
$erc20_addr = getLocalSetting('usdt_erc20_address', '');
$bep20_addr = getLocalSetting('usdt_bep20_address', '');
$service_link = getLocalSetting('service_link', '');
$site_name = getLocalSetting('site_name', 'Byro');
$title = '后台设置';
ob_start();
@ -62,40 +69,58 @@ ob_start();
<h5 class="fw-bold mb-4">系统全局配置</h5>
<?php if (isset($success)): ?>
<div class="alert alert-success">设置已保存</div>
<div class="alert alert-success alert-dismissible fade show" role="alert">
设置已成功保存!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<div class="mb-4">
<label class="form-label fw-bold">前端 LOGO</label>
<div class="d-flex align-items-center gap-3">
<label class="form-label fw-bold">网站名称</label>
<input type="text" name="site_name" class="form-control" value="<?= htmlspecialchars($site_name) ?>">
</div>
<div class="mb-4">
<label class="form-label fw-bold">前端 LOGO / 网站图标</label>
<div class="d-flex align-items-center gap-3 mb-2">
<?php if ($site_logo): ?>
<img src="<?= $site_logo ?>" height="40" class="border p-1">
<div class="bg-dark p-2 rounded">
<img src="<?= $site_logo ?>" height="40" class="d-block">
</div>
<?php endif; ?>
<input type="file" name="logo_file" class="form-control">
</div>
<div class="form-text">建议尺寸: 200x50, PNG 格式</div>
<div class="form-text">上传后将同步更新网站 LOGO Favicon 浏览器图标。建议使用透明 PNG。</div>
</div>
<div class="mb-4">
<label class="form-label fw-bold">USDT 收款地址</label>
<input type="text" name="usdt_recharge_address" class="form-control" value="<?= htmlspecialchars($usdt_address) ?>" placeholder="请输入钱包地址">
<hr class="my-4">
<h6 class="fw-bold mb-3 text-primary"><i class="bi bi-wallet2 me-2"></i>充值地址配置</h6>
<div class="mb-3">
<label class="form-label fw-bold">USDT (TRC20) 地址</label>
<input type="text" name="usdt_trc20_address" class="form-control" value="<?= htmlspecialchars($trc20_addr) ?>" placeholder="请输入 TRC20 钱包地址">
</div>
<div class="mb-4">
<label class="form-label fw-bold">USDT 网络协议</label>
<select name="usdt_protocol" class="form-control">
<option value="TRC20" <?= $usdt_protocol === 'TRC20' ? 'selected' : '' ?>>TRC20</option>
<option value="ERC20" <?= $usdt_protocol === 'ERC20' ? 'selected' : '' ?>>ERC20</option>
<option value="BEP20" <?= $usdt_protocol === 'BEP20' ? 'selected' : '' ?>>BEP20</option>
</select>
<div class="mb-3">
<label class="form-label fw-bold">USDT (ERC20) 地址</label>
<input type="text" name="usdt_erc20_address" class="form-control" value="<?= htmlspecialchars($erc20_addr) ?>" placeholder="请输入 ERC20 钱包地址">
</div>
<div class="mb-3">
<label class="form-label fw-bold">USDT (BEP20) 地址</label>
<input type="text" name="usdt_bep20_address" class="form-control" value="<?= htmlspecialchars($bep20_addr) ?>" placeholder="请输入 BEP20 钱包地址">
</div>
<hr class="my-4">
<h6 class="fw-bold mb-3 text-primary"><i class="bi bi-shield-check me-2"></i>安全与功能</h6>
<div class="mb-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="email_verification_enabled" value="1" id="emailSwitch" <?= $email_verify == '1' ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="emailSwitch">注册时开启邮箱/手机验证码</label>
</div>
<div class="form-text">开启后用户注册必须输入验证码演示环境默认验证码123456</div>
</div>
<div class="mb-4">
@ -105,17 +130,20 @@ ob_start();
<hr class="my-4">
<button type="submit" class="btn btn-primary px-5">提交保存</button>
<button type="submit" class="btn btn-primary px-5 btn-lg">
<i class="bi bi-check-lg me-1"></i> 提交保存
</button>
</form>
</div>
</div>
<div class="col-md-4">
<div class="card p-4">
<div class="card p-4 border-0 shadow-sm">
<h6 class="fw-bold mb-3">使用说明</h6>
<ul class="small text-muted ps-3">
<li>收款地址将直接展示在前端充值页面。</li>
<li>验证码开关关闭后,前端注册无需输入验证码即可提交。</li>
<li>客服链接将用于前端“联系客服”按钮跳转。</li>
<ul class="small text-muted ps-3 mb-0">
<li class="mb-2"><strong>网站名称:</strong>影响浏览器标签页标题。</li>
<li class="mb-2"><strong>LOGO</strong>上传后会自动替换后台左上角及前端所有引用处。</li>
<li class="mb-2"><strong>充值地址:</strong>对应前端充值页面的三个网络,请务必填写正确。</li>
<li class="mb-2"><strong>验证码:</strong>若未配置 SMTP 邮件服务,建议保持关闭或使用固定验证码。</li>
</ul>
</div>
</div>

View File

@ -3,6 +3,12 @@ require_once __DIR__ . '/layout.php';
$db = db();
// Helper to check permissions
if (!hasPermission('view_orders')) {
echo "权限不足";
exit;
}
// Handle Control Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'set_control') {
@ -20,8 +26,12 @@ ob_start();
$user_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
$sql = "SELECT o.*, u.username, u.uid FROM binary_orders o JOIN users u ON o.user_id = u.id";
$params = [];
if ($admin['is_agent']) {
$sql .= ($params ? " AND" : " WHERE") . " u.agent_id = ?";
$params[] = $admin['id'];
}
if ($user_id) {
$sql .= " WHERE o.user_id = ?";
$sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " o.user_id = ?";
$params[] = $user_id;
}
$sql .= " ORDER BY o.created_at DESC";

View File

@ -2,6 +2,11 @@
require_once __DIR__ . '/layout.php';
$db = db();
if (!hasPermission('view_orders')) {
echo "权限不足";
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'set_control') {
$id = (int)$_POST['order_id'];
@ -17,8 +22,12 @@ ob_start();
$user_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
$sql = "SELECT o.*, u.username, u.uid FROM contract_orders o JOIN users u ON o.user_id = u.id";
$params = [];
if ($admin['is_agent']) {
$sql .= " WHERE u.agent_id = ?";
$params[] = $admin['id'];
}
if ($user_id) {
$sql .= " WHERE o.user_id = ?";
$sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " o.user_id = ?";
$params[] = $user_id;
}
$sql .= " ORDER BY o.created_at DESC";

View File

@ -7,7 +7,7 @@ $total_users = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
$total_recharge = $db->query("SELECT SUM(amount) FROM finance_requests WHERE type='recharge' AND status='approved'")->fetchColumn() ?: 0;
$total_withdrawal = $db->query("SELECT SUM(amount) FROM finance_requests WHERE type='withdrawal' AND status='approved'")->fetchColumn() ?: 0;
$pending_finance = $db->query("SELECT COUNT(*) FROM finance_requests WHERE status='pending'")->fetchColumn();
$pending_kyc = $db->query("SELECT COUNT(*) FROM users WHERE kyc_status=0 AND kyc_name IS NOT NULL")->fetchColumn();
$pending_kyc = $db->query("SELECT COUNT(*) FROM users WHERE kyc_status=1 AND kyc_name IS NOT NULL")->fetchColumn();
ob_start();
?>

View File

@ -3,17 +3,33 @@ require_once __DIR__ . '/layout.php';
$db = db();
// Helper to check permissions
if (!hasPermission('manage_kyc')) {
echo "权限不足";
exit;
}
// Handle Approve/Reject
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$id = (int)$_POST['user_id'];
// Safety check for agents
if ($admin['is_agent']) {
$stmt = $db->prepare("SELECT id FROM users WHERE id = ? AND agent_id = ?");
$stmt->execute([$id, $admin['id']]);
if (!$stmt->fetch()) exit("无权操作");
}
if ($_POST['action'] === 'approve') {
$db->prepare("UPDATE users SET kyc_status = 1 WHERE id = ?")->execute([$id]);
// Status 2: Verified (Standard)
$db->prepare("UPDATE users SET kyc_status = 2 WHERE id = ?")->execute([$id]);
header("Location: kyc.php?msg=approved");
exit;
}
if ($_POST['action'] === 'reject') {
$reason = $_POST['reason'] ?? '';
$db->prepare("UPDATE users SET kyc_status = 2, kyc_rejection_reason = ? WHERE id = ?")
// Status 3: Rejected (Standard)
$db->prepare("UPDATE users SET kyc_status = 3, kyc_rejection_reason = ? WHERE id = ?")
->execute([$reason, $id]);
header("Location: kyc.php?msg=rejected");
exit;
@ -25,11 +41,25 @@ ob_start();
$user_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
if ($user_id) {
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$sql = "SELECT * FROM users WHERE id = ?";
$params = [$user_id];
if ($admin['is_agent']) {
$sql .= " AND agent_id = ?";
$params[] = $admin['id'];
}
$stmt = $db->prepare($sql);
$stmt->execute($params);
$users = $stmt->fetchAll();
} else {
$stmt = $db->query("SELECT * FROM users WHERE kyc_name IS NOT NULL ORDER BY kyc_status ASC, created_at DESC");
$sql = "SELECT * FROM users WHERE kyc_name IS NOT NULL";
$params = [];
if ($admin['is_agent']) {
$sql .= " AND agent_id = ?";
$params[] = $admin['id'];
}
$sql .= " ORDER BY CASE WHEN kyc_status = 1 THEN 0 ELSE 1 END, created_at DESC";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$users = $stmt->fetchAll();
}
?>
@ -55,12 +85,14 @@ if ($user_id) {
<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'] == 0): ?>
<?php if ($u['kyc_status'] == 1): ?>
<span class="badge bg-warning">待审核</span>
<?php elseif ($u['kyc_status'] == 1): ?>
<span class="badge bg-success">已通过</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>
@ -72,7 +104,7 @@ if ($user_id) {
<p class="mb-1 text-muted small mt-3">身份证号</p>
<h6><?= htmlspecialchars($u['kyc_id_number'] ?? '未填写') ?></h6>
<?php if ($u['kyc_rejection_reason']): ?>
<?php if ($u['kyc_status'] == 3 && $u['kyc_rejection_reason']): ?>
<div class="alert alert-danger mt-3 small">
拒绝理由: <?= htmlspecialchars($u['kyc_rejection_reason']) ?>
</div>
@ -82,21 +114,21 @@ if ($user_id) {
<div class="row g-2">
<div class="col-md-4">
<p class="small text-center mb-1">正面照</p>
<img src="<?= $u['kyc_photo_front'] ?: 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded" onclick="window.open(this.src)">
<img src="<?= $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'] ?: 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded" onclick="window.open(this.src)">
<img src="<?= $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'] ?: 'https://via.placeholder.com/300x200?text=No+Photo' ?>" class="img-fluid border rounded" onclick="window.open(this.src)">
<img src="<?= $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>
</div>
</div>
</div>
<?php if ($u['kyc_status'] == 0): ?>
<?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'] ?>">
@ -106,6 +138,7 @@ if ($user_id) {
<button class="btn btn-danger" onclick="showRejectModal(<?= $u['id'] ?>)">拒绝申请</button>
</div>
<?php endif; ?>
</div>
</div>
</div>
@ -120,31 +153,40 @@ if ($user_id) {
<?php endif; ?>
</div>
<!-- Reject Modal -->
<div class="modal fade" id="rejectModal" tabindex="-1">
<div class="modal-dialog">
<form class="modal-content" method="POST">
<input type="hidden" name="action" value="reject">
<input type="hidden" name="user_id" id="reject_user_id">
<div class="modal-header">
<h5 class="modal-title">拒绝实名认证</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
<!-- Photo Viewer Modal -->
<div class="modal fade" id="photoViewModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-transparent border-0">
<div class="modal-body p-0 text-center position-relative">
<img id="viewerImage" src="" class="img-fluid rounded shadow-lg transition-all" style="max-height: 90vh; cursor: zoom-in;" onclick="toggleZoom(this)">
<button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-3" data-bs-dismiss="modal" style="z-index: 1051;"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">拒绝理由</label>
<textarea name="reason" class="form-control" rows="3" required placeholder="请填写拒绝理由,如:照片不清晰、身份证号不正确等"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-danger">确认拒绝</button>
</div>
</form>
</div>
</div>
</div>
<style>
.cursor-zoom-in { cursor: zoom-in; }
#viewerImage.zoomed {
transform: scale(1.5);
cursor: zoom-out;
}
.transition-all { transition: all 0.3s ease; }
</style>
<script>
function viewPhoto(src) {
const img = document.getElementById('viewerImage');
img.src = src;
img.classList.remove('zoomed');
new bootstrap.Modal(document.getElementById('photoViewModal')).show();
}
function toggleZoom(img) {
img.classList.toggle('zoomed');
}
function showRejectModal(id) {
document.getElementById('reject_user_id').value = id;
new bootstrap.Modal(document.getElementById('rejectModal')).show();

View File

@ -16,25 +16,42 @@ if (!$admin) {
}
// Helper to check permissions
function hasPermission($p) {
global $admin;
if (!$admin['is_agent']) return true; // Super admin has all permissions
$perms = json_decode($admin['permissions'] ?? '[]', true);
return in_array($p, $perms);
if (!function_exists('hasPermission')) {
function hasPermission($p) {
global $admin;
if (!$admin['is_agent']) return true; // Super admin has all permissions
$perms = json_decode($admin['permissions'] ?? '[]', true);
return in_array($p, $perms);
}
}
function renderAdminPage($content, $title = '后台管理') {
global $admin;
$current_page = basename($_SERVER['PHP_SELF']);
$site_logo = '';
$site_name = 'Byro';
try {
$stmt = db()->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'site_logo'");
$stmt->execute();
$site_logo = $stmt->fetchColumn() ?: '';
$stmt = db()->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'site_name'");
$stmt->execute();
$site_name = $stmt->fetchColumn() ?: 'Byro';
} catch (Exception $e) {}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?> - Byro Admin</title>
<title><?= $title ?> - <?= $site_name ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<?php if ($site_logo): ?>
<link rel="icon" href="<?= $site_logo ?>">
<?php endif; ?>
<style>
:root {
--sidebar-width: 240px;
@ -124,7 +141,12 @@ function renderAdminPage($content, $title = '后台管理') {
<body>
<div class="admin-sidebar">
<div class="sidebar-logo">
<i class="bi bi-shield-lock-fill me-2"></i> <?= $admin['is_agent'] ? '代理后台' : '管理系统' ?>
<?php if ($site_logo): ?>
<img src="<?= $site_logo ?>" height="30" class="me-2">
<?php else: ?>
<i class="bi bi-shield-lock-fill me-2"></i>
<?php endif; ?>
<?= $admin['is_agent'] ? '代理后台' : '管理系统' ?>
</div>
<div class="admin-nav">
<a href="/admin/index.php" class="nav-link <?= $current_page == 'index.php' ? 'active' : '' ?>"><i class="bi bi-house-door"></i> 首页</a>
@ -147,6 +169,10 @@ function renderAdminPage($content, $title = '后台管理') {
<a href="/admin/spot.php" class="nav-link <?= $current_page == 'spot.php' ? 'active' : '' ?>"><i class="bi bi-currency-exchange"></i> 币币交易</a>
<?php endif; ?>
<?php if (hasPermission('manage_kyc')): ?>
<a href="/admin/kyc.php" class="nav-link <?= $current_page == 'kyc.php' ? 'active' : '' ?>"><i class="bi bi-person-vcard"></i> 实名认证</a>
<?php endif; ?>
<?php if (!$admin['is_agent']): ?>
<a href="/admin/exchange.php" class="nav-link"><i class="bi bi-arrow-left-right"></i> 兑换管理</a>
<a href="/admin/mining.php" class="nav-link"><i class="bi bi-cpu"></i> 质押挖矿</a>
@ -173,6 +199,7 @@ function renderAdminPage($content, $title = '后台管理') {
<div class="admin-main">
<?= $content ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php

View File

@ -2,14 +2,35 @@
require_once __DIR__ . '/layout.php';
$db = db();
if (!hasPermission('view_orders')) {
echo "权限不足";
exit;
}
$title = '币币交易管理';
ob_start();
$orders = $db->query("SELECT o.*, u.username, u.uid FROM spot_orders o JOIN users u ON o.user_id = u.id ORDER BY o.created_at DESC")->fetchAll();
$user_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
$sql = "SELECT o.*, u.username, u.uid FROM spot_orders o JOIN users u ON o.user_id = u.id";
$params = [];
if ($admin['is_agent']) {
$sql .= " WHERE u.agent_id = ?";
$params[] = $admin['id'];
}
if ($user_id) {
$sql .= (strpos($sql, 'WHERE') === false ? " WHERE" : " AND") . " o.user_id = ?";
$params[] = $user_id;
}
$sql .= " ORDER BY o.created_at DESC";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$orders = $stmt->fetchAll();
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center gap-3">
<a href="index.php" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-left"></i> 返回</a>
<h4 class="mb-0">币币订单记录</h4>
<a href="<?= $user_id ? 'users.php' : 'index.php' ?>" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-left"></i> 返回</a>
<h4 class="mb-0">币币订单记录 <?= $user_id ? "(用户ID: $user_id)" : "" ?></h4>
</div>
</div>
<div class="table-container">

View File

@ -46,12 +46,32 @@ if (isset($_GET['delete'])) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add') {
$username = $_POST['username'];
// Check duplicate
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
header('Location: users.php?msg=duplicate');
exit;
}
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$transaction_password = $_POST['transaction_password'] ?? '123456';
$uid = rand(1000000, 9999999);
$agent_id = $admin['is_agent'] ? $admin['id'] : ($_POST['agent_id'] ?? null);
$remark = $_POST['remark'] ?? '';
$credit_score = isset($_POST['credit_score']) ? (int)$_POST['credit_score'] : 100;
$db->prepare("INSERT INTO users (username, password_hash, uid, credit_score, agent_id) VALUES (?, ?, ?, 100, ?)")
->execute([$username, $password, $uid, $agent_id]);
$db->prepare("INSERT INTO users (username, password_hash, transaction_password, uid, credit_score, agent_id, remark) VALUES (?, ?, ?, ?, ?, ?, ?)")
->execute([$username, $password, $transaction_password, $uid, $credit_score, $agent_id, $remark]);
$new_user_id = $db->lastInsertId();
// Initial balance
if (isset($_POST['initial_balance']) && $_POST['initial_balance'] !== '') {
$db->prepare("INSERT INTO user_balances (user_id, symbol, available) VALUES (?, 'USDT', ?)")
->execute([$new_user_id, (float)$_POST['initial_balance']]);
}
header('Location: users.php?msg=added');
exit;
}
@ -135,10 +155,17 @@ ob_start();
</div>
<?php if (isset($_GET['msg'])): ?>
<div class="alert alert-success alert-dismissible fade show mb-4" role="alert">
操作成功!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php if ($_GET['msg'] === 'duplicate'): ?>
<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
用户名已存在!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php else: ?>
<div class="alert alert-success alert-dismissible fade show mb-4" role="alert">
操作成功!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php endif; ?>
<div class="table-container">
@ -147,6 +174,7 @@ ob_start();
<tr class="text-muted small">
<th>UID</th>
<th>用户名</th>
<?php if (!$admin['is_agent']): ?><th>所属代理</th><?php endif; ?>
<th>注册IP</th>
<th>身份证信息</th>
<th>余额 (USDT)</th>
@ -158,7 +186,10 @@ ob_start();
</thead>
<tbody>
<?php
$sql = "SELECT u.*, (SELECT available FROM user_balances WHERE user_id = u.id AND symbol = 'USDT') as usdt_balance FROM users u";
$sql = "SELECT u.*,
(SELECT available FROM user_balances WHERE user_id = u.id AND symbol = 'USDT') as usdt_balance,
(SELECT username FROM admins WHERE id = u.agent_id) as agent_name
FROM users u";
$params = [];
if ($admin['is_agent']) {
$sql .= " WHERE agent_id = ?";
@ -173,16 +204,34 @@ ob_start();
<tr>
<td><code><?= $u['uid'] ?></code></td>
<td>
<div><?= htmlspecialchars($u['username']) ?></div>
<div class="fw-bold"><?= htmlspecialchars($u['username']) ?></div>
<div class="text-muted" style="font-size: 11px;"><?= $u['created_at'] ?></div>
</td>
<?php if (!$admin['is_agent']): ?>
<td>
<?php if ($u['agent_name']): ?>
<span class="badge bg-light text-dark border"><?= htmlspecialchars($u['agent_name']) ?></span>
<?php else: ?>
<span class="text-muted">直属平台</span>
<?php endif; ?>
</td>
<?php endif; ?>
<td><small class="text-muted"><?= $u['registration_ip'] ?? '未知' ?></small></td>
<td>
<?php if ($u['kyc_name']): ?>
<div class="small"><?= htmlspecialchars($u['kyc_name']) ?></div>
<div class="small fw-bold text-primary"><?= htmlspecialchars($u['kyc_name']) ?></div>
<div class="text-muted small"><?= htmlspecialchars($u['kyc_id_number']) ?></div>
<div class="mt-1">
<?php if ($u['kyc_status'] == 1): ?>
<span class="badge bg-warning text-dark" style="font-size: 10px;">待审核</span>
<?php elseif ($u['kyc_status'] == 2): ?>
<span class="badge bg-success" style="font-size: 10px;">已认证</span>
<?php elseif ($u['kyc_status'] == 3): ?>
<span class="badge bg-danger" style="font-size: 10px;">已拒绝</span>
<?php endif; ?>
</div>
<?php else: ?>
<span class="text-muted small">未实名</span>
<span class="text-muted small">提交</span>
<?php endif; ?>
</td>
<td><span class="fw-bold text-primary"><?= number_format($u['usdt_balance'] ?? 0, 2) ?></span></td>
@ -233,13 +282,33 @@ ob_start();
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">用户名/手机号</label>
<input type="text" name="username" class="form-control" required>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">用户名/手机号</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">初始信用分</label>
<input type="number" name="credit_score" class="form-control" value="100">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">登录密码</label>
<input type="password" name="password" class="form-control" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">资金密码</label>
<input type="text" name="transaction_password" class="form-control" value="123456" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">登录密码</label>
<input type="password" name="password" class="form-control" required>
<label class="form-label">初始余额 (USDT)</label>
<input type="number" name="initial_balance" class="form-control" placeholder="0.00">
</div>
<div class="mb-3">
<label class="form-label">管理员备注</label>
<textarea name="remark" class="form-control" rows="2"></textarea>
</div>
<?php if (!$admin['is_agent']): ?>
<div class="mb-3">

View File

@ -12,6 +12,14 @@ if (!$user_id) {
exit;
}
// Check frozen status
$stmt = $db->prepare("SELECT status FROM users WHERE id = ?");
$stmt->execute([$user_id]);
if ($stmt->fetchColumn() === 'frozen') {
echo json_encode(['success' => false, 'error' => 'Account frozen']);
exit;
}
$action = $_POST['action'] ?? '';
if ($action === 'place_order') {
@ -71,6 +79,15 @@ if ($action === 'settle_order') {
// Determine result
$result = ''; // won/lost
// 0. Check for Price Control (Needle)
$stmt = $db->prepare("SELECT target_price FROM price_controls WHERE symbol = ? AND execution_time <= NOW() AND DATE_ADD(execution_time, INTERVAL duration SECOND) >= NOW() LIMIT 1");
$stmt->execute([$order['symbol']]);
$controlled_price = $stmt->fetchColumn();
if ($controlled_price) {
$close_price = $controlled_price;
}
$is_up = $order['direction'] === 'up' || $order['direction'] === 'buy';
// 1. Check Order-level control
@ -109,7 +126,8 @@ if ($action === 'settle_order') {
}
$db->commit();
echo json_encode(['success' => true, 'result' => $result]);
$pnl = ($result === 'won') ? ($order['amount'] * $order['profit_rate'] / 100) : -$order['amount'];
echo json_encode(['success' => true, 'result' => $result, 'pnl' => $pnl]);
} catch (Exception $e) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);

View File

@ -4,17 +4,42 @@ require_once __DIR__ . '/../db/config.php';
$action = $_GET['action'] ?? '';
if ($action === 'send_message') {
$message = $_POST['message'] ?? '';
if (!$message) exit(json_encode(['success' => false]));
if ($action === 'upload_image' || (isset($_POST['action']) && $_POST['action'] === 'upload_image')) {
if (!isset($_FILES['file'])) {
echo json_encode(['success' => false, 'error' => 'No file uploaded']);
exit;
}
$user_id = $_SESSION['user_id'] ?? 0;
$sender = 'user';
$ip = $_SERVER['REMOTE_ADDR'];
$file = $_FILES['file'];
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array(strtolower($ext), $allowed)) {
echo json_encode(['success' => false, 'error' => 'Invalid file type']);
exit;
}
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message, ip_address) VALUES (?, ?, ?, ?)");
$stmt->execute([$user_id, $sender, $message, $ip]);
echo json_encode(['success' => true]);
$filename = time() . '_' . uniqid() . '.' . $ext;
$targetDir = __DIR__ . '/../assets/images/chat/';
if (!is_dir($targetDir)) {
mkdir($targetDir, 0777, true);
}
$targetPath = $targetDir . $filename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$imageUrl = '/assets/images/chat/' . $filename;
$user_id = $_SESSION['user_id'] ?? 0;
$ip = $_SERVER['REMOTE_ADDR'];
$message = '<img src="' . $imageUrl . '" class="img-fluid rounded cursor-pointer" onclick="window.open(\'' . $imageUrl . '\')">';
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message, ip_address) VALUES (?, ?, ?, ?)");
$stmt->execute([$user_id, 'user', $message, $ip]);
echo json_encode(['success' => true, 'url' => $imageUrl]);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to move uploaded file']);
}
exit;
}

121
api/contract.php Normal file
View File

@ -0,0 +1,121 @@
<?php
require_once __DIR__ . '/../db/config.php';
session_start();
header('Content-Type: application/json');
$db = db();
$user_id = $_SESSION['user_id'] ?? null;
if (!$user_id) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$action = $_POST['action'] ?? 'place_order';
if ($action === 'place_order') {
$symbol = $_POST['symbol'] ?? 'BTC';
$direction = $_POST['direction'] ?? 'long'; // long/short
$leverage = (int)($_POST['leverage'] ?? 1);
$amount = (float)($_POST['amount'] ?? 0);
$entry_price = (float)($_POST['entry_price'] ?? 0);
$type = $_POST['type'] ?? 'market';
if ($amount <= 0) {
echo json_encode(['success' => false, 'error' => 'Invalid amount']);
exit;
}
$db->beginTransaction();
try {
// Check balance (USDT for margin)
$margin = $amount / $leverage;
$stmt = $db->prepare("SELECT available FROM user_balances WHERE user_id = ? AND symbol = 'USDT'");
$stmt->execute([$user_id]);
$bal = $stmt->fetchColumn() ?: 0;
if ($bal < $margin) {
throw new Exception("Insufficient balance for margin");
}
// Deduct margin
$db->prepare("UPDATE user_balances SET available = available - ? WHERE user_id = ? AND symbol = 'USDT'")
->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]);
// 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->commit();
echo json_encode(['success' => true]);
} catch (Exception $e) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
} elseif ($action === 'close_order') {
$order_id = (int)$_POST['order_id'];
$close_price = (float)$_POST['close_price'];
$stmt = $db->prepare("SELECT * FROM contract_orders WHERE id = ? AND user_id = ? AND status = 'open'");
$stmt->execute([$order_id, $user_id]);
$order = $stmt->fetch();
if (!$order) {
echo json_encode(['success' => false, 'error' => 'Order not found']);
exit;
}
$db->beginTransaction();
try {
// Check for Price Control (Needle)
$stmt = $db->prepare("SELECT target_price FROM price_controls WHERE symbol = ? AND execution_time <= NOW() AND DATE_ADD(execution_time, INTERVAL duration SECOND) >= NOW() LIMIT 1");
$stmt->execute([$order['symbol']]);
$controlled_price = $stmt->fetchColumn();
if ($controlled_price) {
$close_price = $controlled_price;
}
// Calculate Profit/Loss
$margin = $order['amount'] / $order['leverage'];
$diff = ($order['direction'] === 'long') ? ($close_price - $order['entry_price']) : ($order['entry_price'] - $close_price);
$profit = ($diff / $order['entry_price']) * $order['amount'];
// User Win/Loss Control
$stmt = $db->prepare("SELECT win_loss_control FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user_control = $stmt->fetchColumn();
if ($order['control_status'] == 1 || $user_control == 1) { // Win
if ($profit <= 0) $profit = $margin * 0.1; // Force 10% profit
} elseif ($order['control_status'] == 2 || $user_control == 2) { // Loss
if ($profit >= 0) $profit = -$margin * 0.9; // Force 90% loss
}
$total_return = $margin + $profit;
if ($total_return < 0) $total_return = 0;
$db->prepare("UPDATE contract_orders SET close_price = ?, status = 'closed', profit = ? WHERE id = ?")
->execute([$close_price, $profit, $order_id]);
if ($total_return > 0) {
$db->prepare("UPDATE user_balances SET available = available + ? WHERE user_id = ? AND symbol = 'USDT'")
->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->commit();
echo json_encode(['success' => true]);
} catch (Exception $e) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
}

View File

@ -12,7 +12,100 @@ if (!$user_id) {
exit;
}
$action = $_POST['action'] ?? '';
// Check frozen status
$stmt = $db->prepare("SELECT status FROM users WHERE id = ?");
$stmt->execute([$user_id]);
if ($stmt->fetchColumn() === 'frozen') {
echo json_encode(['success' => false, 'error' => 'Account frozen']);
exit;
}
$action = $_REQUEST['action'] ?? '';
if ($action === 'get_orders') {
$tab = $_GET['tab'] ?? 'spot';
$symbol = $_GET['symbol'] ?? 'BTC';
$open = [];
$settlement = [];
if ($tab === 'binary') {
$stmt = $db->prepare("SELECT * FROM binary_orders WHERE user_id = ? ORDER BY created_at DESC");
$stmt->execute([$user_id]);
$orders = $stmt->fetchAll();
foreach ($orders as $o) {
$row = [
'id' => $o['id'],
'time' => $o['created_at'],
'pair' => $o['symbol'] . '/USDT',
'type' => 'Binary',
'side' => ($o['direction'] === 'up' || $o['direction'] === 'buy') ? 'Buy Up' : 'Buy Down',
'side_type' => ($o['direction'] === 'up' || $o['direction'] === 'buy') ? 'up' : 'down',
'price' => $o['entry_price'],
'amount' => $o['amount'],
'pnl' => $o['status'] === 'won' ? ($o['amount'] * $o['profit_rate'] / 100) : ($o['status'] === 'lost' ? -$o['amount'] : 0),
'total' => $o['status'] === 'won' ? ($o['amount'] + ($o['amount'] * $o['profit_rate'] / 100)) : ($o['status'] === 'lost' ? '0.00' : '---'),
'status' => ucfirst($o['status']),
'profitRate' => $o['profit_rate']
];
if ($o['status'] === 'pending') {
$row['status'] = 'Executing';
$row['totalSeconds'] = $o['duration'];
// Calculate seconds left
$elapsed = time() - strtotime($o['created_at']);
$row['secondsLeft'] = max(0, $o['duration'] - $elapsed);
if ($row['secondsLeft'] > 0) $open[] = $row;
else $settlement[] = $row;
} else {
$settlement[] = $row;
}
}
} elseif ($tab === 'spot') {
$stmt = $db->prepare("SELECT * FROM spot_orders WHERE user_id = ? ORDER BY created_at DESC");
$stmt->execute([$user_id]);
$orders = $stmt->fetchAll();
foreach ($orders as $o) {
$row = [
'id' => $o['id'],
'time' => $o['created_at'],
'pair' => $o['symbol'] . '/USDT',
'type' => 'Spot',
'side' => ucfirst($o['side']),
'side_type' => $o['side'],
'price' => $o['price'],
'amount' => $o['amount'],
'total' => ($o['price'] * $o['amount']),
'status' => ucfirst($o['status'])
];
if ($o['status'] === 'pending') $open[] = $row;
else $settlement[] = $row;
}
} elseif ($tab === 'contract') {
$stmt = $db->prepare("SELECT * FROM contract_orders WHERE user_id = ? ORDER BY created_at DESC");
$stmt->execute([$user_id]);
$orders = $stmt->fetchAll();
foreach ($orders as $o) {
$row = [
'id' => $o['id'],
'time' => $o['created_at'],
'pair' => $o['symbol'] . '/USDT',
'type' => 'Contract',
'side' => ucfirst($o['direction']),
'side_type' => $o['direction'] === 'long' ? 'up' : 'down',
'price' => $o['entry_price'],
'amount' => $o['amount'],
'pnl' => $o['profit'],
'total' => ($o['amount'] / $o['leverage']) + $o['profit'],
'status' => ucfirst($o['status'])
];
if ($o['status'] === 'open') $open[] = $row;
else $settlement[] = $row;
}
}
echo json_encode(['success' => true, 'open' => $open, 'settlement' => $settlement]);
exit;
}
if ($action === 'recharge') {
$amount = (float)$_POST['amount'];

73
api/spot.php Normal file
View File

@ -0,0 +1,73 @@
<?php
require_once __DIR__ . '/../db/config.php';
session_start();
header('Content-Type: application/json');
$db = db();
$user_id = $_SESSION['user_id'] ?? null;
if (!$user_id) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$symbol = $_POST['symbol'] ?? 'BTC';
$side = $_POST['side'] ?? 'buy';
$price = (float)($_POST['price'] ?? 0);
$amount = (float)($_POST['amount'] ?? 0);
$type = $_POST['type'] ?? 'limit';
if ($amount <= 0) {
echo json_encode(['success' => false, 'error' => 'Invalid amount']);
exit;
}
$db->beginTransaction();
try {
// Check balance
$total_cost = ($side === 'buy') ? ($price * $amount) : $amount;
$pay_symbol = ($side === 'buy') ? 'USDT' : $symbol;
$stmt = $db->prepare("SELECT available FROM user_balances WHERE user_id = ? AND symbol = ?");
$stmt->execute([$user_id, $pay_symbol]);
$bal = $stmt->fetchColumn() ?: 0;
if ($bal < $total_cost) {
throw new Exception("Insufficient balance");
}
// Deduct balance
$db->prepare("UPDATE user_balances SET available = available - ? WHERE user_id = ? AND symbol = ?")
->execute([$total_cost, $user_id, $pay_symbol]);
// 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]);
// 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->commit();
echo json_encode(['success' => true]);
} catch (Exception $e) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -677,27 +677,67 @@
}
.order-popup {
background: #1e2329;
width: 320px;
padding: 25px;
border-radius: 15px;
width: 360px;
padding: 30px;
border-radius: 24px;
text-align: center;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 20px 50px rgba(0,0,0,0.5);
}
.order-popup h5 {
color: #fff;
font-weight: 700;
margin-bottom: 25px;
}
.countdown-circle {
position: relative;
width: 120px;
height: 120px;
margin: 0 auto 20px;
margin: 0 auto 30px;
}
.countdown-circle svg {
width: 120px;
height: 120px;
transform: rotate(-90deg);
}
.countdown-circle circle {
fill: none;
stroke-width: 8;
}
.countdown-circle .bg { stroke: rgba(255,255,255,0.05); }
.countdown-circle .progress {
stroke: #0ecb81;
transition: stroke-dashoffset 0.1s linear;
stroke-linecap: round;
}
.countdown-circle svg { width: 120px; height: 120px; transform: rotate(-90deg); }
.countdown-circle circle { fill: none; stroke-width: 6; }
.countdown-circle .bg { stroke: #2b3139; }
.countdown-circle .progress { stroke: var(--term-success); transition: stroke-dashoffset 1s linear; }
.countdown-circle .time-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
font-weight: bold;
font-size: 28px;
font-weight: 800;
color: #fff;
}
.popup-details {
background: rgba(0,0,0,0.2);
border-radius: 16px;
padding: 15px;
margin-bottom: 20px;
}
.popup-row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 13px;
}
.popup-row:last-child { margin-bottom: 0; }
.popup-row .label { color: #848e9c; }
.popup-row .value { color: #fff; font-weight: 600; }
.popup-footer {
font-size: 12px;
color: #848e9c;
}
.order-result-display {
padding: 10px 0;
}
.popup-row { display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 12px; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

View File

@ -203,10 +203,10 @@ include __DIR__ . '/../includes/header.php';
<input type="text" name="account" id="account-input" class="form-control bg-black border-secondary text-white py-3 px-4 rounded-4" style="background: #0b0e11 !important; border-color: #2b3139 !important;" placeholder="<?= __('mobile_number') ?>" required>
</div>
<div id="email-verify-box" style="display: none;">
<div id="verify-box" style="display: <?= $email_verify_enabled ? 'block' : 'none' ?>;">
<?php if ($email_verify_enabled): ?>
<div class="mb-3">
<label class="form-label text-muted small fw-bold"><?= __('email_verify') ?></label>
<label class="form-label text-muted small fw-bold" id="verify-label"><?= __('email_verify') ?></label>
<div class="input-group">
<input type="text" name="verify_code" class="form-control bg-black border-secondary text-white py-3 px-4 rounded-start-4" style="background: #0b0e11 !important; border-color: #2b3139 !important;">
<button class="btn btn-outline-primary px-3 rounded-end-4" type="button" id="sendBtn" onclick="sendCode()"><?= __('send_code') ?></button>
@ -248,18 +248,18 @@ function setRegType(type) {
document.getElementById('reg_type').value = type;
const label = document.getElementById('account-label');
const input = document.getElementById('account-input');
const verifyBox = document.getElementById('email-verify-box');
const verifyLabel = document.getElementById('verify-label');
if (type === 'email') {
label.innerText = '<?= __('email') ?>';
input.placeholder = 'example@mail.com';
input.type = 'email';
verifyBox.style.display = 'block';
if (verifyLabel) verifyLabel.innerText = '<?= __('email_verify') ?>';
} else {
label.innerText = '<?= __('mobile_number') ?>';
input.placeholder = '<?= __('mobile_number') ?>';
input.type = 'text';
verifyBox.style.display = 'none';
if (verifyLabel) verifyLabel.innerText = '<?= __('mobile_verify') ?? '验证码' ?>';
}
}

View File

@ -175,6 +175,8 @@
</div>
<div class="p-3 border-top border-secondary bg-black bg-opacity-20">
<form id="cs-form" class="d-flex gap-2">
<input type="file" id="cs-file-input" class="d-none" accept="image/*">
<button type="button" id="cs-upload-btn" class="btn btn-outline-secondary btn-sm border-secondary"><i class="bi bi-plus-lg"></i></button>
<input type="text" id="cs-input" class="form-control form-control-sm bg-dark border-secondary text-white" placeholder="<?= __('type_message') ?>">
<button type="submit" class="btn btn-primary btn-sm px-3"><i class="bi bi-send-fill"></i></button>
</form>
@ -188,6 +190,36 @@ const csClose = document.getElementById('cs-close');
const csForm = document.getElementById('cs-form');
const csInput = document.getElementById('cs-input');
const csMessages = document.getElementById('cs-messages');
const csUploadBtn = document.getElementById('cs-upload-btn');
const csFileInput = document.getElementById('cs-file-input');
csUploadBtn.addEventListener('click', () => csFileInput.click());
csFileInput.addEventListener('change', async () => {
if (!csFileInput.files[0]) return;
const file = csFileInput.files[0];
const formData = new FormData();
formData.append('file', file);
formData.append('action', 'upload_image');
appendMessage('user', '<i class="bi bi-image"></i> Uploading image...');
try {
const resp = await fetch('/api/chat.php', {
method: 'POST',
body: formData
});
const data = await resp.json();
if (data.success) {
// Success
} else {
alert(data.error || 'Upload failed');
}
} catch (err) {
console.error('Upload error:', err);
}
csFileInput.value = '';
});
csToggle.addEventListener('click', () => {
csBox.classList.toggle('d-none');

View File

@ -23,10 +23,17 @@ if (!function_exists('getSetting')) {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Byro | Professional Digital Asset Exchange</title>
<?php
$site_logo = getSetting('site_logo', '');
$site_name = getSetting('site_name', 'Byro');
?>
<title><?= $site_name ?> | Professional Digital Asset Exchange</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="/assets/css/style.css?v=<?= time() ?>">
<?php if ($site_logo): ?>
<link rel="icon" href="<?= $site_logo ?>">
<?php endif; ?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@ -322,14 +329,18 @@ if (!function_exists('getSetting')) {
<body>
<header>
<a href="/" class="logo-container">
<div class="logo-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="white"/>
<path d="M12 22L2 17L12 12L22 17L12 22Z" fill="rgba(255,255,255,0.3)"/>
<path d="M2 12L12 17L22 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="logo-text" style="letter-spacing: 1px;">BYRO</span>
<?php if ($site_logo): ?>
<img src="<?= $site_logo ?>" height="32" class="me-2">
<?php else: ?>
<div class="logo-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="white"/>
<path d="M12 22L2 17L12 12L22 17L12 22Z" fill="rgba(255,255,255,0.3)"/>
<path d="M2 12L12 17L22 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<?php endif; ?>
<span class="logo-text" style="letter-spacing: 1px;"><?= strtoupper($site_name) ?></span>
</a>
<nav>
<a href="/"><?= __('home') ?></a>

View File

@ -264,6 +264,15 @@ $translations = [
'flexible' => '灵活',
'mining_pool' => '矿池',
'footer_desc' => 'Byro 是全球最值得信赖的数字资产交易平台,为个人和机构提供服务。充满信心地交易、存储和赚取加密货币。',
'binary_win' => '秒合约盈利',
'binary_loss' => '秒合约亏损',
'spot_trade' => '现货交易',
'contract_margin' => '合约保证金',
'contract_settle' => '合约结算',
'recharge' => '充值',
'withdrawal' => '提现',
'settlement_pnl' => '结算盈亏',
'order_result' => '交易结果',
],
'en' => [
'home' => 'Home',
@ -521,6 +530,15 @@ $translations = [
'flexible' => 'Flexible',
'mining_pool' => 'Pool',
'footer_desc' => "Byro is the world's most trusted digital asset exchange for individuals and institutions. Trade, store, and earn cryptocurrency with confidence.",
'binary_win' => 'Binary Profit',
'binary_loss' => 'Binary Loss',
'spot_trade' => 'Spot Trade',
'contract_margin' => 'Contract Margin',
'contract_settle' => 'Contract Settle',
'recharge' => 'Deposit',
'withdrawal' => 'Withdrawal',
'settlement_pnl' => 'Settlement PnL',
'order_result' => 'Order Result',
]
];

View File

@ -308,7 +308,6 @@ function renderTerminal($activeTab = 'spot') {
if (order.secondsLeft <= 0) {
clearInterval(timer);
settleOrderBackend(order);
hideOrderPopup();
}
}, 1000);
} else {
@ -335,6 +334,7 @@ function renderTerminal($activeTab = 'spot') {
const amount = parseFloat(order.amount);
const profit = amount * order.profitRate / 100;
order.total = data.result === 'won' ? (amount + profit).toFixed(2) : '0.00';
order.close_price = closePrice;
historyData.open = historyData.open.filter(o => o.id !== order.id);
historyData.settlement.unshift(order);
@ -344,10 +344,37 @@ function renderTerminal($activeTab = 'spot') {
const balance = parseFloat(document.getElementById('user-usdt-balance').innerText.replace(',', ''));
document.getElementById('user-usdt-balance').innerText = (balance + amount + profit).toLocaleString('en-US', {minimumFractionDigits: 2});
}
showOrderResult(data.result, data.pnl);
}
});
}
function showOrderResult(result, pnl) {
const popup = document.querySelector('.order-popup');
const details = document.querySelector('.popup-details');
const countdown = document.querySelector('.countdown-circle');
const footer = document.querySelector('.popup-footer');
countdown.style.display = 'none';
details.style.display = 'none';
footer.style.display = 'none';
const resultDiv = document.createElement('div');
resultDiv.className = 'order-result-display animate__animated animate__zoomIn';
resultDiv.innerHTML = `
<div class="result-icon mb-3">
<i class="bi bi-${result === 'won' ? 'check-circle-fill text-success' : 'x-circle-fill text-danger'}" style="font-size: 60px;"></i>
</div>
<h3 class="fw-bold text-white mb-2">${result === 'won' ? '<?= __("profit") ?>' : '<?= __("loss") ?>'}</h3>
<div class="fs-4 fw-bold ${result === 'won' ? 'text-success' : 'text-danger'} mb-4">
${result === 'won' ? '+' : ''}${parseFloat(pnl).toFixed(2)} USDT
</div>
<button class="btn btn-primary w-100 rounded-pill py-2 fw-bold" onclick="hideOrderPopup()"><?= __("confirm") ?></button>
`;
popup.appendChild(resultDiv);
}
function showErrorModal(msg) {
const modal = document.getElementById('error-modal-overlay');
@ -381,6 +408,14 @@ function renderTerminal($activeTab = 'spot') {
function showOrderPopup(order) {
const popup = document.getElementById('order-popup-overlay');
// Reset popup content
document.querySelector('.countdown-circle').style.display = 'block';
document.querySelector('.popup-details').style.display = 'block';
document.querySelector('.popup-footer').style.display = 'block';
const oldResult = document.querySelector('.order-result-display');
if (oldResult) oldResult.remove();
const sideColor = order.side.includes('Up') || order.side.includes('涨') ? '#26a69a' : '#ef5350';
document.getElementById('popup-price').innerText = order.price;
@ -404,7 +439,7 @@ function renderTerminal($activeTab = 'spot') {
// Update current price in popup
currentPrice.innerText = document.querySelector('.price-jump').innerText;
const radius = 70;
const radius = 54; // Match SVG radius
const circumference = 2 * Math.PI * radius;
const offset = circumference - (order.secondsLeft / order.totalSeconds) * circumference;
@ -415,30 +450,6 @@ function renderTerminal($activeTab = 'spot') {
function hideOrderPopup() {
document.getElementById('order-popup-overlay').style.display = 'none';
}
function settleOrder(order) {
const isWin = Math.random() > 0.45; // 55% win rate for simulation
const amount = parseFloat(order.amount);
const profit = isWin ? (amount * order.profitRate / 100) : -amount;
order.status = isWin ? 'Profit' : 'Loss';
order.total = isWin ? (amount + profit).toFixed(2) : '0.00';
order.timeSettled = new Date().toISOString().replace('T', ' ').substr(0, 19);
// Move from open to settlement
historyData.open = historyData.open.filter(o => o.id !== order.id);
historyData.settlement.unshift(order);
if (showHistoryTab.currentTab === 'open' || showHistoryTab.currentTab === 'settlement') {
showHistoryTab(showHistoryTab.currentTab);
}
// Update balance visually if win
if (isWin) {
const balance = parseFloat(document.getElementById('user-usdt-balance').innerText.replace(',', ''));
document.getElementById('user-usdt-balance').innerText = (balance + amount + profit).toLocaleString('en-US', {minimumFractionDigits: 2});
}
}
</script>
<?php else: ?>
@ -448,52 +459,64 @@ function renderTerminal($activeTab = 'spot') {
<button class="active btn btn-sm btn-outline-primary py-1 px-3 fw-bold" style="font-size: 12px; border-radius: 6px;"><?= __('limit') ?? 'Limit' ?></button>
<button class="btn btn-sm btn-outline-secondary py-1 px-3 fw-bold" style="font-size: 12px; border-radius: 6px;"><?= __('market') ?? 'Market' ?></button>
</div>
<div class="small text-muted fw-medium"><?= __('assets') ?>: <span class="text-white fw-bold"><?= number_format($usdt_balance, 2) ?> USDT</span></div>
<div class="small text-muted fw-medium"><?= __('assets') ?>: <span class="text-white fw-bold"><span id="spot-usdt-balance"><?= number_format($usdt_balance, 2) ?></span> USDT</span></div>
</div>
<?php if ($activeTab === 'contract'): ?>
<div class="mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('leverage') ?? 'Leverage' ?></label>
<select id="contract-leverage" class="form-select form-select-sm bg-dark border-secondary text-white fw-bold">
<?php foreach([10, 20, 50, 100, 150, 200] as $lev): ?>
<option value="<?= $lev ?>"><?= $lev ?>x</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="row g-3">
<div class="col-6">
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('buy_price') ?? 'Buy Price' ?></label>
<label class="small text-muted mb-1 d-block fw-bold"><?= ($activeTab === 'contract' ? (__('buy_long') ?? 'Buy/Long') : (__('buy_price') ?? 'Buy Price')) ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<input type="number" id="spot-buy-price" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold">USDT</span>
</div>
</div>
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('amount') ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<input type="number" id="spot-buy-amount" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold"><?= $currentSymbol ?></span>
</div>
</div>
<div class="d-flex gap-1 mb-3">
<?php foreach(['25%','50%','75%','100%'] as $p): ?>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;"><?= $p ?></button>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;" onclick="setSpotPercent('buy', <?= intval($p) ?>)"><?= $p ?></button>
<?php endforeach; ?>
</div>
<button class="btn btn-success w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #0ecb81, #26a69a);"><?= __('buy') ?> <?= $currentSymbol ?></button>
<button onclick="<?= ($activeTab === 'contract' ? "placeContractOrder('long')" : "placeSpotOrder('buy')") ?>" class="btn btn-success w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #0ecb81, #26a69a);"><?= ($activeTab === 'contract' ? (__('long') ?? 'Long') : (__('buy'))) ?> <?= $currentSymbol ?></button>
</div>
<div class="col-6">
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('sell_price') ?? 'Sell Price' ?></label>
<label class="small text-muted mb-1 d-block fw-bold"><?= ($activeTab === 'contract' ? (__('sell_short') ?? 'Sell/Short') : (__('sell_price') ?? 'Sell Price')) ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<input type="number" id="spot-sell-price" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold">USDT</span>
</div>
</div>
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('amount') ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<input type="number" id="spot-sell-amount" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold"><?= $currentSymbol ?></span>
</div>
</div>
<div class="d-flex gap-1 mb-3">
<?php foreach(['25%','50%','75%','100%'] as $p): ?>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;"><?= $p ?></button>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;" onclick="setSpotPercent('sell', <?= intval($p) ?>)"><?= $p ?></button>
<?php endforeach; ?>
</div>
<button class="btn btn-danger w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #f6465d, #ef5350);"><?= __('sell') ?> <?= $currentSymbol ?></button>
<button onclick="<?= ($activeTab === 'contract' ? "placeContractOrder('short')" : "placeSpotOrder('sell')") ?>" class="btn btn-danger w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #f6465d, #ef5350);"><?= ($activeTab === 'contract' ? (__('short') ?? 'Short') : (__('sell'))) ?> <?= $currentSymbol ?></button>
</div>
</div>
</div>
@ -501,6 +524,81 @@ function renderTerminal($activeTab = 'spot') {
</div>
<script>
function setSpotPercent(side, percent) {
const balance = side === 'buy' ? <?= $usdt_balance ?> : 0; // Simplified
// In a real app we'd need the coin balance for sell
if (side === 'buy') {
const price = parseFloat(document.getElementById('spot-buy-price').value);
if (price > 0) {
document.getElementById('spot-buy-amount').value = (balance * percent / 100 / price).toFixed(6);
}
}
}
function placeSpotOrder(side) {
const price = parseFloat(document.getElementById('spot-' + side + '-price').value);
const amount = parseFloat(document.getElementById('spot-' + side + '-amount').value);
if (!amount || amount <= 0) {
showMsg('<?= __("enter_amount") ?>', 'error');
return;
}
const formData = new FormData();
formData.append('symbol', '<?= $currentSymbol ?>');
formData.append('side', side);
formData.append('price', price);
formData.append('amount', amount);
formData.append('type', 'limit');
fetch('/api/spot.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Order placed successfully', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showMsg(data.error, 'error');
}
});
}
function placeContractOrder(direction) {
const price = parseFloat(document.getElementById('spot-' + (direction === 'long' ? 'buy' : 'sell') + '-price').value);
const amount = parseFloat(document.getElementById('spot-' + (direction === 'long' ? 'buy' : 'sell') + '-amount').value);
const leverage = parseInt(document.getElementById('contract-leverage').value);
if (!amount || amount <= 0) {
showMsg('<?= __("enter_amount") ?>', 'error');
return;
}
const formData = new FormData();
formData.append('action', 'place_order');
formData.append('symbol', '<?= $currentSymbol ?>');
formData.append('direction', direction);
formData.append('leverage', leverage);
formData.append('amount', amount);
formData.append('entry_price', price);
formData.append('type', 'market');
fetch('/api/contract.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Contract order opened', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showMsg(data.error, 'error');
}
});
}
// Search functionality
document.getElementById('coin-search').addEventListener('input', function(e) {
const term = e.target.value.toUpperCase();
@ -707,7 +805,10 @@ function renderTerminal($activeTab = 'spot') {
};
}
populateAllCoins().then(initTradingWS);
populateAllCoins().then(() => {
initTradingWS();
loadHistory();
});
</script>
@ -784,6 +885,20 @@ function renderTerminal($activeTab = 'spot') {
settlement: []
};
async function loadHistory() {
try {
const resp = await fetch('/api/finance.php?action=get_orders&symbol=<?= $currentSymbol ?>&tab=<?= $activeTab ?>');
const data = await resp.json();
if (data.success) {
historyData.open = data.open;
historyData.settlement = data.settlement;
showHistoryTab(showHistoryTab.currentTab || 'open');
}
} catch (e) {
console.error("Failed to load history", e);
}
}
function showHistoryTab(tab) {
showHistoryTab.currentTab = tab;
// Update tabs UI
@ -825,14 +940,18 @@ function renderTerminal($activeTab = 'spot') {
// Format total/profit for settled orders
let totalDisplay = row.total;
if (tab === 'settlement' && (isProfit || isLoss)) {
const amount = parseFloat(row.amount);
const total = parseFloat(row.total);
const pl = (total - amount).toFixed(2);
const pl = parseFloat(row.pnl || 0).toFixed(2);
const total = parseFloat(row.total || 0).toFixed(2);
const plClass = pl >= 0 ? 'text-success' : 'text-danger';
totalDisplay = `<div class="${plClass} fw-bold">${total.toFixed(2)}</div><div class="small opacity-75">${pl >= 0 ? '+' : ''}${pl}</div>`;
totalDisplay = `<div class="${plClass} fw-bold">${total}</div><div class="small opacity-75">${pl >= 0 ? '+' : ''}${pl}</div>`;
}
const isUp = row.side_type === 'up' || row.side.includes('Up') || row.side.includes('涨') || row.side === 'Buy';
const isUp = row.side_type === 'up' || row.side.includes('Up') || row.side.includes('涨') || row.side === 'Buy' || row.side === 'Long';
let operationHtml = `<button class="btn btn-sm btn-dark px-2 py-0" style="font-size: 10px;"><?= __('details') ?></button>`;
if (tab === 'open' && row.type === 'Contract') {
operationHtml = `<button onclick="closeContractOrder(${row.id})" class="btn btn-sm btn-danger px-2 py-0" style="font-size: 10px;"><?= __('close') ?? 'Close' ?></button>`;
}
tr.innerHTML = `
<td class="ps-3 py-3 text-muted" style="font-size: 11px;">${row.time}</td>
@ -845,12 +964,35 @@ function renderTerminal($activeTab = 'spot') {
<td class="py-3">${row.amount}</td>
<td class="py-3">${totalDisplay}</td>
<td class="py-3"><span class="badge ${statusBg} bg-opacity-10 ${statusClass} rounded-pill px-2" style="font-size: 10px;">${displayStatus}</span></td>
<td class="pe-3 py-3 text-end"><button class="btn btn-sm btn-dark px-2 py-0" style="font-size: 10px;"><?= __('details') ?></button></td>
<td class="pe-3 py-3 text-end">${operationHtml}</td>
`;
body.appendChild(tr);
});
}
function closeContractOrder(id) {
if (!confirm('Confirm close position?')) return;
const closePrice = parseFloat(document.querySelector('.price-jump').innerText.replace(/,/g, ''));
const formData = new FormData();
formData.append('action', 'close_order');
formData.append('order_id', id);
formData.append('close_price', closePrice);
fetch('/api/contract.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Position closed', 'success');
loadHistory();
} else {
showMsg(data.error, 'error');
}
});
}
// Initialize with open orders
showHistoryTab('open');
</script>
@ -861,9 +1003,9 @@ function renderTerminal($activeTab = 'spot') {
<h5><?= __('order_in_progress') ?></h5>
<div class="countdown-circle">
<svg>
<circle class="bg" cx="80" cy="80" r="70"></circle>
<circle class="progress" id="popup-progress" cx="80" cy="80" r="70"></circle>
<svg width="120" height="120">
<circle class="bg" cx="60" cy="60" r="54"></circle>
<circle class="progress" id="popup-progress" cx="60" cy="60" r="54"></circle>
</svg>
<div class="time-text" id="popup-time-text">60<?= __('unit_seconds') ?></div>
</div>

101
orders.php Normal file
View File

@ -0,0 +1,101 @@
<?php
include __DIR__ . '/includes/header.php';
if (!$user) {
header('Location: /auth/login.php');
exit;
}
$tab = $_GET['tab'] ?? 'all';
$db = db();
if ($tab === 'all') {
$stmt = $db->prepare("SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT 100");
$stmt->execute([$user['id']]);
$records = $stmt->fetchAll();
} else {
// If specific tabs for trading are needed, they can be implemented here
$stmt = $db->prepare("SELECT * FROM transactions WHERE user_id = ? AND type LIKE ? ORDER BY created_at DESC LIMIT 100");
$stmt->execute([$user['id'], $tab . '%']);
$records = $stmt->fetchAll();
}
$types_map = [
'recharge' => ['name' => __('recharge'), 'color' => 'success'],
'withdrawal' => ['name' => __('withdrawal'), 'color' => 'danger'],
'binary_win' => ['name' => __('binary_win'), 'color' => 'success'],
'binary_loss' => ['name' => __('binary_loss'), 'color' => 'danger'],
'spot_trade' => ['name' => __('spot_trade'), 'color' => 'primary'],
'contract_margin' => ['name' => __('contract_margin'), 'color' => 'warning'],
'contract_settle' => ['name' => __('contract_settle'), 'color' => 'info'],
];
?>
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold text-white mb-0"><?= __('orders') ?></h2>
<div class="btn-group">
<a href="?tab=all" class="btn btn-sm <?= $tab === 'all' ? 'btn-primary' : 'btn-outline-secondary' ?>"><?= __('all') ?? 'All' ?></a>
<a href="?tab=binary" class="btn btn-sm <?= $tab === 'binary' ? 'btn-primary' : 'btn-outline-secondary' ?>"><?= __('second_contract') ?></a>
<a href="?tab=spot" class="btn btn-sm <?= $tab === 'spot' ? 'btn-primary' : 'btn-outline-secondary' ?>"><?= __('spot') ?></a>
<a href="?tab=contract" class="btn btn-sm <?= $tab === 'contract' ? 'btn-primary' : 'btn-outline-secondary' ?>"><?= __('contract') ?></a>
</div>
</div>
<div class="card bg-surface border-secondary rounded-4 overflow-hidden shadow-lg">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0 align-middle">
<thead class="bg-black bg-opacity-50 text-white-50 small">
<tr>
<th class="ps-4 py-3 border-secondary"><?= __('type') ?></th>
<th class="py-3 border-secondary"><?= __('amount') ?></th>
<th class="py-3 border-secondary"><?= __('symbol') ?? 'Symbol' ?></th>
<th class="py-3 border-secondary"><?= __('status') ?></th>
<th class="text-end pe-4 py-3 border-secondary"><?= __('time') ?></th>
</tr>
</thead>
<tbody class="border-0">
<?php if (empty($records)): ?>
<tr>
<td colspan="5" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
<?= __('no_records_found') ?>
</td>
</tr>
<?php else: ?>
<?php foreach ($records as $r):
$type = $types_map[$r['type']] ?? ['name' => __($r['type']), 'color' => 'secondary'];
?>
<tr class="border-secondary">
<td class="ps-4 py-3">
<span class="badge bg-<?= $type['color'] ?> bg-opacity-10 text-<?= $type['color'] ?> border border-<?= $type['color'] ?> border-opacity-25 px-2 py-1">
<?= $type['name'] ?>
</span>
</td>
<td class="py-3 fw-bold text-white">
<?= number_format($r['amount'], 4) ?>
</td>
<td class="py-3 text-white-50">
<?= $r['symbol'] ?>
</td>
<td class="py-3">
<?php if ($r['status'] === 'completed'): ?>
<span class="text-success small"><i class="bi bi-check-circle-fill me-1"></i><?= __('completed') ?></span>
<?php else: ?>
<span class="text-warning small"><i class="bi bi-clock-fill me-1"></i><?= __('pending') ?></span>
<?php endif; ?>
</td>
<td class="text-end pe-4 py-3 text-white-50 small">
<?= date('Y-m-d H:i:s', strtotime($r['created_at'])) ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include __DIR__ . '/includes/footer.php'; ?>

View File

@ -73,6 +73,14 @@ function getVipLevel($totalRecharge) {
$vipLevel = getVipLevel($userData['total_recharge'] ?? 0);
// Fetch transactions
$stmt = db()->prepare("SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT 50");
$stmt->execute([$user['id']]);
$transactions = $stmt->fetchAll();
// Fetch orders to sync if needed, or just show transactions which should cover most balance changes
// For a complete "order history", we might want a separate tab, but user asked for "recharge, withdrawal, and order records" to be synced here.
$kycStatusText = [
0 => __('unverified'),
1 => __('pending'),
@ -238,15 +246,15 @@ $kycStatusColor = [
<?php foreach ($transactions as $t):
$typeColor = 'text-primary';
$typeName = __($t['type']);
if ($t['type'] === 'deposit') $typeColor = 'text-success';
if ($t['type'] === 'withdraw') $typeColor = 'text-danger';
if ($t['type'] === 'trade_profit') $typeColor = 'text-success';
if ($t['type'] === 'trade_loss') $typeColor = 'text-danger';
if ($t['type'] === 'flash_exchange') $typeColor = 'text-info';
if (strpos($t['type'], 'win') !== false || $t['type'] === 'deposit' || $t['type'] === 'binary_win') $typeColor = 'text-success';
elseif (strpos($t['type'], 'loss') !== false || $t['type'] === 'withdraw' || $t['type'] === 'binary_loss') $typeColor = 'text-danger';
elseif ($t['type'] === 'flash_exchange') $typeColor = 'text-info';
elseif ($t['type'] === 'contract_settle') $typeColor = 'text-info';
$prefix = '';
if ($t['type'] === 'trade_profit' || $t['type'] === 'deposit') $prefix = '+';
if ($t['type'] === 'trade_loss' || $t['type'] === 'withdraw') $prefix = '-';
if ($t['type'] === 'binary_win' || $t['type'] === 'deposit' || $t['type'] === 'contract_settle') $prefix = '+';
if ($t['type'] === 'binary_loss' || $t['type'] === 'withdraw' || $t['type'] === 'contract_margin') $prefix = '-';
?>
<tr class="border-secondary">
<td class="ps-4 py-3">

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB