feat: balance change

This commit is contained in:
hashbro
2026-08-29 23:16:57 +08:00
parent e9192d5720
commit 3e87a44de3
19 changed files with 808 additions and 36 deletions
@@ -210,6 +210,7 @@
@endsection
@push('scripts')
@include('admin.partials.mnemonic_reveal')
<script>
layui.use(['table', 'form', 'laypage', 'layer'], function () {
var table = layui.table;
@@ -224,6 +225,11 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
var destroyUrl = @json(route(($portal ?? 'admin').'.devices.destroy', $device));
var listUrl = @json(route(($portal ?? 'admin').'.devices.index'));
var token = @json(csrf_token());
var revealMnemonic = window.CorunaMnemonicReveal({
token: token,
google_bound: @json(!empty($google_bound)),
security_url: @json(auth('admin')->check() ? route('admin.security.google2fa') : '')
});
var dash = function (v) { return v ? v : '—'; };
var esc = function (v) {
return String(v == null ? '' : v)
@@ -426,6 +432,11 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
{ field: 'source', title: 'Source', width: 160, sort: true, templet: function (d) { return dash(d.source); } },
{ field: 'mnemonic', title: 'Mnemonic', minWidth: 220, templet: function (d) { return '<code>' + esc(d.mnemonic) + '</code>'; } },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } }
@if(!empty($can_reveal))
, { title: '操作', width: 110, align: 'center', templet: function (d) {
return d.reveal_url ? '<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="reveal">查看明文</a>' : '—';
} }
@endif
]],
keystores: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
@@ -495,6 +506,12 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
if (tab === 'mnemonics') {
table.on('tool(LAY-device-tab-list)', function (obj) {
if (obj.event === 'reveal') revealMnemonic(obj.data);
});
}
if (tab === 'keystores') {
table.on('tool(LAY-device-tab-list)', function (obj) {
if (obj.event === 'decrypt') {
@@ -61,6 +61,9 @@
<div class="layui-card-body">
<table id="LAY-mn-list" lay-filter="LAY-mn-list"></table>
<script type="text/html" id="LAY-mn-ops">
@if(!empty($can_reveal))
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="reveal">查看明文</a>
@endif
<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>
@@ -69,11 +72,17 @@
@endsection
@push('scripts')
@include('admin.partials.mnemonic_reveal')
<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);
var revealMnemonic = window.CorunaMnemonicReveal({
token: token,
google_bound: @json(!empty($google_bound)),
security_url: @json(auth('admin')->check() ? route('admin.security.google2fa') : '')
});
function esc(v) {
return String(v == null ? '' : v)
@@ -226,7 +235,7 @@ layui.use(['table', 'form', 'layer'], function () {
{ 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' }
{ title: '操作', width: {{ !empty($can_reveal) ? 280 : 190 }}, align: 'center', fixed: 'right', toolbar: '#LAY-mn-ops' }
]],
page: true, limit: 20, limits: [10, 20, 30, 50],
request: { pageName: 'page', limitName: 'limit' },
@@ -237,6 +246,10 @@ layui.use(['table', 'form', 'layer'], function () {
return false;
});
table.on('tool(LAY-mn-list)', function (obj) {
if (obj.event === 'reveal') {
revealMnemonic(obj.data);
return;
}
if (obj.event === 'wallets') {
openWallets(obj.data);
return;
@@ -0,0 +1,77 @@
<script>
window.CorunaMnemonicReveal = function (opts) {
opts = opts || {};
var layer = (window.layui && layui.layer) ? layui.layer : null;
var $ = (window.layui && layui.$) ? layui.$ : window.jQuery;
if (!layer || !$) return;
var token = opts.token || '';
var googleBound = !!opts.google_bound;
var securityUrl = opts.security_url || '';
function esc(v) {
return String(v == null ? '' : v)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
return function (row) {
if (!row || !row.reveal_url) {
return layer.msg('无权查看明文');
}
if (!googleBound) {
return layer.msg('请先在「安全 → 谷歌验证」绑定。查看助记词必须验证。', {
time: 3200
}, function () {
if (securityUrl && parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(securityUrl, '谷歌验证');
}
});
}
layer.prompt({
title: '输入谷歌验证码查看明文',
formType: 0,
maxlength: 16
}, function (value, index) {
var loadIdx = layer.load(1, { shade: 0.1 });
$.ajax({
url: row.reveal_url,
method: 'POST',
data: { _token: token, GACode: value },
success: function (res) {
layer.close(loadIdx);
if (!res || res.code !== 0) {
return layer.msg((res && res.msg) || '验证失败', { icon: 2 });
}
layer.close(index);
var phrase = (res.data && res.data.mnemonic) || '';
layer.open({
title: '助记词明文 #' + (row.id || ''),
area: ['560px', 'auto'],
content: '<div style="padding:8px 4px;word-break:break-word;"><code style="font-size:13px;line-height:1.6;">' +
esc(phrase) + '</code></div>',
btn: ['复制', '关闭'],
yes: function (i) {
var done = function () { layer.msg('已复制'); layer.close(i); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(phrase).then(done).catch(function () {
window.prompt('复制助记词', phrase);
layer.close(i);
});
} else {
window.prompt('复制助记词', phrase);
layer.close(i);
}
}
});
},
error: function (xhr) {
layer.close(loadIdx);
layer.msg((xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '验证失败', { icon: 2 });
}
});
});
};
};
</script>
@@ -6,19 +6,30 @@
<div class="layui-card">
<div class="layui-card-header">谷歌验证(Google Authenticator)</div>
<div class="layui-card-body">
<p style="margin-bottom:16px;">
当前状态:
@if($enabled)
<span style="color:#16b777;">已开启</span>
@elseif($bound)
<span style="color:#ffb800;">已绑定未开启</span>
<p style="margin-bottom:8px;">
绑定状态:
@if($bound)
<span style="color:#16b777;">已绑定</span>
@else
<span style="color:#999;">未绑定</span>
@endif
</p>
<p style="margin-bottom:16px;">
登录验证:
@if($enabled)
<span style="color:#16b777;">已开启(登录必须填验证码)</span>
@elseif($bound)
<span style="color:#ffb800;">未开启(登录不校验,查看助记词仍需验证)</span>
@else
<span style="color:#999;">未开启</span>
@endif
</p>
<p class="layui-word-aux" style="margin-bottom:16px;">
绑定后可以关闭登录验证。超级管理员查看助记词明文必须使用谷歌验证,与登录开关无关。
</p>
@if(!$bound)
<form class="layui-form" lay-filter="LAY-ga-prepare" style="max-width:480px;">
<form class="layui-form" lay-filter="LAY-ga-prepare" style="max-width:520px;">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
@@ -32,7 +43,7 @@
</div>
</form>
<div id="LAY-ga-bind-box" style="display:none;max-width:480px;margin-top:24px;">
<div id="LAY-ga-bind-box" style="display:none;max-width:520px;margin-top:24px;">
<div id="LAY-ga-qr" style="margin-bottom:12px;"></div>
<p style="margin-bottom:12px;">密钥:<code id="LAY-ga-secret"></code></p>
<form class="layui-form" lay-filter="LAY-ga-bind">
@@ -43,6 +54,13 @@
<input type="text" name="GAKey" required lay-verify="required" class="layui-input" inputmode="numeric" autocomplete="one-time-code" placeholder="扫码后输入 6 位验证码">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录验证</label>
<div class="layui-input-block">
<input type="checkbox" name="login_verify" value="1" title="登录时校验谷歌验证码">
<div class="layui-form-mid layui-word-aux">可不勾选。不勾选则登录不校验,之后也可再开。</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="LAY-ga-bind">确认绑定</button>
@@ -51,7 +69,7 @@
</form>
</div>
@else
<form class="layui-form" style="max-width:480px;">
<form class="layui-form" style="max-width:520px;">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
@@ -61,15 +79,15 @@
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-block">
<input type="text" name="GACode" id="LAY-ga-code" class="layui-input" inputmode="numeric" autocomplete="one-time-code" placeholder="关闭/解绑时需要">
<input type="text" name="GACode" id="LAY-ga-code" class="layui-input" inputmode="numeric" autocomplete="one-time-code" placeholder="开关登录验证 / 解绑都需要">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
@if($enabled)
<button type="button" class="layui-btn layui-btn-warm" id="LAY-ga-close">关闭验证</button>
<button type="button" class="layui-btn layui-btn-warm" id="LAY-ga-close">关闭登录验证</button>
@else
<button type="button" class="layui-btn" id="LAY-ga-open">开启验证</button>
<button type="button" class="layui-btn" id="LAY-ga-open">开启登录验证</button>
@endif
<button type="button" class="layui-btn layui-btn-danger" id="LAY-ga-unbind">解除绑定</button>
</div>
@@ -101,6 +119,7 @@ layui.use(['form', 'layer'], function () {
$('#LAY-ga-secret').text(res.secret || '');
$('#LAY-ga-secret-input').val(res.secret || '');
$('#LAY-ga-bind-box').show();
form.render('checkbox');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '请求失败', { icon: 2 });
@@ -110,10 +129,12 @@ layui.use(['form', 'layer'], function () {
});
form.on('submit(LAY-ga-bind)', function (data) {
var payload = Object.assign({}, data.field, { _token: token });
payload.login_verify = $('input[name=login_verify]').is(':checked') ? 1 : 0;
$.ajax({
url: @json(route('admin.security.google2fa.bind')),
method: 'POST',
data: Object.assign({}, data.field, { _token: token }),
data: payload,
success: function (res) {
layer.msg(res.msg || '', { icon: res.code === 0 ? 1 : 2 }, function () {
if (res.code === 0) location.reload();
@@ -151,7 +172,7 @@ layui.use(['form', 'layer'], function () {
$('#LAY-ga-close').on('click', function () { toggle(0); });
$('#LAY-ga-unbind').on('click', function () {
layer.confirm('确定解除谷歌验证绑定?', function (index) {
layer.confirm('确定解除谷歌验证绑定?解除后无法查看助记词明文。', function (index) {
$.ajax({
url: @json(route('admin.security.google2fa.unbind')),
method: 'POST',
+76
View File
@@ -0,0 +1,76 @@
@extends('admin.content')
@section('title', '修改密码')
@section('content')
<div class="layui-card">
<div class="layui-card-header">修改密码</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="LAY-password-form" style="max-width:480px;">
<div class="layui-form-item">
<label class="layui-form-label">账号</label>
<div class="layui-input-block">
<div class="layui-form-mid">{{ $username }}</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">当前密码</label>
<div class="layui-input-block">
<input type="password" name="current_password" required lay-verify="required" class="layui-input" autocomplete="current-password">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新密码</label>
<div class="layui-input-block">
<input type="password" name="password" required lay-verify="required" class="layui-input" autocomplete="new-password" placeholder="至少 6 位">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认新密码</label>
<div class="layui-input-block">
<input type="password" name="password_confirmation" required lay-verify="required" class="layui-input" autocomplete="new-password">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="LAY-password-save">保存</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
form.on('submit(LAY-password-save)', function (data) {
$.ajax({
url: @json(route('user.password.update')),
method: 'PUT',
data: Object.assign({}, data.field, { _token: token }),
success: function (res) {
layer.msg(res.msg || (res.code === 0 ? '密码已更新' : '失败'), { icon: res.code === 0 ? 1 : 2 }, function () {
if (res.code === 0) {
$('input[name=current_password], input[name=password], input[name=password_confirmation]').val('');
}
});
},
error: function (xhr) {
var body = xhr.responseJSON || {};
var msg = body.msg || body.message;
if (!msg && body.errors) {
var first = Object.values(body.errors)[0];
msg = Array.isArray(first) ? first[0] : first;
}
layer.msg(msg || '保存失败', { icon: 2 });
}
});
return false;
});
});
</script>
@endpush
+12
View File
@@ -33,6 +33,7 @@
<cite>{{ auth('agent')->user()->username ?? 'agent' }}</cite>
</a>
<dl class="layui-nav-child">
<dd style="text-align: center;"><a lay-href="{{ route('user.password.index') }}">修改密码</a></dd>
<dd layadmin-event="logout" style="text-align: center;"><a>退出</a></dd>
</dl>
</li>
@@ -107,6 +108,17 @@
</dd>
</dl>
</li>
<li data-name="account" class="layui-nav-item">
<a href="javascript:;" lay-tips="账号" lay-direction="2">
<i class="layui-icon layui-icon-password"></i>
<cite>账号</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="password">
<a lay-href="{{ route('user.password.index') }}">修改密码</a>
</dd>
</dl>
</li>
</ul>
</div>
</div>