Autosave: 20260218-062147
This commit is contained in:
parent
b573ba93a2
commit
e78b6820eb
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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();
|
||||
?>
|
||||
|
||||
108
admin/kyc.php
108
admin/kyc.php
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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()]);
|
||||
|
||||
43
api/chat.php
43
api/chat.php
@ -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
121
api/contract.php
Normal 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()]);
|
||||
}
|
||||
}
|
||||
@ -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
73
api/spot.php
Normal 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()]);
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
BIN
assets/images/logo_1771392222.png
Normal file
BIN
assets/images/logo_1771392222.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 571 KiB |
BIN
assets/images/logo_1771392246.png
Normal file
BIN
assets/images/logo_1771392246.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 571 KiB |
@ -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') ?? '验证码' ?>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
@ -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
101
orders.php
Normal 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'; ?>
|
||||
22
profile.php
22
profile.php
@ -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">
|
||||
|
||||
BIN
uploads/kyc/2_back_1771391536.jpg
Normal file
BIN
uploads/kyc/2_back_1771391536.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
uploads/kyc/2_front_1771391536.jpg
Normal file
BIN
uploads/kyc/2_front_1771391536.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
uploads/kyc/2_handheld_1771391536.jpg
Normal file
BIN
uploads/kyc/2_handheld_1771391536.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Loading…
x
Reference in New Issue
Block a user