256 lines
12 KiB
HTML
256 lines
12 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}订阅中心 — AlphaX{% endblock %}
|
||
{% block nav_links %}
|
||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||
<a class="sidebar-link" href="/watchlist"><svg class="link-icon"><use href="#svg-star"/></svg>关注</a>
|
||
<a class="sidebar-link" href="/strategy"><svg class="link-icon"><use href="#svg-target"/></svg>策略</a>
|
||
<a class="sidebar-link" href="/iteration"><svg class="link-icon"><use href="#svg-iterate"/></svg>迭代</a>
|
||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||
<a class="sidebar-link active" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
||
<a class="sidebar-link" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>推荐</a>
|
||
<a class="sidebar-link admin-link" href="/admin.html" style="display:none"><svg class="link-icon"><use href="#svg-admin"/></svg>管理</a>
|
||
{% endblock %}
|
||
{% block extra_head_css %}
|
||
<style>
|
||
|
||
.shell { position: relative; z-index: 1; width: min(100% - 64px, 900px); margin: 0 auto; padding: 48px 0 64px; }
|
||
.shell h1 { font-size: 32px; font-weight: 500; letter-spacing: -1px; margin-bottom: 8px; }
|
||
.shell .sub { color: var(--slate); font-size: 15px; margin-bottom: 32px; }
|
||
|
||
/* Status bar */
|
||
.status-bar {
|
||
border: 1px solid var(--hairline-soft); background: var(--canvas);
|
||
border-radius: var(--radius-lg); padding: 16px 20px; margin-bottom: 32px;
|
||
display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
|
||
}
|
||
.status-bar .status-item { font-size: 13px; color: var(--slate); }
|
||
.status-bar .status-item strong { color: var(--ink); }
|
||
.guide-box {
|
||
border: 1px solid rgba(66,98,255,.16);
|
||
background: linear-gradient(135deg, rgba(66,98,255,.08), rgba(255,208,47,.16));
|
||
border-radius: var(--radius-xl); padding: 18px 20px; margin-bottom: 22px;
|
||
display: none;
|
||
}
|
||
.guide-box.show { display: block; }
|
||
.guide-title { font-size: 16px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
|
||
.guide-text { font-size: 14px; color: var(--slate); line-height: 1.7; }
|
||
|
||
/* Plans grid */
|
||
.plans { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
||
.plan {
|
||
border-radius: var(--radius-xl); padding: 26px; border: 1px solid var(--hairline);
|
||
background: var(--canvas); display: flex; flex-direction: column; gap: 16px;
|
||
transition: transform .15s, border-color .15s, box-shadow .15s;
|
||
min-height: 230px;
|
||
}
|
||
.plan:hover { transform: translateY(-2px); border-color: var(--hairline-strong); box-shadow: 0 8px 22px rgba(5,0,56,.05); }
|
||
.plan.featured { border: 2px solid var(--blue); }
|
||
.plan-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
|
||
.plan h3 { font-size: 20px; font-weight: 600; letter-spacing: -.3px; }
|
||
.plan .price { font-size: 34px; font-weight: 700; line-height: 1.15; margin-top: 2px; }
|
||
.plan .price unit { font-size: 14px; font-weight: 500; color: var(--slate); }
|
||
.price-pending { display: inline-flex; align-items: center; min-height: 39px; color: var(--stone); font-size: 17px; font-weight: 600; letter-spacing: -.2px; }
|
||
.plan .desc { font-size: 13px; color: var(--slate); line-height: 1.5; min-height: 40px; }
|
||
.plan-spacer { flex: 1; }
|
||
|
||
/* Plan button */
|
||
.btn-plan {
|
||
display: flex; align-items: center; justify-content: center;
|
||
width: 100%; height: 44px; border: 0; border-radius: var(--radius-full);
|
||
font-weight: 500; font-size: 14px; cursor: pointer;
|
||
transition: background .15s, transform .15s;
|
||
}
|
||
.btn-plan.green { background: var(--green); color: #fff; }
|
||
.btn-plan.green:hover { opacity: .9; }
|
||
.btn-plan.disabled { background: var(--hairline-soft); color: var(--muted); cursor: not-allowed; }
|
||
|
||
.badge { display: inline-flex; align-items: center; font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: var(--radius-full); }
|
||
.badge.yellow { background: var(--yellow); color: var(--primary); }
|
||
.badge.gray { background: var(--surface); color: var(--stone); }
|
||
.badge.blue { background: rgba(66,98,255,.1); color: var(--blue); }
|
||
|
||
.msg { font-size: 13px; min-height: 20px; margin-top: 8px; }
|
||
.msg.ok { color: var(--green); } .msg.err { color: #e53e3e; }
|
||
|
||
@media (max-width: 700px) {
|
||
.plans { grid-template-columns: 1fr; }
|
||
.shell { width: min(100% - 32px, 900px); padding: 24px 0 48px; }
|
||
.status-bar { flex-direction: column; gap: 8px; align-items: flex-start; }
|
||
.shell h1 { font-size: 24px; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
<div class="shell">
|
||
<h1>订阅中心</h1>
|
||
<p class="sub">新用户可免费体验 1 个月,后续按需选择方案。</p>
|
||
|
||
<div id="guideBox" class="guide-box">
|
||
<div class="guide-title" id="guideTitle">先开通套餐,开始使用 AlphaX</div>
|
||
<div class="guide-text" id="guideText">新用户可领取 30 天免费体验。开通后即可进入看板、策略、迭代和舆情页面。</div>
|
||
</div>
|
||
|
||
<div class="status-bar" id="statusBar" style="display:none">
|
||
<div class="status-item">当前方案: <strong id="mePlan">--</strong></div>
|
||
<div class="status-item">到期时间: <strong id="meEnd">--</strong></div>
|
||
</div>
|
||
|
||
<div class="plans">
|
||
<div class="plan featured">
|
||
<div class="plan-top"><h3>免费体验</h3><span class="badge yellow">推荐</span></div>
|
||
<div class="price"><unit>$</unit>0</div>
|
||
<p class="desc">新用户可领取 30 天 Beta 体验。</p>
|
||
<div id="trialMsg" class="msg"></div>
|
||
<div class="plan-spacer"></div>
|
||
<button class="btn-plan green" id="freeBtn" onclick="claimFreeTrial()">立即开通</button>
|
||
</div>
|
||
|
||
<div class="plan">
|
||
<div class="plan-top"><h3>月付</h3><span class="badge gray">即将上线</span></div>
|
||
<div class="price"><span class="price-pending">价格开放前公布</span></div>
|
||
<p class="desc">适合短期体验和灵活使用。</p>
|
||
<div class="plan-spacer"></div>
|
||
<button class="btn-plan disabled">即将上线</button>
|
||
</div>
|
||
|
||
<div class="plan">
|
||
<div class="plan-top"><h3>季付</h3><span class="badge gray">即将上线</span></div>
|
||
<div class="price"><span class="price-pending">价格开放前公布</span></div>
|
||
<p class="desc">适合持续跟踪市场机会。</p>
|
||
<div class="plan-spacer"></div>
|
||
<button class="btn-plan disabled">即将上线</button>
|
||
</div>
|
||
|
||
<div class="plan">
|
||
<div class="plan-top"><h3>年付</h3><span class="badge gray">即将上线</span></div>
|
||
<div class="price"><span class="price-pending">价格开放前公布</span></div>
|
||
<p class="desc">适合长期使用和策略复盘。</p>
|
||
<div class="plan-spacer"></div>
|
||
<button class="btn-plan disabled">即将上线</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
{% block extra_script %}
|
||
<script>
|
||
var $ = function(id){ return document.getElementById(id); };
|
||
|
||
// ====== USER ======
|
||
var currentUser = null;
|
||
function readablePlanName(code) {
|
||
var names = {
|
||
free_trial_1m: '免费体验',
|
||
monthly_usdt: '月付',
|
||
quarterly_usdt: '季付',
|
||
yearly_usdt: '年付'
|
||
};
|
||
return names[code] || code || '未开通';
|
||
}
|
||
|
||
async function loadUser() {
|
||
try {
|
||
var resp = await fetch('/api/auth/me');
|
||
if (!resp.ok) return;
|
||
var data = await resp.json();
|
||
currentUser = data.user;
|
||
var email = currentUser.email || '--';
|
||
$('userInitial').textContent = email.charAt(0).toUpperCase();
|
||
$('userEmailShort').textContent = email.length > 14 ? email.slice(0,12) + '…' : email;
|
||
$('ddEmail').textContent = email;
|
||
} catch(e) {}
|
||
}
|
||
|
||
function toggleUserMenu() {
|
||
$('userDropdown').classList.toggle('show');
|
||
}
|
||
document.addEventListener('click', function(e) {
|
||
if (!e.target.closest('.sidebar-user') && !e.target.closest('.user-dropdown')) $('userDropdown').classList.remove('show');
|
||
});
|
||
|
||
function showChangePwd() {
|
||
$('userDropdown').classList.remove('show');
|
||
$('pwdModal').classList.add('show');
|
||
$('oldPwd').value = ''; $('newPwd').value = ''; $('cfmPwd').value = ''; $('pwdMsg').textContent = '';
|
||
}
|
||
function closePwdModal() { $('pwdModal').classList.remove('show'); }
|
||
|
||
async function changePwd() {
|
||
var old = $('oldPwd').value, nw = $('newPwd').value, cf = $('cfmPwd').value;
|
||
if (!old || !nw) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '请填写所有字段'; return; }
|
||
if (nw.length < 8) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '新密码至少 8 位'; return; }
|
||
if (nw !== cf) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = '两次密码不一致'; return; }
|
||
try {
|
||
var r = await fetch('/api/auth/change-password', {
|
||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({old_password: old, new_password: nw})
|
||
});
|
||
var d = await r.json();
|
||
if (!r.ok) throw new Error(d.detail || '修改失败');
|
||
$('pwdMsg').className = 'modal-msg ok'; $('pwdMsg').textContent = d.message || '修改成功';
|
||
setTimeout(closePwdModal, 1200);
|
||
} catch(e) { $('pwdMsg').className = 'modal-msg err'; $('pwdMsg').textContent = e.message; }
|
||
}
|
||
|
||
async function doLogout() {
|
||
await fetch('/api/auth/logout', { method: 'POST' });
|
||
window.location.href = '/auth';
|
||
}
|
||
|
||
// ====== SUBSCRIPTION ======
|
||
async function post(url, body) {
|
||
var r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
|
||
var data = await r.json().catch(function(){ return { detail: '请求失败' }; });
|
||
if (!r.ok) throw new Error(data.detail || '请求失败');
|
||
return data;
|
||
}
|
||
|
||
async function loadMe() {
|
||
try {
|
||
var r = await fetch('/api/auth/me');
|
||
if (!r.ok) { window.location.href = '/auth'; return; }
|
||
var data = await r.json();
|
||
var s = data.subscription;
|
||
var params = new URLSearchParams(location.search);
|
||
if (!s || params.get('welcome') === '1' || params.get('expired') === '1') {
|
||
document.getElementById('guideBox').classList.add('show');
|
||
if (params.get('expired') === '1') {
|
||
document.getElementById('guideTitle').textContent = '订阅已到期,请先续订';
|
||
document.getElementById('guideText').textContent = '当前账号没有有效订阅。续订或开通套餐后,才能继续访问看板、策略、迭代和舆情页面。';
|
||
} else if (!s) {
|
||
document.getElementById('guideTitle').textContent = '欢迎使用 AlphaX,请先开通套餐';
|
||
document.getElementById('guideText').textContent = '新用户可领取 30 天免费体验。开通后即可进入完整功能页面。';
|
||
}
|
||
}
|
||
document.getElementById('statusBar').style.display = 'flex';
|
||
document.getElementById('mePlan').textContent = s ? readablePlanName(s.plan_code) : '未开通';
|
||
document.getElementById('meEnd').textContent = s ? String(s.end_at).slice(0, 10) : '--';
|
||
if (s) {
|
||
document.getElementById('freeBtn').textContent = '已开通';
|
||
document.getElementById('freeBtn').className = 'btn-plan disabled';
|
||
document.getElementById('freeBtn').onclick = null;
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
async function claimFreeTrial() {
|
||
try {
|
||
var data = await post('/api/subscriptions/free-trial');
|
||
var el = document.getElementById('trialMsg');
|
||
var sub = data.subscription;
|
||
el.className = 'msg ok'; el.textContent = '开通成功!到期: ' + String(sub.end_at).slice(0, 10);
|
||
document.getElementById('freeBtn').textContent = '已开通';
|
||
document.getElementById('freeBtn').className = 'btn-plan disabled';
|
||
document.getElementById('freeBtn').onclick = null;
|
||
document.getElementById('mePlan').textContent = readablePlanName(sub.plan_code);
|
||
document.getElementById('meEnd').textContent = String(sub.end_at).slice(0, 10);
|
||
setTimeout(function(){ window.location.href = '/app'; }, 900);
|
||
} catch (e) {
|
||
var el = document.getElementById('trialMsg');
|
||
el.className = 'msg err'; el.textContent = e.message;
|
||
}
|
||
}
|
||
|
||
loadUser();
|
||
loadMe();
|
||
</script>
|
||
{% endblock %} |