- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
132 lines
6.3 KiB
PHP
132 lines
6.3 KiB
PHP
<?php use App\Core\I18n; I18n::init(); $t = function($k,$p=[]){return I18n::t($k,$p);}; ?>
|
|
<!DOCTYPE html><html lang="<?=I18n::getLang()?>">
|
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title><?=$t('follow_plan')?></title>
|
|
<link rel="stylesheet" href="/Static/css/app.css">
|
|
<style>
|
|
.page-header{display:flex;align-items:center;padding:12px 16px;background:#fff;border-bottom:1px solid #F0F0F0;position:sticky;top:0;z-index:10}
|
|
.page-header .back{width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
|
.page-header .title{flex:1;text-align:center;font-size:17px;font-weight:600;color:#333}
|
|
.page-header .balance{font-size:13px;color:#1E90FF;display:flex;align-items:center;gap:4px}
|
|
.game-select{background:#fff;padding:12px 16px;border-bottom:1px solid #F0F0F0}
|
|
.game-select select{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:8px;font-size:14px;color:#333;background:#F9F9F9}
|
|
.stats-cards{display:flex;gap:12px;padding:16px;background:#fff;margin-top:1px}
|
|
.stat-card{flex:1;background:linear-gradient(135deg,#E8F4FF,#D0E8FF);border-radius:12px;padding:16px;text-align:center}
|
|
.stat-card .label{font-size:12px;color:#1E90FF;margin-bottom:6px}
|
|
.stat-card .value{font-size:22px;font-weight:700;color:#333}
|
|
.plan-card{background:#fff;margin:8px 16px;border-radius:12px;padding:16px;box-shadow:0 2px 8px rgba(0,0,0,.06)}
|
|
.plan-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
|
.plan-name{font-size:15px;font-weight:600;color:#333}
|
|
.plan-type-badge{padding:2px 10px;border-radius:12px;font-size:11px;font-weight:600;color:#fff;background:#1E90FF}
|
|
.plan-stats{display:flex;gap:16px;margin-bottom:12px}
|
|
.plan-stat{text-align:center;flex:1}
|
|
.plan-stat .ps-label{font-size:11px;color:#999;margin-bottom:2px}
|
|
.plan-stat .ps-value{font-size:16px;font-weight:700}
|
|
.plan-stat .ps-value.win{color:#4CAF50}
|
|
.plan-stat .ps-value.lose{color:#FF4444}
|
|
.plan-recent{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:12px}
|
|
.plan-dot{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#fff}
|
|
.plan-dot.w{background:#4CAF50}
|
|
.plan-dot.l{background:#FF4444}
|
|
.plan-actions{display:flex;gap:8px}
|
|
.follow-btn{flex:1;padding:10px;border-radius:8px;text-align:center;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;border:none}
|
|
.follow-btn.active{background:#FF4444;color:#fff}
|
|
.follow-btn.inactive{background:linear-gradient(135deg,#1E90FF,#0066CC);color:#fff}
|
|
.follow-btn:active{transform:scale(.98)}
|
|
.empty-plans{text-align:center;padding:60px 20px}
|
|
.empty-plans .icon{font-size:48px;margin-bottom:16px;opacity:.3}
|
|
.empty-plans .text{font-size:14px;color:#999}
|
|
.plan-type-select{background:#fff;padding:12px 16px;border-bottom:1px solid #F0F0F0}
|
|
.type-tabs{display:flex;gap:8px;flex-wrap:wrap}
|
|
.type-tab{padding:6px 16px;border-radius:20px;font-size:13px;color:#666;background:#F0F0F0;cursor:pointer;border:none}
|
|
.type-tab.active{background:#1E90FF;color:#fff}
|
|
</style>
|
|
</head>
|
|
<body style="background:#F5F5F5;padding-bottom:20px">
|
|
|
|
<div class="page-header">
|
|
<a href="/profile" class="back"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg></a>
|
|
<div class="title"><?=$t('follow_plan')?></div>
|
|
<div class="balance"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v12M8 10h8M8 14h8"/></svg> <?=number_format($user['balance']??0,2)?></div>
|
|
</div>
|
|
|
|
<div class="game-select">
|
|
<select>
|
|
<option><?=htmlspecialchars($game['name'] ?? $t('pk10_title'))?></option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="stats-cards">
|
|
<div class="stat-card">
|
|
<div class="label"><?=$t('total_win_rate')?></div>
|
|
<div class="value"><?=$summary['win_rate']?>%</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="label"><?=$t('total_profit')?></div>
|
|
<div class="value" style="color:<?=$summary['profit']>=0?'#4CAF50':'#FF4444'?>"><?=$summary['profit']>=0?'+':''?><?=number_format($summary['profit'],2)?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if(empty($plans)): ?>
|
|
<div class="empty-plans">
|
|
<div class="icon">
|
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg>
|
|
</div>
|
|
<div class="text"><?=$t('no_follow_plan')?></div>
|
|
</div>
|
|
<?php else: ?>
|
|
|
|
<?php
|
|
$typeNames = ['bs'=>$t('big').'/'.$t('small'), 'oe'=>$t('odd').'/'.$t('even'), 'dt'=>$t('dragon').'/'.$t('tiger'), 'sum_bs'=>$t('sum_big').'/'.$t('sum_small')];
|
|
foreach($plans as $plan):
|
|
$isFollowing = isset($userFollows[$plan['id']]);
|
|
?>
|
|
<div class="plan-card">
|
|
<div class="plan-header">
|
|
<div class="plan-name"><?=htmlspecialchars($plan['name'])?></div>
|
|
<span class="plan-type-badge"><?=$typeNames[$plan['plan_type']]??$plan['plan_type']?></span>
|
|
</div>
|
|
<div class="plan-stats">
|
|
<div class="plan-stat">
|
|
<div class="ps-label"><?=$t('total_win_rate')?></div>
|
|
<div class="ps-value <?=$plan['win_rate']>=50?'win':'lose'?>"><?=$plan['win_rate']?>%</div>
|
|
</div>
|
|
<div class="plan-stat">
|
|
<div class="ps-label"><?=$t('bet_amount')?></div>
|
|
<div class="ps-value"><?=number_format($plan['bet_amount'],0)?></div>
|
|
</div>
|
|
<div class="plan-stat">
|
|
<div class="ps-label">总期数</div>
|
|
<div class="ps-value"><?=$plan['total_records']?></div>
|
|
</div>
|
|
</div>
|
|
<?php if(!empty($plan['recent'])): ?>
|
|
<div class="plan-recent">
|
|
<?php foreach(array_reverse($plan['recent']) as $rec): ?>
|
|
<div class="plan-dot <?=$rec['is_win']?'w':'l'?>"><?=$rec['is_win']?'W':'L'?></div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="plan-actions">
|
|
<button class="follow-btn <?=$isFollowing?'active':'inactive'?>" onclick="toggleFollow(<?=$plan['id']?>,this)">
|
|
<?=$isFollowing?'取消跟单':'开始跟单'?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
|
|
<script>
|
|
function toggleFollow(planId, btn) {
|
|
fetch('/api/follow-plan/toggle', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({plan_id: planId})
|
|
}).then(function(r) { return r.json(); }).then(function(d) {
|
|
if (d.success) location.reload();
|
|
else alert(d.message || '操作失败');
|
|
});
|
|
}
|
|
</script>
|
|
</body></html>
|