Files
amlhc/public/assets/js/backend/history.js
T

1054 lines
57 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},
]
]
});
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-dashboard').on('click', '.btn-dashboard', function () {
Controller.api.showDashboard();
});
},
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);
},
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);
}
}
};
return Controller;
});