Files
2026-09-09 02:52:24 +08:00

274 lines
9.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.content')
@section('title', '每日报表')
@section('content')
<style>
.report-filter .layui-form-item { margin-bottom: 0; }
.report-filter .layui-inline {
margin-right: 10px;
margin-bottom: 0;
}
.report-filter .layui-form-label {
width: 72px;
padding: 9px 10px 9px 0;
text-align: left;
box-sizing: border-box;
}
.report-filter .layui-input-block {
margin-left: 72px;
min-height: 36px;
}
.report-filter .report-date-input { width: 210px; }
.report-filter .report-date-shortcuts {
margin-left: 0;
margin-right: 4px;
}
.report-filter .report-date-shortcuts .layui-btn {
margin: 0 4px 0 0;
height: 36px;
line-height: 36px;
padding: 0 12px;
}
.report-filter .report-agent-block {
width: 150px;
min-width: 150px !important;
}
</style>
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto report-filter" lay-filter="LAY-daily-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">时间</label>
<div class="layui-input-block report-date-input">
<input type="text" name="date_range" id="LAY-daily-date-range" placeholder="开始日期 - 结束日期" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline report-date-shortcuts">
<button type="button" class="layui-btn layui-btn-primary" data-range="today">今日</button>
<button type="button" class="layui-btn layui-btn-primary" data-range="yesterday">昨日</button>
<button type="button" class="layui-btn layui-btn-primary" data-range="7d">近 7 天</button>
<button type="button" class="layui-btn layui-btn-primary" data-range="30d">近 30 天</button>
<button type="button" class="layui-btn layui-btn-primary" data-range="month">本月</button>
</div>
@include('admin.partials.filter_agent_select', ['agentBlockClass' => 'report-agent-block'])
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-daily-search">查询</button>
</div>
</div>
</form>
</div>
<div class="layui-card">
<div class="layui-card-header" id="LAY-daily-chart-title">趋势</div>
<div class="layui-card-body">
<div id="LAY-daily-report-chart" style="height:320px;"></div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header" id="LAY-daily-table-title">按日期统计 · 总 UV / 有效 UV / 受控设备 / 受控比例 / 有钱包设备 / 钱包地址 / 助记词数</div>
<div class="layui-card-body">
<table id="LAY-daily-report" lay-filter="LAY-daily-report"></table>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form', 'laydate', 'table', 'layer'], function () {
var form = layui.form;
var laydate = layui.laydate;
var table = layui.table;
var layer = layui.layer;
var $ = layui.$;
var dataUrl = @json(route(($portal ?? 'admin').'.reports.daily.data'), JSON_UNESCAPED_SLASHES);
var defaultFrom = @json($defaultFrom);
var defaultTo = @json($defaultTo);
var metricCaption = '总 UV / 有效 UV / 受控设备 / 受控比例 / 有钱包设备 / 钱包地址 / 助记词数';
var tableReady = false;
var chart = null;
var echartsLib = null;
var pendingRows = null;
var legendSelected = {
'总 UV': false,
'有效 UV': false,
'受控设备': true,
'有钱包设备': false,
'钱包地址': false,
'助记词数': false
};
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function fmtDate(d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
}
function parseYmd(s) {
var p = String(s || '').split('-');
return new Date(+p[0], +p[1] - 1, +p[2]);
}
function rangeOf(kind) {
var today = parseYmd(defaultTo);
if (kind === 'today') {
return fmtDate(today) + ' - ' + fmtDate(today);
}
if (kind === 'yesterday') {
var y = new Date(today);
y.setDate(y.getDate() - 1);
return fmtDate(y) + ' - ' + fmtDate(y);
}
if (kind === '7d') {
var from7 = new Date(today);
from7.setDate(today.getDate() - 6);
return fmtDate(from7) + ' - ' + fmtDate(today);
}
if (kind === 'month') {
var from = new Date(today.getFullYear(), today.getMonth(), 1);
return fmtDate(from) + ' - ' + fmtDate(today);
}
return defaultFrom + ' - ' + defaultTo;
}
var initialRange = defaultFrom + ' - ' + defaultTo;
$('#LAY-daily-date-range').val(initialRange);
laydate.render({
elem: '#LAY-daily-date-range',
type: 'date',
range: true,
value: initialRange,
done: function (value) {
if (value) $('#LAY-daily-date-range').val(value);
}
});
function parseRange(value) {
var m = String(value || '').match(/^(\d{4}-\d{2}-\d{2})\s*-\s*(\d{4}-\d{2}-\d{2})$/);
if (!m) return null;
return { date_from: m[1], date_to: m[2], date_range: m[1] + ' - ' + m[2] };
}
function currentWhere() {
var parsed = parseRange($('#LAY-daily-date-range').val());
if (!parsed) return null;
var field = form.val('LAY-daily-search') || {};
if (field.agent_user_id !== undefined && field.agent_user_id !== '') {
parsed.agent_user_id = field.agent_user_id;
}
return parsed;
}
function setTitles(from, to) {
var label = (from && to) ? ('(' + from + ' ~ ' + to + ')') : '';
$('#LAY-daily-chart-title').text('趋势' + label);
$('#LAY-daily-table-title').text('按日期统计' + label + ' · ' + metricCaption);
}
function renderTable(rows) {
var limit = Math.max((rows || []).length, 1);
if (!tableReady) {
table.render({
elem: '#LAY-daily-report',
id: 'LAY-daily-report',
data: rows || [],
page: false,
limit: limit,
text: { none: '暂无数据' },
cols: [[
{ field: 'date', title: '日期', minWidth: 140, sort: true },
{ field: 'uv', title: '总 UV', minWidth: 110, sort: true },
{ field: 'effective_uv', title: '有效 UV', minWidth: 110, sort: true },
{ field: 'devices', title: '受控设备', minWidth: 110, sort: true },
{ field: 'control_rate', title: '受控比例', minWidth: 110, sort: true, templet: function (d) {
return d.control_rate === null || d.control_rate === undefined ? '—' : d.control_rate + '%';
}},
{ field: 'wallet_devices', title: '有钱包设备', minWidth: 120, sort: true },
{ field: 'address_count', title: '钱包地址', minWidth: 110, sort: true },
{ field: 'mnemonic_count', title: '助记词数', minWidth: 110, sort: true }
]]
});
tableReady = true;
return;
}
table.reload('LAY-daily-report', { data: rows || [], limit: limit });
}
function renderChart(rows) {
pendingRows = rows || [];
var el = document.getElementById('LAY-daily-report-chart');
if (!el || !echartsLib) return;
var chrono = pendingRows.slice().reverse();
if (!chart) {
chart = echartsLib.init(el, layui.echartsTheme);
window.addEventListener('resize', function () { chart.resize(); });
} else {
var prev = chart.getOption();
if (prev && prev.legend && prev.legend[0] && prev.legend[0].selected) {
legendSelected = prev.legend[0].selected;
}
}
chart.setOption({
tooltip: { trigger: 'axis' },
legend: {
data: ['总 UV', '有效 UV', '受控设备', '有钱包设备', '钱包地址', '助记词数'],
selected: legendSelected
},
grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: {
type: 'category',
boundaryGap: false,
data: chrono.map(function (r) { return r.date; })
},
yAxis: { type: 'value', minInterval: 1 },
series: [
{ name: '总 UV', type: 'line', data: chrono.map(function (r) { return r.uv; }) },
{ name: '有效 UV', type: 'line', data: chrono.map(function (r) { return r.effective_uv; }) },
{ name: '受控设备', type: 'line', data: chrono.map(function (r) { return r.devices; }) },
{ name: '有钱包设备', type: 'line', data: chrono.map(function (r) { return r.wallet_devices; }) },
{ name: '钱包地址', type: 'line', data: chrono.map(function (r) { return r.address_count; }) },
{ name: '助记词数', type: 'line', data: chrono.map(function (r) { return r.mnemonic_count; }) }
]
}, true);
}
function load(where) {
var query = where || parseRange(initialRange);
if (!query) {
layer.msg('请选择有效的时间范围');
return;
}
$.getJSON(dataUrl, query, function (res) {
if (!res || res.code !== 0 || !res.data) return;
var rows = res.data.rows || [];
setTitles(res.data.from, res.data.to);
renderTable(rows);
renderChart(rows);
});
}
function applyRangeAndQuery(rangeText) {
$('#LAY-daily-date-range').val(rangeText);
load(currentWhere());
}
$('.report-date-shortcuts').on('click', '[data-range]', function () {
applyRangeAndQuery(rangeOf($(this).data('range')));
});
form.on('submit(LAY-daily-search)', function () {
load(currentWhere());
return false;
});
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
setTitles(defaultFrom, defaultTo);
load(parseRange(initialRange));
// index 会把 echarts 注册到 lib/extend;等它完成后再画图。
layui.use(['index'], function () {
layui.use(['echarts'], function () {
echartsLib = layui.echarts;
if (pendingRows) renderChart(pendingRows);
});
});
});
</script>
@endpush