Files
amlhc/public/assets/js/backend/history.js
T
916117771 8b2590c5b5 docs(predictV3): 添加predictV3算法优化研究文档和前端功能实现
- 完成Phase 11: predictV3算法优化研究文档,涵盖6个优化方向的技术分析
- 实现置信度评估功能,提供历史命中率、得分分布、多维度一致性置信度指标
- 扩展回测指标体系,新增NDCG@K、MRR、命中率分布等排名质量评估指标
- 优化转移概率算法,引入二阶马尔可夫链和多属性联合转移增强预测准确性
- 设计权重训练机制,支持网格搜索和遗传算法进行数据驱动的参数优化
- 集成组合特征挖掘功能,采用关联规则和序列模式发现号码间潜在关联
- 实现完整的前端交互界面,支持预测结果显示、置信度展示和回测验证功能
- 建立性能优化策略,包括预计算缓存、批量计算和降级策略保障响应速度
2026-05-01 23:17:24 +08:00

2171 lines
127 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
Table.api.init({
extend: {
index_url: 'history/index' + location.search,
add_url: 'history/add',
edit_url: 'history/edit',
del_url: 'history/del',
multi_url: 'history/multi',
import_url: 'history/import',
table: 'history',
}
});
var table = $("#table");
// 从后端获取颜色和生肖映射
Controller.api.loadColorMap(function () {
Controller.api.loadAnimalMap(function () {
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'expect',
sortName: 'expect',
columns: [
[
{checkbox: true},
{field: 'expect', title: __('Expect')},
{field: 'num1', title: __('Num1'), formatter: Controller.api.formatter.numBall},
{field: 'num2', title: __('Num2'), formatter: Controller.api.formatter.numBall},
{field: 'num3', title: __('Num3'), formatter: Controller.api.formatter.numBall},
{field: 'num4', title: __('Num4'), formatter: Controller.api.formatter.numBall},
{field: 'num5', title: __('Num5'), formatter: Controller.api.formatter.numBall},
{field: 'num6', title: __('Num6'), formatter: Controller.api.formatter.numBall},
{field: 'num7', title: __('Num7'), formatter: Controller.api.formatter.numBall},
{field: 'openTime', title: __('OpenTime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'search_day', title: '每月日号', visible: false, searchable: true, operate: '=', style: 'width:80px;', extend: 'min="1" max="31" placeholder="1-31"'}
]
]
});
Table.api.bindevent(table);
});
});
// 遗漏号码按钮事件
$(document).off('click', '.btn-missingnum').on('click', '.btn-missingnum', function () {
Controller.api.showMissingNumDialog();
});
// 走势图按钮事件
$(document).off('click', '.btn-trend').on('click', '.btn-trend', function () {
Controller.api.showTrendDialog();
});
// 冷热分析按钮事件
$(document).off('click', '.btn-hotcold').on('click', '.btn-hotcold', function () {
Controller.api.showHotColdDialog();
});
// 波色分析按钮事件
$(document).off('click', '.btn-colorwave').on('click', '.btn-colorwave', function () {
Controller.api.showAnalysisDialog('colorWave');
});
// 生肖分析按钮事件
$(document).off('click', '.btn-zodiac').on('click', '.btn-zodiac', function () {
Controller.api.showAnalysisDialog('zodiac');
});
// 奇偶分析按钮事件
$(document).off('click', '.btn-oddeven').on('click', '.btn-oddeven', function () {
Controller.api.showAnalysisDialog('oddEven');
});
// 大小分析按钮事件
$(document).off('click', '.btn-bigsmall').on('click', '.btn-bigsmall', function () {
Controller.api.showAnalysisDialog('bigSmall');
});
// 和值分析按钮事件
$(document).off('click', '.btn-sumchart').on('click', '.btn-sumchart', function () {
Controller.api.showSumDialog();
});
// 连号分析按钮事件
$(document).off('click', '.btn-consecutive').on('click', '.btn-consecutive', function () {
Controller.api.showConsecutiveDialog();
});
// 尾数分析按钮事件
$(document).off('click', '.btn-tailnums').on('click', '.btn-tailnums', function () {
Controller.api.showAnalysisDialog('tailNumbers');
});
// 特码冷热按钮事件
$(document).off('click', '.btn-specialhotcold').on('click', '.btn-specialhotcold', function () {
Controller.api.showSpecialHotColdDialog();
});
// 综合统计面板按钮事件
$(document).off('click', '.btn-dashboard').on('click', '.btn-dashboard', function () {
Controller.api.showDashboard();
});
// 筛号器按钮事件
$(document).off('click', '.btn-numberfilter').on('click', '.btn-numberfilter', function () {
Controller.api.showNumberFilterDialog();
});
// 预测号码按钮事件
$(document).off('click', '.btn-predict').on('click', '.btn-predict', function () {
Controller.api.showPredictDialog();
});
// 正码关联预测按钮事件
$(document).off('click', '.btn-normal-relation').on('click', '.btn-normal-relation', function () {
Controller.api.showNormalRelationDialog();
});
},
add: function () {
Controller.api.bindevent();
},
edit: function () {
Controller.api.bindevent();
},
api: {
colorMap: {},
colorMapLoaded: false,
animalMap: {},
animalMapLoaded: false,
/**
* 从后端加载颜色映射并缓存
*/
loadColorMap: function (callback) {
if (Controller.api.colorMapLoaded) {
callback();
return;
}
$.ajax({
url: 'num/getColorMap',
type: 'GET',
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.colorMap = ret.msg;
}
Controller.api.colorMapLoaded = true;
callback();
}
});
},
/**
* 从后端加载生肖映射并缓存
*/
loadAnimalMap: function (callback) {
if (Controller.api.animalMapLoaded) {
callback();
return;
}
$.ajax({
url: 'num/getAnimalMap',
type: 'GET',
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.animalMap = ret.msg;
}
Controller.api.animalMapLoaded = true;
callback();
}
});
},
/**
* 根据数字从映射表中获取生肖
*/
getAnimalByNum: function (num) {
return Controller.api.animalMap[num] || '';
},
/**
* 根据数字从映射表中获取颜色
*/
getColorByNum: function (num) {
var color = Controller.api.colorMap[num];
if (!color) return '#95a5a6';
// 后端返回中文波色,前端映射为CSS颜色
if (color.indexOf('红') !== -1) return '#e74c3c';
if (color.indexOf('蓝') !== -1) return '#3498db';
if (color.indexOf('绿') !== -1) return '#2ecc71';
return '#95a5a6';
},
formatter: {
numBall: function (value, row, index) {
if (value === null || value === undefined || value === '') return '';
var num = parseInt(value);
var color = Controller.api.getColorByNum(num);
var animal = Controller.api.getAnimalByNum(num);
var html = '<div style="text-align:center;">' +
'<span class="num-ball" style="display:inline-block;width:32px;height:32px;line-height:32px;text-align:center;border-radius:50%;color:#fff;background-color:' + color + ';font-weight:bold;">' + value + '</span>';
if (animal) {
html += '<div style="font-size:10px;color:#666;line-height:1.2;">' + animal + '</div>';
}
html += '</div>';
return html;
}
},
/**
* 显示冷热分析弹窗
*/
showHotColdDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:15px;">' + __('Query Type') + '</label>' +
' <label class="radio-inline" style="margin-right:15px;">' +
' <input type="radio" name="hc-type" value="all" checked> ' + __('All Numbers') +
' </label>' +
' <label class="radio-inline">' +
' <input type="radio" name="hc-type" value="special"> ' + __('Special Only') +
' </label>' +
'</div>' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="hc-periods" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-hc-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="hc-result" style="margin-top:15px;overflow-x:auto;"></div>' +
'</div>';
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('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.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('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#hc-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
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 = '<div style="margin-bottom:15px;"><h4 style="margin:0 0 8px 0;border-bottom:1px solid #eee;padding-bottom:5px;">' + icon + ' ' + title + '</h4>';
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;">';
for (var i = 0; i < items.length; i++) {
var item = items[i];
var color = getColor(item.num);
var animal = getAnimal(item.num);
html += '<div style="text-align:center;background:#f9f9f9;padding:8px;border-radius:6px;min-width:70px;">' +
'<span style="display:inline-block;width:36px;height:36px;line-height:36px;text-align:center;border-radius:50%;color:#fff;background-color:' + color + ';font-weight:bold;">' + item.num + '</span>' +
'<div style="margin-top:4px;font-size:10px;color:#666;">' + (animal ? animal + '<br>' : '') + '<b>' + item.count + '</b> (' + item.percent + '%)</div>' +
'</div>';
}
html += '</div></div>';
return html;
};
var html = '<div style="padding:10px;">' +
renderSection('热号 Top 10', data.hot, '<span style="color:#e74c3c;">&#x1F525;</span>') +
renderSection('冷号 Top 10', data.cold, '<span style="color:#3498db;">&#x2744;</span>') +
'</div>';
$('#hc-result', layero).html(html);
},
/**
* 显示走势图弹窗
*/
showTrendDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:15px;">' + __('Query Type') + '</label>' +
' <label class="radio-inline" style="margin-right:15px;">' +
' <input type="radio" name="trend-type" value="all" checked> ' + __('All Numbers') +
' </label>' +
' <label class="radio-inline">' +
' <input type="radio" name="trend-type" value="special"> ' + __('Special Only') +
' </label>' +
'</div>' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="trend-periods" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-trend-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="trend-result" style="margin-top:15px;overflow-x:auto;"></div>' +
'</div>';
Layer.open({
type: 1,
title: __('Trend Chart'),
area: ['90%', '80%'],
content: html,
shadeClose: false,
maxmin: true,
success: function (layero, index) {
$('#btn-trend-query', layero).on('click', function () {
var periods = parseInt($('#trend-periods', layero).val()) || 30;
var type = $('input[name="trend-type"]:checked', layero).val();
Controller.api.queryTrend(periods, type, layero);
});
$('input[name="trend-type"]', layero).on('change', function () {
var periods = parseInt($('#trend-periods', layero).val()) || 30;
Controller.api.queryTrend(periods, $(this).val(), layero);
});
}
});
},
/**
* 查询走势图
*/
queryTrend: function (periods, type, layero) {
var $btn = $('#btn-trend-query', layero);
$btn.prop('disabled', true);
$('#trend-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/trendData',
type: 'GET',
data: {periods: periods, type: type},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderTrend(ret.data, type, layero);
} else {
$('#trend-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#trend-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
/**
* 渲染走势图(ECharts 折线图)
*/
renderTrend: function (data, type, layero) {
var expects = data.expects;
var rows = data.data;
var colorMap = data.colorMap;
if (!expects || expects.length === 0) {
$('#trend-result', layero).html('<div class="alert alert-info">' + __('No data available') + '</div>');
return;
}
var getColor = function (num) {
var color = colorMap[num];
if (!color) return '#95a5a6';
if (color.indexOf('红') !== -1) return '#e74c3c';
if (color.indexOf('蓝') !== -1) return '#3498db';
if (color.indexOf('绿') !== -1) return '#2ecc71';
return '#95a5a6';
};
$('#trend-result', layero).html('<div id="trend-chart" style="width:100%;height:500px;"></div>');
var chartDom = document.getElementById('trend-chart');
var myChart = echarts.init(chartDom);
var series = [];
if (type === 'special') {
series = [{
name: '特码',
type: 'line',
data: rows.map(function (r) { return r.num7; }),
smooth: false,
symbol: 'circle',
symbolSize: 8,
lineStyle: { width: 2 },
itemStyle: {
color: function (params) {
var num = params.data;
return getColor(num);
}
},
label: {
show: true,
position: 'top',
fontSize: 11,
color: '#333'
}
}];
} else {
var numFields = [];
for (var c = 1; c <= 7; c++) {
numFields.push('num' + c);
}
var colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#e67e22'];
for (var f = 0; f < numFields.length; f++) {
(function (idx) {
series.push({
name: '第' + (idx + 1) + '码',
type: 'line',
data: rows.map(function (r) { return r[numFields[idx]]; }),
smooth: false,
symbol: 'circle',
symbolSize: 6,
lineStyle: { width: 2 },
itemStyle: { color: colors[idx] },
label: {
show: true,
position: 'top',
fontSize: 10,
color: '#333'
}
});
})(f);
}
}
var option = {
title: { text: type === 'special' ? '特码走势' : '全部号码走势', left: 'center', textStyle: { fontSize: 14 } },
tooltip: { trigger: 'axis', formatter: function (params) {
var tip = '期号: ' + params[0].axisValueLabel + '<br/>';
for (var i = 0; i < params.length; i++) {
tip += params[i].seriesName + ': <b>' + params[i].data + '</b><br/>';
}
return tip;
}},
legend: { bottom: 10, data: series.map(function (s) { return s.name; }) },
grid: { left: 40, right: 20, bottom: 50, top: 40 },
xAxis: { type: 'category', data: expects, axisLabel: { rotate: 45, fontSize: 10 } },
yAxis: { type: 'value', min: 0, max: 50, splitLine: { show: true, lineStyle: { type: 'dashed' } } },
dataZoom: [{ type: 'slider', bottom: 30, height: 20, start: 0, end: 100 }],
series: series
};
myChart.setOption(option);
window.addEventListener('resize', function () { myChart.resize(); });
},
/**
* 显示遗漏号码分析弹窗
*/
showMissingNumDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:15px;">' + __('Query Type') + '</label>' +
' <label class="radio-inline" style="margin-right:15px;">' +
' <input type="radio" name="missing-type" value="all" checked> ' + __('All Numbers') +
' </label>' +
' <label class="radio-inline">' +
' <input type="radio" name="missing-type" value="special"> ' + __('Special Only') +
' </label>' +
'</div>' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="missing-periods" class="form-control" value="10" min="1" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-missing-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="missing-result" style="margin-top:15px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: __('Missing Number Analysis'),
area: ['650px', '550px'],
content: html,
shadeClose: true,
success: function (layero, index) {
// 绑定查询按钮事件
$('#btn-missing-query', layero).on('click', function () {
var periods = parseInt($('#missing-periods', layero).val()) || 10;
var type = $('input[name="missing-type"]:checked', layero).val();
Controller.api.queryMissingNum(periods, type, layero);
});
// 切换类型时自动查询
$('input[name="missing-type"]', layero).on('change', function () {
var periods = parseInt($('#missing-periods', layero).val()) || 10;
Controller.api.queryMissingNum(periods, $(this).val(), layero);
});
}
});
},
/**
* 查询遗漏号码
*/
queryMissingNum: function (periods, type, layero) {
// 确保颜色映射已加载
if (!Controller.api.colorMapLoaded) {
Controller.api.loadColorMap(function () {
Controller.api._doQueryMissingNum(periods, type, layero);
});
} else {
Controller.api._doQueryMissingNum(periods, type, layero);
}
},
/**
* 执行遗漏号码查询(内部方法)
*/
_doQueryMissingNum: function (periods, type, layero) {
var $btn = $('#btn-missing-query', layero);
$btn.prop('disabled', true);
$('#missing-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/missingNum',
type: 'GET',
data: {periods: periods, type: type},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderMissingNum(ret.data, periods, layero);
} else {
$('#missing-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#missing-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
/**
* 渲染遗漏号码结果
*/
renderMissingNum: function (data, periods, layero) {
if (!data || data.length === 0) {
$('#missing-result', layero).html('<div class="alert alert-info">' + __('No missing numbers found') + '</div>');
return;
}
var container = $('<div style="display:flex;flex-wrap:wrap;gap:12px;"></div>');
for (var i = 0; i < data.length; i++) {
var color = Controller.api.getColorByNum(data[i].num);
var animal = Controller.api.getAnimalByNum(data[i].num);
var $item = $('<div style="text-align:center;"></div>');
var $ball = $('<span class="num-ball"></span>').css({
'display': 'inline-block',
'width': '48px',
'height': '48px',
'line-height': '48px',
'text-align': 'center',
'border-radius': '50%',
'color': '#fff',
'background-color': color,
'font-weight': 'bold',
'font-size': '18px'
}).text(data[i].num);
var $content = $('<div></div>').append($ball);
if (animal) {
$content.append($('<div style="margin-top:3px;font-size:11px;color:#666;line-height:1.2;"></div>').text(animal));
}
$content.append($('<div style="margin-top:3px;font-size:12px;color:#666;"></div>').text(
__('Missing') + ' ' + data[i].omit + ' ' + __('periods')
));
$item.append($content);
container.append($item);
}
$('#missing-result', layero).html('').append(container);
},
/**
* 特码冷热列表(每期相对于前N期的冷热状态)
*/
showSpecialHotColdDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label>向前期数:</label>' +
' <input type="number" id="shc-lookback" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-shc-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="shc-result" style="margin-top:10px;max-height:500px;overflow-y:auto;"></div>' +
'</div>';
Layer.open({
type: 1,
title: '特码冷热列表',
area: ['650px', '600px'],
content: html,
shadeClose: true,
success: function (layero, index) {
$('#btn-shc-query', layero).on('click', function () {
var lookback = parseInt($('#shc-lookback', layero).val()) || 30;
Controller.api.querySpecialHotCold(lookback, layero);
});
// 打开时自动查询
var lookback = parseInt($('#shc-lookback', layero).val()) || 30;
Controller.api.querySpecialHotCold(lookback, layero);
}
});
},
querySpecialHotCold: function (lookback, layero) {
var $btn = $('#btn-shc-query', layero);
$btn.prop('disabled', true);
$('#shc-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/specialHotColdAction',
type: 'GET',
data: {lookback: lookback},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderSpecialHotCold(ret.data, lookback, layero);
} else {
$('#shc-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#shc-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
renderSpecialHotCold: function (data, lookback, layero) {
var getColor = function (num) {
return Controller.api.getColorByNum(num);
};
var statusTag = function (status) {
var map = {
'hot': '<span style="color:#e74c3c;font-weight:bold;">&#x1F525; 热号</span>',
'cold': '<span style="color:#3498db;font-weight:bold;">&#x2744; 冷号</span>',
'normal': '<span style="color:#f39c12;font-weight:bold;">&#x279C; 温号</span>',
'unknown': '<span style="color:#999;">数据不足</span>'
};
return map[status] || '';
};
// 新接口返回 {list: [...], current: {hot: [], cold: [], warm: []}}
var listData = data.list || data;
var current = data.current || null;
if (!listData || listData.length === 0) {
$('#shc-result', layero).html('<div class="alert alert-info">暂无数据</div>');
return;
}
var html = '<div style="padding:0 5px;">';
// 当前冷热号汇总区域
if (current && (current.hot.length > 0 || current.cold.length > 0)) {
var renderNumBall = function (item) {
var color = getColor(item.num);
return '<span style="display:inline-block;width:30px;height:30px;line-height:30px;text-align:center;border-radius:50%;color:#fff;background-color:' + color + ';font-weight:bold;font-size:14px;" title="' + item.num + ' ×' + item.count + '">' + item.num + '</span>';
};
html += '<div style="background:#fafafa;border:1px solid #e0e0e0;border-radius:6px;padding:12px;margin-bottom:12px;">';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;color:#333;"><i class="fa fa-bullseye"></i> 当前热号</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;">';
for (var h = 0; h < current.hot.length; h++) {
html += renderNumBall(current.hot[h]);
}
if (current.hot.length === 0) {
html += '<span style="color:#999;font-size:12px;">暂无热号</span>';
}
html += '</div>';
html += '<div style="height:8px;"></div>';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;color:#333;"><i class="fa fa-snowflake-o"></i> 当前冷号</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;">';
for (var c = 0; c < current.cold.length; c++) {
html += renderNumBall(current.cold[c]);
}
if (current.cold.length === 0) {
html += '<span style="color:#999;font-size:12px;">暂无冷号</span>';
}
html += '</div>';
html += '</div>';
}
// 历史记录表格
html += '<table class="table table-striped table-bordered table-hover" style="font-size:13px;">' +
'<thead><tr>' +
'<th style="text-align:center;width:120px;">期号</th>' +
'<th style="text-align:center;width:60px;">特码</th>' +
'<th style="text-align:center;width:80px;">冷热</th>' +
'<th style="text-align:center;width:60px;">次数</th>' +
'<th style="text-align:center;width:60px;">平均</th>' +
'<th style="text-align:center;width:60px;">排名</th>' +
'</tr></thead><tbody>';
for (var i = 0; i < listData.length; i++) {
var item = listData[i];
var rowClass = '';
if (item.status === 'hot') rowClass = 'style="background:#fff5f5;"';
else if (item.status === 'cold') rowClass = 'style="background:#f5f8ff;"';
html += '<tr ' + rowClass + '>' +
'<td style="text-align:center;">' + item.expect + '</td>' +
'<td style="text-align:center;">' +
'<span style="display:inline-block;width:30px;height:30px;line-height:30px;text-align:center;border-radius:50%;color:#fff;background-color:' + getColor(item.specialNum) + ';font-weight:bold;font-size:14px;">' + item.specialNum + '</span>' +
'</td>' +
'<td style="text-align:center;">' + statusTag(item.status) + '</td>' +
'<td style="text-align:center;">' + item.count + '</td>' +
'<td style="text-align:center;">' + item.avgCount + '</td>' +
'<td style="text-align:center;">' + item.rank + '</td>' +
'</tr>';
}
html += '</tbody></table></div>';
$('#shc-result', layero).html(html);
},
/**
* 筛号器弹窗
*/
showNumberFilterDialog: function () {
var html = '<style>.btn-gray{background-color:#d2d2d2!important;border-color:#adadad!important;color:#999!important;}</style>' +
'<div style="padding:20px;">' +
'<div style="margin-bottom:15px;display:flex;gap:15px;align-items:center;flex-wrap:wrap;">' +
' <div class="form-group" style="margin:0;">' +
' <label>尾号筛选:</label>' +
' <button class="btn btn-xs btn-primary btn-nf-add-tail"><i class="fa fa-plus"></i> 新增</button>' +
' </div>' +
' <button class="btn btn-default btn-nf-reset" style="margin-left:auto;"><i class="fa fa-refresh"></i> 重置</button>' +
'</div>' +
'<div id="nf-tail-list" style="margin-bottom:15px;display:flex;flex-wrap:wrap;gap:6px;"></div>' +
'<div style="margin-bottom:15px;">' +
' <label style="margin-right:10px;">生肖:</label>' +
' <div id="nf-zodiac" style="display:inline-flex;gap:6px;flex-wrap:wrap;">' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="鼠">鼠</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="牛">牛</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="虎">虎</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="兔">兔</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="龙">龙</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="蛇">蛇</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="马">马</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="羊">羊</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="猴">猴</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="鸡">鸡</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="狗">狗</button>' +
' <button class="btn btn-default btn-xs nf-zodiac" data-zodiac="猪">猪</button>' +
' </div>' +
'</div>' +
'<div style="margin-bottom:15px;">' +
' <label style="margin-right:10px;">波色:</label>' +
' <div id="nf-colorwrap" style="display:inline-flex;gap:6px;">' +
' <button class="btn btn-default btn-xs nf-color-btn" data-color="红" style="color:#e74c3c;">红波</button>' +
' <button class="btn btn-default btn-xs nf-color-btn" data-color="蓝" style="color:#3498db;">蓝波</button>' +
' <button class="btn btn-default btn-xs nf-color-btn" data-color="绿" style="color:#2ecc71;">绿波</button>' +
' </div>' +
'</div>' +
'<div style="margin-bottom:15px;">' +
' <label style="margin-right:10px;">单双:</label>' +
' <button class="btn btn-default btn-xs nf-parity" data-parity="单">单</button>' +
' <button class="btn btn-default btn-xs nf-parity" data-parity="双">双</button>' +
'</div>' +
'<div style="margin-bottom:15px;">' +
' <label style="margin-right:10px;">区间:</label>' +
' <button class="btn btn-xs btn-primary btn-nf-add-range"><i class="fa fa-plus"></i> 新增</button>' +
'</div>' +
'<div id="nf-range-list" style="margin-bottom:15px;"></div>' +
'<div id="nf-numbers" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: '筛号器',
area: ['700px', '600px'],
content: html,
shadeClose: true,
success: function (layero, index) {
// 手动屏蔽的号码列表
var blockedNums = [];
// 渲染号码网格
Controller.api.renderNumberFilterGrid(layero);
// 号码点击屏蔽
$('#nf-numbers', layero).on('click', '.nf-number', function () {
var num = parseInt($(this).data('num'));
var idx = blockedNums.indexOf(num);
if (idx === -1) {
blockedNums.push(num);
} else {
blockedNums.splice(idx, 1);
}
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 新增尾号
$('.btn-nf-add-tail', layero).on('click', function () {
Controller.api.addTailRow(layero, 0);
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 尾号输入 & 删除事件委托
$('#nf-tail-list', layero).on('input change', '.nf-tail-select', function () {
Controller.api.applyNumberFilters(layero, blockedNums);
});
$('#nf-tail-list', layero).on('click', '.nf-tail-del', function () {
$(this).closest('.nf-tail-row').remove();
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 生肖按钮点击
$('.nf-zodiac', layero).on('click', function () {
var $btn = $(this);
$btn.toggleClass('btn-default').toggleClass('btn-gray');
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 波色按钮点击
$('.nf-color-btn', layero).on('click', function () {
var $btn = $(this);
$btn.toggleClass('btn-default').toggleClass('btn-gray');
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 单双按钮点击
$('.nf-parity', layero).on('click', function () {
var $btn = $(this);
$btn.toggleClass('btn-default').toggleClass('btn-gray');
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 新增区间
$('.btn-nf-add-range', layero).on('click', function () {
Controller.api.addRangeRow(layero, 1, 49, 'include');
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 区间输入 & 删除事件委托
$('#nf-range-list', layero).on('input change', '.nf-range-min, .nf-range-max', function () {
Controller.api.applyNumberFilters(layero, blockedNums);
});
$('#nf-range-list', layero).on('click', '.nf-range-mode', function () {
var $btn = $(this);
if ($btn.hasClass('btn-info')) {
$btn.removeClass('btn-info').addClass('btn-default');
} else {
$btn.removeClass('btn-default').addClass('btn-info');
}
Controller.api.applyNumberFilters(layero, blockedNums);
});
$('#nf-range-list', layero).on('click', '.nf-range-del', function () {
$(this).closest('.nf-range-row').remove();
Controller.api.applyNumberFilters(layero, blockedNums);
});
// 重置按钮
$('.btn-nf-reset', layero).on('click', function () {
blockedNums = [];
$('#nf-tail-list', layero).html('');
$('.nf-zodiac', layero).removeClass('btn-gray').addClass('btn-default');
$('.nf-color-btn', layero).removeClass('btn-gray').addClass('btn-default');
$('.nf-parity', layero).removeClass('btn-gray').addClass('btn-default');
$('#nf-range-list', layero).html('');
Controller.api.applyNumberFilters(layero, blockedNums);
});
}
});
},
/**
* 新增一行尾号筛选
*/
addTailRow: function (layero, value) {
var rowId = 'nf-tail-' + Date.now() + Math.random().toString(36).substr(2, 5);
var opts = '<option value="">尾号</option>';
for (var t = 0; t <= 9; t++) {
opts += '<option value="' + t + '"' + (t === value ? ' selected' : '') + '>' + t + '</option>';
}
var html = '<div class="nf-tail-row" id="' + rowId + '" style="display:inline-flex;align-items:center;gap:4px;">' +
' <select class="form-control nf-tail-select" style="width:80px;display:inline-block;">' + opts + '</select>' +
' <button class="btn btn-xs btn-danger nf-tail-del"><i class="fa fa-times"></i></button>' +
'</div>';
$('#nf-tail-list', layero).append(html);
},
/**
* 新增一行区间筛选
*/
addRangeRow: function (layero, min, max, mode) {
var rowId = 'nf-range-' + Date.now();
var isInclude = mode === 'include';
var html = '<div class="nf-range-row" id="' + rowId + '" style="display:flex;align-items:center;gap:6px;margin-bottom:6px;">' +
' <span style="font-size:12px;color:#999;white-space:nowrap;">' + (isInclude ? '在区间' : '排除') + '</span>' +
' <input type="number" class="form-control nf-range-min" value="' + min + '" min="1" max="49" style="width:65px;display:inline-block;">' +
' <span style="margin:0 3px;">—</span>' +
' <input type="number" class="form-control nf-range-max" value="' + max + '" min="1" max="49" style="width:65px;display:inline-block;">' +
' <button class="btn btn-xs ' + (isInclude ? 'btn-info' : 'btn-default') + ' nf-range-mode" data-mode="' + mode + '">' + (isInclude ? '在区间' : '排除区间') + '</button>' +
' <button class="btn btn-xs btn-danger nf-range-del"><i class="fa fa-times"></i></button>' +
'</div>';
$('#nf-range-list', layero).append(html);
},
/**
* 渲染筛号器号码网格
*/
renderNumberFilterGrid: function (layero) {
var colorMap = Controller.api.colorMap;
var html = '';
for (var num = 1; num <= 49; num++) {
var colorHex = Controller.api.getColorByNum(num);
var colorRaw = colorMap[num] || '';
var animal = Controller.api.getAnimalByNum(num);
var colorLabel = '';
if (colorRaw.indexOf('红') !== -1) colorLabel = '红';
else if (colorRaw.indexOf('蓝') !== -1) colorLabel = '蓝';
else if (colorRaw.indexOf('绿') !== -1) colorLabel = '绿';
html += '<div class="nf-number" data-num="' + num + '" data-color="' + colorLabel + '" data-animal="' + animal + '" data-tail="' + (num % 10) + '" data-parity="' + (num % 2 === 1 ? '单' : '双') + '" style="text-align:center;background:#f9f9f9;padding:6px 4px;border-radius:6px;min-width:60px;transition:opacity 0.2s;cursor:pointer;" title="点击屏蔽/恢复">' +
'<span style="display:inline-block;width:36px;height:36px;line-height:36px;text-align:center;border-radius:50%;color:#fff;background-color:' + colorHex + ';font-weight:bold;">' + num + '</span>' +
'<div style="font-size:10px;color:#666;line-height:1.2;">' + animal + '</div>' +
'</div>';
}
$('#nf-numbers', layero).html(html);
},
/**
* 应用筛号器过滤条件
* @param {object} layero Layer弹窗对象
* @param {Array} blockedNums 手动屏蔽的号码列表
*/
applyNumberFilters: function (layero, blockedNums) {
blockedNums = blockedNums || [];
// 收集所有选中的尾号
var excludedTails = [];
$('.nf-tail-select', layero).each(function () {
var val = $(this).val();
if (val !== '' && excludedTails.indexOf(parseInt(val)) === -1) {
excludedTails.push(parseInt(val));
}
});
// 收集被点击(置灰)的生肖
var excludedZodiacs = [];
$('.nf-zodiac.btn-gray', layero).each(function () {
excludedZodiacs.push($(this).data('zodiac'));
});
// 收集被点击(置灰)的波色
var excludedColors = [];
$('.nf-color-btn.btn-gray', layero).each(function () {
excludedColors.push($(this).data('color'));
});
// 收集被点击(置灰)的单双
var excludedParities = [];
$('.nf-parity.btn-gray', layero).each(function () {
excludedParities.push($(this).data('parity'));
});
// 收集所有区间
var ranges = [];
$('.nf-range-row', layero).each(function () {
var $row = $(this);
var min = parseInt($row.find('.nf-range-min').val()) || 1;
var max = parseInt($row.find('.nf-range-max').val()) || 49;
var mode = $row.find('.nf-range-mode').hasClass('btn-info') ? 'include' : 'exclude';
ranges.push({min: min, max: max, mode: mode});
});
$('.nf-number', layero).each(function () {
var $num = $(this);
var num = parseInt($num.data('num'));
var tail = $num.data('tail');
var animal = $num.data('animal');
var color = $num.data('color');
var parity = $num.data('parity');
var hidden = false;
// 尾号筛选:选中多个尾号,任一命中即屏蔽
if (excludedTails.length > 0 && excludedTails.indexOf(tail) !== -1) {
hidden = true;
}
// 单双筛选:选中单或双,匹配则屏蔽
if (excludedParities.indexOf(parity) !== -1) {
hidden = true;
}
// 区间筛选:在区间=白名单(OR)、排除区间=黑名单(OR)
if (!hidden) {
var includeRanges = [];
var excludeRanges = [];
for (var r = 0; r < ranges.length; r++) {
if (ranges[r].mode === 'include') {
includeRanges.push(ranges[r]);
} else {
excludeRanges.push(ranges[r]);
}
}
// 黑名单:任一排除区间命中则屏蔽
for (var r = 0; r < excludeRanges.length; r++) {
if (num >= excludeRanges[r].min && num <= excludeRanges[r].max) {
hidden = true;
break;
}
}
// 白名单:有在区间规则时,号码不在任一区间则屏蔽
if (!hidden && includeRanges.length > 0) {
var inAnyInclude = false;
for (var r = 0; r < includeRanges.length; r++) {
if (num >= includeRanges[r].min && num <= includeRanges[r].max) {
inAnyInclude = true;
break;
}
}
if (!inAnyInclude) {
hidden = true;
}
}
}
// 排除的生肖
if (excludedZodiacs.indexOf(animal) !== -1) {
hidden = true;
}
// 排除的波色
if (excludedColors.indexOf(color) !== -1) {
hidden = true;
}
// 手动屏蔽的号码
if (blockedNums.indexOf(num) !== -1) {
hidden = true;
}
if (hidden) {
$num.css('opacity', '0.25').css('filter', 'grayscale(100%)');
} else {
$num.css('opacity', '1').css('filter', 'none');
}
});
},
bindevent: function () {
Form.api.bindevent($("form[role=form]"));
},
/**
* 通用分析弹窗(波色、生肖、奇偶、大小、尾数)
*/
showAnalysisDialog: function (type) {
var titles = {
colorWave: __('Color Wave'),
zodiac: __('Zodiac'),
oddEven: __('Odd/Even'),
bigSmall: __('Big/Small'),
tailNumbers: __('Tail Numbers')
};
var endpoints = {
colorWave: 'history/colorWaveAnalysis',
zodiac: 'history/zodiacAnalysis',
oddEven: 'history/oddEvenAnalysis',
bigSmall: 'history/bigSmallAnalysis',
tailNumbers: 'history/tailNumbers'
};
var hasType = ['colorWave', 'zodiac', 'oddEven', 'bigSmall', 'tailNumbers'].indexOf(type) !== -1;
var html = '<div style="padding:20px;">';
if (hasType) {
html += '<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:15px;">' + __('Query Type') + '</label>' +
' <label class="radio-inline" style="margin-right:15px;">' +
' <input type="radio" name="analysis-type-' + type + '" value="all" checked> ' + __('All Numbers') +
' </label>' +
' <label class="radio-inline">' +
' <input type="radio" name="analysis-type-' + type + '" value="special"> ' + __('Special Only') +
' </label>' +
'</div>';
}
html += '<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="analysis-periods-' + type + '" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-analysis-' + type + '" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="analysis-result-' + type + '" style="margin-top:15px;overflow-x:auto;"></div>' +
'</div>';
Layer.open({
type: 1,
title: titles[type] || type,
area: ['650px', '550px'],
content: html,
shadeClose: true,
success: function (layero, index) {
$('#btn-analysis-' + type, layero).on('click', function () {
var periods = parseInt($('#analysis-periods-' + type, layero).val()) || 30;
var tp = hasType ? $('input[name="analysis-type-' + type + '"]:checked', layero).val() : 'all';
Controller.api.queryAnalysis(periods, tp, type, endpoints[type], layero);
});
if (hasType) {
$('input[name="analysis-type-' + type + '"]', layero).on('change', function () {
var periods = parseInt($('#analysis-periods-' + type, layero).val()) || 30;
Controller.api.queryAnalysis(periods, $(this).val(), type, endpoints[type], layero);
});
}
}
});
},
queryAnalysis: function (periods, type, analysisType, endpoint, layero) {
var $btn = $('#btn-analysis-' + analysisType, layero);
$btn.prop('disabled', true);
$('#analysis-result-' + analysisType, layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: endpoint,
type: 'GET',
data: {periods: periods, type: type},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
var renderMap = {
colorWave: 'renderColorWaveAnalysis',
zodiac: 'renderZodiacAnalysis',
oddEven: 'renderOddEvenAnalysis',
bigSmall: 'renderBigSmallAnalysis',
tailNumbers: 'renderTailNumbers'
};
var renderFn = renderMap[analysisType];
if (renderFn && typeof Controller.api[renderFn] === 'function') {
Controller.api[renderFn](ret.data, layero);
} else {
$('#analysis-result-' + analysisType, layero).html('<div class="alert alert-danger">渲染方法不存在: ' + renderFn + '</div>');
}
} else {
$('#analysis-result-' + analysisType, layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#analysis-result-' + analysisType, layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
renderColorWaveAnalysis: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;justify-content:space-around;">';
var items = [
{label: '红波', value: data.red, pct: data.red_pct, color: '#e74c3c'},
{label: '蓝波', value: data.blue, pct: data.blue_pct, color: '#3498db'},
{label: '绿波', value: data.green, pct: data.green_pct, color: '#2ecc71'}
];
for (var i = 0; i < items.length; i++) {
var item = items[i];
html += '<div style="text-align:center;padding:20px;border-radius:8px;background:#f5f5f5;min-width:120px;">' +
'<div style="width:60px;height:60px;line-height:60px;border-radius:50%;background-color:' + item.color + ';color:#fff;font-size:24px;font-weight:bold;margin:0 auto;">' + item.value + '</div>' +
'<div style="margin-top:10px;font-size:14px;color:#333;">' + item.label + '</div>' +
'<div style="font-size:12px;color:#999;">' + item.pct + '%</div>' +
'</div>';
}
html += '</div><div style="margin-top:15px;color:#999;font-size:12px;text-align:center;">总计: ' + data.total + ' 个号码</div></div>';
$('#analysis-result-colorWave', layero).html(html);
},
renderZodiacAnalysis: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;flex-wrap:wrap;gap:8px;">';
for (var i = 0; i < data.list.length; i++) {
var item = data.list[i];
html += '<div style="text-align:center;background:#f9f9f9;padding:10px 15px;border-radius:6px;min-width:90px;">' +
'<div style="font-size:18px;font-weight:bold;color:#333;">' + item.animal + '</div>' +
'<div style="font-size:12px;color:#666;">' + item.count + ' (' + item.percent + '%)</div>' +
'</div>';
}
html += '</div></div>';
$('#analysis-result-zodiac', layero).html(html);
},
renderOddEvenAnalysis: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;justify-content:space-around;margin-bottom:15px;">';
html += '<div style="text-align:center;padding:15px;border-radius:8px;background:#f5f5f5;min-width:140px;">' +
'<div style="font-size:28px;font-weight:bold;color:#e74c3c;">' + data.odd + '</div>' +
'<div style="font-size:14px;color:#333;">奇数</div>' +
'<div style="font-size:12px;color:#999;">' + data.odd_pct + '%</div></div>';
html += '<div style="text-align:center;padding:15px;border-radius:8px;background:#f5f5f5;min-width:140px;">' +
'<div style="font-size:28px;font-weight:bold;color:#3498db;">' + data.even + '</div>' +
'<div style="font-size:14px;color:#333;">偶数</div>' +
'<div style="font-size:12px;color:#999;">' + data.even_pct + '%</div></div>';
html += '</div></div>';
$('#analysis-result-oddEven', layero).html(html);
},
renderBigSmallAnalysis: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;justify-content:space-around;margin-bottom:15px;">';
html += '<div style="text-align:center;padding:15px;border-radius:8px;background:#f5f5f5;min-width:140px;">' +
'<div style="font-size:28px;font-weight:bold;color:#f39c12;">' + data.big + '</div>' +
'<div style="font-size:14px;color:#333;">大数(25-49)</div>' +
'<div style="font-size:12px;color:#999;">' + data.big_pct + '%</div></div>';
html += '<div style="text-align:center;padding:15px;border-radius:8px;background:#f5f5f5;min-width:140px;">' +
'<div style="font-size:28px;font-weight:bold;color:#2ecc71;">' + data.small + '</div>' +
'<div style="font-size:14px;color:#333;">小数(1-24)</div>' +
'<div style="font-size:12px;color:#999;">' + data.small_pct + '%</div></div>';
html += '</div></div>';
$('#analysis-result-bigSmall', layero).html(html);
},
renderTailNumbers: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;flex-wrap:wrap;gap:8px;">';
for (var i = 0; i < data.all.length; i++) {
var item = data.all[i];
html += '<div style="text-align:center;background:#f9f9f9;padding:10px 15px;border-radius:6px;min-width:70px;">' +
'<div style="font-size:22px;font-weight:bold;color:#333;">' + item.tail + '</div>' +
'<div style="font-size:12px;color:#666;">' + item.count + ' (' + item.percent + '%)</div></div>';
}
html += '</div></div>';
$('#analysis-result-tailNumbers', layero).html(html);
},
/**
* 和值分析弹窗
*/
showSumDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="sum-periods" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-sum-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="sum-result" style="margin-top:15px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: __('Sum Chart'),
area: ['750px', '400px'],
content: html,
shadeClose: true,
success: function (layero, index) {
$('#btn-sum-query', layero).on('click', function () {
var periods = parseInt($('#sum-periods', layero).val()) || 30;
Controller.api.querySum(periods, layero);
});
}
});
},
querySum: function (periods, layero) {
var $btn = $('#btn-sum-query', layero);
$btn.prop('disabled', true);
$('#sum-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/sumAnalysis',
type: 'GET',
data: {periods: periods},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderSum(ret.data, layero);
} else {
$('#sum-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#sum-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
renderSum: function (data, layero) {
var html = '<div style="padding:15px;"><div style="display:flex;justify-content:space-around;margin-bottom:15px;">';
html += '<div style="text-align:center;padding:10px;"><div style="font-size:20px;font-weight:bold;color:#333;">' + data.avg + '</div><div style="font-size:12px;color:#999;">平均和值</div></div>';
html += '<div style="text-align:center;padding:10px;"><div style="font-size:20px;font-weight:bold;color:#e74c3c;">' + data.max + '</div><div style="font-size:12px;color:#999;">最大和值</div></div>';
html += '<div style="text-align:center;padding:10px;"><div style="font-size:20px;font-weight:bold;color:#3498db;">' + data.min + '</div><div style="font-size:12px;color:#999;">最小和值</div></div>';
html += '</div><div id="sum-chart" style="width:100%;height:250px;"></div></div>';
$('#sum-result', layero).html(html);
var chartDom = document.getElementById('sum-chart');
if (chartDom && typeof echarts !== 'undefined') {
var myChart = echarts.init(chartDom);
myChart.setOption({
xAxis: {type: 'category', data: data.expects, axisLabel: {rotate: 45, fontSize: 10}},
yAxis: {type: 'value'},
series: [{type: 'line', data: data.sums, smooth: true, itemStyle: {color: '#3498db'}, areaStyle: {color: 'rgba(52,152,219,0.1)'}}],
grid: {left: 40, right: 20, bottom: 40, top: 20}
});
}
},
/**
* 连号分析弹窗
*/
showConsecutiveDialog: function () {
var html = '<div style="padding:20px;">' +
'<div class="form-group">' +
' <label>' + __('Query Periods') + '</label>' +
' <input type="number" id="consecutive-periods" class="form-control" value="30" min="10" max="100" style="width:120px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-consecutive-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
'</div>' +
'<div id="consecutive-result" style="margin-top:15px;overflow-x:auto;"></div>' +
'</div>';
Layer.open({
type: 1,
title: __('Consecutive'),
area: ['600px', '500px'],
content: html,
shadeClose: true,
success: function (layero, index) {
$('#btn-consecutive-query', layero).on('click', function () {
var periods = parseInt($('#consecutive-periods', layero).val()) || 30;
Controller.api.queryConsecutive(periods, layero);
});
}
});
},
queryConsecutive: function (periods, layero) {
var $btn = $('#btn-consecutive-query', layero);
$btn.prop('disabled', true);
$('#consecutive-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
$.ajax({
url: 'history/consecutiveNumbers',
type: 'GET',
data: {periods: periods},
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderConsecutive(ret.data, layero);
} else {
$('#consecutive-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
}
},
error: function () {
$('#consecutive-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
renderConsecutive: function (data, layero) {
var html = '<div style="padding:15px;">';
html += '<h4 style="margin:0 0 10px 0;border-bottom:1px solid #eee;padding-bottom:5px;">连号对</h4>';
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:15px;">';
var pairs = data.pairs;
if (pairs && Object.keys(pairs).length > 0) {
for (var pair in pairs) {
html += '<div style="background:#f5f5f5;padding:6px 12px;border-radius:4px;font-size:13px;">' + pair + ' <b>×' + pairs[pair] + '</b></div>';
}
} else {
html += '<div style="color:#999;font-size:13px;">暂无连号数据</div>';
}
html += '</div><h4 style="margin:0 0 10px 0;border-bottom:1px solid #eee;padding-bottom:5px;">三连号</h4>';
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;">';
var triples = data.triples;
if (triples && Object.keys(triples).length > 0) {
for (var triple in triples) {
html += '<div style="background:#f5f5f5;padding:6px 12px;border-radius:4px;font-size:13px;">' + triple + ' <b>×' + triples[triple] + '</b></div>';
}
} else {
html += '<div style="color:#999;font-size:13px;">暂无三连号数据</div>';
}
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);
},
/**
* 预测号码弹窗
*/
showPredictDialog: function () {
var html = '<div style="padding:20px;">' +
'<div style="margin-bottom:15px;background:#fff3cd;border:1px solid #ffc107;padding:10px;border-radius:6px;">' +
' <div style="font-size:13px;font-weight:bold;color:#856404;margin-bottom:8px;"><i class="fa fa-lightbulb-o"></i> 预测算法说明</div>' +
' <div style="font-size:12px;color:#666;">' +
' <b>V1版本</b>:基于转移概率分析(区域、生肖、尾号、首号、波色转移 + 冷热系数)<br>' +
' <b>V2版本</b>:基于统计回归分析(遗漏回归、频率回归、区域平衡、波色平衡等)+ 历史回测验证<br>' +
' <b>V3版本(推荐)</b>:多维度综合预测,新增转移概率(马尔可夫链)、单双规律、大小规律、走势方向分析' +
' </div>' +
'</div>' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">算法版本:</label>' +
' <label class="radio-inline" style="margin-right:15px;"><input type="radio" name="predict-version" value="v3" checked> <b>V3</b>(多维度综合)</label>' +
' <label class="radio-inline" style="margin-right:15px;"><input type="radio" name="predict-version" value="v2"> V2(统计回归)</label>' +
' <label class="radio-inline"><input type="radio" name="predict-version" value="v1"> V1(转移概率)</label>' +
'</div>' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">验证期号:</label>' +
' <input type="text" id="predict-target" class="form-control" placeholder="输入期号验证历史预测(可选)" style="width:180px;display:inline-block;">' +
' <span style="font-size:11px;color:#999;margin-left:5px;">留空则预测下一期</span>' +
'</div>' +
'<div class="form-group" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">统计期数:</label>' +
' <input type="number" id="predict-periods" class="form-control" value="200" min="30" max="500" style="width:120px;display:inline-block;">' +
' <span style="font-size:11px;color:#999;margin-left:5px;">建议200期以上</span>' +
'</div>' +
// V3权重配置
'<div id="predict-v3-weights" style="border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">V3权重配置:</label>' +
' <div style="display:flex;flex-wrap:wrap;gap:10px;font-size:12px;">' +
' <div>遗漏回归: <input type="number" class="predict-weight-v3" data-key="omit_regression" value="0.20" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>频率回归: <input type="number" class="predict-weight-v3" data-key="freq_regression" value="0.15" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>转移概率: <input type="number" class="predict-weight-v3" data-key="transition_prob" value="0.20" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>单双平衡: <input type="number" class="predict-weight-v3" data-key="oddeven_balance" value="0.10" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>大小平衡: <input type="number" class="predict-weight-v3" data-key="bigsmall_balance" value="0.10" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>走势方向: <input type="number" class="predict-weight-v3" data-key="trend_direction" value="0.15" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>区域平衡: <input type="number" class="predict-weight-v3" data-key="zone_balance" value="0.05" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>波色平衡: <input type="number" class="predict-weight-v3" data-key="color_balance" value="0.05" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' </div>' +
'</div>' +
// V2权重配置
'<div id="predict-v2-weights" style="display:none;border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">V2权重配置:</label>' +
' <div style="display:flex;flex-wrap:wrap;gap:10px;font-size:12px;">' +
' <div>遗漏回归: <input type="number" class="predict-weight-v2" data-key="omit_regression" value="0.30" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>频率回归: <input type="number" class="predict-weight-v2" data-key="freq_regression" value="0.25" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>近期趋势: <input type="number" class="predict-weight-v2" data-key="recent_trend" value="0.20" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>区域平衡: <input type="number" class="predict-weight-v2" data-key="zone_balance" value="0.10" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>波色平衡: <input type="number" class="predict-weight-v2" data-key="color_balance" value="0.08" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>统计显著性: <input type="number" class="predict-weight-v2" data-key="stat_significance" value="0.07" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' </div>' +
'</div>' +
// V1权重配置
'<div id="predict-v1-weights" style="display:none;border-bottom:1px solid #eee;padding-bottom:10px;margin-bottom:10px;">' +
' <label style="margin-right:10px;">V1权重配置:</label>' +
' <div style="display:flex;flex-wrap:wrap;gap:10px;font-size:12px;">' +
' <div>区域: <input type="number" class="predict-weight" data-key="zone" value="0.25" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>生肖: <input type="number" class="predict-weight" data-key="zodiac" value="0.20" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>尾号: <input type="number" class="predict-weight" data-key="tail" value="0.20" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>首号: <input type="number" class="predict-weight" data-key="head" value="0.15" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>波色: <input type="number" class="predict-weight" data-key="color" value="0.10" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' <div>冷热: <input type="number" class="predict-weight" data-key="hotcold" value="0.10" min="0" max="1" step="0.05" style="width:60px;"></div>' +
' </div>' +
'</div>' +
'<div style="text-align:center;margin-bottom:15px;">' +
' <button class="btn btn-primary" id="btn-predict-query"><i class="fa fa-magic"></i> 开始预测</button>' +
'</div>' +
'<div id="predict-result" style="margin-top:15px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: '🎯 智能预测号码',
area: ['850px', '750px'],
content: html,
shadeClose: true,
success: function (layero, index) {
// 切换版本时显示对应权重配置
$('input[name="predict-version"]', layero).on('change', function () {
var val = $(this).val();
$('#predict-v3-weights', layero).hide();
$('#predict-v2-weights', layero).hide();
$('#predict-v1-weights', layero).hide();
if (val === 'v3') {
$('#predict-v3-weights', layero).show();
$('#predict-periods', layero).val(200);
} else if (val === 'v2') {
$('#predict-v2-weights', layero).show();
$('#predict-periods', layero).val(200);
} else {
$('#predict-v1-weights', layero).show();
$('#predict-periods', layero).val(100);
}
});
$('#btn-predict-query', layero).on('click', function () {
Controller.api.queryPredict(layero);
});
}
});
},
/**
* 查询预测结果
*/
queryPredict: function (layero) {
var $btn = $('#btn-predict-query', layero);
$btn.prop('disabled', true);
$('#predict-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> 正在分析历史数据...</div>');
var version = $('input[name="predict-version"]:checked', layero).val();
var periods = parseInt($('#predict-periods', layero).val()) || 200;
var targetExpect = $('#predict-target', layero).val().trim();
var weights = {};
// 根据版本获取权重
if (version === 'v3') {
$('.predict-weight-v3', layero).each(function () {
var key = $(this).data('key');
var val = parseFloat($(this).val()) || 0;
weights[key] = val;
});
} else if (version === 'v2') {
$('.predict-weight-v2', layero).each(function () {
var key = $(this).data('key');
var val = parseFloat($(this).val()) || 0;
weights[key] = val;
});
} else {
$('.predict-weight', layero).each(function () {
var key = $(this).data('key');
var val = parseFloat($(this).val()) || 0;
weights[key] = val;
});
}
// 根据版本选择URL
var url = 'history/predict';
if (version === 'v3') {
url = 'history/predictV3';
} else if (version === 'v2') {
url = 'history/predictV2';
}
$.ajax({
url: url,
type: 'GET',
data: { periods: periods, weights: JSON.stringify(weights), target_expect: targetExpect },
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderPredict(ret.data, layero, version);
} else {
$('#predict-result', layero).html('<div class="alert alert-danger">' + (ret.msg || '预测失败') + '</div>');
}
},
error: function () {
$('#predict-result', layero).html('<div class="alert alert-danger">预测请求失败</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
/**
* 渲染预测结果
*/
renderPredict: function (data, layero, version) {
var predictions = data.predictions || [];
var analysis = data.analysis || {};
var hitInfo = data.hit_info || null;
var actualResult = data.actual_result || null;
var backtest = data.backtest || null;
var confidence = data.confidence || null;
if (predictions.length === 0) {
$('#predict-result', layero).html('<div class="alert alert-info">暂无预测结果</div>');
return;
}
// 上期特码信息
var lastSpecial = analysis.last_special || 0;
var lastExpect = analysis.last_expect || '';
var lastColor = Controller.api.getColorByNum(lastSpecial);
var lastAnimal = Controller.api.getAnimalByNum(lastSpecial);
// 版本名称映射
var versionNames = {
'v1': 'V1(转移概率)',
'v2': 'V2(统计回归)',
'v3': 'V3(多维度综合)'
};
var html = '<div style="padding:10px;">';
// 基准期号标题
html += '<div style="font-size:12px;color:#666;margin-bottom:10px;">基于期号 <b>' + lastExpect + '</b>(特码 ' + lastSpecial + ')进行预测 | 算法版本: <b>' + versionNames[version] + '</b></div>';
// 置信度评估展示(V2和V3版本)
if (confidence && (version === 'v2' || version === 'v3')) {
html += '<div style="background:#fff8e1;border:1px solid #ffb300;border-radius:6px;padding:12px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;color:#ff8f00;margin-bottom:8px;"><i class="fa fa-star-half-o"></i> 预测置信度评估</div>';
// 数据警告提示(数据不足时显示)
if (confidence.data_warning) {
html += '<div style="font-size:11px;color:#d32f2f;background:#ffebee;padding:6px;border-radius:4px;margin-bottom:8px;"><i class="fa fa-exclamation-triangle"></i> ' + confidence.data_warning + '</div>';
}
html += '<div style="display:flex;gap:20px;align-items:center;">';
html += '<div style="text-align:center;"><div style="font-size:24px;font-weight:bold;color:#ff8f00;">' + confidence.overall_confidence + '%</div><div style="font-size:12px;color:#666;">整体置信度</div></div>';
// 各排名置信度(使用得分集中度维度)
if (confidence.confidence_scores && confidence.confidence_scores.length > 0) {
html += '<div style="display:flex;gap:8px;">';
for (var i = 0; i < confidence.confidence_scores.length; i++) {
var cs = confidence.confidence_scores[i];
// 阈值定义:>=70%高(绿)、50-70%中(橙)、<50%低(红)
var confLevel = cs.confidence >= 70 ? '高' : (cs.confidence >= 50 ? '中' : '低');
var confColor = cs.confidence >= 70 ? '#4caf50' : (cs.confidence >= 50 ? '#ff9800' : '#f44336');
html += '<div style="text-align:center;padding:5px;background:#fff;border-radius:4px;">';
html += '<div style="font-size:14px;font-weight:bold;color:' + confColor + ';">' + cs.confidence + '%</div>';
html += '<div style="font-size:10px;color:#999;">#' + cs.rank + '</div>';
html += '</div>';
}
html += '</div>';
}
html += '</div></div>';
}
// 回测验证结果(V2和V3版本)
if (backtest && (version === 'v2' || version === 'v3')) {
html += '<div style="background:#e3f2fd;border:1px solid #2196f3;border-radius:6px;padding:12px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;color:#1565c0;margin-bottom:8px;"><i class="fa fa-chart-line"></i> 历史回测验证(最近' + backtest.total_tests + '期)</div>';
// 回测数据警告提示
if (backtest.data_warning) {
html += '<div style="font-size:11px;color:#d32f2f;background:#ffebee;padding:6px;border-radius:4px;margin-bottom:8px;"><i class="fa fa-exclamation-triangle"></i> ' + backtest.data_warning + '</div>';
}
html += '<div style="display:flex;gap:15px;flex-wrap:wrap;">';
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#2196f3;">' + backtest.hit_rate + '%</div><div style="font-size:11px;color:#666;">命中率(Top5</div></div>';
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#4caf50;">' + backtest.total_hits + '/' + backtest.total_tests + '</div><div style="font-size:11px;color:#666;">命中次数</div></div>';
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#ff9800;">' + (backtest.avg_rank || '—') + '</div><div style="font-size:11px;color:#666;">平均排名</div></div>';
// 新增指标:NDCG@5 和 MRR(百分比展示)
if (backtest.ndcg_5 !== undefined) {
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#9c27b0;">' + (backtest.ndcg_5 * 100).toFixed(1) + '%</div><div style="font-size:11px;color:#666;">NDCG@5</div></div>';
}
if (backtest.mrr !== undefined) {
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#00bcd4;">' + (backtest.mrr * 100).toFixed(1) + '%</div><div style="font-size:11px;color:#666;">MRR</div></div>';
}
// 转移概率阶数显示(来自analysis.transition_order字段)
if (analysis && analysis.transition_order !== undefined) {
html += '<div style="text-align:center;padding:8px;"><div style="font-size:22px;font-weight:bold;color:#607d8b;">' + analysis.transition_order + '阶</div><div style="font-size:11px;color:#666;">转移概率</div></div>';
}
html += '</div>';
// 命中分布柱状图(使用rank_1..rank_5键名)
if (backtest.hit_distribution && Object.keys(backtest.hit_distribution).length > 0) {
var distribution = backtest.hit_distribution;
var maxHit = 0;
// 找最大值用于计算柱状图高度比例
for (var r = 1; r <= 5; r++) {
var key = 'rank_' + r;
if (distribution[key] > maxHit) {
maxHit = distribution[key];
}
}
html += '<div style="margin-top:10px;font-size:11px;color:#666;">命中分布(各排名命中次数):</div>';
html += '<div style="display:flex;gap:8px;align-items:flex-end;height:60px;margin-top:5px;padding:5px;background:#f5f5f5;border-radius:4px;">';
for (var r = 1; r <= 5; r++) {
var key = 'rank_' + r;
var hitCount = distribution[key] || 0;
var barHeight = maxHit > 0 ? (hitCount / maxHit * 45) : 0;
var barColor = hitCount > 0 ? '#4caf50' : '#e0e0e0';
html += '<div style="text-align:center;min-width:50px;">';
html += '<div style="height:' + barHeight + 'px;background:' + barColor + ';border-radius:2px 2px 0 0;width:35px;margin:0 auto;"></div>';
html += '<div style="font-size:10px;color:#666;margin-top:2px;">#' + r + '</div>';
html += '<div style="font-size:11px;color:#333;font-weight:bold;">' + hitCount + '</div>';
html += '</div>';
}
html += '</div>';
}
// 回测详情
if (backtest.details && backtest.details.length > 0) {
html += '<div style="margin-top:10px;font-size:11px;color:#666;">最近' + backtest.details.length + '期验证详情:</div>';
html += '<div style="margin-top:5px;max-height:300px;overflow-y:auto;">';
for (var i = 0; i < backtest.details.length; i++) {
var bd = backtest.details[i];
var hitTag = bd.hit ? '<span style="color:#4caf50;">✓</span>' : '<span style="color:#f44336;">✗</span>';
html += '<div style="font-size:11px;padding:3px 0;border-bottom:1px dashed #eee;">期号' + bd.expect + ': 实际<b>' + bd.actual + '</b> ' + hitTag + ' 预测[' + bd.predictions.join(',') + ']</div>';
}
html += '</div>';
}
html += '</div>';
}
// V3版本特有的分析信息
if (version === 'v3' && analysis) {
html += '<div style="background:#f3e5f5;border:1px solid #9c27b0;border-radius:6px;padding:10px;margin-bottom:15px;font-size:12px;">';
html += '<div style="font-weight:bold;color:#7b1fa2;margin-bottom:8px;"><i class="fa fa-chart-bar"></i> V3多维度分析</div>';
// 单双统计
if (analysis.oddeven_stats) {
var oe = analysis.oddeven_stats;
html += '<div style="margin-bottom:5px;"><b>单双规律:</b> 单号' + oe.odd_pct + '% / 双号' + oe.even_pct + '%';
if (oe.recent_streak >= 2) {
html += ' | 近期连续' + (oe.recent_type === 'odd' ? '单号' : '双号') + oe.recent_streak + '期';
html += '(平均连续' + (oe.recent_type === 'odd' ? oe.avg_odd_streak : oe.avg_even_streak) + '期)';
}
html += '</div>';
}
// 大小统计
if (analysis.bigsmall_stats) {
var bs = analysis.bigsmall_stats;
html += '<div style="margin-bottom:5px;"><b>大小规律:</b> 大号' + bs.big_pct + '% / 小号' + bs.small_pct + '%';
if (bs.recent_streak >= 2) {
html += ' | 近期连续' + (bs.recent_type === 'big' ? '大号' : '小号') + bs.recent_streak + '期';
html += '(平均连续' + (bs.recent_type === 'big' ? bs.avg_big_streak : bs.avg_small_streak) + '期)';
}
html += '</div>';
}
// 走势方向
if (analysis.trend_direction) {
var td = analysis.trend_direction;
var trendNames = { 'ascending': '上升(号码减小)', 'descending': '下降(号码增大)', 'jump': '跳跃震荡' };
html += '<div style="margin-bottom:5px;"><b>走势方向:</b> ' + trendNames[td.trend_type];
html += ' | 强度' + (td.trend_strength * 100).toFixed(0) + '% | 平均变化' + td.avg_change + '</div>';
}
// 上期属性
html += '<div><b>上期属性:</b> 区域[' + (analysis.last_zone || '') + '] 尾数[' + (analysis.last_tail || '') + '] 首号[' + (analysis.last_head || '') + ']</div>';
html += '</div>';
}
// 遗漏统计信息(V2和V3版本)
if (analysis.omit_stats && (version === 'v2' || version === 'v3')) {
html += '<div style="background:#fce4ec;border:1px solid #e91e63;border-radius:6px;padding:10px;margin-bottom:15px;font-size:12px;">';
html += '<b>遗漏值统计:</b> 平均遗漏 ' + (analysis.omit_stats.avg || 0).toFixed(1) + ' 期 | 最大遗漏 ' + (analysis.omit_stats.max || 0) + ' 期 | 期望频率 ' + analysis.expected_freq + '';
html += '</div>';
}
// 命中结果(验证模式下显示)
if (hitInfo && actualResult) {
var hitBgColor = hitInfo.hit ? '#d4edda' : '#f8d7da';
var hitBorderColor = hitInfo.hit ? '#28a745' : '#dc3545';
var hitTitleColor = hitInfo.hit ? '#155724' : '#721c24';
var actualColorHex = Controller.api.getColorByNum(hitInfo.actual_num);
html += '<div style="background:' + hitBgColor + ';border:2px solid ' + hitBorderColor + ';border-radius:6px;padding:12px;margin-bottom:15px;">';
html += '<div style="font-size:14px;font-weight:bold;color:' + hitTitleColor + ';margin-bottom:8px;">';
if (hitInfo.hit) {
html += '<i class="fa fa-check-circle"></i> 预测命中!排名第 ' + hitInfo.rank + ' 位';
} else {
html += '<i class="fa fa-times-circle"></i> 未命中(实际号码不在预测Top' + predictions.length + '中)';
}
html += '</div>';
html += '<div style="display:flex;align-items:center;gap:15px;">';
html += '<div style="font-size:12px;">期号 <b>' + hitInfo.actual_expect + '</b> 实际开奖:</div>';
html += '<span style="display:inline-block;width:40px;height:40px;line-height:40px;text-align:center;border-radius:50%;color:#fff;background-color:' + actualColorHex + ';font-weight:bold;font-size:18px;">' + hitInfo.actual_num + '</span>';
html += '<div style="font-size:12px;">' + hitInfo.actual_animal + ' / ' + hitInfo.actual_color + '</div>';
html += '</div></div>';
}
// 预测号码列表
var topCount = predictions.length;
html += '<div style="background:#e8f5e9;border:1px solid #4caf50;border-radius:6px;padding:12px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;color:#2e7d32;margin-bottom:10px;"><i class="fa fa-bullseye"></i> 预测推荐号码(Top ' + topCount + '</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:10px;">';
for (var i = 0; i < predictions.length; i++) {
var p = predictions[i];
var colorHex = Controller.api.getColorByNum(p.num);
var animal = Controller.api.getAnimalByNum(p.num);
// 验证模式下,命中号码高亮
var isHit = hitInfo && hitInfo.hit && p.num === hitInfo.actual_num;
var cardBg = isHit ? '#fffacd' : '#fff';
var cardBorder = isHit ? '2px solid #f39c12' : 'none';
var rankBadge = i < 3 ? '<span style="position:absolute;top:-5px;right:-5px;background:#f39c12;color:#fff;font-size:10px;padding:2px 4px;border-radius:50%;">' + (i + 1) + '</span>' : '';
// 根据版本显示不同的详情信息
var detailInfo = '';
if (p.detail) {
if (version === 'v3') {
// V3版本显示更多维度信息
var omitInfo = p.detail.omit || 0;
var transScore = p.detail.trans_score || 0;
var oddevenScore = p.detail.oddeven_score || 0;
var bigsmallScore = p.detail.bigsmall_score || 0;
var trendScore = p.detail.trend_score || 0;
detailInfo = '<div style="font-size:9px;color:#999;line-height:1.3;">';
detailInfo += '遗漏:' + omitInfo + '期 | ';
detailInfo += '转移:' + transScore + ' | ';
detailInfo += (p.detail.is_odd ? '单' : '双') + ':' + oddevenScore + ' | ';
detailInfo += (p.detail.is_big ? '大' : '小') + ':' + bigsmallScore;
detailInfo += '</div>';
} else if (version === 'v2') {
var omitInfo = p.detail.omit || 0;
detailInfo = '<div style="font-size:9px;color:#999;">遗漏:' + omitInfo + '期</div>';
}
}
html += '<div style="text-align:center;background:' + cardBg + ';border:' + cardBorder + ';padding:8px;border-radius:8px;min-width:80px;position:relative;box-shadow:0 1px 3px rgba(0,0,0,0.1);">' + rankBadge;
html += '<span style="display:inline-block;width:36px;height:36px;line-height:36px;text-align:center;border-radius:50%;color:#fff;background-color:' + colorHex + ';font-weight:bold;font-size:16px;">' + p.num + '</span>';
html += '<div style="font-size:10px;color:#666;line-height:1.2;margin-top:2px;">' + animal + '</div>';
html += '<div style="font-size:11px;color:#2e7d32;font-weight:bold;">得分:' + p.score + '</div>';
// 显示置信度(V3版本)
if (version === 'v3' && confidence && confidence.confidence_scores) {
var csForNum = confidence.confidence_scores.find(function(c) { return c.num === p.num; });
if (csForNum) {
// 阈值定义:>=70%高(绿)、50-70%中(橙)、<50%低(红)
var confLevel = csForNum.confidence >= 70 ? '高' : (csForNum.confidence >= 50 ? '中' : '低');
var confColor = csForNum.confidence >= 70 ? '#4caf50' : (csForNum.confidence >= 50 ? '#ff9800' : '#f44336');
html += '<div style="font-size:10px;"><span style="color:' + confColor + ';font-weight:bold;">置信度:' + confLevel + '</span> <span style="color:#666;">(' + csForNum.confidence + '%)</span></div>';
}
}
html += detailInfo;
html += '</div>';
}
html += '</div></div>';
html += '</div>';
$('#predict-result', layero).html(html);
},
/**
* 显示正码关联预测弹窗(修正版)
* 核心规律:上期正码 → 当期特码
*/
showNormalRelationDialog: function () {
var html = '<div style="padding:20px;">' +
'<div style="margin-bottom:15px;background:#e8f5e9;border:1px solid #4caf50;padding:10px;border-radius:6px;">' +
' <div style="font-size:13px;font-weight:bold;color:#2e7d32;margin-bottom:8px;"><i class="fa fa-link"></i> 正码关联预测算法(修正版)</div>' +
' <div style="font-size:12px;color:#666;">' +
' <b>核心逻辑</b>:用<b>上期正码(num1-6)</b>预测<b>当期特码(num7)</b><br>' +
' <b>1. 覆盖区间规律(91.44%</b>:当期特码在上期正码覆盖的细区间内<br>' +
' <b>2. 特码区间转移(77.54%</b>:基于上期特码区间预测当期特码大区间<br>' +
' <b>3. 双波色预测(69.52%</b>:当期特码波色在上期正码前2种主导波色内<br>' +
' <b>4. 正码±3距离(59.36%</b>:当期特码与上期正码某号码距离≤3<br>' +
' <b>5. 尾数±250%</b>:上期正码和值尾数与当期特码尾数差≤2<br>' +
' <b>6. 平均值±1041.98%</b>:当期特码在上期正码平均值±10范围' +
' </div>' +
'</div>' +
'<div class="form-group">' +
' <label>回测期数:</label>' +
' <input type="number" id="nr-periods" class="form-control" value="100" min="30" max="500" style="width:120px;display:inline-block;">' +
' <label style="margin-left:15px;">目标期号(可选):</label>' +
' <input type="text" id="nr-target" class="form-control" placeholder="如2026120" style="width:150px;display:inline-block;">' +
' <button class="btn btn-primary" id="btn-nr-query" style="margin-left:10px;"><i class="fa fa-search"></i> 查询</button>' +
'</div>' +
'<div id="nr-result" style="margin-top:15px;"></div>' +
'</div>';
Layer.open({
type: 1,
title: '正码关联预测(上期正码→当期特码)',
area: ['900px', '750px'],
content: html,
shadeClose: true,
success: function (layero, index) {
$('#btn-nr-query', layero).on('click', function () {
Controller.api.queryNormalRelation(layero);
});
// 自动执行一次查询
Controller.api.queryNormalRelation(layero);
}
});
},
/**
* 查询正码关联预测
*/
queryNormalRelation: function (layero) {
var $btn = $('#btn-nr-query', layero);
$btn.prop('disabled', true);
$('#nr-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> 正在分析...</div>');
var periods = parseInt($('#nr-periods', layero).val()) || 100;
var targetExpect = $('#nr-target', layero).val().trim();
$.ajax({
url: 'history/predictByNormalRelation',
type: 'GET',
data: { periods: periods, target_expect: targetExpect },
dataType: 'json',
success: function (ret) {
if (ret.code == 1) {
Controller.api.renderNormalRelation(ret.data, layero);
} else {
$('#nr-result', layero).html('<div class="alert alert-danger">' + (ret.msg || '查询失败') + '</div>');
}
},
error: function () {
$('#nr-result', layero).html('<div class="alert alert-danger">查询失败</div>');
},
complete: function () {
$btn.prop('disabled', false);
}
});
},
/**
* 渲染正码关联预测结果
*/
renderNormalRelation: function (data, layero) {
if (!data || !data.predictions || data.predictions.length === 0) {
$('#nr-result', layero).html('<div class="alert alert-info">无预测结果</div>');
return;
}
var analysis = data.analysis || {};
var predictions = data.predictions;
var hitInfo = data.hit_info;
var backtest = data.backtest;
var html = '';
// 分析信息
html += '<div style="background:#f5f5f5;padding:10px;border-radius:6px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;"><i class="fa fa-info-circle"></i> 上期开奖信息(预测基准)</div>';
html += '<div style="font-size:12px;">';
html += '期号:<b>' + analysis.last_expect + '</b> | ';
html += '正码:<b>' + analysis.last_normals.join(', ') + '</b> | ';
html += '特码:<b>' + analysis.last_special + '</b><br>';
html += '正码范围:' + analysis.normal_min + ' ~ ' + analysis.normal_max + ' | ';
html += '平均值:' + analysis.normal_avg + ' | ';
html += '和值:' + analysis.normal_sum + '<br>';
html += '正码波色:<b>' + (analysis.top2_colors || analysis.normal_colors || []).join('/') + '</b> | ';
html += '覆盖区间:' + (analysis.normal_fine_zones || analysis.normal_zones || []).map(function(z) {
return ['1-10','11-20','21-30','31-40','41-49'][z];
}).join(',');
html += '</div></div>';
// 预测号码展示
html += '<div style="margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;"><i class="fa fa-star"></i> 推荐号码(Top 15</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;">';
for (var i = 0; i < predictions.length; i++) {
var p = predictions[i];
var colorHex = Controller.api.getColorByNum(p.num);
var animal = Controller.api.animalMap[p.num] || '';
var bgColor = '#fff';
var borderColor = '1px solid #ddd';
if (i < 5) {
bgColor = '#fff8e1';
borderColor = '2px solid #ffc107';
}
html += '<div style="background:' + bgColor + ';border:' + borderColor + ';padding:8px;border-radius:8px;min-width:90px;text-align:center;">';
html += '<span style="display:inline-block;width:36px;height:36px;line-height:36px;border-radius:50%;color:#fff;background:' + colorHex + ';font-weight:bold;font-size:16px;">' + p.num + '</span>';
html += '<div style="font-size:10px;color:#666;">' + animal + '</div>';
html += '<div style="font-size:11px;color:#2e7d32;font-weight:bold;">得分:' + p.score + '</div>';
html += '<div style="font-size:9px;color:#999;">';
html += (p.color_in_top2 || p.color_match) ? '✓波色 ' : '';
html += '距离' + (p.min_distance || 0);
html += '</div>';
html += '</div>';
}
html += '</div></div>';
// 规律命中情况(如果有回测)
if (backtest) {
html += '<div style="background:#e3f2fd;padding:10px;border-radius:6px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;"><i class="fa fa-chart-line"></i> 回测验证(最近' + backtest.periods + '期)</div>';
html += '<div style="font-size:12px;">';
html += '命中率(Top15内):<b style="color:#2e7d32;">' + backtest.hit_rate + '%</b> (' + backtest.hits + '/' + backtest.periods + ')<br>';
html += '平均排名:<b>' + backtest.avg_rank + '</b> / 49<br>';
html += '<span style="color:#666;font-size:11px;">注:命中率越高越好,平均排名越低越好</span>';
html += '</div>';
// 显示前50期命中详情表格
if (backtest.details && backtest.details.length > 0) {
html += '<div style="margin-top:10px;max-height:300px;overflow-y:auto;">';
html += '<table class="table table-striped table-bordered" style="font-size:11px;">';
html += '<thead><tr><th>期号</th><th>特码</th><th>排名</th><th>命中</th></tr></thead>';
html += '<tbody>';
for (var d = 0; d < backtest.details.length; d++) {
var det = backtest.details[d];
var hitBadge = det.hit ? '<span style="color:#2e7d32;font-weight:bold;">✓</span>' : '<span style="color:#c62828;">✗</span>';
var rankColor = det.rank <= 5 ? '#2e7d32' : (det.rank <= 15 ? '#ff9800' : '#c62828');
html += '<tr>';
html += '<td>' + det.expect + '</td>';
html += '<td>' + det.actual + '</td>';
html += '<td><b style="color:' + rankColor + ';">' + det.rank + '</b></td>';
html += '<td>' + hitBadge + '</td>';
html += '</tr>';
}
html += '</tbody></table>';
html += '</div>';
}
html += '</div>';
}
// 实际命中情况(如果有目标期号)
if (hitInfo) {
var hitBg = hitInfo.hit ? '#e8f5e9' : '#ffebee';
var hitColor = hitInfo.hit ? '#2e7d32' : '#c62828';
html += '<div style="background:' + hitBg + ';padding:10px;border-radius:6px;margin-bottom:15px;">';
html += '<div style="font-size:13px;font-weight:bold;color:' + hitColor + ';">';
html += hitInfo.hit ? '✓ 命中!排名:' + hitInfo.rank_in_top + ' / 15' : '✗ 未命中,排名:' + hitInfo.rank_in_all + ' / 49';
html += '</div>';
html += '<div style="font-size:12px;">实际特码:<b>' + hitInfo.actual_num + '</b> (' + hitInfo.actual_color + '/' + hitInfo.actual_animal + ') 期号:' + hitInfo.actual_expect + '</div>';
html += '</div>';
}
// 规律说明表
if (analysis.rules) {
html += '<div style="margin-top:15px;">';
html += '<div style="font-size:13px;font-weight:bold;margin-bottom:8px;"><i class="fa fa-table"></i> 规律命中率表</div>';
html += '<table class="table table-bordered" style="font-size:12px;">';
html += '<tr><th>规律名称</th><th>命中率</th><th>说明</th></tr>';
for (var r = 0; r < analysis.rules.length; r++) {
var rule = analysis.rules[r];
html += '<tr><td>' + rule.name + '</td><td><b style="color:#2e7d32;">' + rule.rate + '</b></td><td>' + rule.desc + '</td></tr>';
}
html += '</table></div>';
}
$('#nr-result', layero).html(html);
}
}
};
return Controller;
});