diff --git a/.planning/STATE.md b/.planning/STATE.md index 9d3e87d..a48d539 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-04-21) Phase: 01 (omitted-number-analysis) — COMPLETE Plan: 3 of 3 Status: Phase 1 complete, ready to plan next phase -Last activity: 2026-04-21 -- Phase 01 complete, Phases 2-10 added +Last activity: 2026-04-22 -- Completed quick task 260422-vep: 特码热力图功能 Progress: [████░░░░░░] 10% @@ -69,6 +69,12 @@ None yet. None yet. +### Quick Tasks Completed + +| # | Description | Date | Commit | Directory | +|---|-------------|------|--------|-----------| +| 260422-vep | 在控制台增加特码热力图功能 | 2026-04-22 | 73e7403 | [260422-vep](./quick/260422-vep/) | + ## Deferred Items | Category | Item | Status | Deferred At | diff --git a/.planning/quick/260422-vep/260422-vep-PLAN.md b/.planning/quick/260422-vep/260422-vep-PLAN.md new file mode 100644 index 0000000..1814e70 --- /dev/null +++ b/.planning/quick/260422-vep/260422-vep-PLAN.md @@ -0,0 +1,48 @@ +--- +quick_id: 260422-vep +description: 在控制台增加特码热力图功能 +mode: quick +date: 2026-04-22 +--- + +# Quick Task Plan: 特码热力图功能 + +## Goal +在控制台(Dashboard)增加特码热力图功能,直观展示特码号码在近 N 期内的出现分布情况。 + +## Implementation Design + +### 热力图数据结构 +- X轴:期号(最近30期) +- Y轴:号码 1-49 +- 颜色:基于号码的波色(红/蓝/绿),出现时显示颜色,未出现时显示浅灰 + +### Files to Modify +1. `application/admin/model/History.php` - 添加 `getSpecialHeatmap()` 方法 +2. `application/admin/controller/History.php` - 添加 `specialHeatmap` API 接口和权限声明 +3. `public/assets/js/backend/dashboard.js` - 添加热力图渲染逻辑 +4. `application/admin/view/dashboard/index.html` - 无需修改(热力图容器由 JS 动态生成) + +## Tasks + +### Task 1: 后端数据模型 +**File:** `application/admin/model/History.php` +**Action:** 添加 `getSpecialHeatmap()` 方法 +**Verify:** 方法返回热力图数据(expects, heatmap_data, colorMap) +**Done:** 方法可被正确调用并返回结构化数据 + +### Task 2: API 接口 +**File:** `application/admin/controller/History.php` +**Action:** +1. 在 `$noNeedRight` 数组添加 `specialHeatmap` +2. 添加 `specialHeatmap()` 方法 +**Verify:** API 可通过 AJAX 访问并返回正确数据 +**Done:** 接口可被前端调用 + +### Task 3: 前端渲染 +**File:** `public/assets/js/backend/dashboard.js` +**Action:** +1. 在 AJAX 请求中添加热力图数据获取 +2. 在 render 函数中添加热力图 HTML 和 ECharts 渲染 +**Verify:** 热力图正确显示在 Dashboard 页面 +**Done:** 热力图可视化完成 \ No newline at end of file diff --git a/.planning/quick/260422-vep/260422-vep-SUMMARY.md b/.planning/quick/260422-vep/260422-vep-SUMMARY.md new file mode 100644 index 0000000..1ad99f7 --- /dev/null +++ b/.planning/quick/260422-vep/260422-vep-SUMMARY.md @@ -0,0 +1,34 @@ +# Quick Task 260422-vep: 特码热力图功能 - Summary + +**Status:** Completed +**Date:** 2026-04-22 + +## Implementation Summary + +在控制台(Dashboard)增加了特码热力图功能,直观展示特码号码在近 N 期内的出现分布情况。 + +### Changes Made + +**1. Backend Model (`application/admin/model/History.php`)** +- 新增 `getSpecialHeatmap($periods)` 方法 +- 返回热力图数据结构:expects(期号列表)、heatmap(出现数据)、colors(号码波色)、nums(号码列表) + +**2. Backend Controller (`application/admin/controller/History.php`)** +- 在 `$noNeedRight` 数组添加 `specialHeatmap` 权限声明 +- 新增 `specialHeatmap()` API 接口方法 +- `getDashboardData()` 方法已自动包含热力图数据 + +**3. Frontend JS (`public/assets/js/backend/dashboard.js`)** +- 在 `render()` 函数中添加热力图 HTML 部分 +- 新增 ECharts 热力图渲染逻辑,使用号码波色作为单元格颜色 + +### Features +- X轴:期号(从左往右,从远到近) +- Y轴:号码 1-49 +- 颜色:号码对应波色(红/蓝/绿),未开出显示浅灰 +- Tooltip:显示期号、号码、状态 + +### Files Modified +1. `application/admin/model/History.php` - 添加热力图数据方法 +2. `application/admin/controller/History.php` - 添加 API 接口 +3. `public/assets/js/backend/dashboard.js` - 添加前端渲染逻辑 \ No newline at end of file diff --git a/application/admin/controller/History.php b/application/admin/controller/History.php index 5ff1533..bc2166e 100644 --- a/application/admin/controller/History.php +++ b/application/admin/controller/History.php @@ -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']; + protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard', 'specialHeatmap']; public function _initialize() { @@ -236,5 +236,20 @@ class History extends Backend } } + /** + * 特码热力图 + */ + public function specialHeatmap() + { + if ($this->request->isAjax()) { + $periods = $this->request->get('periods', 30, 'intval'); + if ($periods < 10 || $periods > 100) { + $this->error('期数范围必须在 10-100 之间'); + } + $result = $this->model->getSpecialHeatmap($periods); + $this->success('查询成功', null, $result); + } + } + } diff --git a/application/admin/model/History.php b/application/admin/model/History.php index 18adc27..1ab210b 100644 --- a/application/admin/model/History.php +++ b/application/admin/model/History.php @@ -463,7 +463,70 @@ class History extends Model 'oddeven' => $this->getOddEvenAnalysis($periods, 'special'), 'bigsmall' => $this->getBigSmallAnalysis($periods, 'special'), 'special' => $this->getSpecialTrend($periods), - 'tailnumbers' => $this->getTailNumbers($periods, 'special') + 'tailnumbers' => $this->getTailNumbers($periods, 'special'), + 'heatmap' => $this->getSpecialHeatmap($periods) + ]; + } + + /** + * 特码热力图数据 + * @param int $periods 查询最近多少期 + * @return array {expects: [], heatmap: [[x, y, value]], colors: [号码对应颜色]} + */ + public function getSpecialHeatmap($periods = 30) + { + $num_model = new Num(); + $colorMap = $num_model->column('color', 'num'); + + // 查询最近 N 期特码数据 + $history = $this + ->field('expect,num7,openTime') + ->order('openTime', 'desc') + ->limit($periods) + ->select(); + + if (empty($history)) { + return ['expects' => [], 'heatmap' => [], 'colors' => []]; + } + + // 反转数组,使数据从左到右为从远到近 + $history = array_reverse($history); + $expects = []; + $heatmap = []; + + // 构建热力图数据:[x_index, y_index, value] + // x_index = 期号索引(0到periods-1) + // y_index = 号码-1(0到48,号码1-49) + // value = 1(出现)或 0(未出现) + foreach ($history as $idx => $row) { + $expects[] = (string)$row['expect']; + $specialNum = (int)$row['num7']; + // 标记该期特码号码出现 + if ($specialNum >= 1 && $specialNum <= 49) { + $heatmap[] = [$idx, $specialNum - 1, 1]; + } + } + + // 补充号码颜色映射(索引0对应号码1) + $colors = []; + for ($num = 1; $num <= 49; $num++) { + $color = $colorMap[$num] ?? ''; + if (strpos($color, '红') !== false) { + $colors[] = '#e74c3c'; + } elseif (strpos($color, '蓝') !== false) { + $colors[] = '#3498db'; + } elseif (strpos($color, '绿') !== false) { + $colors[] = '#2ecc71'; + } else { + $colors[] = '#95a5a6'; + } + } + + return [ + 'expects' => $expects, + 'heatmap' => $heatmap, + 'colors' => $colors, + 'nums' => range(1, 49) // 号码列表 ]; } diff --git a/public/assets/js/backend/dashboard.js b/public/assets/js/backend/dashboard.js index 68b725b..fd43c16 100644 --- a/public/assets/js/backend/dashboard.js +++ b/public/assets/js/backend/dashboard.js @@ -63,6 +63,10 @@ define(['jquery'], function ($) { html += '

🔢 尾数频率

'; + // 热力图部分 + var hm = data.heatmap; + html += '

🎨 特码热力图

X轴:期号(从左往右,从远到近) | Y轴:号码1-49 | 颜色:号码波色
'; + $('#dash-content').html(html); // 波色饼图 @@ -185,6 +189,78 @@ define(['jquery'], function ($) { $(window).on('resize', function(){ tChart.resize(); }); } } + + // 特码热力图 + if (typeof echarts !== 'undefined' && hm && hm.expects && hm.expects.length > 0) { + var hmDom = document.getElementById('heatmap-chart'); + if (hmDom) { + var hmChart = echarts.init(hmDom); + // 构建完整的热力图数据矩阵(每期每号码) + var hmData = []; + for (var x = 0; x < hm.expects.length; x++) { + for (var y = 0; y < 49; y++) { + // 检查该期是否有该号码作为特码 + var found = false; + for (var i = 0; i < hm.heatmap.length; i++) { + if (hm.heatmap[i][0] === x && hm.heatmap[i][1] === y) { + found = true; + break; + } + } + hmData.push([x, y, found ? 1 : 0]); + } + } + hmChart.setOption({ + tooltip: { + position: 'top', + formatter: function(p) { + var periodIdx = p.data[0]; + var num = p.data[1] + 1; + var val = p.data[2]; + return '期号: ' + hm.expects[periodIdx] + '
号码: ' + num + '
状态: ' + (val === 1 ? '已开出' : '未开出'); + } + }, + grid: {left: 80, right: 30, bottom: 60, top: 30}, + xAxis: { + type: 'category', + data: hm.expects, + axisLabel: {rotate: 45, fontSize: 10, interval: 0}, + splitArea: {show: true} + }, + yAxis: { + type: 'category', + data: hm.nums, + axisLabel: {fontSize: 10}, + splitArea: {show: true} + }, + visualMap: { + min: 0, + max: 1, + show: false, + inRange: { + color: ['#f0f0f0', '#e74c3c'] + } + }, + series: [{ + type: 'heatmap', + data: hmData, + label: {show: false}, + itemStyle: { + color: function(p) { + // 未开出显示浅灰,开出显示该号码的波色 + if (p.data[2] === 0) return '#f5f5f5'; + var numIdx = p.data[1]; + return hm.colors[numIdx] || '#95a5a6'; + } + }, + emphasis: { + itemStyle: {shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)'} + } + }] + }); + $(window).on('resize', function(){ hmChart.resize(); }); + } + } } $('#btn-dash-refresh').on('click', loadData);