Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d9161eb1a | |||
| c3e430a24b | |||
| 8c8dec620e | |||
| af15aef37d | |||
| 84f0d60817 | |||
| 0c2d410642 | |||
| 26d20dbd7c | |||
| ae6dcc228b | |||
| 6df33a663c | |||
| 28415a1d4d |
@@ -75,6 +75,7 @@ None yet.
|
||||
|---|-------------|------|--------|-----------|
|
||||
| 260422-vep | 在控制台增加特码热力图功能 | 2026-04-22 | 73e7403 | [260422-vep](./quick/260422-vep/) |
|
||||
| 260424-roj | 在history页面新增特码冷热查询功能 | 2026-04-24 | 2513bbb | [260424-roj](./quick/260424-roj-history-y/) |
|
||||
| 260425-w2i | 在控制台页面新增区域转移概率统计功能 | 2026-04-25 | 28415a1 | [260425-w2i](./quick/260425-w2i-5-10/) |
|
||||
|
||||
## Deferred Items
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
description: "在控制台页面新增区域转移概率统计:1-49分5区,统计特码所在区下一期特码出现在各区的概率"
|
||||
status: in-progress
|
||||
---
|
||||
# Quick Task 260425-w2i: 区域转移概率统计
|
||||
|
||||
## Plan
|
||||
|
||||
1. **Model** — Add `getZoneTransition($periods)` method to `app\admin\model\History`
|
||||
- Query latest `$periods` periods ordered by openTime ASC
|
||||
- Divide numbers 1-49 into 5 zones: 1-10, 11-20, 21-30, 31-40, 41-49
|
||||
- For each consecutive pair of periods, record (current_zone → next_zone)
|
||||
- Return: `{zones: ['1-10','11-20','21-30','31-40','41-49'], matrix: [[count...]], probabilities: [[prob...]], total_transitions: N}`
|
||||
|
||||
2. **Controller** — Add `zoneTransition()` method to `app\admin\controller\History`
|
||||
- Accept `periods` param (default 100, range 10-500)
|
||||
- Add to `$noNeedRight` list
|
||||
- Call model method, return JSON response
|
||||
|
||||
3. **Dashboard** — Include `zoneTransition` in `getDashboardData()` response
|
||||
|
||||
4. **Frontend JS** — Add zone transition heatmap/table in `public/assets/js/backend/dashboard.js`
|
||||
- Render as a 5×5 matrix table with color-coded cells showing count and percentage
|
||||
- Append below existing heatmap section
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
status: complete
|
||||
---
|
||||
# Quick Task 260425-w2i Summary
|
||||
|
||||
## Description
|
||||
在控制台页面新增区域转移概率统计功能:将1-49数字分为5个区域(每10个数一个区域),统计每一区域出现特码后下一期出现特码的区域概率。
|
||||
|
||||
## Changes
|
||||
- `application/admin/model/History.php` — 新增 `getZoneTransition($periods)` 方法,返回5×5转移矩阵和概率
|
||||
- `application/admin/controller/History.php` — 新增 `zoneTransition()` AJAX接口,加入 `$noNeedRight`
|
||||
- `application/admin/model/History.php` — `getDashboardData()` 增加 `zonetransition` 字段
|
||||
- `public/assets/js/backend/dashboard.js` — 渲染区域转移概率彩色表格
|
||||
|
||||
## Commits
|
||||
- `28415a1` feat(dashboard): 添加区域转移概率统计功能
|
||||
- `6df33a6` docs(state): 记录区域转移概率统计 quick task 完成
|
||||
@@ -22,7 +22,7 @@ class History extends Backend
|
||||
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
|
||||
* @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', 'colorWaveTransition', 'zoneToColorTransition'];
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
@@ -313,5 +313,35 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 波色转移概率统计
|
||||
*/
|
||||
public function colorWaveTransition()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$periods = $this->request->get('periods', 100, 'intval');
|
||||
if ($periods < 10 || $periods > 500) {
|
||||
$this->error('期数范围必须在 10-500 之间');
|
||||
}
|
||||
$result = $this->model->getColorWaveTransition($periods);
|
||||
$this->success('查询成功', null, $result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -464,7 +464,10 @@ class History extends Model
|
||||
'bigsmall' => $this->getBigSmallAnalysis($periods, 'special'),
|
||||
'special' => $this->getSpecialTrend($periods),
|
||||
'tailnumbers' => $this->getTailNumbers($periods, 'special'),
|
||||
'heatmap' => $this->getSpecialHeatmap($periods)
|
||||
'heatmap' => $this->getSpecialHeatmap($periods),
|
||||
'zonetransition' => $this->getZoneTransition($periods),
|
||||
'colorwavetransition' => $this->getColorWaveTransition($periods),
|
||||
'zonetocolortransition' => $this->getZoneToZoneColor($periods)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -662,6 +665,211 @@ class History extends Model
|
||||
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', 'desc')
|
||||
->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];
|
||||
}
|
||||
|
||||
// 反转为升序,从旧到新
|
||||
$history = array_reverse($history);
|
||||
|
||||
$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 查询最近多少期
|
||||
* @return array {colors: [], matrix: [], probabilities: [], row_totals: [], total_transitions: int}
|
||||
*/
|
||||
public function getColorWaveTransition($periods = 100)
|
||||
{
|
||||
$history = $this
|
||||
->field('expect,num7,openTime')
|
||||
->order('openTime', 'desc')
|
||||
->limit($periods)
|
||||
->select();
|
||||
|
||||
if (empty($history) || count($history) < 2) {
|
||||
return ['colors' => ['红波','蓝波','绿波'], 'matrix' => [], 'probabilities' => [], 'row_totals' => [], 'total_transitions' => 0];
|
||||
}
|
||||
|
||||
$history = array_reverse($history);
|
||||
$num_model = new Num();
|
||||
$colorMap = $num_model->column('color', 'num');
|
||||
|
||||
$colorLabels = ['红波', '蓝波', '绿波'];
|
||||
$matrix = array_fill(0, 3, array_fill(0, 3, 0));
|
||||
$rowTotals = array_fill(0, 3, 0);
|
||||
|
||||
$getColorIdx = function ($num) use ($colorMap) {
|
||||
$color = $colorMap[$num] ?? '';
|
||||
if (strpos($color, '红') !== false) return 0;
|
||||
if (strpos($color, '蓝') !== false) return 1;
|
||||
if (strpos($color, '绿') !== false) return 2;
|
||||
return -1;
|
||||
};
|
||||
|
||||
$totalTransitions = 0;
|
||||
for ($i = 0; $i < count($history) - 1; $i++) {
|
||||
$currentNum = (int)$history[$i]['num7'];
|
||||
$nextNum = (int)$history[$i + 1]['num7'];
|
||||
$from = $getColorIdx($currentNum);
|
||||
$to = $getColorIdx($nextNum);
|
||||
if ($from < 0 || $to < 0) continue;
|
||||
|
||||
$matrix[$from][$to]++;
|
||||
$rowTotals[$from]++;
|
||||
$totalTransitions++;
|
||||
}
|
||||
|
||||
$probabilities = array_fill(0, 3, array_fill(0, 3, 0));
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
if ($rowTotals[$i] > 0) {
|
||||
for ($j = 0; $j < 3; $j++) {
|
||||
$probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'colors' => $colorLabels,
|
||||
'matrix' => $matrix,
|
||||
'probabilities' => $probabilities,
|
||||
'row_totals' => $rowTotals,
|
||||
'total_transitions' => $totalTransitions
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域→区域转移矩阵,单元格内显示波色概率
|
||||
* 统计上一期特码所在区域后,下一期特码在各区域的分布,以及每个区域内的波色占比
|
||||
* @param int $periods 查询最近多少期
|
||||
* @return array {zones: [], matrix: [[zone_count]], color_probs: [[{red, blue, green}]]}
|
||||
*/
|
||||
public function getZoneToZoneColor($periods = 100)
|
||||
{
|
||||
$history = $this
|
||||
->field('expect,num7,openTime')
|
||||
->order('openTime', 'desc')
|
||||
->limit($periods)
|
||||
->select();
|
||||
|
||||
if (empty($history) || count($history) < 2) {
|
||||
return ['zones' => ['1-10','11-20','21-30','31-40','41-49'], 'matrix' => [], 'color_probs' => []];
|
||||
}
|
||||
|
||||
$history = array_reverse($history);
|
||||
$num_model = new Num();
|
||||
$colorMap = $num_model->column('color', 'num');
|
||||
|
||||
$zoneLabels = ['1-10', '11-20', '21-30', '31-40', '41-49'];
|
||||
$matrix = array_fill(0, 5, array_fill(0, 5, 0));
|
||||
$colorCounts = [];
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$colorCounts[$i][$j] = ['red' => 0, 'blue' => 0, 'green' => 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;
|
||||
};
|
||||
|
||||
for ($i = 0; $i < count($history) - 1; $i++) {
|
||||
$currentNum = (int)$history[$i]['num7'];
|
||||
$nextNum = (int)$history[$i + 1]['num7'];
|
||||
$from = $getZone($currentNum);
|
||||
$to = $getZone($nextNum);
|
||||
$color = $colorMap[$nextNum] ?? '';
|
||||
|
||||
$matrix[$from][$to]++;
|
||||
if (strpos($color, '红') !== false) $colorCounts[$from][$to]['red']++;
|
||||
elseif (strpos($color, '蓝') !== false) $colorCounts[$from][$to]['blue']++;
|
||||
elseif (strpos($color, '绿') !== false) $colorCounts[$from][$to]['green']++;
|
||||
}
|
||||
|
||||
// 计算每个单元格波色概率
|
||||
$colorProbs = array_fill(0, 5, array_fill(0, 5, ['red' => 0, 'blue' => 0, 'green' => 0]));
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$total = $matrix[$i][$j];
|
||||
if ($total > 0) {
|
||||
$colorProbs[$i][$j] = [
|
||||
'red' => round($colorCounts[$i][$j]['red'] / $total * 100, 1),
|
||||
'blue' => round($colorCounts[$i][$j]['blue'] / $total * 100, 1),
|
||||
'green' => round($colorCounts[$i][$j]['green'] / $total * 100, 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'zones' => $zoneLabels,
|
||||
'matrix' => $matrix,
|
||||
'color_probs' => $colorProbs
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 特码热力图数据
|
||||
* @param int $periods 查询最近多少期
|
||||
|
||||
@@ -40,6 +40,7 @@ define(['jquery'], function ($) {
|
||||
function render(data) {
|
||||
var hc = data.hotcold, cw = data.colorwave, zo = data.zodiac;
|
||||
var oe = data.oddeven, bs = data.bigsmall, sp = data.special, tn = data.tailnumbers;
|
||||
var zt = data.zonetransition;
|
||||
|
||||
var html = '';
|
||||
|
||||
@@ -84,6 +85,77 @@ define(['jquery'], function ($) {
|
||||
|
||||
html += '<div class="dash-section"><h4>🔢 尾数频率</h4><div id="tail-chart" style="width:100%;height:220px;"></div></div>';
|
||||
|
||||
// 区域转移概率 + 波色转移概率
|
||||
if (zt && zt.matrix && zt.matrix.length > 0) {
|
||||
var cwt = data.colorwavetransition;
|
||||
html += '<div class="dash-section"><h4>🔄 转移概率</h4><div class="row">';
|
||||
// 左侧:区域转移
|
||||
html += '<div class="col-sm-7"><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="margin:0 auto;"><thead><tr><th style="width:80px;position:relative;"><div style="width:100%;height:35px;border-bottom:1px solid #e5e5e5;position:relative;"><span style="position:absolute;top:2px;right:5px;font-size:12px;">区域</span><span style="position:absolute;bottom:2px;left:5px;font-size:12px;">特码</span></div></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>';
|
||||
// 右侧:波色转移
|
||||
if (cwt && cwt.matrix && cwt.matrix.length > 0) {
|
||||
html += '<div class="col-sm-5"><div style="font-size:12px;color:#999;margin-bottom:8px;">波色转移(共 ' + cwt.total_transitions + ' 次)</div>';
|
||||
html += '<table class="table table-bordered table-condensed text-center" style="margin:0 auto;"><thead><tr><th style="width:70px;position:relative;"><div style="width:100%;height:35px;border-bottom:1px solid #e5e5e5;position:relative;"><span style="position:absolute;top:2px;right:5px;font-size:12px;">区域</span><span style="position:absolute;bottom:2px;left:5px;font-size:12px;">特码</span></div></th>';
|
||||
for (var z = 0; z < cwt.colors.length; z++) {
|
||||
html += '<th>' + cwt.colors[z] + '</th>';
|
||||
}
|
||||
html += '</tr></thead><tbody>';
|
||||
var cwColors = ['#e74c3c', '#3498db', '#2ecc71'];
|
||||
for (var r = 0; r < 3; r++) {
|
||||
html += '<tr><td style="font-weight:bold;color:' + cwColors[r] + ';">' + cwt.colors[r] + '</td>';
|
||||
for (var c = 0; c < 3; c++) {
|
||||
var pct = cwt.probabilities[r][c];
|
||||
var cnt = cwt.matrix[r][c];
|
||||
var bg = pct > 40 ? cwColors[r] : pct > 20 ? cwColors[c] : pct > 0 ? '#95a5a6' : '#f5f5f5';
|
||||
var txt = pct > 20 ? '#fff' : '#333';
|
||||
html += '<td style="background-color:' + bg + ';color:' + txt + ';">' + cnt + '次<br>' + pct + '%</td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
// 下方:区域→区域转移矩阵,单元格内显示波色概率
|
||||
var ztc = data.zonetocolortransition;
|
||||
if (ztc && ztc.matrix && ztc.matrix.length > 0) {
|
||||
html += '<div style="margin-top:20px;font-size:12px;color:#999;margin-bottom:8px;">区域→区域转移矩阵(单元格内为波色概率)</div>';
|
||||
html += '<table class="table table-bordered table-condensed text-center" style="max-width:600px;margin:0 auto;"><thead><tr><th style="width:80px;position:relative;"><div style="width:100%;height:35px;border-bottom:1px solid #e5e5e5;position:relative;"><span style="position:absolute;top:2px;right:5px;font-size:12px;">区域</span><span style="position:absolute;bottom:2px;left:5px;font-size:12px;">特码</span></div></th>';
|
||||
for (var z = 0; z < ztc.zones.length; z++) {
|
||||
html += '<th>' + ztc.zones[z] + '</th>';
|
||||
}
|
||||
html += '</tr></thead><tbody>';
|
||||
for (var r = 0; r < 5; r++) {
|
||||
html += '<tr><td style="font-weight:bold;">' + ztc.zones[r] + '</td>';
|
||||
for (var c = 0; c < 5; c++) {
|
||||
var cnt = ztc.matrix[r][c];
|
||||
var cp = ztc.color_probs[r][c];
|
||||
var bg = cnt > 0 ? '#fafafa' : '#fff';
|
||||
var txt = (cp.red > 40 || cp.blue > 40 || cp.green > 40) ? '#fff' : '#333';
|
||||
html += '<td style="background-color:' + bg + ';color:' + txt + ';"><span style="color:#e74c3c;">红' + cp.red + '%</span><br><span style="color:#3498db;">蓝' + cp.blue + '%</span><br><span style="color:#2ecc71;">绿' + cp.green + '%</span><br><span style="font-size:11px;color:#000;">(' + cnt + '次)</span></td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// 热力图部分
|
||||
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>';
|
||||
|
||||
Reference in New Issue
Block a user