feat(dashboard): 添加区域转移概率统计功能

将1-49数字分为5个区域(1-10/11-20/21-30/31-40/41-49),
统计特码从一个区域转移到另一个区域的概率矩阵,
在控制台页面以彩色表格展示
This commit is contained in:
2026-04-25 23:07:26 +08:00
parent 78e7233bc0
commit 28415a1d4d
3 changed files with 104 additions and 2 deletions
+16 -1
View File
@@ -22,7 +22,7 @@ class History extends Backend
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录) * 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
* @var array * @var array
*/ */
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap', 'specialHotColdAction']; protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap', 'specialHotColdAction', 'zoneTransition'];
public function _initialize() public function _initialize()
{ {
@@ -313,5 +313,20 @@ class History extends Backend
} }
} }
/**
* 区域转移概率统计
*/
public function zoneTransition()
{
if ($this->request->isAjax()) {
$periods = $this->request->get('periods', 100, 'intval');
if ($periods < 10 || $periods > 500) {
$this->error('期数范围必须在 10-500 之间');
}
$result = $this->model->getZoneTransition($periods);
$this->success('查询成功', null, $result);
}
}
} }
+65 -1
View File
@@ -464,7 +464,8 @@ class History extends Model
'bigsmall' => $this->getBigSmallAnalysis($periods, 'special'), 'bigsmall' => $this->getBigSmallAnalysis($periods, 'special'),
'special' => $this->getSpecialTrend($periods), 'special' => $this->getSpecialTrend($periods),
'tailnumbers' => $this->getTailNumbers($periods, 'special'), 'tailnumbers' => $this->getTailNumbers($periods, 'special'),
'heatmap' => $this->getSpecialHeatmap($periods) 'heatmap' => $this->getSpecialHeatmap($periods),
'zonetransition' => $this->getZoneTransition($periods)
]; ];
} }
@@ -662,6 +663,69 @@ class History extends Model
return ['hot' => $hot, 'cold' => $cold, 'warm' => $warm]; return ['hot' => $hot, 'cold' => $cold, 'warm' => $warm];
} }
/**
* 区域转移概率统计
* 将1-49分为5个区域,统计特码从一个区域转移到另一个区域的概率
* 区域1: 1-10, 区域2: 11-20, 区域3: 21-30, 区域4: 31-40, 区域5: 41-49
* @param int $periods 查询最近多少期
* @return array {zones: [], matrix: [], probabilities: [], total_transitions: int}
*/
public function getZoneTransition($periods = 100)
{
$history = $this
->field('expect,num7,openTime')
->order('openTime', 'asc')
->limit($periods)
->select();
if (empty($history) || count($history) < 2) {
return ['zones' => ['1-10','11-20','21-30','31-40','41-49'], 'matrix' => [], 'probabilities' => [], 'total_transitions' => 0];
}
$zoneLabels = ['1-10', '11-20', '21-30', '31-40', '41-49'];
$matrix = array_fill(0, 5, array_fill(0, 5, 0));
$rowTotals = array_fill(0, 5, 0);
$getZone = function ($num) {
if ($num <= 10) return 0;
if ($num <= 20) return 1;
if ($num <= 30) return 2;
if ($num <= 40) return 3;
return 4;
};
$totalTransitions = 0;
for ($i = 0; $i < count($history) - 1; $i++) {
$currentNum = (int)$history[$i]['num7'];
$nextNum = (int)$history[$i + 1]['num7'];
if ($currentNum < 1 || $currentNum > 49 || $nextNum < 1 || $nextNum > 49) continue;
$from = $getZone($currentNum);
$to = $getZone($nextNum);
$matrix[$from][$to]++;
$rowTotals[$from]++;
$totalTransitions++;
}
// 计算概率矩阵
$probabilities = array_fill(0, 5, array_fill(0, 5, 0));
for ($i = 0; $i < 5; $i++) {
if ($rowTotals[$i] > 0) {
for ($j = 0; $j < 5; $j++) {
$probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
}
}
}
return [
'zones' => $zoneLabels,
'matrix' => $matrix,
'probabilities' => $probabilities,
'row_totals' => $rowTotals,
'total_transitions' => $totalTransitions
];
}
/** /**
* 特码热力图数据 * 特码热力图数据
* @param int $periods 查询最近多少期 * @param int $periods 查询最近多少期
+23
View File
@@ -40,6 +40,7 @@ define(['jquery'], function ($) {
function render(data) { function render(data) {
var hc = data.hotcold, cw = data.colorwave, zo = data.zodiac; var hc = data.hotcold, cw = data.colorwave, zo = data.zodiac;
var oe = data.oddeven, bs = data.bigsmall, sp = data.special, tn = data.tailnumbers; var oe = data.oddeven, bs = data.bigsmall, sp = data.special, tn = data.tailnumbers;
var zt = data.zonetransition;
var html = ''; var html = '';
@@ -88,6 +89,28 @@ define(['jquery'], function ($) {
var hm = data.heatmap; var hm = data.heatmap;
html += '<div class="dash-section"><h4>🎨 特码热力图</h4><div style="font-size:12px;color:#999;margin-bottom:8px;">X轴:期号(从左往右,从远到近) | Y轴:号码1-49 | 颜色:号码波色</div><div id="heatmap-chart" style="width:100%;height:500px;"></div></div>'; html += '<div class="dash-section"><h4>🎨 特码热力图</h4><div style="font-size:12px;color:#999;margin-bottom:8px;">X轴:期号(从左往右,从远到近) | Y轴:号码1-49 | 颜色:号码波色</div><div id="heatmap-chart" style="width:100%;height:500px;"></div></div>';
// 区域转移概率
if (zt && zt.matrix && zt.matrix.length > 0) {
html += '<div class="dash-section"><h4>🔄 区域转移概率</h4><div style="font-size:12px;color:#999;margin-bottom:8px;">当前共 ' + zt.total_transitions + ' 次区域转移 | 行=上一期特码所在区域,列=下一期特码所在区域</div>';
html += '<table class="table table-bordered table-condensed text-center" style="max-width:600px;margin:0 auto;"><thead><tr><th>区域→</th>';
for (var z = 0; z < zt.zones.length; z++) {
html += '<th>' + zt.zones[z] + '</th>';
}
html += '</tr></thead><tbody>';
for (var r = 0; r < 5; r++) {
html += '<tr><td style="font-weight:bold;">' + zt.zones[r] + '</td>';
for (var c = 0; c < 5; c++) {
var pct = zt.probabilities[r][c];
var cnt = zt.matrix[r][c];
var bg = pct > 30 ? '#e74c3c' : pct > 20 ? '#f39c12' : pct > 10 ? '#3498db' : pct > 0 ? '#95a5a6' : '#f5f5f5';
var txt = pct > 10 ? '#fff' : '#333';
html += '<td style="background-color:' + bg + ';color:' + txt + ';">' + cnt + '次<br>' + pct + '%</td>';
}
html += '</tr>';
}
html += '</tbody></table></div>';
}
$('#dash-content').html(html); $('#dash-content').html(html);
// 波色饼图 // 波色饼图