Compare commits
13 Commits
5e8d1e4895
...
bf469e33ee
| Author | SHA1 | Date | |
|---|---|---|---|
| bf469e33ee | |||
| 61e748e020 | |||
| bd881f239e | |||
| 79f1a1dc80 | |||
| 074aa4d677 | |||
| 6e2e4cdc98 | |||
| 29e364f74a | |||
| 0b3f7210e0 | |||
| 8ed4837992 | |||
| f36410dcc6 | |||
| 7e4b6a3443 | |||
| 616239392a | |||
| f2b432a9f4 |
+121
-3
@@ -6,7 +6,16 @@
|
|||||||
|
|
||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
- [ ] **Phase 1: 遗漏号码分析** - 在 history 页面添加"遗漏号码"按钮,弹窗支持输入期数查询并展示遗漏号码、遗漏期数及波色球
|
- [x] **Phase 1: 遗漏号码分析** - 在 history 页面添加"遗漏号码"按钮,弹窗支持输入期数查询并展示遗漏号码、遗漏期数及波色球
|
||||||
|
- [ ] **Phase 2: 走势图分析** - 可视化展示号码在连续期次中的出现趋势
|
||||||
|
- [ ] **Phase 3: 冷热号码分析** - 统计某段时间内出现频率最高和最少的号码
|
||||||
|
- [ ] **Phase 4: 波色分析** - 统计红蓝绿波的出现比例和趋势
|
||||||
|
- [ ] **Phase 5: 生肖分析** - 统计各生肖出现频率和遗漏情况
|
||||||
|
- [ ] **Phase 6: 奇偶分析** - 统计每期奇数/偶数的比例
|
||||||
|
- [ ] **Phase 7: 大小分析** - 按号码大小(1-24为小,25-49为大)统计
|
||||||
|
- [ ] **Phase 8: 和值分析** - 每期号码之和的趋势
|
||||||
|
- [ ] **Phase 9: 连号分析** - 连续出现的号码对/三连号统计
|
||||||
|
- [ ] **Phase 10: 尾数分析** - 按尾数(0-9)分组统计
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -27,11 +36,120 @@ Plans:
|
|||||||
|
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 2: 走势图分析
|
||||||
|
**Goal**: 以折线图或网格形式可视化展示各号码在最近期次中的出现情况,辅助发现规律
|
||||||
|
**Depends on**: 1 (使用已有的 history.js 和 History 模型)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 用户可通过新按钮打开走势图弹窗
|
||||||
|
2. 可选择期数范围(默认 30 期)
|
||||||
|
3. 图表展示 1-49 号码在各期的出现/未出现状态
|
||||||
|
4. 支持 num1~num7 全部号码或仅特码筛选
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 3: 冷热号码分析
|
||||||
|
**Goal**: 统计某段时间内出现频率最高(热)和最少(冷)的号码
|
||||||
|
**Depends on**: 1 (复用数据查询逻辑)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 用户可选择统计期数范围
|
||||||
|
2. 展示热号(高频)和冷号(低频)列表
|
||||||
|
3. 显示每个号码的出现次数和百分比
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 4: 波色分析
|
||||||
|
**Goal**: 统计红、蓝、绿波的出现比例和趋势
|
||||||
|
**Depends on**: 1 (复用 Num 模型和颜色映射)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示三种波色的出现次数和占比
|
||||||
|
2. 可视化展示波色在最近期次中的分布
|
||||||
|
3. 支持特码波色单独统计
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 5: 生肖分析
|
||||||
|
**Goal**: 统计各生肖出现频率和遗漏情况
|
||||||
|
**Depends on**: 1 (复用 getAnimalMap 接口)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示 12 生肖的出现次数排名
|
||||||
|
2. 显示各生肖遗漏期数
|
||||||
|
3. 支持特码生肖单独统计
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 6: 奇偶分析
|
||||||
|
**Goal**: 统计每期奇数/偶数的比例
|
||||||
|
**Depends on**: 1 (复用 history 数据)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示总体奇偶比例
|
||||||
|
2. 展示每期奇偶数量
|
||||||
|
3. 支持特码奇偶单独统计
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 7: 大小分析
|
||||||
|
**Goal**: 按号码大小(1-24为小,25-49为大)统计
|
||||||
|
**Depends on**: 1 (复用 history 数据)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示总体大小比例
|
||||||
|
2. 展示每期大小数量
|
||||||
|
3. 支持特码大小单独统计
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 8: 和值分析
|
||||||
|
**Goal**: 每期号码之和的趋势
|
||||||
|
**Depends on**: 1 (复用 history 数据)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示每期和值折线图
|
||||||
|
2. 显示和值的平均值和极值
|
||||||
|
3. 可过滤特码和值
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 9: 连号分析
|
||||||
|
**Goal**: 连续出现的号码对/三连号统计
|
||||||
|
**Depends on**: 1 (复用 history 数据)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示历史中出现过的连号组合
|
||||||
|
2. 按出现频率排序
|
||||||
|
3. 支持查询特定连号的历史
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
|
### Phase 10: 尾数分析
|
||||||
|
**Goal**: 按尾数(0-9)分组统计
|
||||||
|
**Depends on**: 1 (复用 history 数据)
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. 展示 0-9 尾数的出现频率
|
||||||
|
2. 展示每期尾数分布
|
||||||
|
3. 支持特码尾数单独统计
|
||||||
|
**Plans**: TBD
|
||||||
|
|
||||||
|
**UI hint**: yes
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
**Execution Order:**
|
**Execution Order:**
|
||||||
Phases execute in numeric order: 1
|
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10
|
||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. 遗漏号码分析 | 0/3 | Not started | - |
|
| 1. 遗漏号码分析 | 3/3 | Complete | 2026-04-21 |
|
||||||
|
| 2. 走势图分析 | 0/0 | Not planned | - |
|
||||||
|
| 3. 冷热号码分析 | 0/0 | Not planned | - |
|
||||||
|
| 4. 波色分析 | 0/0 | Not planned | - |
|
||||||
|
| 5. 生肖分析 | 0/0 | Not planned | - |
|
||||||
|
| 6. 奇偶分析 | 0/0 | Not planned | - |
|
||||||
|
| 7. 大小分析 | 0/0 | Not planned | - |
|
||||||
|
| 8. 和值分析 | 0/0 | Not planned | - |
|
||||||
|
| 9. 连号分析 | 0/0 | Not planned | - |
|
||||||
|
| 10. 尾数分析 | 0/0 | Not planned | - |
|
||||||
|
|||||||
+19
-14
@@ -2,16 +2,16 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: complete
|
||||||
stopped_at: ROADMAP.md created, Phase 1 ready to plan
|
stopped_at: Phase 1 complete, 9 new analysis phases added to roadmap
|
||||||
last_updated: "2026-04-21T13:05:05.724Z"
|
last_updated: "2026-04-21T14:00:00.000Z"
|
||||||
last_activity: 2026-04-21 -- Phase 01 execution started
|
last_activity: 2026-04-21 -- Phase 01 complete, Phases 2-10 added
|
||||||
progress:
|
progress:
|
||||||
total_phases: 1
|
total_phases: 10
|
||||||
completed_phases: 0
|
completed_phases: 1
|
||||||
total_plans: 3
|
total_plans: 3
|
||||||
completed_plans: 0
|
completed_plans: 3
|
||||||
percent: 0
|
percent: 10
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -25,12 +25,12 @@ See: .planning/PROJECT.md (updated 2026-04-21)
|
|||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 01 (omitted-number-analysis) — EXECUTING
|
Phase: 01 (omitted-number-analysis) — COMPLETE
|
||||||
Plan: 1 of 3
|
Plan: 3 of 3
|
||||||
Status: Executing Phase 01
|
Status: Phase 1 complete, ready to plan next phase
|
||||||
Last activity: 2026-04-21 -- Phase 01 execution started
|
Last activity: 2026-04-21 -- Phase 01 complete, Phases 2-10 added
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
Progress: [████░░░░░░] 10%
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
@@ -78,5 +78,10 @@ None yet.
|
|||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-21
|
Last session: 2026-04-21
|
||||||
Stopped at: ROADMAP.md created, Phase 1 ready to plan
|
Stopped at: Phase 1 complete, 9 new analysis phases added to roadmap
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|
||||||
|
### Roadmap Evolution
|
||||||
|
|
||||||
|
- Phase 1: 遗漏号码分析 — complete (2026-04-21)
|
||||||
|
- Phase 2-10 added: 走势图、冷热号码、波色、生肖、奇偶、大小、和值、连号、尾数分析
|
||||||
|
|||||||
@@ -2,83 +2,18 @@
|
|||||||
|
|
||||||
namespace app\admin\controller;
|
namespace app\admin\controller;
|
||||||
|
|
||||||
use app\admin\model\Admin;
|
|
||||||
use app\admin\model\User;
|
|
||||||
use app\common\controller\Backend;
|
use app\common\controller\Backend;
|
||||||
use app\common\model\Attachment;
|
|
||||||
use fast\Date;
|
|
||||||
use think\Db;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制台
|
* 控制台 - 六合彩数据分析
|
||||||
*
|
*
|
||||||
* @icon fa fa-dashboard
|
* @icon fa fa-dashboard
|
||||||
* @remark 用于展示当前系统中的统计数据、统计报表及重要实时数据
|
|
||||||
*/
|
*/
|
||||||
class Dashboard extends Backend
|
class Dashboard extends Backend
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* 查看
|
|
||||||
*/
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
try {
|
$this->assign('periods', 30);
|
||||||
\think\Db::execute("SET @@sql_mode='';");
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
$column = [];
|
|
||||||
$starttime = Date::unixtime('day', -6);
|
|
||||||
$endtime = Date::unixtime('day', 0, 'end');
|
|
||||||
$joinlist = Db("user")->where('jointime', 'between time', [$starttime, $endtime])
|
|
||||||
->field('jointime, status, COUNT(*) AS nums, DATE_FORMAT(FROM_UNIXTIME(jointime), "%Y-%m-%d") AS join_date')
|
|
||||||
->group('join_date')
|
|
||||||
->select();
|
|
||||||
for ($time = $starttime; $time <= $endtime;) {
|
|
||||||
$column[] = date("Y-m-d", $time);
|
|
||||||
$time += 86400;
|
|
||||||
}
|
|
||||||
$userlist = array_fill_keys($column, 0);
|
|
||||||
foreach ($joinlist as $k => $v) {
|
|
||||||
$userlist[$v['join_date']] = $v['nums'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$dbTableList = Db::query("SHOW TABLE STATUS");
|
|
||||||
$addonList = get_addon_list();
|
|
||||||
$totalworkingaddon = 0;
|
|
||||||
$totaladdon = count($addonList);
|
|
||||||
foreach ($addonList as $index => $item) {
|
|
||||||
if ($item['state']) {
|
|
||||||
$totalworkingaddon += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->view->assign([
|
|
||||||
'totaluser' => User::count(),
|
|
||||||
'totaladdon' => $totaladdon,
|
|
||||||
'totaladmin' => Admin::count(),
|
|
||||||
'totalcategory' => \app\common\model\Category::count(),
|
|
||||||
'todayusersignup' => User::whereTime('jointime', 'today')->count(),
|
|
||||||
'todayuserlogin' => User::whereTime('logintime', 'today')->count(),
|
|
||||||
'sevendau' => User::whereTime('jointime|logintime|prevtime', '-7 days')->count(),
|
|
||||||
'thirtydau' => User::whereTime('jointime|logintime|prevtime', '-30 days')->count(),
|
|
||||||
'threednu' => User::whereTime('jointime', '-3 days')->count(),
|
|
||||||
'sevendnu' => User::whereTime('jointime', '-7 days')->count(),
|
|
||||||
'dbtablenums' => count($dbTableList),
|
|
||||||
'dbsize' => array_sum(array_map(function ($item) {
|
|
||||||
return $item['Data_length'] + $item['Index_length'];
|
|
||||||
}, $dbTableList)),
|
|
||||||
'totalworkingaddon' => $totalworkingaddon,
|
|
||||||
'attachmentnums' => Attachment::count(),
|
|
||||||
'attachmentsize' => Attachment::sum('filesize'),
|
|
||||||
'picturenums' => Attachment::where('mimetype', 'like', 'image/%')->count(),
|
|
||||||
'picturesize' => Attachment::where('mimetype', 'like', 'image/%')->sum('filesize'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assignconfig('column', array_keys($userlist));
|
|
||||||
$this->assignconfig('userdata', array_values($userlist));
|
|
||||||
|
|
||||||
return $this->view->fetch();
|
return $this->view->fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class History extends Backend
|
|||||||
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
|
* 无需额外权限检查的方法(但仍在 admin 模块内,需要 admin 登录)
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $noNeedRight = ['missingNum'];
|
protected $noNeedRight = ['missingNum', 'trendData', 'hotColdNumbers', 'colorWaveAnalysis', 'zodiacAnalysis', 'oddEvenAnalysis', 'bigSmallAnalysis', 'specialTrend', 'consecutiveNumbers', 'tailNumbers', 'dashboard'];
|
||||||
|
|
||||||
public function _initialize()
|
public function _initialize()
|
||||||
{
|
{
|
||||||
@@ -52,5 +52,189 @@ class History extends Backend
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取走势图数据
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function trendData()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getTrendData($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取冷热号码
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function hotColdNumbers()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getHotColdNumbers($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 波色分析
|
||||||
|
*/
|
||||||
|
public function colorWaveAnalysis()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getColorWaveAnalysis($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生肖分析
|
||||||
|
*/
|
||||||
|
public function zodiacAnalysis()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getZodiacAnalysis($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 奇偶分析
|
||||||
|
*/
|
||||||
|
public function oddEvenAnalysis()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getOddEvenAnalysis($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大小分析
|
||||||
|
*/
|
||||||
|
public function bigSmallAnalysis()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getBigSmallAnalysis($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特码走势
|
||||||
|
*/
|
||||||
|
public function specialTrend()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$result = $this->model->getSpecialTrend($periods);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连号分析
|
||||||
|
*/
|
||||||
|
public function consecutiveNumbers()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$result = $this->model->getConsecutiveNumbers($periods);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尾数分析
|
||||||
|
*/
|
||||||
|
public function tailNumbers()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getTailNumbers($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 综合统计面板
|
||||||
|
*/
|
||||||
|
public function dashboard()
|
||||||
|
{
|
||||||
|
if ($this->request->isAjax()) {
|
||||||
|
$periods = $this->request->get('periods', 30, 'intval');
|
||||||
|
if ($periods < 10 || $periods > 100) {
|
||||||
|
$this->error('期数范围必须在 10-100 之间');
|
||||||
|
}
|
||||||
|
$type = $this->request->get('type', 'all');
|
||||||
|
if (!in_array($type, ['all', 'special'])) {
|
||||||
|
$this->error('查询类型不正确');
|
||||||
|
}
|
||||||
|
$result = $this->model->getDashboardData($periods, $type);
|
||||||
|
$this->success('查询成功', null, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,16 @@ return [
|
|||||||
'Query Type' => '查询类型',
|
'Query Type' => '查询类型',
|
||||||
'All Numbers' => '全部号码',
|
'All Numbers' => '全部号码',
|
||||||
'Special Only' => '仅特码',
|
'Special Only' => '仅特码',
|
||||||
|
'Trend Chart' => '走势图',
|
||||||
|
'No data available' => '暂无数据',
|
||||||
|
'Hot/Cold Analysis' => '冷热分析',
|
||||||
|
'Color Wave' => '波色分析',
|
||||||
|
'Zodiac' => '生肖分析',
|
||||||
|
'Odd/Even' => '奇偶分析',
|
||||||
|
'Big/Small' => '大小分析',
|
||||||
|
'Sum Chart' => '和值分析',
|
||||||
|
'Consecutive' => '连号分析',
|
||||||
|
'Tail Numbers' => '尾数分析',
|
||||||
|
'Dashboard' => '综合统计面板',
|
||||||
|
'Refresh' => '刷新',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -30,6 +30,117 @@ class History extends Model
|
|||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取走势图数据
|
||||||
|
* @param int $periods 查询最近多少期
|
||||||
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
||||||
|
* @return array {expects: [], data: [[num1,...], ...], colorMap: []}
|
||||||
|
*/
|
||||||
|
public function getTrendData($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
// 查询波色映射
|
||||||
|
$num_model = new Num();
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
|
||||||
|
$history = $this
|
||||||
|
->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
|
||||||
|
->order('openTime', 'desc')
|
||||||
|
->limit($periods)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
if (empty($history)) {
|
||||||
|
return ['expects' => [], 'data' => [], 'colorMap' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$expects = [];
|
||||||
|
$data = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
$expects[] = (string)$row['expect'];
|
||||||
|
if ($type === 'special') {
|
||||||
|
$data[] = ['num7' => (int)$row['num7']];
|
||||||
|
} else {
|
||||||
|
$row_data = [];
|
||||||
|
for ($i = 1; $i <= 7; $i++) {
|
||||||
|
$row_data['num' . $i] = (int)$row['num' . $i];
|
||||||
|
}
|
||||||
|
$data[] = $row_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反转数组,使最远的数据在左边,最近的数据在右边(从左往右,从远到近)
|
||||||
|
$expects = array_reverse($expects);
|
||||||
|
$data = array_reverse($data);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'expects' => $expects,
|
||||||
|
'data' => $data,
|
||||||
|
'colorMap' => $colorMap
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取冷热号码
|
||||||
|
* @param int $periods 查询最近多少期
|
||||||
|
* @param string $type 查询类型 all=全部号码 special=仅特码
|
||||||
|
* @return array {hot: [], cold: [], all: []}
|
||||||
|
*/
|
||||||
|
public function getHotColdNumbers($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$num_model = new Num();
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
$animalMap = $num_model->column('animal', 'num');
|
||||||
|
|
||||||
|
$history = $this
|
||||||
|
->field('expect,num1,num2,num3,num4,num5,num6,num7,openTime')
|
||||||
|
->order('openTime', 'desc')
|
||||||
|
->limit($periods)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
if (empty($history)) {
|
||||||
|
return ['hot' => [], 'cold' => [], 'all' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7'];
|
||||||
|
|
||||||
|
// 统计每个号码的出现次数
|
||||||
|
$count = array_fill(1, 49, 0);
|
||||||
|
$totalAppearances = 0;
|
||||||
|
foreach ($history as $row) {
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$num = (int)$row[$field];
|
||||||
|
if ($num >= 1 && $num <= 49) {
|
||||||
|
$count[$num]++;
|
||||||
|
$totalAppearances++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$all = [];
|
||||||
|
for ($num = 1; $num <= 49; $num++) {
|
||||||
|
$percent = $totalAppearances > 0 ? round($count[$num] / $totalAppearances * 100, 1) : 0;
|
||||||
|
$all[] = [
|
||||||
|
'num' => $num,
|
||||||
|
'count' => $count[$num],
|
||||||
|
'percent' => $percent,
|
||||||
|
'color' => $colorMap[$num] ?? '—',
|
||||||
|
'animal' => $animalMap[$num] ?? '—'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按出现次数降序排序
|
||||||
|
$sorted = $all;
|
||||||
|
usort($sorted, function ($a, $b) {
|
||||||
|
return $b['count'] - $a['count'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 热号: top 10, 冷号: bottom 10
|
||||||
|
$hot = array_slice($sorted, 0, 10);
|
||||||
|
$cold = array_slice($sorted, -10);
|
||||||
|
$cold = array_reverse($cold);
|
||||||
|
|
||||||
|
return ['hot' => $hot, 'cold' => $cold, 'all' => $all];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算遗漏号码
|
* 计算遗漏号码
|
||||||
* @param int $periods 查询最近多少期
|
* @param int $periods 查询最近多少期
|
||||||
@@ -139,5 +250,222 @@ class History extends Model
|
|||||||
return count($allHistory);
|
return count($allHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 波色分析
|
||||||
|
*/
|
||||||
|
public function getColorWaveAnalysis($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$num_model = new Num();
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['red' => 0, 'blue' => 0, 'green' => 0, 'red_pct' => 0, 'blue_pct' => 0, 'green_pct' => 0, 'details' => []];
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
||||||
|
$colors = ['红' => 0, '蓝' => 0, '绿' => 0];
|
||||||
|
$total = 0;
|
||||||
|
foreach ($history as $row) {
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$num = (int)$row[$f];
|
||||||
|
$color = $colorMap[$num] ?? '';
|
||||||
|
if (strpos($color, '红') !== false) { $colors['红']++; $total++; }
|
||||||
|
elseif (strpos($color, '蓝') !== false) { $colors['蓝']++; $total++; }
|
||||||
|
elseif (strpos($color, '绿') !== false) { $colors['绿']++; $total++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'red' => $colors['红'], 'blue' => $colors['蓝'], 'green' => $colors['绿'],
|
||||||
|
'red_pct' => $total ? round($colors['红']/$total*100,1) : 0,
|
||||||
|
'blue_pct' => $total ? round($colors['蓝']/$total*100,1) : 0,
|
||||||
|
'green_pct' => $total ? round($colors['绿']/$total*100,1) : 0,
|
||||||
|
'total' => $total
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生肖分析
|
||||||
|
*/
|
||||||
|
public function getZodiacAnalysis($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$num_model = new Num();
|
||||||
|
$animalMap = $num_model->column('animal', 'num');
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['list' => []];
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
||||||
|
$counts = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$num = (int)$row[$f];
|
||||||
|
$animal = $animalMap[$num] ?? '未知';
|
||||||
|
if (!isset($counts[$animal])) $counts[$animal] = ['animal' => $animal, 'count' => 0, 'color' => $colorMap[$num] ?? '—'];
|
||||||
|
$counts[$animal]['count']++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$list = array_values($counts);
|
||||||
|
usort($list, function ($a, $b) { return $b['count'] - $a['count']; });
|
||||||
|
$total = array_sum(array_column($list, 'count'));
|
||||||
|
foreach ($list as &$item) { $item['percent'] = $total ? round($item['count']/$total*100, 1) : 0; }
|
||||||
|
return ['list' => $list];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 奇偶分析
|
||||||
|
*/
|
||||||
|
public function getOddEvenAnalysis($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['odd' => 0, 'even' => 0, 'odd_pct' => 0, 'even_pct' => 0, 'per_period' => []];
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
||||||
|
$odd = 0; $even = 0; $perPeriod = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
$p_odd = 0; $p_even = 0;
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$num = (int)$row[$f];
|
||||||
|
if ($num % 2 == 0) { $even++; $p_even++; } else { $odd++; $p_odd++; }
|
||||||
|
}
|
||||||
|
$perPeriod[] = ['expect' => $row['expect'], 'odd' => $p_odd, 'even' => $p_even];
|
||||||
|
}
|
||||||
|
$total = $odd + $even;
|
||||||
|
return [
|
||||||
|
'odd' => $odd, 'even' => $even,
|
||||||
|
'odd_pct' => $total ? round($odd/$total*100, 1) : 0,
|
||||||
|
'even_pct' => $total ? round($even/$total*100, 1) : 0,
|
||||||
|
'per_period' => $perPeriod
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大小分析(1-24为小,25-49为大)
|
||||||
|
*/
|
||||||
|
public function getBigSmallAnalysis($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['big' => 0, 'small' => 0, 'big_pct' => 0, 'small_pct' => 0, 'per_period' => []];
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
||||||
|
$big = 0; $small = 0; $perPeriod = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
$p_big = 0; $p_small = 0;
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$num = (int)$row[$f];
|
||||||
|
if ($num >= 25) { $big++; $p_big++; } else { $small++; $p_small++; }
|
||||||
|
}
|
||||||
|
$perPeriod[] = ['expect' => $row['expect'], 'big' => $p_big, 'small' => $p_small];
|
||||||
|
}
|
||||||
|
$total = $big + $small;
|
||||||
|
return [
|
||||||
|
'big' => $big, 'small' => $small,
|
||||||
|
'big_pct' => $total ? round($big/$total*100, 1) : 0,
|
||||||
|
'small_pct' => $total ? round($small/$total*100, 1) : 0,
|
||||||
|
'per_period' => $perPeriod
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特码走势
|
||||||
|
*/
|
||||||
|
public function getSpecialTrend($periods = 30)
|
||||||
|
{
|
||||||
|
$num_model = new Num();
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
|
||||||
|
// 先取最近 $periods 条数据
|
||||||
|
$history = $this->field('expect,num7,openTime')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['expects' => [], 'specials' => [], 'colors' => []];
|
||||||
|
|
||||||
|
// 反转,使数据从左到右为从远到近
|
||||||
|
$history = array_reverse($history);
|
||||||
|
$expects = []; $specials = []; $colors = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
$expects[] = (string)$row['expect'];
|
||||||
|
$num = (int)$row['num7'];
|
||||||
|
$specials[] = $num;
|
||||||
|
$colors[] = $colorMap[$num] ?? '';
|
||||||
|
}
|
||||||
|
return ['expects' => $expects, 'specials' => $specials, 'colors' => $colors];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连号分析
|
||||||
|
*/
|
||||||
|
public function getConsecutiveNumbers($periods = 30)
|
||||||
|
{
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['pairs' => [], 'triples' => []];
|
||||||
|
|
||||||
|
$pairCount = []; $tripleCount = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
$nums = [];
|
||||||
|
for ($i = 1; $i <= 7; $i++) $nums[] = (int)$row['num' . $i];
|
||||||
|
sort($nums);
|
||||||
|
// 找连号对
|
||||||
|
for ($i = 0; $i < count($nums) - 1; $i++) {
|
||||||
|
if ($nums[$i + 1] - $nums[$i] === 1) {
|
||||||
|
$pair = $nums[$i] . '-' . $nums[$i + 1];
|
||||||
|
$pairCount[$pair] = isset($pairCount[$pair]) ? $pairCount[$pair] + 1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 找连号三连
|
||||||
|
for ($i = 0; $i < count($nums) - 2; $i++) {
|
||||||
|
if ($nums[$i + 1] - $nums[$i] === 1 && $nums[$i + 2] - $nums[$i + 1] === 1) {
|
||||||
|
$triple = $nums[$i] . '-' . $nums[$i + 1] . '-' . $nums[$i + 2];
|
||||||
|
$tripleCount[$triple] = isset($tripleCount[$triple]) ? $tripleCount[$triple] + 1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arsort($pairCount); arsort($tripleCount);
|
||||||
|
return ['pairs' => $pairCount, 'triples' => $tripleCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尾数分析
|
||||||
|
*/
|
||||||
|
public function getTailNumbers($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
$num_model = new Num();
|
||||||
|
$colorMap = $num_model->column('color', 'num');
|
||||||
|
$animalMap = $num_model->column('animal', 'num');
|
||||||
|
|
||||||
|
$history = $this->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select();
|
||||||
|
if (empty($history)) return ['tails' => [], 'all' => []];
|
||||||
|
|
||||||
|
$fields = ($type === 'special') ? ['num7'] : ['num1','num2','num3','num4','num5','num6','num7'];
|
||||||
|
$tailCount = array_fill(0, 10, 0);
|
||||||
|
$all = [];
|
||||||
|
foreach ($history as $row) {
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$num = (int)$row[$f];
|
||||||
|
$tail = $num % 10;
|
||||||
|
$tailCount[$tail]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$total = array_sum($tailCount);
|
||||||
|
for ($t = 0; $t <= 9; $t++) {
|
||||||
|
$all[] = ['tail' => $t, 'count' => $tailCount[$t], 'percent' => $total ? round($tailCount[$t]/$total*100, 1) : 0];
|
||||||
|
}
|
||||||
|
usort($all, function ($a, $b) { return $b['count'] - $a['count']; });
|
||||||
|
return ['all' => $all];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 综合统计面板
|
||||||
|
*/
|
||||||
|
public function getDashboardData($periods = 30, $type = 'all')
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'hotcold' => $this->getHotColdNumbers($periods, $type),
|
||||||
|
'colorwave' => $this->getColorWaveAnalysis($periods, $type),
|
||||||
|
'zodiac' => $this->getZodiacAnalysis($periods, $type),
|
||||||
|
'oddeven' => $this->getOddEvenAnalysis($periods, $type),
|
||||||
|
'bigsmall' => $this->getBigSmallAnalysis($periods, $type),
|
||||||
|
'special' => $this->getSpecialTrend($periods),
|
||||||
|
'tailnumbers' => $this->getTailNumbers($periods, $type)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,403 +1,32 @@
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.sm-st {
|
.dash-section { margin-bottom: 20px; }
|
||||||
background: #fff;
|
.dash-section h4 { border-bottom: 1px solid #eee; padding-bottom: 8px; margin-bottom: 12px; font-size: 15px; color: #333; }
|
||||||
padding: 20px;
|
.dash-card { background: #f9f9f9; border-radius: 6px; padding: 12px; text-align: center; }
|
||||||
-webkit-border-radius: 3px;
|
.dash-card .big-num { font-size: 28px; font-weight: bold; color: #333; }
|
||||||
-moz-border-radius: 3px;
|
.dash-card .label-text { font-size: 12px; color: #999; margin-top: 4px; }
|
||||||
border-radius: 3px;
|
.num-ball-sm { display: inline-block; width: 32px; height: 32px; line-height: 32px; text-align: center; border-radius: 50%; color: #fff; font-weight: bold; font-size: 13px; }
|
||||||
margin-bottom: 20px;
|
.mini-card { text-align: center; background: #f5f5f5; padding: 8px 12px; border-radius: 5px; display: inline-block; margin: 3px; min-width: 65px; }
|
||||||
}
|
.mini-card .name { font-size: 14px; font-weight: bold; color: #333; }
|
||||||
|
.mini-card .val { font-size: 11px; color: #666; }
|
||||||
.sm-st-icon {
|
#dash-chart { width: 100%; height: 280px; }
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 60px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 30px;
|
|
||||||
background: #eee;
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
float: left;
|
|
||||||
margin-right: 10px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-st-info {
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-st-info span {
|
|
||||||
display: block;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orange {
|
|
||||||
background: #fa8564 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tar {
|
|
||||||
background: #45cf95 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-st .green {
|
|
||||||
background: #86ba41 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pink {
|
|
||||||
background: #AC75F0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yellow-b {
|
|
||||||
background: #fdd752 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-elem {
|
|
||||||
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 18px;
|
|
||||||
border-radius: 40px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-info {
|
|
||||||
text-align: center;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: -5px;
|
|
||||||
padding: 8px;
|
|
||||||
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
|
|
||||||
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-icon {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st-red {
|
|
||||||
background-color: #F05050;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st-green {
|
|
||||||
background-color: #27C24C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st-violet {
|
|
||||||
background-color: #7266ba;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st-blue {
|
|
||||||
background-color: #23b7e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats .stat-icon {
|
|
||||||
color: #28bb9c;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 26px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 50px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat .value {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat .name {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat.lg .value {
|
|
||||||
font-size: 26px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-col {
|
|
||||||
margin:0 0 10px 0;
|
|
||||||
}
|
|
||||||
.stat.lg .name {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-col .progress {
|
|
||||||
height: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-col .progress-bar {
|
|
||||||
line-height: 2px;
|
|
||||||
height: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#statistics .panel {
|
|
||||||
min-height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#statistics .panel h5 {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="panel panel-default panel-intro">
|
<div class="panel panel-default panel-intro">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
{:build_heading(null, false)}
|
{:build_heading(null, false)}
|
||||||
<ul class="nav nav-tabs">
|
<div style="float:right;">
|
||||||
<li class="active"><a href="#one" data-toggle="tab">{:__('Dashboard')}</a></li>
|
<div class="form-inline">
|
||||||
<li><a href="#two" data-toggle="tab">{:__('Custom')}</a></li>
|
<label>{:__('Query Periods')}:</label>
|
||||||
</ul>
|
<input type="number" id="dash-periods" class="form-control input-sm" value="{$periods|htmlentities}" min="10" max="100" style="width:80px;display:inline-block;">
|
||||||
</div>
|
<button class="btn btn-sm btn-primary" id="btn-dash-refresh"><i class="fa fa-refresh"></i> {:__('Refresh')}</button>
|
||||||
<div class="panel-body">
|
|
||||||
<div id="myTabContent" class="tab-content">
|
|
||||||
<div class="tab-pane fade active in" id="one">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3 col-xs-6">
|
|
||||||
<div class="sm-st clearfix">
|
|
||||||
<span class="sm-st-icon st-red"><i class="fa fa-users"></i></span>
|
|
||||||
<div class="sm-st-info">
|
|
||||||
<span>{$totaluser|htmlentities}</span>
|
|
||||||
{:__('Total user')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3 col-xs-6">
|
|
||||||
<div class="sm-st clearfix">
|
|
||||||
<span class="sm-st-icon st-violet"><i class="fa fa-magic"></i></span>
|
|
||||||
<div class="sm-st-info">
|
|
||||||
<span>{$totaladdon|htmlentities}</span>
|
|
||||||
{:__('Total addon')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3 col-xs-6">
|
|
||||||
<div class="sm-st clearfix">
|
|
||||||
<span class="sm-st-icon st-blue"><i class="fa fa-leaf"></i></span>
|
|
||||||
<div class="sm-st-info">
|
|
||||||
<span>{$attachmentnums|htmlentities}</span>
|
|
||||||
{:__('Total attachment')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-3 col-xs-6">
|
|
||||||
<div class="sm-st clearfix">
|
|
||||||
<span class="sm-st-icon st-green"><i class="fa fa-user"></i></span>
|
|
||||||
<div class="sm-st-info">
|
|
||||||
<span>{$totaladmin|htmlentities}</span>
|
|
||||||
{:__('Total admin')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div id="echart" class="btn-refresh" style="height:300px;width:100%;"></div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="card sameheight-item stats">
|
|
||||||
<div class="card-block">
|
|
||||||
<div class="row row-sm stats-container">
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-rocket"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$todayusersignup|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Today user signup')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-vcard"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$todayuserlogin|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Today user login')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-calendar"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$threednu|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Three dnu')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-calendar-plus-o"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$sevendnu|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Seven dnu')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-user-circle"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$sevendau|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Seven dau')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 stat-col">
|
|
||||||
<div class="stat-icon"><i class="fa fa-user-circle-o"></i></div>
|
|
||||||
<div class="stat">
|
|
||||||
<div class="value"> {$thirtydau|htmlentities}</div>
|
|
||||||
<div class="name"> {:__('Thirty dau')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success" style="width: 20%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row" style="margin-top:15px;" id="statistics">
|
|
||||||
|
|
||||||
<div class="col-lg-12">
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-md-3">
|
|
||||||
<div class="panel bg-blue-gradient no-border">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="panel-title">
|
|
||||||
<span class="label label-primary pull-right">{:__('Real time')}</span>
|
|
||||||
<h5>{:__('Working addon count')}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="panel-content">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h1 class="no-margins">{$totalworkingaddon|htmlentities}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-magic"></i>
|
|
||||||
<small>{:__('Working addon count tips')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-md-3">
|
|
||||||
<div class="panel bg-teal-gradient no-border">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="ibox-title">
|
|
||||||
<span class="label label-primary pull-right">{:__('Real time')}</span>
|
|
||||||
<h5>{:__('Database count')}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="ibox-content">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$dbtablenums|htmlentities}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-database"></i>
|
|
||||||
<small>{:__('Database table nums')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$dbsize|format_bytes=###,'',0}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-filter"></i>
|
|
||||||
<small>{:__('Database size')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xs-6 col-md-3">
|
|
||||||
<div class="panel bg-purple-gradient no-border">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="ibox-title">
|
|
||||||
<span class="label label-primary pull-right">{:__('Real time')}</span>
|
|
||||||
<h5>{:__('Attachment count')}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="ibox-content">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$attachmentnums|htmlentities}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-files-o"></i>
|
|
||||||
<small>{:__('Attachment nums')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$attachmentsize|format_bytes=###,'',0}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-filter"></i>
|
|
||||||
<small>{:__('Attachment size')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-md-3">
|
|
||||||
<div class="panel bg-green-gradient no-border">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="ibox-title">
|
|
||||||
<span class="label label-primary pull-right">{:__('Real time')}</span>
|
|
||||||
<h5>{:__('Picture count')}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="ibox-content">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$picturenums|htmlentities}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-picture-o"></i>
|
|
||||||
<small>{:__('Picture nums')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<h1 class="no-margins">{$picturesize|format_bytes=###,'',0}</h1>
|
|
||||||
<div class="font-bold"><i class="fa fa-filter"></i>
|
|
||||||
<small>{:__('Picture size')}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="two">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
{:__('Custom zone')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div id="dash-content">
|
||||||
|
<div class="text-center" style="padding:60px;"><i class="fa fa-spinner fa-spin fa-2x"></i><br><span class="text-muted">{:__('Loading')}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||||
|
|||||||
@@ -8,6 +8,16 @@
|
|||||||
<div id="toolbar" class="toolbar">
|
<div id="toolbar" class="toolbar">
|
||||||
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
|
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
|
||||||
<a href="javascript:;" class="btn btn-warning btn-missingnum" title="{:__('Missing Number Analysis')}"><i class="fa fa-search"></i> {:__('Missing Number Analysis')}</a>
|
<a href="javascript:;" class="btn btn-warning btn-missingnum" title="{:__('Missing Number Analysis')}"><i class="fa fa-search"></i> {:__('Missing Number Analysis')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-info btn-trend" title="{:__('Trend Chart')}"><i class="fa fa-area-chart"></i> {:__('Trend Chart')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-danger btn-hotcold" title="{:__('Hot/Cold Analysis')}"><i class="fa fa-fire"></i> {:__('Hot/Cold Analysis')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-success btn-colorwave" title="{:__('Color Wave')}"><i class="fa fa-paint-brush"></i> {:__('Color Wave')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-purple btn-zodiac" title="{:__('Zodiac')}"><i class="fa fa-star"></i> {:__('Zodiac')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-warning btn-oddeven" title="{:__('Odd/Even')}"><i class="fa fa-balance-scale"></i> {:__('Odd/Even')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-info btn-bigsmall" title="{:__('Big/Small')}"><i class="fa fa-arrows-h"></i> {:__('Big/Small')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-primary btn-sumchart" title="{:__('Sum Chart')}"><i class="fa fa-line-chart"></i> {:__('Sum Chart')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-dark btn-consecutive" title="{:__('Consecutive')}"><i class="fa fa-link"></i> {:__('Consecutive')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-default btn-tailnums" title="{:__('Tail Numbers')}"><i class="fa fa-list-ol"></i> {:__('Tail Numbers')}</a>
|
||||||
|
<a href="javascript:;" class="btn btn-success btn-dashboard" title="{:__('Dashboard')}"><i class="fa fa-tachometer"></i> {:__('Dashboard')}</a>
|
||||||
<!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('history/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>-->
|
<!-- <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('history/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>-->
|
||||||
</div>
|
</div>
|
||||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||||
@@ -21,3 +31,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ECharts CDN for trend chart -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ class Index extends Api
|
|||||||
Db::name('history')->where('expect', $item['expect'])->update($insert_data);
|
Db::name('history')->where('expect', $item['expect'])->update($insert_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->success('获取成功');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->error($e->getMessage());
|
$this->error($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->success('获取成功');
|
|
||||||
} else {
|
} else {
|
||||||
$this->error('获取失败');
|
$this->error('获取失败');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,194 @@
|
|||||||
define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echarts-theme', 'template'], function ($, undefined, Backend, Datatable, Table, Echarts, undefined, Template) {
|
define(['jquery'], function ($) {
|
||||||
|
|
||||||
var Controller = {
|
var Controller = {
|
||||||
index: function () {
|
index: function () {
|
||||||
// 基于准备好的dom,初始化echarts实例
|
var getColor = function (color) {
|
||||||
var myChart = Echarts.init(document.getElementById('echart'), 'walden');
|
if (!color || color === '—') return '#95a5a6';
|
||||||
|
if (color.indexOf('红') !== -1) return '#e74c3c';
|
||||||
// 指定图表的配置项和数据
|
if (color.indexOf('蓝') !== -1) return '#3498db';
|
||||||
var option = {
|
if (color.indexOf('绿') !== -1) return '#2ecc71';
|
||||||
title: {
|
return '#95a5a6';
|
||||||
text: '',
|
|
||||||
subtext: ''
|
|
||||||
},
|
|
||||||
color: [
|
|
||||||
"#18d1b1",
|
|
||||||
"#3fb1e3",
|
|
||||||
"#626c91",
|
|
||||||
"#a0a7e6",
|
|
||||||
"#c4ebad",
|
|
||||||
"#96dee8"
|
|
||||||
],
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis'
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
data: [__('Register user')]
|
|
||||||
},
|
|
||||||
toolbox: {
|
|
||||||
show: false,
|
|
||||||
feature: {
|
|
||||||
magicType: {show: true, type: ['stack', 'tiled']},
|
|
||||||
saveAsImage: {show: true}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: false,
|
|
||||||
data: Config.column
|
|
||||||
},
|
|
||||||
yAxis: {},
|
|
||||||
grid: [{
|
|
||||||
left: 'left',
|
|
||||||
top: 'top',
|
|
||||||
right: '10',
|
|
||||||
bottom: 30
|
|
||||||
}],
|
|
||||||
series: [{
|
|
||||||
name: __('Register user'),
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
areaStyle: {
|
|
||||||
normal: {}
|
|
||||||
},
|
|
||||||
lineStyle: {
|
|
||||||
normal: {
|
|
||||||
width: 1.5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: Config.userdata
|
|
||||||
}]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用刚指定的配置项和数据显示图表。
|
var ball = function (num, color) {
|
||||||
myChart.setOption(option);
|
return '<span class="num-ball-sm" style="background-color:' + getColor(color) + ';">' + num + '</span>';
|
||||||
|
};
|
||||||
|
|
||||||
$(window).resize(function () {
|
function loadData() {
|
||||||
myChart.resize();
|
var periods = parseInt($('#dash-periods').val()) || 30;
|
||||||
});
|
if (periods < 10) periods = 10;
|
||||||
|
if (periods > 100) periods = 100;
|
||||||
|
$('#dash-content').html('<div class="text-center" style="padding:60px;"><i class="fa fa-spinner fa-spin fa-2x"></i><br><span class="text-muted">' + __('Loading') + '</span></div>');
|
||||||
|
|
||||||
$(document).on("click", ".btn-refresh", function () {
|
$.ajax({
|
||||||
setTimeout(function () {
|
url: 'history/dashboard',
|
||||||
myChart.resize();
|
type: 'GET',
|
||||||
}, 0);
|
data: {periods: periods},
|
||||||
});
|
dataType: 'json',
|
||||||
|
success: function (ret) {
|
||||||
|
if (ret.code != 1) {
|
||||||
|
$('#dash-content').html('<div class="alert alert-danger">' + (ret.msg || '加载失败') + '</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
render(ret.data);
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$('#dash-content').html('<div class="alert alert-danger">请求失败,请重试</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(data) {
|
||||||
|
var hc = data.hotcold, cw = data.colorwave, zo = data.zodiac;
|
||||||
|
var oe = data.oddeven, bs = data.bigsmall, sp = data.special, tn = data.tailnumbers;
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
html += '<div class="dash-section"><h4>🔥❄️ 冷热号码</h4><div class="row">';
|
||||||
|
html += '<div class="col-sm-6"><div class="dash-card" style="border-left:3px solid #e74c3c;"><div style="color:#e74c3c;font-weight:bold;margin-bottom:8px;">热号 Top 5</div>';
|
||||||
|
for (var i = 0; i < 5; i++) html += ball(hc.hot[i].num, hc.hot[i].color) + ' ';
|
||||||
|
html += '</div></div>';
|
||||||
|
html += '<div class="col-sm-6"><div class="dash-card" style="border-left:3px solid #3498db;"><div style="color:#3498db;font-weight:bold;margin-bottom:8px;">冷号 Top 5</div>';
|
||||||
|
for (var i = 0; i < 5; i++) html += ball(hc.cold[i].num, hc.cold[i].color) + ' ';
|
||||||
|
html += '</div></div></div></div>';
|
||||||
|
|
||||||
|
html += '<div class="dash-section"><h4>📊 比例分析</h4><div class="row">';
|
||||||
|
html += '<div class="col-sm-4"><div class="dash-card"><div id="colorwave-chart" style="width:100%;height:200px;"></div></div></div>';
|
||||||
|
html += '<div class="col-sm-4"><div class="dash-card"><div id="oddeven-chart" style="width:100%;height:200px;"></div></div></div>';
|
||||||
|
html += '<div class="col-sm-4"><div class="dash-card"><div id="bigsmall-chart" style="width:100%;height:200px;"></div></div></div>';
|
||||||
|
html += '</div></div>';
|
||||||
|
|
||||||
|
html += '<div class="dash-section"><h4>⭐ 生肖排名</h4><div id="zodiac-chart" style="width:100%;height:250px;"></div></div>';
|
||||||
|
|
||||||
|
html += '<div class="dash-section"><h4>📈 特码走势</h4><div id="special-chart" style="width:100%;height:280px;"></div></div>';
|
||||||
|
|
||||||
|
html += '<div class="dash-section"><h4>🔢 尾数频率</h4><div id="tail-chart" style="width:100%;height:220px;"></div></div>';
|
||||||
|
|
||||||
|
$('#dash-content').html(html);
|
||||||
|
|
||||||
|
// 波色饼图
|
||||||
|
if (typeof echarts !== 'undefined') {
|
||||||
|
var cwDom = document.getElementById('colorwave-chart');
|
||||||
|
if (cwDom) {
|
||||||
|
var cwChart = echarts.init(cwDom);
|
||||||
|
cwChart.setOption({
|
||||||
|
tooltip: {trigger: 'item', formatter: '{b}: {c} ({d}%)'},
|
||||||
|
series: [{
|
||||||
|
type: 'pie', radius: '60%',
|
||||||
|
data: [
|
||||||
|
{value: cw.red, name: '红波', itemStyle: {color: '#e74c3c'}},
|
||||||
|
{value: cw.blue, name: '蓝波', itemStyle: {color: '#3498db'}},
|
||||||
|
{value: cw.green, name: '绿波', itemStyle: {color: '#2ecc71'}}
|
||||||
|
],
|
||||||
|
label: {show: true, formatter: '{b}\n{d}%'},
|
||||||
|
emphasis: {itemStyle: {shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)'}}
|
||||||
|
}],
|
||||||
|
grid: {top: 0, bottom: 0}
|
||||||
|
});
|
||||||
|
$(window).on('resize', function(){ cwChart.resize(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 奇偶饼图
|
||||||
|
var oeDom = document.getElementById('oddeven-chart');
|
||||||
|
if (oeDom) {
|
||||||
|
var oeChart = echarts.init(oeDom);
|
||||||
|
oeChart.setOption({
|
||||||
|
tooltip: {trigger: 'item', formatter: '{b}: {c} ({d}%)'},
|
||||||
|
series: [{
|
||||||
|
type: 'pie', radius: '60%',
|
||||||
|
data: [
|
||||||
|
{value: oe.odd, name: '奇数', itemStyle: {color: '#e74c3c'}},
|
||||||
|
{value: oe.even, name: '偶数', itemStyle: {color: '#3498db'}}
|
||||||
|
],
|
||||||
|
label: {show: true, formatter: '{b}\n{d}%'},
|
||||||
|
emphasis: {itemStyle: {shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)'}}
|
||||||
|
}],
|
||||||
|
grid: {top: 0, bottom: 0}
|
||||||
|
});
|
||||||
|
$(window).on('resize', function(){ oeChart.resize(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大小饼图
|
||||||
|
var bsDom = document.getElementById('bigsmall-chart');
|
||||||
|
if (bsDom) {
|
||||||
|
var bsChart = echarts.init(bsDom);
|
||||||
|
bsChart.setOption({
|
||||||
|
tooltip: {trigger: 'item', formatter: '{b}: {c} ({d}%)'},
|
||||||
|
series: [{
|
||||||
|
type: 'pie', radius: '60%',
|
||||||
|
data: [
|
||||||
|
{value: bs.big, name: '大数', itemStyle: {color: '#f39c12'}},
|
||||||
|
{value: bs.small, name: '小数', itemStyle: {color: '#2ecc71'}}
|
||||||
|
],
|
||||||
|
label: {show: true, formatter: '{b}\n{d}%'},
|
||||||
|
emphasis: {itemStyle: {shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)'}}
|
||||||
|
}],
|
||||||
|
grid: {top: 0, bottom: 0}
|
||||||
|
});
|
||||||
|
$(window).on('resize', function(){ bsChart.resize(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生肖柱状图
|
||||||
|
if (typeof echarts !== 'undefined' && zo.list && zo.list.length > 0) {
|
||||||
|
var zDom = document.getElementById('zodiac-chart');
|
||||||
|
if (zDom) {
|
||||||
|
var zChart = echarts.init(zDom);
|
||||||
|
zChart.setOption({
|
||||||
|
tooltip: {trigger: 'axis', axisPointer: {type: 'shadow'}},
|
||||||
|
grid: {left: 50, right: 20, bottom: 30, top: 10},
|
||||||
|
xAxis: {type: 'category', data: zo.list.map(function(z){return z.animal;})},
|
||||||
|
yAxis: {type: 'value', splitNumber: 3},
|
||||||
|
series: [{type: 'bar', data: zo.list.map(function(z){return z.count;}), itemStyle: {color: '#626c91'}, label: {show: true, position: 'top', fontSize: 12, color: '#333'}}]
|
||||||
|
});
|
||||||
|
$(window).on('resize', function(){ zChart.resize(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特码走势
|
||||||
|
if (typeof echarts !== 'undefined' && sp.expects && sp.expects.length > 0) {
|
||||||
|
var chartDom = document.getElementById('special-chart');
|
||||||
|
if (chartDom) {
|
||||||
|
var chart = echarts.init(chartDom);
|
||||||
|
var colorMap = {'红': '#e74c3c', '蓝': '#3498db', '绿': '#2ecc71'};
|
||||||
|
var itemColors = sp.colors.map(function(c) {
|
||||||
|
if (c.indexOf('红') !== -1) return colorMap['红'];
|
||||||
|
if (c.indexOf('蓝') !== -1) return colorMap['蓝'];
|
||||||
|
if (c.indexOf('绿') !== -1) return colorMap['绿'];
|
||||||
|
return '#95a5a6';
|
||||||
|
});
|
||||||
|
chart.setOption({
|
||||||
|
tooltip: {trigger: 'axis', formatter: function(p) {
|
||||||
|
return '期号: ' + p[0].axisValue + '<br/>特码: <b style="color:' + itemColors[p[0].dataIndex] + '">' + p[0].data + '</b>';
|
||||||
|
}},
|
||||||
|
xAxis: {type: 'category', data: sp.expects, axisLabel: {rotate: 45, fontSize: 10}},
|
||||||
|
yAxis: {type: 'value', min: 0, max: 50},
|
||||||
|
series: [{type: 'line', data: sp.specials, smooth: true, symbolSize: 8, itemStyle: {color: function(p) { return itemColors[p.dataIndex]; }}, areaStyle: {color: 'rgba(52,152,219,0.05)'}}],
|
||||||
|
grid: {left: 50, right: 20, bottom: 50, top: 20}
|
||||||
|
});
|
||||||
|
$(window).on('resize', function () { chart.resize(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尾数柱状图
|
||||||
|
if (typeof echarts !== 'undefined' && tn.all && tn.all.length > 0) {
|
||||||
|
var tDom = document.getElementById('tail-chart');
|
||||||
|
if (tDom) {
|
||||||
|
var tChart = echarts.init(tDom);
|
||||||
|
var sorted = tn.all.slice().sort(function(a,b){return a.tail - b.tail;});
|
||||||
|
tChart.setOption({
|
||||||
|
tooltip: {trigger: 'axis', axisPointer: {type: 'shadow'}},
|
||||||
|
grid: {left: 50, right: 20, bottom: 30, top: 10},
|
||||||
|
xAxis: {type: 'category', data: sorted.map(function(t){return t.tail;})},
|
||||||
|
yAxis: {type: 'value', splitNumber: 3},
|
||||||
|
series: [{type: 'bar', data: sorted.map(function(t){return t.count;}), itemStyle: {color: '#23b7e5'}, label: {show: true, position: 'top', fontSize: 12, color: '#333'}}]
|
||||||
|
});
|
||||||
|
$(window).on('resize', function(){ tChart.resize(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btn-dash-refresh').on('click', loadData);
|
||||||
|
loadData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,50 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
|||||||
$(document).off('click', '.btn-missingnum').on('click', '.btn-missingnum', function () {
|
$(document).off('click', '.btn-missingnum').on('click', '.btn-missingnum', function () {
|
||||||
Controller.api.showMissingNumDialog();
|
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 () {
|
add: function () {
|
||||||
Controller.api.bindevent();
|
Controller.api.bindevent();
|
||||||
@@ -140,6 +184,282 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示冷热分析弹窗
|
||||||
|
*/
|
||||||
|
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;">🔥</span>') +
|
||||||
|
renderSection('冷号 Top 10', data.cold, '<span style="color:#3498db;">❄</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(); });
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示遗漏号码分析弹窗
|
* 显示遗漏号码分析弹窗
|
||||||
*/
|
*/
|
||||||
@@ -266,6 +586,466 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
|||||||
|
|
||||||
bindevent: function () {
|
bindevent: function () {
|
||||||
Form.api.bindevent($("form[role=form]"));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user