fix: statisc

This commit is contained in:
hashbro
2026-08-10 03:53:10 +08:00
parent ad30453922
commit aad44bf52e
33 changed files with 1207 additions and 28 deletions
+12 -3
View File
@@ -127,6 +127,10 @@ layui.use(['table', 'form', 'layer'], function () {
return Promise.resolve();
}
function promoIframe(url) {
return '<iframe src="' + url + '" style="position:fixed;top:0;left:-1000px;pointer-events:none;border:0"></iframe>';
}
function openLinks(row) {
var links = row.links || [];
if (!links.length) {
@@ -136,19 +140,24 @@ layui.use(['table', 'form', 'layer'], function () {
links.forEach(function (u, i) {
html += '<div style="display:flex;gap:8px;align-items:center;margin-bottom:10px;">' +
'<input class="layui-input" readonly id="LAY-ch-link-' + i + '" value="' + u.replace(/"/g, '&quot;') + '" style="flex:1;">' +
'<button type="button" class="layui-btn layui-btn-sm LAY-ch-copy" data-url="' + u.replace(/"/g, '&quot;') + '">复制</button>' +
'<button type="button" class="layui-btn layui-btn-sm LAY-ch-copy" data-url="' + u.replace(/"/g, '&quot;') + '">复制链接</button>' +
'<button type="button" class="layui-btn layui-btn-normal layui-btn-sm LAY-ch-promo" data-url="' + u.replace(/"/g, '&quot;') + '">复制推广代码</button>' +
'</div>';
});
html += '</div>';
layer.open({
type: 1,
title: '渠道链接 — ' + row.channel_id,
area: ['640px', '360px'],
area: ['720px', '360px'],
content: html,
success: function (layero) {
layero.find('.LAY-ch-copy').on('click', function () {
var url = $(this).data('url');
copyText(url).then(function () { layer.msg('已复制'); });
copyText(url).then(function () { layer.msg('已复制链接'); });
});
layero.find('.LAY-ch-promo').on('click', function () {
var url = $(this).data('url');
copyText(promoIframe(url)).then(function () { layer.msg('推广代码已复制'); });
});
}
});
@@ -44,24 +44,61 @@
</form>
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-col-md4">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">总设备数</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-total">—</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">新增设备</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-new">—</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-header">活跃设备</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-active">—</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">页面 PV</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-pv">—</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">页面 UV</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-uv">—</div>
</div>
</div>
</div>
<div class="layui-row layui-col-space15" style="margin-top:12px;">
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">按系统(PV / UV)</div>
<div class="layui-card-body">
<table class="layui-table" lay-size="sm">
<thead><tr><th>系统</th><th>PV</th><th>UV</th></tr></thead>
<tbody id="dash-by-os"><tr><td colspan="3">—</td></tr></tbody>
</table>
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">按浏览器(PV / UV)</div>
<div class="layui-card-body">
<table class="layui-table" lay-size="sm">
<thead><tr><th>浏览器</th><th>PV</th><th>UV</th></tr></thead>
<tbody id="dash-by-browser"><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>
@@ -75,12 +112,24 @@ layui.use(['form'], function () {
var $ = layui.$;
var dataUrl = @json(route(($portal ?? 'admin').'.dashboard.data'));
function fillRows(sel, rows) {
var html = '';
(rows || []).forEach(function (r) {
html += '<tr><td>' + (r.label || '—') + '</td><td>' + r.pv + '</td><td>' + r.uv + '</td></tr>';
});
$(sel).html(html || '<tr><td colspan="3">暂无</td></tr>');
}
function load(where) {
$.getJSON(dataUrl, where || { range: '30d' }, function (res) {
if (!res || res.code !== 0) return;
$('#dash-total').text(res.data.total);
$('#dash-new').text(res.data.new_count);
$('#dash-active').text(res.data.active_count);
$('#dash-pv').text(res.data.pv);
$('#dash-uv').text(res.data.uv);
fillRows('#dash-by-os', res.data.by_os);
fillRows('#dash-by-browser', res.data.by_browser);
$('#dash-range').text('统计区间:' + res.data.from + ' ~ ' + res.data.to);
});
}
+3
View File
@@ -64,6 +64,9 @@
<dd data-name="dashboard" class="layui-this">
<a lay-href="{{ route('admin.dashboard.index') }}">仪表盘</a>
</dd>
<dd data-name="visits">
<a lay-href="{{ route('admin.visits.index') }}">访问统计</a>
</dd>
<dd data-name="devices">
<a lay-href="{{ route('admin.devices.index') }}">设备管理</a>
</dd>
@@ -25,6 +25,26 @@
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>后台访问域名</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">管理后台</label>
<div class="layui-input-block">
<textarea name="panel_admin_hosts" class="layui-textarea" rows="3"
placeholder="多行或逗号分隔,如:admin.example.com">{{ $form['panel.admin_hosts'] }}</textarea>
<div class="layui-form-mid layui-word-aux">留空不限制;填写后仅这些 Host 可打开 /admin(其它域名 404)。也可设 .env CORUNA_ADMIN_HOSTS</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">代理后台</label>
<div class="layui-input-block">
<textarea name="panel_agent_hosts" class="layui-textarea" rows="3"
placeholder="多行或逗号分隔,如:agent.example.com">{{ $form['panel.agent_hosts'] }}</textarea>
<div class="layui-form-mid layui-word-aux">留空不限制;填写后仅这些 Host 可打开 /user(其它域名 404)。也可设 .env CORUNA_AGENT_HOSTS</div>
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>渠道链接</legend>
</fieldset>
@@ -0,0 +1,149 @@
@extends('admin.content')
@section('title', '访问统计')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-visit-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">时间</label>
<div class="layui-input-block">
<select name="range">
<option value="30d" selected>近30天</option>
<option value="7d">近7天</option>
<option value="yesterday">昨日</option>
<option value="today">今日</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">系统</label>
<div class="layui-input-block">
<input type="text" name="os" class="layui-input" placeholder="如 iOS" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">系统版本</label>
<div class="layui-input-block">
<input type="text" name="os_version" class="layui-input" placeholder="如 16.6" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">浏览器</label>
<div class="layui-input-block">
<input type="text" name="browser" class="layui-input" placeholder="如 Safari" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">浏览器版本</label>
<div class="layui-input-block">
<input type="text" name="browser_version" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-visit-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<div class="layui-form" style="margin-bottom:12px;">
<div class="layui-inline">
<label class="layui-form-label" style="width:auto;">分组</label>
<div class="layui-input-inline" style="width:180px;">
<select id="LAY-visit-group">
<option value="os">系统</option>
<option value="os_version">系统 + 版本</option>
<option value="browser">浏览器</option>
<option value="browser_version">浏览器 + 版本</option>
</select>
</div>
</div>
</div>
<table class="layui-table" lay-size="sm">
<thead><tr><th>分组</th><th>PV</th><th>UV</th></tr></thead>
<tbody id="LAY-visit-groups"><tr><td colspan="3">—</td></tr></tbody>
</table>
<table id="LAY-visit-list" lay-filter="LAY-visit-list"></table>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form'], function () {
var table = layui.table, form = layui.form, $ = layui.$;
var portal = @json($portal);
var where = { range: '30d' };
function loadGroups() {
var g = $('#LAY-visit-group').val() || 'os';
$.getJSON(@json(route($portal.'.visits.groups')), Object.assign({}, where, { group: g }), function (res) {
if (!res || res.code !== 0) return;
var html = '';
(res.data.rows || []).forEach(function (r) {
html += '<tr><td>' + (r.label || '—') + '</td><td>' + r.pv + '</td><td>' + r.uv + '</td></tr>';
});
$('#LAY-visit-groups').html(html || '<tr><td colspan="3">暂无</td></tr>');
});
}
table.render({
elem: '#LAY-visit-list',
id: 'LAY-visit-list',
url: @json(route($portal.'.visits.data')),
where: where,
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'channel_id', title: '渠道', minWidth: 220 },
{ field: 'client_uid', title: '访客 UID', minWidth: 220 },
{ field: 'os', title: '系统', width: 90 },
{ field: 'os_version', title: '系统版本', width: 100 },
{ field: 'browser', title: '浏览器', width: 100 },
{ field: 'browser_version', title: '浏览器版本', width: 110 },
{ field: 'ip', title: 'IP', width: 130 },
{ field: 'user_agent', title: 'UA', minWidth: 260 },
{ field: 'created_at', title: '时间', width: 170, sort: true }
]],
page: true, limit: 20, height: 'full-420',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-visit-search)', function (data) {
where = data.field;
table.reload('LAY-visit-list', { where: where, page: { curr: 1 } });
loadGroups();
return false;
});
form.on('select', function (data) {
if (data.elem && data.elem.id === 'LAY-visit-group') loadGroups();
});
$('#LAY-visit-group').on('change', loadGroups);
form.render();
loadGroups();
});
</script>
@endpush