Compare commits

...

14 Commits

Author SHA1 Message Date
916117771 7fb2ba4dcf feat(dashboard): 新增生肖转移概率表格
在控制台页面转移概率区域新增生肖(12生肖)转移概率矩阵,与现有
的区域转移和波色转移保持一致的展示风格。
2026-04-26 16:52:22 +08:00
916117771 cbca6217d6 test pull 2026-04-26 00:25:25 +08:00
916117771 04be883e4d test pull 2026-04-26 00:23:28 +08:00
916117771 cbdc5199f4 change(admin): 修改仪表板默认统计周期为100天
- 将dashboard控制器中的默认periods值从30修改为100
- 更新了数据统计展示的时间范围配置
2026-04-26 00:19:16 +08:00
916117771 8d9161eb1a feat(dashboard): 添加后台仪表板功能并优化数据分析
- 新增 dashboard.js 文件实现前端图表展示功能
- 添加彩球渲染和颜色映射功能
- 实现数据加载和渲染逻辑
- 添加多种统计图表包括冷热号码、比例分析、生肖排名等
- 重构区域转移动态数据方法
- 更新区域到区域颜色转移概率计算逻辑
- 优化转移概率数据结构和显示方式
- 添加热力图和各种统计图表的响应式支持
2026-04-26 00:14:50 +08:00
916117771 c3e430a24b feat(dashboard): 新增区域→波色交叉转移概率表格 2026-04-25 23:53:33 +08:00
916117771 8c8dec620e feat(dashboard): 新增波色转移概率表格,与区域转移概率并排展示 2026-04-25 23:31:18 +08:00
916117771 af15aef37d fix(dashboard): 区域转移概率移至热力图上方;修正查询顺序为最近数据 2026-04-25 23:28:39 +08:00
916117771 84f0d60817 fix(dashboard): 修正表头斜线分割文字位置,左上"区域"右下"特码" 2026-04-25 23:13:35 +08:00
916117771 0c2d410642 fix(dashboard): 区域转移概率表头改为斜线分割样式,左下"特码"右上"区域" 2026-04-25 23:12:09 +08:00
916117771 26d20dbd7c fix(dashboard): 区域转移概率表头改为"特码/区域",更直观表达行列含义 2026-04-25 23:10:56 +08:00
916117771 ae6dcc228b docs(quick-260425-w2i): 区域转移概率统计 — 计划与总结 2026-04-25 23:08:01 +08:00
916117771 6df33a663c docs(state): 记录区域转移概率统计 quick task 完成 2026-04-25 23:07:47 +08:00
916117771 28415a1d4d feat(dashboard): 添加区域转移概率统计功能
将1-49数字分为5个区域(1-10/11-20/21-30/31-40/41-49),
统计特码从一个区域转移到另一个区域的概率矩阵,
在控制台页面以彩色表格展示
2026-04-25 23:07:26 +08:00
8 changed files with 460 additions and 5 deletions
+1
View File
@@ -75,6 +75,7 @@ None yet.
|---|-------------|------|--------|-----------| |---|-------------|------|--------|-----------|
| 260422-vep | 在控制台增加特码热力图功能 | 2026-04-22 | 73e7403 | [260422-vep](./quick/260422-vep/) | | 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/) | | 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 ## 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 完成
+1 -1
View File
@@ -13,7 +13,7 @@ class Dashboard extends Backend
{ {
public function index() public function index()
{ {
$this->assign('periods', 30); $this->assign('periods', 100);
return $this->view->fetch(); return $this->view->fetch();
} }
} }
+46 -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', 'colorWaveTransition', 'zoneToColorTransition', 'zodiacTransition'];
public function _initialize() public function _initialize()
{ {
@@ -313,5 +313,50 @@ 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);
}
}
/**
* 生肖转移概率统计
*/
public function zodiacTransition()
{
if ($this->request->isAjax()) {
$periods = $this->request->get('periods', 100, 'intval');
if ($periods < 10 || $periods > 500) {
$this->error('期数范围必须在 10-500 之间');
}
$result = $this->model->getZodiacTransition($periods);
$this->success('查询成功', null, $result);
}
}
} }
+274 -1
View File
@@ -464,7 +464,11 @@ 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),
'colorwavetransition' => $this->getColorWaveTransition($periods),
'zonetocolortransition' => $this->getZoneToZoneColor($periods),
'zodiactransition' => $this->getZodiacTransition($periods)
]; ];
} }
@@ -662,6 +666,275 @@ 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', '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
];
}
/**
* 生肖转移概率统计
* 统计特码从一种生肖转移到另一种生肖的概率(12生肖)
* @param int $periods 查询最近多少期
* @return array {animals: [], matrix: [], probabilities: [], row_totals: [], total_transitions: int}
*/
public function getZodiacTransition($periods = 100)
{
$history = $this
->field('expect,num7,openTime')
->order('openTime', 'desc')
->limit($periods)
->select();
if (empty($history) || count($history) < 2) {
return ['animals' => ['鼠','牛','虎','兔','龙','蛇','马','羊','猴','鸡','狗','猪'], 'matrix' => [], 'probabilities' => [], 'row_totals' => [], 'total_transitions' => 0];
}
$history = array_reverse($history);
$num_model = new Num();
$animalMap = $num_model->column('animal', 'num');
$animalLabels = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
$numAnimals = 12;
$matrix = array_fill(0, $numAnimals, array_fill(0, $numAnimals, 0));
$rowTotals = array_fill(0, $numAnimals, 0);
$getAnimalIdx = function ($num) use ($animalMap, $animalLabels) {
$animal = $animalMap[$num] ?? '';
$idx = array_search($animal, $animalLabels);
return $idx === false ? -1 : $idx;
};
$totalTransitions = 0;
for ($i = 0; $i < count($history) - 1; $i++) {
$currentNum = (int)$history[$i]['num7'];
$nextNum = (int)$history[$i + 1]['num7'];
$from = $getAnimalIdx($currentNum);
$to = $getAnimalIdx($nextNum);
if ($from < 0 || $to < 0) continue;
$matrix[$from][$to]++;
$rowTotals[$from]++;
$totalTransitions++;
}
$probabilities = array_fill(0, $numAnimals, array_fill(0, $numAnimals, 0));
for ($i = 0; $i < $numAnimals; $i++) {
if ($rowTotals[$i] > 0) {
for ($j = 0; $j < $numAnimals; $j++) {
$probabilities[$i][$j] = round($matrix[$i][$j] / $rowTotals[$i] * 100, 1);
}
}
}
return [
'animals' => $animalLabels,
'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 查询最近多少期 * @param int $periods 查询最近多少期
+95
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 = '';
@@ -84,6 +85,100 @@ define(['jquery'], function ($) {
html += '<div class="dash-section"><h4>🔢 尾数频率</h4><div id="tail-chart" style="width:100%;height:220px;"></div></div>'; 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;
var zt2 = data.zodiactransition;
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>';
}
// 下方:生肖转移矩阵
if (zt2 && zt2.matrix && zt2.matrix.length > 0) {
html += '<div style="margin-top:20px;font-size:12px;color:#999;margin-bottom:8px;">生肖转移(共 ' + zt2.total_transitions + ' 次)</div>';
html += '<table class="table table-bordered table-condensed text-center" style="max-width:720px;margin:0 auto;"><thead><tr><th style="width:50px;">特码</th>';
for (var z = 0; z < zt2.animals.length; z++) {
html += '<th>' + zt2.animals[z] + '</th>';
}
html += '</tr></thead><tbody>';
var animalColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e67e22', '#34495e', '#e74c3c', '#3498db', '#2ecc71', '#f39c12'];
for (var r = 0; r < zt2.animals.length; r++) {
html += '<tr><td style="font-weight:bold;color:' + animalColors[r] + ';">' + zt2.animals[r] + '</td>';
for (var c = 0; c < zt2.animals.length; c++) {
var pct = zt2.probabilities[r][c];
var cnt = zt2.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>';
}
html += '</div>';
}
// 热力图部分 // 热力图部分
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>';