274 lines
9.5 KiB
PHP
274 lines
9.5 KiB
PHP
@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
|