BIT
This commit is contained in:
parent
65d8cd957f
commit
eb60696dd9
Binary file not shown.
49
core/templates/core/mobile/base_mobile.html
Normal file
49
core/templates/core/mobile/base_mobile.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>{{ site_settings.site_name|default:"BitCrypto" }} - 移动版</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.10.0/font/bootstrap-icons.css">
|
||||||
|
<style>
|
||||||
|
body { background-color: #0b0e11; color: #eaecef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; overflow-x: hidden; }
|
||||||
|
.mobile-nav { position: fixed; bottom: 0; left: 0; right: 0; background: #161a1e; display: flex; justify-content: space-around; padding: 10px 0; border-top: 1px solid #2b3139; z-index: 1000; }
|
||||||
|
.nav-item { text-align: center; color: #848e9c; text-decoration: none; font-size: 10px; }
|
||||||
|
.nav-item.active { color: #f0b90b; }
|
||||||
|
.nav-item i { font-size: 20px; display: block; margin-bottom: 2px; }
|
||||||
|
.glass-card { background: #161a1e; border: 1px solid #2b3139; border-radius: 8px; margin-bottom: 10px; }
|
||||||
|
.x-small { font-size: 11px; }
|
||||||
|
.text-warning { color: #f0b90b !important; }
|
||||||
|
.btn-warning { background-color: #f0b90b; border-color: #f0b90b; color: #000; }
|
||||||
|
.form-control, .input-group-text { background-color: #1e2329 !important; border-color: #2b3139 !important; color: white !important; }
|
||||||
|
.custom-range::-webkit-slider-thumb { background: #f0b90b; cursor: pointer; height: 16px; width: 16px; }
|
||||||
|
.custom-range-danger::-webkit-slider-thumb { background: #f6465d; cursor: pointer; height: 16px; width: 16px; }
|
||||||
|
body { padding-bottom: 70px; }
|
||||||
|
</style>
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="p-2">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-nav">
|
||||||
|
<a href="{% url 'core:index' %}" class="nav-item {% if request.resolver_match.url_name == 'index' %}active{% endif %}">
|
||||||
|
<i class="bi bi-house-door"></i>首页
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:market_center' %}" class="nav-item {% if request.resolver_match.url_name == 'market_center' %}active{% endif %}">
|
||||||
|
<i class="bi bi-graph-up"></i>行情
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:trade' 'spot' %}" class="nav-item {% if 'trade' in request.resolver_match.url_name %}active{% endif %}">
|
||||||
|
<i class="bi bi-arrow-left-right"></i>交易
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:profile' %}" class="nav-item {% if request.resolver_match.url_name == 'profile' %}active{% endif %}">
|
||||||
|
<i class="bi bi-person"></i>我的
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
44
core/templates/core/mobile/deposit.html
Normal file
44
core/templates/core/mobile/deposit.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'core:profile' %}" class="text-decoration-none text-secondary small"><i class="bi bi-chevron-left"></i> 返回</a>
|
||||||
|
<h4 class="fw-bold mt-2">充值 USDT</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-3 mb-3">
|
||||||
|
<div class="text-secondary x-small mb-2">选择币种</div>
|
||||||
|
<div class="bg-dark p-2 rounded d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<span class="fw-bold">USDT</span>
|
||||||
|
<span class="badge bg-warning text-dark">TRC20</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center py-4 bg-white rounded mb-3">
|
||||||
|
<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=TFA7S7S7S7S7S7S7S7S7S7S7S7S7S7" alt="QR" style="width: 120px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-secondary x-small mb-1">充值地址</div>
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input type="text" class="form-control" value="TFA7S7S7S7S7S7S7S7S7S7S7S7S7S7" readonly>
|
||||||
|
<button class="btn btn-warning" onclick="alert('已复制')">复制</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-dark x-small py-2">
|
||||||
|
<i class="bi bi-info-circle me-1"></i> 请务必确认网络为 TRC20,否则资产将无法找回。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="glass-card p-3">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">充值金额</label>
|
||||||
|
<input type="number" name="amount" class="form-control" placeholder="请输入充值金额" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">交易哈希 (TXID)</label>
|
||||||
|
<input type="text" name="tx_id" class="form-control" placeholder="请输入流水号" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold">提交充值申请</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
88
core/templates/core/mobile/index.html
Normal file
88
core/templates/core/mobile/index.html
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="glass-card p-3 text-center mb-3" style="background: linear-gradient(135deg, #161a1e 0%, #1e2329 100%);">
|
||||||
|
<img src="{% static 'images/logo.png' %}" alt="Logo" style="height: 40px;" class="mb-2">
|
||||||
|
<h4 class="fw-bold">全球领先的数字资产交易平台</h4>
|
||||||
|
<p class="text-secondary small">安全、稳定、快捷的加密货币交易体验</p>
|
||||||
|
{% if not user.is_authenticated %}
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="{% url 'core:register' %}" class="btn btn-warning fw-bold">立即注册</a>
|
||||||
|
<a href="{% url 'core:login' %}" class="btn btn-outline-secondary btn-sm text-white">登录已有账户</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="d-flex justify-content-around mt-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-secondary x-small">账户总资产 (USDT)</div>
|
||||||
|
<div class="fs-5 fw-bold">{{ account.balance|add:account.frozen_balance }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-2 mb-3">
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="{% url 'core:deposit' %}" class="glass-card p-2 d-block text-center text-decoration-none text-white">
|
||||||
|
<i class="bi bi-wallet2 fs-4 text-warning"></i>
|
||||||
|
<div class="x-small mt-1">充币</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="{% url 'core:withdraw' %}" class="glass-card p-2 d-block text-center text-decoration-none text-white">
|
||||||
|
<i class="bi bi-cash-stack fs-4 text-info"></i>
|
||||||
|
<div class="x-small mt-1">提币</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="{% url 'core:verify' %}" class="glass-card p-2 d-block text-center text-decoration-none text-white">
|
||||||
|
<i class="bi bi-shield-check fs-4 text-success"></i>
|
||||||
|
<div class="x-small mt-1">认证</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-2">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 px-1">
|
||||||
|
<span class="fw-bold small">热门行情</span>
|
||||||
|
<a href="{% url 'core:market_center' %}" class="text-warning x-small text-decoration-none">更多 <i class="bi bi-chevron-right"></i></a>
|
||||||
|
</div>
|
||||||
|
<div id="hot-list">
|
||||||
|
<!-- JS populated -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hot-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 8px; border-bottom: 1px solid #2b3139; }
|
||||||
|
.hot-item:last-child { border-bottom: 0; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
async function getHot() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/market_data/');
|
||||||
|
const data = await r.json();
|
||||||
|
const list = document.getElementById('hot-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
data.slice(0, 8).forEach(c => {
|
||||||
|
const base = c.symbol.replace('USDT', '');
|
||||||
|
list.innerHTML += `
|
||||||
|
<div class="hot-item" onclick="location.href='{% url 'core:trade' 'spot' %}?symbol=${c.symbol}'">
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold">${base}<span class="text-secondary x-small">/USDT</span></div>
|
||||||
|
<div class="text-secondary x-small">Vol ${ (Math.random()*100).toFixed(2) }M</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<div class="fw-bold">${c.price}</div>
|
||||||
|
<div class="${c.change>=0?'text-success':'text-danger'} x-small">${c.change>=0?'+':''}${c.change}%</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
getHot();
|
||||||
|
setInterval(getHot, 5000);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
26
core/templates/core/mobile/login.html
Normal file
26
core/templates/core/mobile/login.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="text-center mb-4 mt-5">
|
||||||
|
<h3 class="fw-bold">欢迎回来</h3>
|
||||||
|
<p class="text-secondary small">登录您的 {{ site_settings.site_name|default:"BitCrypto" }} 账户</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="glass-card p-4">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">用户名</label>
|
||||||
|
<input type="text" name="username" class="form-control" required placeholder="请输入用户名">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small text-secondary">登录密码</label>
|
||||||
|
<input type="password" name="password" class="form-control" required placeholder="请输入密码">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold py-2 mb-3">立即登录</button>
|
||||||
|
<div class="text-center small">
|
||||||
|
<span class="text-secondary">还没有账户?</span>
|
||||||
|
<a href="{% url 'core:register' %}" class="text-warning text-decoration-none">立即注册</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
63
core/templates/core/mobile/market_center.html
Normal file
63
core/templates/core/mobile/market_center.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="glass-card p-2 mb-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text bg-dark border-secondary"><i class="bi bi-search text-secondary"></i></span>
|
||||||
|
<input type="text" id="market-search" class="form-control bg-dark text-white border-secondary" placeholder="搜索币种" onkeyup="filterMarkets()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-0">
|
||||||
|
<div class="d-flex justify-content-between p-2 text-secondary x-small border-bottom border-secondary">
|
||||||
|
<span style="width: 40%;">名称</span>
|
||||||
|
<span style="width: 30%;" class="text-end">最新价</span>
|
||||||
|
<span style="width: 30%;" class="text-end">24h 涨跌</span>
|
||||||
|
</div>
|
||||||
|
<div id="market-list">
|
||||||
|
<!-- JS populated -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.market-row { display: flex; justify-content: space-between; align-items: center; padding: 12px 8px; border-bottom: 1px solid #2b3139; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
let allMarkets = [];
|
||||||
|
async function getMarkets() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/market_data/');
|
||||||
|
allMarkets = await r.json();
|
||||||
|
renderMarkets();
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMarkets(filter = '') {
|
||||||
|
const list = document.getElementById('market-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
allMarkets.forEach(c => {
|
||||||
|
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
|
||||||
|
const base = c.symbol.replace('USDT', '');
|
||||||
|
list.innerHTML += `
|
||||||
|
<div class="market-row" onclick="location.href='{% url 'core:trade' 'spot' %}?symbol=${c.symbol}'">
|
||||||
|
<div style="width: 40%;">
|
||||||
|
<div class="fw-bold">${base}<span class="text-secondary x-small">/USDT</span></div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 30%;" class="text-end fw-bold">${c.price}</div>
|
||||||
|
<div style="width: 30%;" class="text-end">
|
||||||
|
<span class="badge ${c.change>=0?'bg-success':'bg-danger'} py-1" style="min-width: 60px;">${c.change>=0?'+':''}${c.change}%</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMarkets() {
|
||||||
|
renderMarkets(document.getElementById('market-search').value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarkets();
|
||||||
|
setInterval(getMarkets, 3000);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
83
core/templates/core/mobile/profile.html
Normal file
83
core/templates/core/mobile/profile.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="glass-card p-3 mb-3 d-flex align-items-center">
|
||||||
|
<div class="bg-warning rounded-circle d-flex align-items-center justify-content-center text-dark fw-bold me-3" style="width: 50px; height: 50px; font-size: 20px;">
|
||||||
|
{{ user.username|first|upper }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold">{{ user.username }}</div>
|
||||||
|
<div class="text-secondary x-small">UID: {{ account.uid }} | {{ account.get_account_type_display }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="ms-auto">
|
||||||
|
<span class="badge {% if account.kyc_status == 'VERIFIED' %}bg-success{% else %}bg-secondary{% endif %} x-small">
|
||||||
|
{{ account.get_kyc_status_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-3 mb-3">
|
||||||
|
<div class="text-secondary x-small mb-1">总资产估值 (USDT)</div>
|
||||||
|
<div class="fs-3 fw-bold mb-3">{{ account.balance|add:account.frozen_balance }}</div>
|
||||||
|
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="bg-dark p-2 rounded">
|
||||||
|
<div class="text-secondary x-small">可用</div>
|
||||||
|
<div class="fw-bold small">{{ account.balance }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="bg-dark p-2 rounded">
|
||||||
|
<div class="text-secondary x-small">冻结</div>
|
||||||
|
<div class="fw-bold small">{{ account.frozen_balance }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card mb-3">
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
<a href="{% url 'core:deposit' %}" class="list-group-item list-group-item-action bg-transparent text-white border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-wallet2 me-3 text-warning"></i>充币</span>
|
||||||
|
<i class="bi bi-chevron-right text-secondary"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:withdraw' %}" class="list-group-item list-group-item-action bg-transparent text-white border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-cash-stack me-3 text-info"></i>提币</span>
|
||||||
|
<i class="bi bi-chevron-right text-secondary"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'core:verify' %}" class="list-group-item list-group-item-action bg-transparent text-white border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-shield-check me-3 text-success"></i>实名认证</span>
|
||||||
|
<i class="bi bi-chevron-right text-secondary"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-3">
|
||||||
|
<div class="fw-bold small mb-2">资产明细</div>
|
||||||
|
<table class="table table-dark table-sm x-small mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-secondary">
|
||||||
|
<th>币种</th>
|
||||||
|
<th class="text-end">可用</th>
|
||||||
|
<th class="text-end">冻结</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for asset in account.assets.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ asset.currency }}</td>
|
||||||
|
<td class="text-end">{{ asset.balance }}</td>
|
||||||
|
<td class="text-end">{{ asset.frozen }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 p-2">
|
||||||
|
<form action="{% url 'logout' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-outline-danger w-100 btn-sm">退出登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
45
core/templates/core/mobile/register.html
Normal file
45
core/templates/core/mobile/register.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="text-center mb-4 mt-4">
|
||||||
|
<h3 class="fw-bold">注册账户</h3>
|
||||||
|
<p class="text-secondary small">加入 {{ site_settings.site_name|default:"BitCrypto" }},开启您的交易之旅</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<div class="mb-3">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-danger py-2 small">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" class="glass-card p-4">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">用户名 / 手机号</label>
|
||||||
|
<input type="text" name="username" class="form-control" required placeholder="请输入用户名">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">登录密码</label>
|
||||||
|
<input type="password" name="password" class="form-control" required placeholder="请输入密码">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">确认密码</label>
|
||||||
|
<input type="password" name="password_confirm" class="form-control" required placeholder="请再次输入密码">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label small text-secondary">图形验证码</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" name="captcha_input" class="form-control" required placeholder="结果">
|
||||||
|
<span class="input-group-text bg-dark text-warning fw-bold">{{ captcha_text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold py-2 mb-3">立即注册</button>
|
||||||
|
<div class="text-center small">
|
||||||
|
<span class="text-secondary">已有账户?</span>
|
||||||
|
<a href="{% url 'core:login' %}" class="text-warning text-decoration-none">立即登录</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
376
core/templates/core/mobile/trade.html
Normal file
376
core/templates/core/mobile/trade.html
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 px-1 py-1" style="background: #161a1e;">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-dark dropdown-toggle fs-6 fw-bold text-warning" type="button" data-bs-toggle="dropdown">
|
||||||
|
<span id="current-symbol-display">{{ symbol }}</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-dark shadow-lg" id="mobile-coin-list" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
<!-- JS populated -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-1 bg-dark p-1 rounded">
|
||||||
|
<a href="{% url 'core:trade' 'spot' %}?symbol={{ symbol }}" class="btn btn-xs {% if trade_type == 'SPOT' %}btn-warning text-dark{% else %}btn-dark text-secondary{% endif %} py-1 px-3 small fw-bold">现货</a>
|
||||||
|
<a href="{% url 'core:trade' 'contract' %}?symbol={{ symbol }}" class="btn btn-xs {% if trade_type == 'CONTRACT' %}btn-warning text-dark{% else %}btn-dark text-secondary{% endif %} py-1 px-3 small fw-bold">合约</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-1 mb-2">
|
||||||
|
<!-- Market Data Column -->
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="glass-card p-2" style="height: 100%; font-size: 11px; background: #161a1e; border: 1px solid #2b3139;">
|
||||||
|
<div class="d-flex justify-content-between text-secondary mb-2">
|
||||||
|
<span>价格</span><span>数量</span>
|
||||||
|
</div>
|
||||||
|
<div id="m-asks" class="mb-2"></div>
|
||||||
|
<div class="text-center py-2 fw-bold text-success border-top border-bottom border-secondary my-2" id="m-current-price" style="font-size: 16px;">--</div>
|
||||||
|
<div id="m-bids"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Order Form Column -->
|
||||||
|
<div class="col-7">
|
||||||
|
<div class="glass-card p-2" style="background: #161a1e; border: 1px solid #2b3139;">
|
||||||
|
{% if trade_type == 'SPOT' %}
|
||||||
|
<div class="btn-group w-100 mb-2" role="group">
|
||||||
|
<input type="radio" class="btn-check" name="trade-side" id="buy-side" checked onchange="toggleSide('BUY')">
|
||||||
|
<label class="btn btn-outline-success btn-sm py-1" for="buy-side">买入</label>
|
||||||
|
<input type="radio" class="btn-check" name="trade-side" id="sell-side" onchange="toggleSide('SELL')">
|
||||||
|
<label class="btn btn-outline-danger btn-sm py-1" for="sell-side">卖出</label>
|
||||||
|
</div>
|
||||||
|
<select class="form-select form-select-sm bg-dark text-white border-secondary mb-2" id="spot-mode-select" onchange="toggleModeMobile()">
|
||||||
|
<option value="limit">限价委托</option>
|
||||||
|
<option value="market">市价委托</option>
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
|
<div class="d-flex gap-2 mb-2">
|
||||||
|
<div class="dropdown w-50">
|
||||||
|
<button class="btn btn-sm btn-dark border-secondary w-100 text-warning fw-bold" id="lev-btn" data-bs-toggle="dropdown">20x</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-dark">
|
||||||
|
<li><a class="dropdown-item" href="javascript:void(0)" onclick="setLev(10)">10x</a></li>
|
||||||
|
<li><a class="dropdown-item" href="javascript:void(0)" onclick="setLev(20)">20x</a></li>
|
||||||
|
<li><a class="dropdown-item" href="javascript:void(0)" onclick="setLev(50)">50x</a></li>
|
||||||
|
<li><a class="dropdown-item" href="javascript:void(0)" onclick="setLev(100)">100x</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<select class="form-select form-select-sm bg-dark text-white border-secondary w-50" id="c-mode-select" onchange="toggleModeMobile()">
|
||||||
|
<option value="limit">限价</option>
|
||||||
|
<option value="market">市价</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text bg-dark border-secondary text-secondary" style="width: 40px; font-size: 10px;">价</span>
|
||||||
|
<input type="text" id="m-price" class="form-control bg-transparent text-white border-secondary" placeholder="价格">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-1">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text bg-dark border-secondary text-secondary" style="width: 40px; font-size: 10px;" id="m-label">数</span>
|
||||||
|
<input type="number" id="m-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0.00" oninput="updateSliderMobile()">
|
||||||
|
<span class="input-group-text bg-dark border-secondary text-secondary x-small" id="m-unit">{{ base_symbol }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 px-1">
|
||||||
|
<input type="range" class="form-range custom-range" id="m-slider" min="0" max="100" step="1" value="0" oninput="applySliderMobile()">
|
||||||
|
<div class="d-flex justify-content-between x-small text-secondary" style="font-size: 9px;">
|
||||||
|
<span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="x-small mb-2 d-flex justify-content-between text-secondary" style="font-size: 10px;">
|
||||||
|
<span>可用:</span>
|
||||||
|
<span id="m-available" class="text-white">--</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if trade_type == 'CONTRACT' %}
|
||||||
|
<div class="row g-1">
|
||||||
|
<div class="col-6"><button class="btn btn-success btn-sm w-100 fw-bold" onclick="submitOrderMobile('BUY')">开多</button></div>
|
||||||
|
<div class="col-6"><button class="btn btn-danger btn-sm w-100 fw-bold" onclick="submitOrderMobile('SELL')">开空</button></div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-success w-100 fw-bold btn-sm" id="m-submit-btn" onclick="submitOrderMobile(side)">买入</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Tab -->
|
||||||
|
<div class="glass-card p-1 mb-2" style="background: #161a1e; border: 1px solid #2b3139;">
|
||||||
|
<div id="tradingview_widget" style="height: 250px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Positions List -->
|
||||||
|
<div class="glass-card p-2" style="background: #161a1e; border: 1px solid #2b3139;">
|
||||||
|
<div class="fw-bold mb-2 small border-bottom border-secondary pb-1 d-flex justify-content-between">
|
||||||
|
<span>当前持仓/挂单</span>
|
||||||
|
<span class="text-warning x-small" style="font-weight: normal; cursor: pointer;" onclick="location.reload()"><i class="bi bi-arrow-clockwise"></i> 刷新</span>
|
||||||
|
</div>
|
||||||
|
<div id="m-positions-list">
|
||||||
|
{% if trade_type == 'CONTRACT' %}
|
||||||
|
{% for p in account.positions.all %}
|
||||||
|
{% if p.is_active %}
|
||||||
|
<div class="mb-2 p-2 bg-dark rounded border-start border-3 {% if p.side == 'LONG' %}border-success{% else %}border-danger{% endif %}" style="border: 1px solid #2b3139;">
|
||||||
|
<div class="d-flex justify-content-between x-small mb-1">
|
||||||
|
<span class="fw-bold text-white">{{ p.symbol }} <span class="badge bg-secondary">{{ p.leverage }}x</span> <span class="{% if p.side == 'LONG' %}text-success{% else %}text-danger{% endif %}">{% if p.side == 'LONG' %}多{% else %}空{% endif %}</span></span>
|
||||||
|
<span class="upl-val fw-bold" data-entry="{{ p.entry_price }}" data-side="{{ p.side }}" data-lots="{{ p.lots }}">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between x-small text-secondary" style="font-size: 10px;">
|
||||||
|
<span>手数: {{ p.lots }}</span>
|
||||||
|
<span>开仓价: {{ p.entry_price|floatformat:2 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between x-small text-secondary mb-1" style="font-size: 10px;">
|
||||||
|
<span>保证金: {{ p.margin|floatformat:2 }}</span>
|
||||||
|
<span class="current-p-val" data-symbol="{{ p.symbol }}">当前: --</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-danger btn-xs py-0 w-100 mt-1" style="font-size: 11px;" onclick="closePos({{ p.id }})">市价平仓</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% for o in account.orders.all %}
|
||||||
|
{% if o.status == 'PENDING' %}
|
||||||
|
<div class="mb-2 p-2 bg-dark rounded border-start border-3 border-warning" style="border: 1px solid #2b3139;">
|
||||||
|
<div class="d-flex justify-content-between x-small">
|
||||||
|
<span class="text-white">{{ o.symbol }} ({{ o.get_trade_type_display }}) <span class="{% if o.side == 'BUY' %}text-success{% else %}text-danger{% endif %}">{{ o.get_side_display }}</span></span>
|
||||||
|
<span class="text-warning">委托中</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between x-small text-secondary mt-1" style="font-size: 10px;">
|
||||||
|
<span>价格: {{ o.price|default:"市价" }}</span>
|
||||||
|
<span>数量: {{ o.amount }}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-secondary btn-xs py-0 w-100 mt-1" style="font-size: 11px;">撤单</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn-xs { padding: 1px 5px; font-size: 10px; }
|
||||||
|
.order-row { display: flex; justify-content: space-between; margin-bottom: 2px; height: 16px; overflow: hidden; position: relative; }
|
||||||
|
.order-row span { position: relative; z-index: 1; }
|
||||||
|
.ask-bg { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(246, 70, 93, 0.1); }
|
||||||
|
.bid-bg { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(14, 203, 129, 0.1); }
|
||||||
|
.custom-range::-webkit-slider-thumb { background: #f0b90b; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||||
|
<script>
|
||||||
|
const symbol = '{{ symbol }}';
|
||||||
|
const tradeType = '{{ trade_type }}';
|
||||||
|
const baseAsset = '{{ base_symbol }}';
|
||||||
|
const balance = parseFloat("{{ account.balance|default:0 }}");
|
||||||
|
const assetBalance = parseFloat("{{ base_asset_balance|default:0 }}");
|
||||||
|
let currentPrice = 0;
|
||||||
|
let leverage = 20;
|
||||||
|
let side = 'BUY';
|
||||||
|
|
||||||
|
function initTV() {
|
||||||
|
new TradingView.widget({
|
||||||
|
"width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol,
|
||||||
|
"interval": "15", "theme": "dark", "style": "1", "locale": "zh_CN",
|
||||||
|
"container_id": "tradingview_widget", "hide_side_toolbar": true, "save_image": false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMarkets() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/market_data/');
|
||||||
|
const data = await r.json();
|
||||||
|
const list = document.getElementById('mobile-coin-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
data.forEach(c => {
|
||||||
|
const b = c.symbol.replace('USDT', '');
|
||||||
|
const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${b.toLowerCase()}.png`;
|
||||||
|
list.innerHTML += `<li><a class="dropdown-item d-flex justify-content-between" href="?symbol=${c.symbol}"><span><img src="${iconUrl}" style="width:16px;margin-right:5px;" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${b}</span><span class="${c.change>=0?'text-success':'text-danger'}">${c.price}</span></a></li>`;
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSide(s) {
|
||||||
|
side = s;
|
||||||
|
const btn = document.getElementById('m-submit-btn');
|
||||||
|
if (btn) {
|
||||||
|
if (s === 'BUY') {
|
||||||
|
btn.className = 'btn btn-success w-100 fw-bold btn-sm';
|
||||||
|
btn.textContent = '立即买入';
|
||||||
|
} else {
|
||||||
|
btn.className = 'btn btn-danger w-100 fw-bold btn-sm';
|
||||||
|
btn.textContent = '立即卖出';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const avail = document.getElementById('m-available');
|
||||||
|
if (tradeType === 'SPOT') {
|
||||||
|
if (s === 'BUY') avail.textContent = balance.toFixed(2) + ' USDT';
|
||||||
|
else avail.textContent = assetBalance.toFixed(4) + ' ' + baseAsset;
|
||||||
|
} else {
|
||||||
|
avail.textContent = balance.toFixed(2) + ' USDT';
|
||||||
|
}
|
||||||
|
toggleModeMobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleModeMobile() {
|
||||||
|
const mode = tradeType === 'SPOT'
|
||||||
|
? document.getElementById('spot-mode-select').value
|
||||||
|
: document.getElementById('c-mode-select').value;
|
||||||
|
const pInput = document.getElementById('m-price');
|
||||||
|
const unit = document.getElementById('m-unit');
|
||||||
|
const label = document.getElementById('m-label');
|
||||||
|
|
||||||
|
if (mode === 'market') {
|
||||||
|
pInput.value = currentPrice || '市价';
|
||||||
|
pInput.disabled = true;
|
||||||
|
pInput.style.color = '#f0b90b';
|
||||||
|
if (tradeType === 'SPOT' && side === 'BUY') {
|
||||||
|
label.textContent = '额';
|
||||||
|
unit.textContent = 'USDT';
|
||||||
|
} else {
|
||||||
|
label.textContent = '数';
|
||||||
|
unit.textContent = tradeType === 'SPOT' ? baseAsset : '张';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pInput.value = '';
|
||||||
|
pInput.disabled = false;
|
||||||
|
pInput.style.color = 'white';
|
||||||
|
label.textContent = '数';
|
||||||
|
unit.textContent = tradeType === 'SPOT' ? baseAsset : '张';
|
||||||
|
}
|
||||||
|
applySliderMobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySliderMobile() {
|
||||||
|
const val = document.getElementById('m-slider').value;
|
||||||
|
const mode = tradeType === 'SPOT'
|
||||||
|
? document.getElementById('spot-mode-select').value
|
||||||
|
: document.getElementById('c-mode-select').value;
|
||||||
|
|
||||||
|
const amountInput = document.getElementById('m-amount');
|
||||||
|
|
||||||
|
if (tradeType === 'SPOT') {
|
||||||
|
if (side === 'BUY') {
|
||||||
|
if (mode === 'market') {
|
||||||
|
amountInput.value = (balance * (val / 100)).toFixed(2);
|
||||||
|
} else {
|
||||||
|
const price = parseFloat(document.getElementById('m-price').value) || currentPrice;
|
||||||
|
if (price > 0) amountInput.value = (balance * (val / 100) / price).toFixed(4);
|
||||||
|
else amountInput.value = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amountInput.value = (assetBalance * (val / 100)).toFixed(4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const maxLots = (balance * leverage) / 100;
|
||||||
|
amountInput.value = Math.floor(maxLots * (val / 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSliderMobile() {
|
||||||
|
const val = parseFloat(document.getElementById('m-amount').value) || 0;
|
||||||
|
const slider = document.getElementById('m-slider');
|
||||||
|
if (tradeType === 'SPOT') {
|
||||||
|
if (side === 'BUY') {
|
||||||
|
const mode = document.getElementById('spot-mode-select').value;
|
||||||
|
if (mode === 'market') {
|
||||||
|
if (balance > 0) slider.value = (val / balance) * 100;
|
||||||
|
} else {
|
||||||
|
const price = parseFloat(document.getElementById('m-price').value) || currentPrice;
|
||||||
|
if (balance > 0 && price > 0) slider.value = (val * price / balance) * 100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (assetBalance > 0) slider.value = (val / assetBalance) * 100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const maxLots = (balance * leverage) / 100;
|
||||||
|
if (maxLots > 0) slider.value = (val / maxLots) * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLev(l) {
|
||||||
|
leverage = l;
|
||||||
|
document.getElementById('lev-btn').textContent = l + 'x';
|
||||||
|
applySliderMobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tick() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/market_data/');
|
||||||
|
const data = await r.json();
|
||||||
|
const d = data.find(c => c.symbol === symbol);
|
||||||
|
if (!d) return;
|
||||||
|
currentPrice = parseFloat(d.price);
|
||||||
|
document.getElementById('m-current-price').textContent = currentPrice.toLocaleString();
|
||||||
|
|
||||||
|
const pInput = document.getElementById('m-price');
|
||||||
|
if (pInput && pInput.disabled) pInput.value = currentPrice;
|
||||||
|
|
||||||
|
const askD = document.getElementById('m-asks'); const bidD = document.getElementById('m-bids');
|
||||||
|
askD.innerHTML = ''; bidD.innerHTML = '';
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const ap = (currentPrice+(5-i)*0.1).toFixed(2);
|
||||||
|
const bp = (currentPrice-(i+1)*0.1).toFixed(2);
|
||||||
|
askD.innerHTML = `<div class="order-row"><span class="text-danger">${ap}</span><span class="text-secondary">${(Math.random()*0.5).toFixed(3)}</span><div class="ask-bg" style="width:${Math.random()*60}%"></div></div>` + askD.innerHTML;
|
||||||
|
bidD.innerHTML += `<div class="order-row"><span class="text-success">${bp}</span><span class="text-secondary">${(Math.random()*0.5).toFixed(3)}</span><div class="bid-bg" style="width:${Math.random()*60}%"></div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.current-p-val').forEach(el => { el.textContent = '当前: ' + currentPrice.toLocaleString(); });
|
||||||
|
document.querySelectorAll('.upl-val').forEach(el => {
|
||||||
|
const entry = parseFloat(el.getAttribute('data-entry'));
|
||||||
|
const sideP = el.getAttribute('data-side');
|
||||||
|
const lots = parseFloat(el.getAttribute('data-lots'));
|
||||||
|
let upl = sideP === 'LONG' ? (currentPrice - entry) / entry * (lots * 100) : (entry - currentPrice) / entry * (lots * 100);
|
||||||
|
el.textContent = (upl >= 0 ? '+' : '') + upl.toFixed(2) + ' USDT';
|
||||||
|
el.className = 'upl-val fw-bold ' + (upl >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitOrderMobile(sideParam) {
|
||||||
|
const mode = tradeType === 'SPOT' ? document.getElementById('spot-mode-select').value : document.getElementById('c-mode-select').value;
|
||||||
|
const price = document.getElementById('m-price').value;
|
||||||
|
const amount = document.getElementById('m-amount').value;
|
||||||
|
|
||||||
|
if (mode === 'limit' && (!price || isNaN(parseFloat(price)))) {
|
||||||
|
alert('请输入有效委托价格'); return;
|
||||||
|
}
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
alert('请输入数量'); return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch('{% url "core:submit_order" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
symbol: symbol, side: sideParam, trade_type: tradeType, order_type: mode.toUpperCase(),
|
||||||
|
price: mode === 'market' ? null : price, amount: amount, leverage: leverage
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const res = await r.json();
|
||||||
|
if (res.status === 'success') { alert('下单成功!'); location.reload(); }
|
||||||
|
else { alert('失败: ' + res.message); }
|
||||||
|
} catch(e) { alert('提交出错'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closePos(id) {
|
||||||
|
if (confirm('确定平仓?')) {
|
||||||
|
const r = await fetch(`/api/close_position/${id}/`, { method: 'POST', headers: { 'X-CSRFToken': '{{ csrf_token }}' } });
|
||||||
|
const res = await r.json();
|
||||||
|
if (res.status === 'success') location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initTV(); getMarkets(); tick();
|
||||||
|
setInterval(tick, 2000);
|
||||||
|
toggleSide('BUY');
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
37
core/templates/core/mobile/verify.html
Normal file
37
core/templates/core/mobile/verify.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'core:profile' %}" class="text-decoration-none text-secondary small"><i class="bi bi-chevron-left"></i> 返回</a>
|
||||||
|
<h4 class="fw-bold mt-2">实名认证</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-4 text-center">
|
||||||
|
{% if account.kyc_status == 'UNVERIFIED' %}
|
||||||
|
<i class="bi bi-shield-lock fs-1 text-secondary mb-3"></i>
|
||||||
|
<h5 class="fw-bold">未认证</h5>
|
||||||
|
<p class="text-secondary small mb-4">完成实名认证以提高账户额度和安全性</p>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3 text-start">
|
||||||
|
<label class="form-label small text-secondary">真实姓名</label>
|
||||||
|
<input type="text" class="form-control" required placeholder="请输入您的真实姓名">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 text-start">
|
||||||
|
<label class="form-label small text-secondary">身份证号 / 护照号</label>
|
||||||
|
<input type="text" class="form-control" required placeholder="请输入证件号码">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold">提交审核</button>
|
||||||
|
</form>
|
||||||
|
{% elif account.kyc_status == 'PENDING' %}
|
||||||
|
<i class="bi bi-clock-history fs-1 text-warning mb-3"></i>
|
||||||
|
<h5 class="fw-bold">审核中</h5>
|
||||||
|
<p class="text-secondary small">您的认证申请正在处理中,请耐心等待。</p>
|
||||||
|
{% elif account.kyc_status == 'VERIFIED' %}
|
||||||
|
<i class="bi bi-patch-check-fill fs-1 text-success mb-3"></i>
|
||||||
|
<h5 class="fw-bold">已认证</h5>
|
||||||
|
<p class="text-secondary small">您的账户已通过实名认证。</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
40
core/templates/core/mobile/withdraw.html
Normal file
40
core/templates/core/mobile/withdraw.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% extends 'core/mobile/base_mobile.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{% url 'core:profile' %}" class="text-decoration-none text-secondary small"><i class="bi bi-chevron-left"></i> 返回</a>
|
||||||
|
<h4 class="fw-bold mt-2">提现 USDT</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card p-3 mb-3">
|
||||||
|
<div class="d-flex justify-content-between x-small mb-1">
|
||||||
|
<span class="text-secondary">可用余额</span>
|
||||||
|
<span class="fw-bold">{{ account.balance }} USDT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="glass-card p-3">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">提币网络</label>
|
||||||
|
<select class="form-select form-select-sm bg-dark text-white border-secondary">
|
||||||
|
<option>TRC20</option>
|
||||||
|
<option>ERC20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">提币地址</label>
|
||||||
|
<input type="text" name="address" class="form-control" placeholder="请输入收币地址" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-secondary">提币数量</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" name="amount" class="form-control" placeholder="最小提币 10" required>
|
||||||
|
<button type="button" class="btn btn-outline-warning x-small" onclick="document.getElementsByName('amount')[0].value={{ account.balance }}">全部</button>
|
||||||
|
</div>
|
||||||
|
<div class="x-small text-secondary mt-1">手续费: 1 USDT</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold">立即提现</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid px-2 py-2" style="background-color: #0b0e11; min-height: 90vh;">
|
<div class="container-fluid px-2 py-2" style="background-color: #0b0e11; min-height: 90vh;">
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<!-- Left: Coin List Sidebar -->
|
<!-- Left: Coin List Sidebar (Desktop Only) -->
|
||||||
<div class="col-lg-2 d-none d-lg-block">
|
<div class="col-lg-2 d-none d-lg-block">
|
||||||
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
<div class="p-2 mb-2">
|
<div class="p-2 mb-2">
|
||||||
@ -23,7 +23,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="left-coin-list">
|
<tbody id="left-coin-list">
|
||||||
<!-- Populated by JS -->
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +39,6 @@
|
|||||||
<span id="current-symbol-display">{{ symbol }}</span> <i class="bi bi-caret-down-fill small"></i>
|
<span id="current-symbol-display">{{ symbol }}</span> <i class="bi bi-caret-down-fill small"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-dark shadow-lg" id="mobile-coin-list" style="max-height: 400px; overflow-y: auto; width: 250px;">
|
<ul class="dropdown-menu dropdown-menu-dark shadow-lg" id="mobile-coin-list" style="max-height: 400px; overflow-y: auto; width: 250px;">
|
||||||
<!-- Populated by JS -->
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="me-4">
|
<div class="me-4">
|
||||||
@ -82,7 +80,7 @@
|
|||||||
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
||||||
<input type="text" id="buy-price" class="form-control bg-transparent text-white border-secondary" placeholder="0.00">
|
<input type="text" id="buy-price" class="form-control bg-transparent text-white border-secondary" placeholder="请输入价格">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -117,7 +115,7 @@
|
|||||||
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
||||||
<input type="text" id="sell-price" class="form-control bg-transparent text-white border-secondary" placeholder="0.00">
|
<input type="text" id="sell-price" class="form-control bg-transparent text-white border-secondary" placeholder="请输入价格">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -164,7 +162,7 @@
|
|||||||
<div class="col-md-6 border-end border-secondary">
|
<div class="col-md-6 border-end border-secondary">
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">价格</span>
|
||||||
<input type="text" id="c-price" class="form-control bg-transparent text-white border-secondary" placeholder="0.00">
|
<input type="text" id="c-price" class="form-control bg-transparent text-white border-secondary" placeholder="请输入价格">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
@ -261,8 +259,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Order Book -->
|
<!-- Right: Order Book (Desktop Only) -->
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3 d-none d-lg-block">
|
||||||
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
<div class="d-flex justify-content-between text-secondary x-small mb-2 px-2">
|
<div class="d-flex justify-content-between text-secondary x-small mb-2 px-2">
|
||||||
<span>价格(USDT)</span>
|
<span>价格(USDT)</span>
|
||||||
@ -316,9 +314,9 @@
|
|||||||
|
|
||||||
async function getMarkets() {
|
async function getMarkets() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
|
const r = await fetch('/api/market_data/');
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
allCoins = data.filter(c => c.symbol.endsWith('USDT')).slice(0, 100);
|
allCoins = data;
|
||||||
renderCoins();
|
renderCoins();
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
@ -326,21 +324,22 @@
|
|||||||
function renderCoins(filter = '') {
|
function renderCoins(filter = '') {
|
||||||
const list = document.getElementById('left-coin-list');
|
const list = document.getElementById('left-coin-list');
|
||||||
const mobileList = document.getElementById('mobile-coin-list');
|
const mobileList = document.getElementById('mobile-coin-list');
|
||||||
list.innerHTML = ''; mobileList.innerHTML = '';
|
if (list) list.innerHTML = '';
|
||||||
|
if (mobileList) mobileList.innerHTML = '';
|
||||||
|
|
||||||
allCoins.forEach(c => {
|
allCoins.forEach(c => {
|
||||||
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
|
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
|
||||||
const base = c.symbol.replace('USDT', '');
|
const base = c.symbol.replace('USDT', '');
|
||||||
const chg = parseFloat(c.priceChangePercent);
|
const chg = parseFloat(c.change);
|
||||||
const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${base.toLowerCase()}.png`;
|
const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${base.toLowerCase()}.png`;
|
||||||
const row = `
|
const row = `
|
||||||
<tr style="cursor: pointer;" onclick="location.href='?symbol=${c.symbol}'">
|
<tr style="cursor: pointer;" onclick="location.href='?symbol=${c.symbol}'">
|
||||||
<td><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</td>
|
<td><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</td>
|
||||||
<td class="text-end fw-bold">${parseFloat(c.lastPrice).toLocaleString()}</td>
|
<td class="text-end fw-bold">${parseFloat(c.price).toLocaleString()}</td>
|
||||||
<td class="text-end ${chg>=0?'text-success':'text-danger'}">${chg>=0?'+':''}${chg.toFixed(2)}%</td>
|
<td class="text-end ${chg>=0?'text-success':'text-danger'}">${chg>=0?'+':''}${chg.toFixed(2)}%</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
list.innerHTML += row;
|
if (list) list.innerHTML += row;
|
||||||
mobileList.innerHTML += `<li><a class="dropdown-item d-flex justify-content-between" href="?symbol=${c.symbol}"><span><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</span><span class="${chg>=0?'text-success':'text-danger'}">${parseFloat(c.lastPrice).toLocaleString()}</span></a></li>`;
|
if (mobileList) mobileList.innerHTML += `<li><a class="dropdown-item d-flex justify-content-between" href="?symbol=${c.symbol}"><span><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</span><span class="${chg>=0?'text-success':'text-danger'}">${parseFloat(c.price).toLocaleString()}</span></a></li>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,42 +348,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleMode(side, mode) {
|
function toggleMode(side, mode) {
|
||||||
|
let pInput;
|
||||||
if (side === 'buy') {
|
if (side === 'buy') {
|
||||||
const pInput = document.getElementById('buy-price');
|
pInput = document.getElementById('buy-price');
|
||||||
const label = document.getElementById('buy-label');
|
const label = document.getElementById('buy-label');
|
||||||
const unit = document.getElementById('buy-unit');
|
const unit = document.getElementById('buy-unit');
|
||||||
if (mode === 'market') {
|
if (mode === 'market') {
|
||||||
pInput.value = '市价价格';
|
pInput.value = currentPrice || '市价';
|
||||||
pInput.disabled = true;
|
pInput.disabled = true;
|
||||||
pInput.style.color = '#f0b90b';
|
pInput.style.color = '#f0b90b';
|
||||||
label.textContent = '成交额';
|
label.textContent = '成交额';
|
||||||
unit.textContent = 'USDT';
|
unit.textContent = 'USDT';
|
||||||
} else {
|
} else {
|
||||||
pInput.value = currentPrice || '';
|
pInput.value = '';
|
||||||
pInput.disabled = false;
|
pInput.disabled = false;
|
||||||
pInput.style.color = 'white';
|
pInput.style.color = 'white';
|
||||||
label.textContent = '数量';
|
label.textContent = '数量';
|
||||||
unit.textContent = baseAsset;
|
unit.textContent = baseAsset;
|
||||||
}
|
}
|
||||||
} else if (side === 'sell') {
|
} else if (side === 'sell') {
|
||||||
const pInput = document.getElementById('sell-price');
|
pInput = document.getElementById('sell-price');
|
||||||
if (mode === 'market') {
|
if (mode === 'market') {
|
||||||
pInput.value = '市价价格';
|
pInput.value = currentPrice || '市价';
|
||||||
pInput.disabled = true;
|
pInput.disabled = true;
|
||||||
pInput.style.color = '#f0b90b';
|
pInput.style.color = '#f0b90b';
|
||||||
} else {
|
} else {
|
||||||
pInput.value = currentPrice || '';
|
pInput.value = '';
|
||||||
pInput.disabled = false;
|
pInput.disabled = false;
|
||||||
pInput.style.color = 'white';
|
pInput.style.color = 'white';
|
||||||
}
|
}
|
||||||
} else if (side === 'contract') {
|
} else if (side === 'contract') {
|
||||||
const pInput = document.getElementById('c-price');
|
pInput = document.getElementById('c-price');
|
||||||
if (mode === 'market') {
|
if (mode === 'market') {
|
||||||
pInput.value = '市价价格';
|
pInput.value = currentPrice || '市价';
|
||||||
pInput.disabled = true;
|
pInput.disabled = true;
|
||||||
pInput.style.color = '#f0b90b';
|
pInput.style.color = '#f0b90b';
|
||||||
} else {
|
} else {
|
||||||
pInput.value = currentPrice || '';
|
pInput.value = '';
|
||||||
pInput.disabled = false;
|
pInput.disabled = false;
|
||||||
pInput.style.color = 'white';
|
pInput.style.color = 'white';
|
||||||
}
|
}
|
||||||
@ -398,15 +398,18 @@
|
|||||||
if (mode === 'market') {
|
if (mode === 'market') {
|
||||||
document.getElementById('buy-amount').value = (balance * (val / 100)).toFixed(2);
|
document.getElementById('buy-amount').value = (balance * (val / 100)).toFixed(2);
|
||||||
} else {
|
} else {
|
||||||
const price = parseFloat(document.getElementById('buy-price').value);
|
const price = parseFloat(document.getElementById('buy-price').value) || currentPrice;
|
||||||
if (price > 0) document.getElementById('buy-amount').value = (balance * (val / 100) / price).toFixed(4);
|
if (price > 0) document.getElementById('buy-amount').value = (balance * (val / 100) / price).toFixed(4);
|
||||||
|
else document.getElementById('buy-amount').value = 0;
|
||||||
}
|
}
|
||||||
} else if (side === 'sell') {
|
} else if (side === 'sell') {
|
||||||
document.getElementById('sell-amount').value = (assetBalance * (val / 100)).toFixed(4);
|
document.getElementById('sell-amount').value = (assetBalance * (val / 100)).toFixed(4);
|
||||||
} else if (side === 'contract') {
|
} else if (side === 'contract') {
|
||||||
const margin = (balance * (val / 100));
|
const maxLots = (balance * leverage) / 100;
|
||||||
|
const lots = Math.floor(maxLots * (val / 100));
|
||||||
|
document.getElementById('c-amount').value = lots;
|
||||||
|
const margin = (100 * lots) / leverage;
|
||||||
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
|
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
|
||||||
document.getElementById('c-amount').value = Math.floor((margin * leverage) / 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,18 +421,19 @@
|
|||||||
if (mode === 'market') {
|
if (mode === 'market') {
|
||||||
percent = (amount / balance) * 100;
|
percent = (amount / balance) * 100;
|
||||||
} else {
|
} else {
|
||||||
const price = parseFloat(document.getElementById('buy-price').value) || 0;
|
const price = parseFloat(document.getElementById('buy-price').value) || currentPrice;
|
||||||
percent = (amount * price / balance) * 100;
|
if (price > 0) percent = (amount * price / balance) * 100;
|
||||||
}
|
}
|
||||||
document.getElementById('buy-slider').value = Math.min(100, percent);
|
document.getElementById('buy-slider').value = Math.min(100, percent || 0);
|
||||||
} else if (side === 'sell') {
|
} else if (side === 'sell') {
|
||||||
const amount = parseFloat(document.getElementById('sell-amount').value) || 0;
|
const amount = parseFloat(document.getElementById('sell-amount').value) || 0;
|
||||||
document.getElementById('sell-slider').value = Math.min(100, (amount / assetBalance) * 100);
|
if (assetBalance > 0) document.getElementById('sell-slider').value = Math.min(100, (amount / assetBalance) * 100);
|
||||||
} else if (side === 'contract') {
|
} else if (side === 'contract') {
|
||||||
const lots = parseFloat(document.getElementById('c-amount').value) || 0;
|
const lots = parseFloat(document.getElementById('c-amount').value) || 0;
|
||||||
|
const maxLots = (balance * leverage) / 100;
|
||||||
|
if (maxLots > 0) document.getElementById('c-slider').value = Math.min(100, (lots / maxLots) * 100);
|
||||||
const margin = (100 * lots) / leverage;
|
const margin = (100 * lots) / leverage;
|
||||||
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
|
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
|
||||||
document.getElementById('c-slider').value = Math.min(100, (margin / balance) * 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,31 +446,35 @@
|
|||||||
|
|
||||||
async function tick() {
|
async function tick() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr?symbol=' + symbol);
|
const r = await fetch('/api/market_data/');
|
||||||
const d = await r.json();
|
const data = await r.json();
|
||||||
currentPrice = parseFloat(d.lastPrice);
|
const d = data.find(c => c.symbol === symbol);
|
||||||
|
if (!d) return;
|
||||||
|
|
||||||
|
currentPrice = parseFloat(d.price);
|
||||||
|
|
||||||
document.getElementById('header-price').textContent = currentPrice.toLocaleString();
|
document.getElementById('header-price').textContent = currentPrice.toLocaleString();
|
||||||
document.getElementById('header-price-usd').textContent = currentPrice.toLocaleString();
|
document.getElementById('header-price-usd').textContent = currentPrice.toLocaleString();
|
||||||
document.getElementById('current-price-book').textContent = currentPrice.toLocaleString();
|
const cpBook = document.getElementById('current-price-book');
|
||||||
|
if (cpBook) cpBook.textContent = currentPrice.toLocaleString();
|
||||||
|
|
||||||
const chg = parseFloat(d.priceChangePercent);
|
// Update market price in box ONLY if disabled (Market Order)
|
||||||
|
['buy-price', 'sell-price', 'c-price'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el && el.disabled) el.value = currentPrice;
|
||||||
|
});
|
||||||
|
|
||||||
|
const chg = parseFloat(d.change);
|
||||||
document.getElementById('header-change').textContent = (chg>=0?'+':'') + chg.toFixed(2) + '%';
|
document.getElementById('header-change').textContent = (chg>=0?'+':'') + chg.toFixed(2) + '%';
|
||||||
document.getElementById('header-change').className = 'fw-bold ' + (chg>=0?'text-success':'text-danger');
|
document.getElementById('header-change').className = 'fw-bold ' + (chg>=0?'text-success':'text-danger');
|
||||||
document.getElementById('header-volume').textContent = (parseFloat(d.quoteVolume)/1000000).toFixed(2) + 'M';
|
|
||||||
|
|
||||||
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
|
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
|
||||||
askD.innerHTML = ''; bidD.innerHTML = '';
|
if (askD && bidD) {
|
||||||
for (let i = 0; i < 10; i++) {
|
askD.innerHTML = ''; bidD.innerHTML = '';
|
||||||
askD.innerHTML = `<div class="order-book-row"><span class="text-danger">${(currentPrice+(10-i)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="ask-bg" style="width:${Math.random()*70}%"></div></div>` + askD.innerHTML;
|
for (let i = 0; i < 10; i++) {
|
||||||
bidD.innerHTML += `<div class="order-book-row"><span class="text-success">${(currentPrice-(i+1)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="bid-bg" style="width:${Math.random()*70}%"></div></div>`;
|
askD.innerHTML = `<div class="order-book-row"><span class="text-danger">${(currentPrice+(10-i)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="ask-bg" style="width:${Math.random()*70}%"></div></div>` + askD.innerHTML;
|
||||||
}
|
bidD.innerHTML += `<div class="order-book-row"><span class="text-success">${(currentPrice-(i+1)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="bid-bg" style="width:${Math.random()*70}%"></div></div>`;
|
||||||
|
}
|
||||||
if (tradeType === 'SPOT') {
|
|
||||||
if (!document.getElementById('buy-price').value && !document.getElementById('buy-price').disabled) document.getElementById('buy-price').value = currentPrice;
|
|
||||||
if (!document.getElementById('sell-price').value && !document.getElementById('sell-price').disabled) document.getElementById('sell-price').value = currentPrice;
|
|
||||||
} else {
|
|
||||||
if (!document.getElementById('c-price').value && !document.getElementById('c-price').disabled) document.getElementById('c-price').value = currentPrice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.current-p-val').forEach(el => { el.textContent = currentPrice.toLocaleString(); });
|
document.querySelectorAll('.current-p-val').forEach(el => { el.textContent = currentPrice.toLocaleString(); });
|
||||||
@ -500,13 +508,22 @@
|
|||||||
amount = document.getElementById('c-amount').value;
|
amount = document.getElementById('c-amount').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode === 'LIMIT' && !price) {
|
||||||
|
alert('请输入委托价格');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
alert('请输入有效数量/手数');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const r = await fetch('{% url "core:submit_order" %}', {
|
const r = await fetch('{% url "core:submit_order" %}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
symbol: symbol, side: side, trade_type: tradeType, order_type: mode,
|
symbol: symbol, side: side, trade_type: tradeType, order_type: mode,
|
||||||
price: price === '市价价格' ? null : price, amount: amount, leverage: leverage
|
price: mode === 'MARKET' ? null : price, amount: amount, leverage: leverage
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const res = await r.json();
|
const res = await r.json();
|
||||||
|
|||||||
@ -12,7 +12,14 @@ import json
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
def is_mobile(request):
|
||||||
|
user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
|
||||||
|
mobile_indicators = ['iphone', 'android', 'phone', 'mobile']
|
||||||
|
return any(indicator in user_agent for indicator in mobile_indicators)
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
|
if is_mobile(request):
|
||||||
|
return render(request, 'core/mobile/index.html')
|
||||||
return render(request, 'core/index.html')
|
return render(request, 'core/index.html')
|
||||||
|
|
||||||
def trade(request, trade_type='spot'):
|
def trade(request, trade_type='spot'):
|
||||||
@ -39,11 +46,14 @@ def trade(request, trade_type='spot'):
|
|||||||
'assets': assets,
|
'assets': assets,
|
||||||
'base_asset_balance': base_asset_balance,
|
'base_asset_balance': base_asset_balance,
|
||||||
}
|
}
|
||||||
return render(request, 'core/trade.html', context)
|
|
||||||
|
template = 'core/mobile/trade.html' if is_mobile(request) else 'core/trade.html'
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
def market_center(request):
|
def market_center(request):
|
||||||
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
||||||
return render(request, 'core/market_center.html', {'cryptos': cryptos})
|
template = 'core/mobile/market_center.html' if is_mobile(request) else 'core/market_center.html'
|
||||||
|
return render(request, template, {'cryptos': cryptos})
|
||||||
|
|
||||||
def placeholder_view(request, title):
|
def placeholder_view(request, title):
|
||||||
settings = SiteSettings.objects.first()
|
settings = SiteSettings.objects.first()
|
||||||
@ -85,7 +95,8 @@ def profile(request):
|
|||||||
'recent_orders': recent_orders,
|
'recent_orders': recent_orders,
|
||||||
'positions': positions,
|
'positions': positions,
|
||||||
}
|
}
|
||||||
return render(request, 'core/profile.html', context)
|
template = 'core/mobile/profile.html' if is_mobile(request) else 'core/profile.html'
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def deposit(request):
|
def deposit(request):
|
||||||
@ -102,7 +113,8 @@ def deposit(request):
|
|||||||
tx_hash=tx_id
|
tx_hash=tx_id
|
||||||
)
|
)
|
||||||
return redirect('core:profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/deposit.html')
|
template = 'core/mobile/deposit.html' if is_mobile(request) else 'core/deposit.html'
|
||||||
|
return render(request, template)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def withdraw(request):
|
def withdraw(request):
|
||||||
@ -123,7 +135,8 @@ def withdraw(request):
|
|||||||
tx_hash=f"wd_{random.randint(1000, 9999)}"
|
tx_hash=f"wd_{random.randint(1000, 9999)}"
|
||||||
)
|
)
|
||||||
return redirect('core:profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/withdraw.html', {'account': account})
|
template = 'core/mobile/withdraw.html' if is_mobile(request) else 'core/withdraw.html'
|
||||||
|
return render(request, template, {'account': account})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def verify(request):
|
def verify(request):
|
||||||
@ -132,7 +145,8 @@ def verify(request):
|
|||||||
account.kyc_status = 'pending'
|
account.kyc_status = 'pending'
|
||||||
account.save()
|
account.save()
|
||||||
return redirect('core:profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/verify.html', {'account': account})
|
template = 'core/mobile/verify.html' if is_mobile(request) else 'core/verify.html'
|
||||||
|
return render(request, template, {'account': account})
|
||||||
|
|
||||||
def register_view(request):
|
def register_view(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -157,7 +171,8 @@ def register_view(request):
|
|||||||
captcha_text, captcha_result = generate_captcha()
|
captcha_text, captcha_result = generate_captcha()
|
||||||
request.session['captcha_result'] = captcha_result
|
request.session['captcha_result'] = captcha_result
|
||||||
|
|
||||||
return render(request, 'core/register.html', {
|
template = 'core/mobile/register.html' if is_mobile(request) else 'core/register.html'
|
||||||
|
return render(request, template, {
|
||||||
'captcha_text': captcha_text
|
'captcha_text': captcha_text
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -179,7 +194,8 @@ def login_view(request):
|
|||||||
return redirect('core:index')
|
return redirect('core:index')
|
||||||
else:
|
else:
|
||||||
form = AuthenticationForm()
|
form = AuthenticationForm()
|
||||||
return render(request, 'core/login.html', {'form': form})
|
template = 'core/mobile/login.html' if is_mobile(request) else 'core/login.html'
|
||||||
|
return render(request, template, {'form': form})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@ -200,10 +216,14 @@ def submit_order(request):
|
|||||||
account = request.user.account
|
account = request.user.account
|
||||||
base_symbol = symbol.replace('USDT', '')
|
base_symbol = symbol.replace('USDT', '')
|
||||||
|
|
||||||
# Get current market price for validations and market orders
|
# Get price (consider manual override)
|
||||||
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
|
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
|
||||||
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
|
settings = SiteSettings.objects.first()
|
||||||
|
|
||||||
|
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
|
||||||
|
if settings and settings.is_pinning_active and crypto and crypto.manual_price:
|
||||||
|
current_price = crypto.manual_price
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if trade_type == 'SPOT':
|
if trade_type == 'SPOT':
|
||||||
if order_type == 'MARKET':
|
if order_type == 'MARKET':
|
||||||
@ -325,17 +345,36 @@ def close_position(request, position_id):
|
|||||||
|
|
||||||
base_symbol = position.symbol.replace('USDT', '')
|
base_symbol = position.symbol.replace('USDT', '')
|
||||||
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
|
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
|
||||||
|
settings = SiteSettings.objects.first()
|
||||||
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
|
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
|
||||||
|
if settings and settings.is_pinning_active and crypto and crypto.manual_price:
|
||||||
|
current_price = crypto.manual_price
|
||||||
|
|
||||||
|
account = request.user.account
|
||||||
face_value = decimal.Decimal('100')
|
face_value = decimal.Decimal('100')
|
||||||
|
|
||||||
|
# Win/Loss Control Logic
|
||||||
|
# 100 = Must Win, -100 = Must Loss
|
||||||
|
forced_upl = None
|
||||||
|
if account.win_loss_control != 0:
|
||||||
|
# Force a small profit or loss
|
||||||
|
total_value = position.lots * face_value
|
||||||
|
if account.win_loss_control > 0: # Force Profit
|
||||||
|
forced_upl = total_value * decimal.Decimal('0.05') # 5% profit
|
||||||
|
else: # Force Loss
|
||||||
|
forced_upl = -total_value * decimal.Decimal('0.05') # 5% loss
|
||||||
|
|
||||||
# Calculate Unrealized P&L
|
# Calculate Unrealized P&L
|
||||||
if position.side == 'LONG':
|
if position.side == 'LONG':
|
||||||
upl = (current_price - position.entry_price) / position.entry_price * (position.lots * face_value)
|
upl = (current_price - position.entry_price) / position.entry_price * (position.lots * face_value)
|
||||||
else:
|
else:
|
||||||
upl = (position.entry_price - current_price) / position.entry_price * (position.lots * face_value)
|
upl = (position.entry_price - current_price) / position.entry_price * (position.lots * face_value)
|
||||||
|
|
||||||
|
# If control is active, override upl
|
||||||
|
if forced_upl is not None:
|
||||||
|
upl = forced_upl
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
account = request.user.account
|
|
||||||
# Settlement: Margin + P&L - Fee (0.05%)
|
# Settlement: Margin + P&L - Fee (0.05%)
|
||||||
fee = (position.lots * face_value) * decimal.Decimal('0.0005')
|
fee = (position.lots * face_value) * decimal.Decimal('0.0005')
|
||||||
account.balance += (position.margin + upl - fee)
|
account.balance += (position.margin + upl - fee)
|
||||||
@ -353,12 +392,19 @@ def close_position(request, position_id):
|
|||||||
return JsonResponse({'status': 'success'})
|
return JsonResponse({'status': 'success'})
|
||||||
|
|
||||||
def market_data(request):
|
def market_data(request):
|
||||||
|
settings = SiteSettings.objects.first()
|
||||||
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
||||||
data = []
|
data = []
|
||||||
for c in cryptos:
|
for c in cryptos:
|
||||||
|
price = float(c.current_price)
|
||||||
|
if settings and settings.is_pinning_active and c.manual_price:
|
||||||
|
price = float(c.manual_price)
|
||||||
|
|
||||||
|
symbol_display = c.symbol if 'USDT' in c.symbol else f"{c.symbol}USDT"
|
||||||
|
|
||||||
data.append({
|
data.append({
|
||||||
'symbol': c.symbol,
|
'symbol': symbol_display,
|
||||||
'price': float(c.current_price),
|
'price': price,
|
||||||
'change': float(c.change_24h)
|
'change': float(c.change_24h)
|
||||||
})
|
})
|
||||||
return JsonResponse(data, safe=False)
|
return JsonResponse(data, safe=False)
|
||||||
Loading…
x
Reference in New Issue
Block a user