From f36410dcc6bd6849e2b53d1881622ba077f5908b Mon Sep 17 00:00:00 2001 From: leon <916117771@qq.com> Date: Tue, 21 Apr 2026 23:41:39 +0800 Subject: [PATCH] feat(phase-3): add hot/cold number analysis with period filter --- application/admin/controller/History.php | 22 ++++- application/admin/lang/zh-cn/history.php | 1 + application/admin/model/History.php | 63 ++++++++++++ application/admin/view/history/index.html | 1 + public/assets/js/backend/history.js | 111 ++++++++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) diff --git a/application/admin/controller/History.php b/application/admin/controller/History.php index 27120ac..f0109c9 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']; + protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers']; public function _initialize() { @@ -72,5 +72,25 @@ class History extends Backend } } + /** + * 获取冷热号码 + * @return void + */ + public function hotColdNumbers() + { + if ($this->request->isAjax()) { + $periods = $this->request->get('periods', 30, 'intval'); + if ($periods < 10 || $periods > 100) { + $this->error('期数范围必须在 10-100 之间'); + } + $type = $this->request->get('type', 'all'); + if (!in_array($type, ['all', 'special'])) { + $this->error('查询类型不正确'); + } + $result = $this->model->getHotColdNumbers($periods, $type); + $this->success('查询成功', null, $result); + } + } + } diff --git a/application/admin/lang/zh-cn/history.php b/application/admin/lang/zh-cn/history.php index 9c9314f..fb10e16 100644 --- a/application/admin/lang/zh-cn/history.php +++ b/application/admin/lang/zh-cn/history.php @@ -17,4 +17,5 @@ return [ 'Special Only' => '仅特码', 'Trend Chart' => '走势图', 'No data available' => '暂无数据', + 'Hot/Cold Analysis' => '冷热分析', ]; diff --git a/application/admin/model/History.php b/application/admin/model/History.php index bb85272..dfafc28 100644 --- a/application/admin/model/History.php +++ b/application/admin/model/History.php @@ -74,6 +74,69 @@ class History extends Model ]; } + /** + * 获取冷热号码 + * @param int $periods 查询最近多少期 + * @param string $type 查询类型 all=全部号码 special=仅特码 + * @return array {hot: [], cold: [], all: []} + */ + public function getHotColdNumbers($periods = 30, $type = 'all') + { + $num_model = new Num(); + $colorMap = $num_model->column('color', 'num'); + $animalMap = $num_model->column('animal', 'num'); + + $history = $this + ->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime') + ->order('openTime', 'desc') + ->limit($periods) + ->select(); + + if (empty($history)) { + return ['hot' => [], 'cold' => [], 'all' => []]; + } + + $fields = ($type === 'special') ? ['num7'] : ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7']; + + // 统计每个号码的出现次数 + $count = array_fill(1, 49, 0); + $totalAppearances = 0; + foreach ($history as $row) { + foreach ($fields as $field) { + $num = (int)$row[$field]; + if ($num >= 1 && $num <= 49) { + $count[$num]++; + $totalAppearances++; + } + } + } + + $all = []; + for ($num = 1; $num <= 49; $num++) { + $percent = $totalAppearances > 0 ? round($count[$num] / $totalAppearances * 100, 1) : 0; + $all[] = [ + 'num' => $num, + 'count' => $count[$num], + 'percent' => $percent, + 'color' => $colorMap[$num] ?? '—', + 'animal' => $animalMap[$num] ?? '—' + ]; + } + + // 按出现次数降序排序 + $sorted = $all; + usort($sorted, function ($a, $b) { + return $b['count'] - $a['count']; + }); + + // 热号: top 10, 冷号: bottom 10 + $hot = array_slice($sorted, 0, 10); + $cold = array_slice($sorted, -10); + $cold = array_reverse($cold); + + return ['hot' => $hot, 'cold' => $cold, 'all' => $all]; + } + /** * 计算遗漏号码 * @param int $periods 查询最近多少期 diff --git a/application/admin/view/history/index.html b/application/admin/view/history/index.html index ded8342..648e2a7 100644 --- a/application/admin/view/history/index.html +++ b/application/admin/view/history/index.html @@ -9,6 +9,7 @@ {:__('Missing Number Analysis')} {:__('Trend Chart')} + {:__('Hot/Cold Analysis')} ' + + ' ' + + ' ' + + ' ' + + '' + + '
' + + ' ' + + ' ' + + ' ' + + '
' + + '
' + + ''; + + Layer.open({ + type: 1, + title: __('Hot/Cold Analysis'), + area: ['700px', '600px'], + content: html, + shadeClose: true, + success: function (layero, index) { + $('#btn-hc-query', layero).on('click', function () { + var periods = parseInt($('#hc-periods', layero).val()) || 30; + var type = $('input[name="hc-type"]:checked', layero).val(); + Controller.api.queryHotCold(periods, type, layero); + }); + $('input[name="hc-type"]', layero).on('change', function () { + var periods = parseInt($('#hc-periods', layero).val()) || 30; + Controller.api.queryHotCold(periods, $(this).val(), layero); + }); + } + }); + }, + + queryHotCold: function (periods, type, layero) { + var $btn = $('#btn-hc-query', layero); + $btn.prop('disabled', true); + $('#hc-result', layero).html('
' + __('Loading') + '
'); + $.ajax({ + url: 'history/hotColdNumbers', + type: 'GET', + data: {periods: periods, type: type}, + dataType: 'json', + success: function (ret) { + if (ret.code == 1) { + Controller.api.renderHotCold(ret.data, layero); + } else { + $('#hc-result', layero).html('
' + (ret.msg || __('Query failed')) + '
'); + } + }, + error: function () { + $('#hc-result', layero).html('
' + __('Query failed') + '
'); + }, + complete: function () { + $btn.prop('disabled', false); + } + }); + }, + + renderHotCold: function (data, layero) { + var getColor = function (num) { + var color = data.all.find(function (item) { return item.num === num; }); + if (!color || !color.color) return '#95a5a6'; + if (color.color.indexOf('红') !== -1) return '#e74c3c'; + if (color.color.indexOf('蓝') !== -1) return '#3498db'; + if (color.color.indexOf('绿') !== -1) return '#2ecc71'; + return '#95a5a6'; + }; + + var getAnimal = function (num) { + var item = data.all.find(function (item) { return item.num === num; }); + return item ? (item.animal || '') : ''; + }; + + var renderSection = function (title, items, icon) { + var html = '

' + icon + ' ' + title + '

'; + html += '
'; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var color = getColor(item.num); + var animal = getAnimal(item.num); + html += '
' + + '' + item.num + '' + + '
' + (animal ? animal + '
' : '') + '' + item.count + ' (' + item.percent + '%)
' + + '
'; + } + html += '
'; + return html; + }; + + var html = '
' + + renderSection('热号 Top 10', data.hot, '🔥') + + renderSection('冷号 Top 10', data.cold, '') + + '
'; + + $('#hc-result', layero).html(html); + }, + /** * 显示走势图弹窗 */