feat: add comprehensive dashboard showing all analysis metrics in one view

This commit is contained in:
2026-04-22 00:01:20 +08:00
parent 0b3f7210e0
commit 29e364f74a
5 changed files with 180 additions and 1 deletions
+20 -1
View File
@@ -22,7 +22,7 @@ class History extends Backend
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
* @var array
*/
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'sumAnalysis', 'consecutiveNumbers', 'tailNumbers'];
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'sumAnalysis', 'consecutiveNumbers', 'tailNumbers', 'dashboard'];
public function _initialize()
{
@@ -217,5 +217,24 @@ class History extends Backend
}
}
/**
* 综合统计面板
*/
public function dashboard()
{
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->getDashboardData($periods, $type);
$this->success('查询成功', null, $result);
}
}
}
+1
View File
@@ -25,4 +25,5 @@ return [
'Sum Chart' => '和值分析',
'Consecutive' => '连号分析',
'Tail Numbers' => '尾数分析',
'Dashboard' => '综合统计面板',
];
+16
View File
@@ -446,5 +446,21 @@ class History extends Model
return ['all' => $all];
}
/**
* 综合统计面板
*/
public function getDashboardData($periods = 30, $type = 'all')
{
return [
'hotcold' => $this->getHotColdNumbers($periods, $type),
'colorwave' => $this->getColorWaveAnalysis($periods, $type),
'zodiac' => $this->getZodiacAnalysis($periods, $type),
'oddeven' => $this->getOddEvenAnalysis($periods, $type),
'bigsmall' => $this->getBigSmallAnalysis($periods, $type),
'sum' => $this->getSumAnalysis($periods),
'tailnumbers' => $this->getTailNumbers($periods, $type)
];
}
}
@@ -17,6 +17,7 @@
<a href="javascript:;" class="btn btn-primary btn-sumchart" title="{:__('Sum Chart')}"><i class="fa fa-line-chart"></i> {:__('Sum Chart')}</a>
<a href="javascript:;" class="btn btn-dark btn-consecutive" title="{:__('Consecutive')}"><i class="fa fa-link"></i> {:__('Consecutive')}</a>
<a href="javascript:;" class="btn btn-default btn-tailnums" title="{:__('Tail Numbers')}"><i class="fa fa-list-ol"></i> {:__('Tail Numbers')}</a>
<a href="javascript:;" class="btn btn-success btn-dashboard" title="{:__('Dashboard')}"><i class="fa fa-tachometer"></i> {:__('Dashboard')}</a>
<!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('history/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>-->
</div>
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+142
View File
@@ -86,6 +86,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
$(document).off('click', '.btn-tailnums').on('click', '.btn-tailnums', function () {
Controller.api.showAnalysisDialog('tailNumbers');
});
// 综合统计面板按钮事件
$(document).off('click', '.btn-dashboard').on('click', '.btn-dashboard', function () {
Controller.api.showDashboard();
});
},
add: function () {
Controller.api.bindevent();
@@ -904,6 +909,143 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
html += '</div></div>';
$('#consecutive-result', layero).html(html);
},
/**
* 综合统计面板
*/
showDashboard: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="dash-periods" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-dash-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="dash-result" style="margin-top:15px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: __('Dashboard'),
area: ['90%', '90%'],
content: html,
shadeClose: false,
maxmin: true,
success: function (layero, index) {
$('#btn-dash-query', layero).on('click', function () {
var periods = parseInt($('#dash-periods', layero).val()) || 30;
Controller.api.queryDashboard(periods, layero);
});
}
});
},
queryDashboard: function (periods, layero) {
var $btn = $('#btn-dash-query', layero);
$btn.prop('disabled', true);
$('#dash-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/dashboard',
type: 'GET',
data: {periods: periods},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderDashboard(ret.data, layero);
} else {
$('#dash-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#dash-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
renderDashboard: function (data, layero) {
var getColor = function (color) {
if (!color) return '#95a5a6';
if (color.indexOf('红') !== -1) return '#e74c3c';
if (color.indexOf('蓝') !== -1) return '#3498db';
if (color.indexOf('绿') !== -1) return '#2ecc71';
return '#95a5a6';
};
var hc = data.hotcold;
var cw = data.colorwave;
var zo = data.zodiac;
var oe = data.oddeven;
var bs = data.bigsmall;
var sm = data.sum;
var tn = data.tailnumbers;
var ballHtml = function (item) {
return '<div style="text-align:center;display:inline-block;margin:3px;"><span style="display:inline-block;width:28px;height:28px;line-height:28px;text-align:center;border-radius:50%;color:#fff;background-color:' + getColor(item.color) + ';font-weight:bold;font-size:12px;">' + item.num + '</span><div style="font-size:9px;color:#666;">' + item.count + '</div></div>';
};
var html = '<div style="padding:10px;max-height:75vh;overflow-y:auto;">';
// 冷热号码
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">🔥❄️ 冷热号码</h4>';
html += '<div style="display:flex;"><div style="flex:1;padding:5px;"><b style="color:#e74c3c;">热号 Top5</b><div>';
for (var i = 0; i < 5; i++) html += ballHtml(hc.hot[i]);
html += '</div></div><div style="flex:1;padding:5px;"><b style="color:#3498db;">冷号 Top5</b><div>';
for (var i = 0; i < 5; i++) html += ballHtml(hc.cold[i]);
html += '</div></div></div>';
// 波色分析
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">🎨 波色比例</h4>';
html += '<div style="display:flex;gap:15px;">';
html += '<div style="flex:1;text-align:center;padding:8px;background:#fce4ec;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#e74c3c;">' + cw.red + '</div><div>红波 ' + cw.red_pct + '%</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#e3f2fd;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#3498db;">' + cw.blue + '</div><div>蓝波 ' + cw.blue_pct + '%</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#e8f5e9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#2ecc71;">' + cw.green + '</div><div>绿波 ' + cw.green_pct + '%</div></div>';
html += '</div>';
// 生肖分析
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">⭐ 生肖排名</h4>';
html += '<div style="display:flex;flex-wrap:wrap;gap:5px;">';
for (var i = 0; i < Math.min(zo.list.length, 12); i++) {
var z = zo.list[i];
html += '<div style="text-align:center;background:#f5f5f5;padding:5px 10px;border-radius:4px;"><div style="font-size:14px;font-weight:bold;">' + z.animal + '</div><div style="font-size:10px;color:#666;">' + z.count + ' (' + z.percent + '%)</div></div>';
}
html += '</div>';
// 奇偶分析
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">⚖️ 奇偶分析</h4>';
html += '<div style="display:flex;gap:15px;">';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#e74c3c;">' + oe.odd + ' (' + oe.odd_pct + '%)</div><div>奇数</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#3498db;">' + oe.even + ' (' + oe.even_pct + '%)</div><div>偶数</div></div>';
html += '</div>';
// 大小分析
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">📊 大小分析</h4>';
html += '<div style="display:flex;gap:15px;">';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#f39c12;">' + bs.big + ' (' + bs.big_pct + '%)</div><div>大数(25-49)</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#2ecc71;">' + bs.small + ' (' + bs.small_pct + '%)</div><div>小数(1-24)</div></div>';
html += '</div>';
// 和值
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">📈 和值统计</h4>';
html += '<div style="display:flex;gap:15px;">';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;">' + sm.avg + '</div><div>平均</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#e74c3c;">' + sm.max + '</div><div>最大</div></div>';
html += '<div style="flex:1;text-align:center;padding:8px;background:#f9f9f9;border-radius:6px;"><div style="font-size:20px;font-weight:bold;color:#3498db;">' + sm.min + '</div><div>最小</div></div>';
html += '</div>';
// 尾数
html += '<h4 style="border-bottom:1px solid #eee;padding-bottom:5px;">🔢 尾数频率</h4>';
html += '<div style="display:flex;flex-wrap:wrap;gap:5px;">';
for (var i = 0; i < tn.all.length; i++) {
var t = tn.all[i];
html += '<div style="text-align:center;background:#f5f5f5;padding:5px 10px;border-radius:4px;"><div style="font-size:16px;font-weight:bold;">' + t.tail + '</div><div style="font-size:10px;color:#666;">' + t.count + ' (' + t.percent + '%)</div></div>';
}
html += '</div>';
html += '</div>';
$('#dash-result', layero).html(html);
}
}
};