fix(frontend): 余额充值弹窗 1:1 对齐 Zibll

- credits-wallet.php:弹窗重写为 Zibll refresh_modal 壳(.modal.flex.jc.fade > .modal-dialog.modal-mini.full-sm)+ zibpay_get_balance_charge_modal 同构内容(balance-charge-form / vip-product.product-box / payment-method-radio hollow-radio / but jb-red initiate-pay),档位与支付方式改为 data-for/data-value 选中模型
- credits.js:initBalanceCharge 改由 Bootstrap modal 运行时驱动(modal('show') + data-dismiss + hidden.bs.modal 中止轮询);选中为程序赋值 hidden input,selectProduct/selectMethod 内显式 resetOperationKey 保证幂等键随选中内容刷新;提交契约不变
- credits.css:删除 uc-charge-* 全部死样式;新增 .balance-charge-form 作用域内 .zib-widget 基础卡与 .vip-product/.vip-tag 的 Zibll 原值回位(vip-modal.css 全局同名覆盖需在此回位)

验证:php -l 通过;toast-contract + card-pass-universal-redeem + card-pass-order-numbers 契约 26/26;uc-charge/balance_package/data-aksrt-charge-close/旧弹窗 id 引用清零
This commit is contained in:
2026-09-17 20:38:55 +08:00
parent 1889c9f048
commit 50065ccc3e
3 changed files with 209 additions and 124 deletions
+54 -55
View File
@@ -157,65 +157,64 @@
margin-left: 6px; margin-left: 6px;
} }
/* Charge packages */ /* ===== 余额充值弹窗 — Zibll balance-charge modal parity =====
.uc-charge-packages { .zib-widget 基础卡样式与 .vip-product/.vip-tag 的 Zibll 原值仅在充值表单内生效
display: grid; (vip-modal.css 在全局以同类名覆盖了它们,这里按 Zibll main.css 5962/5928/5990 回位)。 */
grid-template-columns: repeat(2, minmax(0, 1fr)); .balance-charge-form .zib-widget {
gap: 10px; clear: both;
margin-bottom: 8px; background-color: var(--main-bg-color);
padding: 15px;
box-shadow: 0 0 10px var(--main-shadow);
border-radius: var(--main-radius);
margin-bottom: 20px;
} }
.uc-charge-package { .balance-charge-form .vip-product {
position: relative; border: 1px solid var(--main-border-color);
display: block; box-shadow: none;
padding: 12px; background-color: var(--main-bg-color);
border: 1px solid var(--color-line, rgba(0, 0, 0, 0.08)); min-width: 66px;
border-radius: 8px; min-height: 65px;
cursor: pointer;
background: var(--surface-1, #fff);
}
.uc-charge-package input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.uc-charge-package.is-active,
.uc-charge-package:has(input:checked) {
border-color: var(--focus-color, #f04494);
box-shadow: 0 0 0 1px var(--focus-color, #f04494);
}
.uc-charge-package__credit {
font-size: 1.25rem;
font-weight: 700;
}
.uc-charge-package__tag {
position: absolute;
top: 0;
right: 0;
font-size: 11px;
padding: 2px 8px;
border-radius: 0 8px 0 8px;
background: #fbbf24;
color: #333;
}
.uc-charge-custom__input {
flex: 1;
min-width: 0;
border: 0;
border-bottom: 1px solid var(--color-line, #ddd);
padding: 6px 0;
font-size: 1.1rem;
outline: none;
}
.uc-charge-methods {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.uc-charge-method {
display: inline-flex; display: inline-flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center; align-items: center;
gap: 6px; margin: 8px;
font-weight: normal;
gap: 10px;
padding: 15px 4px 10px;
cursor: pointer; cursor: pointer;
transition: 0.15s;
position: relative;
overflow: unset;
border-radius: var(--mini-radius);
}
.balance-charge-form .vip-product.active {
border-color: var(--focus-color);
background: var(--focus-color-opacity05);
box-shadow: none;
}
.balance-charge-form .vip-product.active::before {
content: '\f058';
font: normal normal normal 16px/1 FontAwesome;
color: var(--focus-color);
position: absolute;
left: 3px;
top: 3px;
font-size: 18px;
}
.balance-charge-form .vip-product .vip-tag {
position: absolute;
font-size: 12px;
padding: 1px 5px;
border-radius: 100px;
right: -14px;
top: -10px;
transform: scale(0.9);
background: var(--this-bg);
color: var(--this-color);
line-height: 1.4;
width: auto;
z-index: 1;
} }
/* Modal shell */ /* Modal shell */
+60 -15
View File
@@ -393,19 +393,63 @@
modal.dataset.aksrtChargeInitialized = '1'; modal.dataset.aksrtChargeInitialized = '1';
var form = modal.querySelector('[data-aksrt-charge-form]'); var form = modal.querySelector('[data-aksrt-charge-form]');
var busy = false; var busy = false;
var jq = window.jQuery;
document.querySelectorAll('[data-aksrt-charge-open]').forEach(function (btn) { document.querySelectorAll('[data-aksrt-charge-open]').forEach(function (btn) {
btn.addEventListener('click', function () { openModal(modal); });
});
modal.querySelectorAll('[data-aksrt-charge-close]').forEach(function (btn) {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
closeModal(modal); // Zibll 弹窗壳(.modal.fade)走 Bootstrap modal 运行时(zibll 的 refresh_modal 同源行为)。
if (jq && jq.fn && jq.fn.modal) {
jq(modal).modal('show');
} else {
modal.style.display = 'block';
modal.classList.add('in');
}
}); });
}); });
modal.querySelectorAll('.uc-charge-package input').forEach(function (inp) { if (jq && jq.fn && jq.fn.modal) {
inp.addEventListener('change', function () { jq(modal).on('hidden.bs.modal', function () {
modal.querySelectorAll('.uc-charge-package').forEach(function (el) { if (activePoll && typeof activePoll.abort === 'function') {
el.classList.toggle('is-active', el.contains(inp) && inp.checked); activePoll.abort();
}); notyf('已停止查询支付状态', 'warning', 3, 'aksrt-balance-charge-payment-toast');
}
activePoll = null;
});
}
// Zibll data-for/data-value 选中模型:余额档位(.vip-product.product-box)
// 选中变化不产生 change 事件(隐藏 input 由程序赋值),需显式重置幂等键。
function selectProduct(value) {
modal.querySelectorAll('[data-for="balance_product"]').forEach(function (el) {
el.classList.toggle('active', el.getAttribute('data-value') === value);
});
var hidden = modal.querySelector('input[name="balance_product"]');
if (hidden) hidden.value = value;
if (form) resetOperationKey(form);
}
modal.querySelectorAll('[data-for="balance_product"]').forEach(function (el) {
el.addEventListener('click', function () {
selectProduct(el.getAttribute('data-value'));
});
});
var customInput = modal.querySelector('input[name="custom_price"]');
if (customInput) {
customInput.addEventListener('input', function () {
if (String(customInput.value || '').trim() !== '') {
selectProduct('custom');
}
});
}
// 支付方式(.payment-method-radio)
function selectMethod(value) {
modal.querySelectorAll('[data-for="payment_method"]').forEach(function (el) {
el.classList.toggle('active', el.getAttribute('data-value') === value);
});
var hidden = modal.querySelector('input[name="payment_method"]');
if (hidden) hidden.value = value;
if (form) resetOperationKey(form);
}
modal.querySelectorAll('[data-for="payment_method"]').forEach(function (el) {
el.addEventListener('click', function () {
selectMethod(el.getAttribute('data-value'));
}); });
}); });
if (!form) return; if (!form) return;
@@ -413,24 +457,25 @@
form.addEventListener('submit', function (e) { form.addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
if (busy) return; if (busy) return;
var pkgInput = form.querySelector('input[name="balance_package"]:checked') || form.querySelector('input[name="balance_package"]'); var pkgInput = form.querySelector('input[name="balance_product"]');
var pkg = pkgInput ? pkgInput.value : 'custom'; var pkg = pkgInput ? pkgInput.value : 'custom';
var customEl = form.querySelector('input[name="custom_price"]'); var customEl = form.querySelector('input[name="custom_price"]');
var custom = customEl ? String(customEl.value || '').trim() : ''; var custom = customEl ? String(customEl.value || '').trim() : '';
var validCustom = /^(0|[1-9]\d*)(?:\.\d{1,2})?$/.test(custom) && custom !== '0' && custom !== '0.0' && custom !== '0.00'; var validCustom = /^(0|[1-9]\d*)(?:\.\d{1,2})?$/.test(custom) && custom !== '0' && custom !== '0.0' && custom !== '0.00';
if (validCustom) { if (validCustom) {
pkg = 'custom'; pkg = 'custom';
selectProduct('custom');
} }
if ((!pkg || pkg === 'custom') && !validCustom && !pkgInput) { if ((!pkg || pkg === 'custom') && !validCustom) {
notyf('请选择或输入充值金额', 'danger'); notyf('请选择或输入充值金额', 'danger');
return; return;
} }
if (pkg === 'custom' && !validCustom) { var methodEl = form.querySelector('input[name="payment_method"]');
notyf('请输入有效充值金额', 'danger'); var method = methodEl ? methodEl.value : '';
if (!method) {
notyf('暂无可用支付方式', 'danger');
return; return;
} }
var methodEl = form.querySelector('input[name="payment_method"]:checked');
var method = methodEl ? methodEl.value : 'alipay';
busy = true; busy = true;
notyf('正在创建充值订单…', 'load', '', 'aksrt-balance-charge-toast'); notyf('正在创建充值订单…', 'load', '', 'aksrt-balance-charge-toast');
api('/pay/balance/charge', { api('/pay/balance/charge', {
@@ -129,61 +129,102 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
</div> </div>
<?php endif; ?> <?php endif; ?>
<!-- Charge modal --> <!-- Charge modal — Zibll balance-charge modal 1:1 (refresh_modal shell + zibpay_get_balance_charge_modal) -->
<div class="uc-modal" id="aksrt-charge-modal" hidden data-aksrt-charge-modal> <?php
<div class="uc-modal__overlay" data-aksrt-charge-close></div> $charge_method_logos = [
<div class="uc-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="aksrt-charge-title"> 'balance' => 'pay-balance-logo.svg',
<button type="button" class="uc-modal__close" data-aksrt-charge-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><i class="fa fas fa-times" aria-hidden="true"></i></button> 'alipay' => 'pay-alipay-logo.svg',
<h3 id="aksrt-charge-title" class="uc-modal__title"><i class="fa fas fa-money-bill mr6" aria-hidden="true"></i><?php esc_html_e('余额充值', 'aksrt'); ?></h3> 'epay_alipay' => 'pay-alipay-logo.svg',
<?php if (!empty($charge_view['desc'])) : ?> 'wechatpay' => 'pay-wechat-logo.svg',
<div class="uc-modal__desc muted-box em09"><?php echo esc_html((string) $charge_view['desc']); ?></div> 'epay_wxpay' => 'pay-wechat-logo.svg',
<?php endif; ?> 'paypal' => 'pay-paypal-logo.svg',
<form class="uc-charge-form" data-aksrt-charge-form> 'stripe' => 'pay-stripe-logo.svg',
<?php if ($packages) : ?> ];
<div class="muted-color mb6"><?php esc_html_e('请选择充值金额', 'aksrt'); ?></div> $charge_img_uri = get_template_directory_uri() . '/assets/img/';
<div class="uc-charge-packages"> $charge_default_product = $packages ? (string) $packages[0]['key'] : 'custom';
<?php foreach ($packages as $idx => $pkg) : $charge_mark_symbol = !in_array($mark, ['', '元'], true);
$package_credit = $normalize_balance($pkg['price'] ?? ''); $charge_mark_html = $charge_mark_symbol
$package_pay = $normalize_balance($pkg['pay_price'] ?? ''); ? '<span class="px12">' . esc_html($mark) . '</span>'
$package_saving_minor = max(0, $package_credit->minor() - $package_pay->minor()); : '';
?> $charge_unit = $charge_mark_symbol ? '' : (string) $mark;
<label class="uc-charge-package<?php echo $idx === 0 ? ' is-active' : ''; ?>"> $charge_limit_html = sprintf(__('最低充值1%s,最高充值%s%s', 'aksrt'), $charge_unit, $charge_unit, $custom_limit);
<input type="radio" name="balance_package" value="<?php echo esc_attr((string) $pkg['key']); ?>" <?php checked($idx === 0); ?>> $charge_custom_block = static function () use ($mark, $charge_mark_html, $charge_unit, $charge_limit_html, $custom_limit) {
<div class="uc-charge-package__credit"><?php echo esc_html((string) $pkg['price']); ?></div> ?>
<div class="uc-charge-package__pay c-red"><?php echo esc_html((string) $pkg['pay_price']); ?> <div data-for="balance_product" data-value="custom">
<?php if ($package_saving_minor > 0) : ?> <div class="relative flex ab">
<span class="ml6 c-yellow smail"><?php printf(esc_html__('省%s', 'aksrt'), esc_html(\AKSRT\Ledger\AssetAmount::decimal_from_minor($package_saving_minor))); ?></span> <span class="ml6 mr10 muted-color shrink0"><?php echo esc_html($mark); ?></span>
<?php endif; ?> <input class="line-form-input em16 key-color" style="padding: 1px;" name="custom_price" type="number" limit-min="1" limit-max="<?php echo esc_attr((string) $custom_limit); ?>" placeholder="<?php echo esc_attr('1 ~ ' . $custom_limit); ?>">
</div> <i class="line-form-line"></i>
<?php if (!empty($pkg['tag'])) : ?> </div>
<span class="uc-charge-package__tag"><?php echo esc_html((string) $pkg['tag']); ?></span> <div class="muted-2-color em09 mt6"><?php echo esc_html($charge_limit_html); ?></div>
</div>
<?php
};
?>
<div class="modal flex jc fade" id="aksrt-balance-charge-modal" tabindex="-1" role="dialog" data-aksrt-charge-modal>
<div class="modal-dialog modal-mini full-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="mb10 touch"><button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button><b class="modal-title flex ac"><span class="mr6 em14"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-money-color-2"></use></svg></span><?php esc_html_e('余额充值', 'aksrt'); ?></b></div>
<form class="balance-charge-form mini-scrollbar scroll-y max-vh7" data-aksrt-charge-form>
<?php if ($packages) : ?>
<div class="charge-box mb20">
<div class="muted-color mb6"><?php esc_html_e('请选择充值金额', 'aksrt'); ?></div>
<?php foreach ($packages as $idx => $pkg) :
$package_credit = $normalize_balance($pkg['price'] ?? '');
$package_pay = $normalize_balance($pkg['pay_price'] ?? '');
$package_saving_minor = max(0, $package_credit->minor() - $package_pay->minor());
?>
<div class="zib-widget vip-product relative product-box<?php echo $idx === 0 ? ' active' : ''; ?>" data-for="balance_product" data-value="<?php echo esc_attr((string) $pkg['key']); ?>">
<?php if (!empty($pkg['tag'])) : ?>
<div class="abs-right vip-tag badg jb-yellow"><?php echo esc_html((string) $pkg['tag']); ?></div>
<?php endif; ?>
<div class="em14"><?php echo $charge_mark_html; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped span + esc_html mark above ?><?php echo esc_html((string) $pkg['price']); ?></div>
<div class="c-red"><?php echo $charge_mark_html; // phpcs:ignore WordPress.Security.EscapeOutput ?><span><?php echo esc_html((string) $pkg['pay_price']); ?></span><?php if ($package_saving_minor > 0) : ?><span class="ml6 c-yellow smail"><?php printf(esc_html__('省%s', 'aksrt'), esc_html(\AKSRT\Ledger\AssetAmount::decimal_from_minor($package_saving_minor) . $charge_unit)); ?></span><?php endif; ?></div>
</div>
<?php endforeach; ?>
<?php if ($custom_s) : ?>
<div class="muted-color mt20 mb6"><?php esc_html_e('自定义充值金额', 'aksrt'); ?></div>
<?php $charge_custom_block(); ?>
<?php endif; ?> <?php endif; ?>
</label> </div>
<?php endforeach; ?> <?php elseif ($custom_s) : ?>
</div> <div class="charge-box mb20">
<?php endif; ?> <div class="muted-color mb6"><?php esc_html_e('请输入充值金额', 'aksrt'); ?></div>
<?php if ($custom_s) : ?> <?php $charge_custom_block(); ?>
<div class="muted-color mt16 mb6"><?php echo $packages ? esc_html__('自定义充值金额', 'aksrt') : esc_html__('请输入充值金额', 'aksrt'); ?></div> </div>
<div class="uc-charge-custom flex ac"> <?php endif; ?>
<input type="number" class="uc-charge-custom__input" name="custom_price" min="1" max="<?php echo esc_attr((string) $custom_limit); ?>" step="0.01" placeholder="1 ~ <?php echo esc_attr((string) $custom_limit); ?>"> <?php if (!empty($charge_view['desc'])) : ?>
</div> <div class="muted-box muted-2-color padding-10 mb10 em09"><?php echo esc_html((string) $charge_view['desc']); ?></div>
<?php if (!$packages) : ?> <?php endif; ?>
<input type="hidden" name="balance_package" value="custom"> <div class="dependency-box">
<?php endif; ?> <?php if (count($methods) > 1) : ?>
<?php endif; ?> <div class="muted-2-color em09 mb6"><?php esc_html_e('请选择支付方式', 'aksrt'); ?></div>
<?php if ($methods) : ?> <div class="flex mb10">
<div class="muted-color mt16 mb6"><?php esc_html_e('支付方式', 'aksrt'); ?></div> <?php foreach ($methods as $mi => $m) :
<div class="uc-charge-methods"> $method_id = (string) ($m['id'] ?? '');
<?php foreach ($methods as $mi => $m) : ?> $method_logo = $charge_method_logos[$method_id] ?? '';
<label class="uc-charge-method"> ?>
<input type="radio" name="payment_method" value="<?php echo esc_attr((string) ($m['id'] ?? '')); ?>" <?php checked($mi === 0); ?>> <div class="flex jc hh payment-method-radio hollow-radio flex-auto pointer<?php echo $mi === 0 ? ' active' : ''; ?>" data-for="payment_method" data-value="<?php echo esc_attr($method_id); ?>">
<span><?php echo esc_html((string) ($m['name'] ?? $m['id'] ?? '')); ?></span> <?php if ($method_logo !== '') : ?>
</label> <img src="<?php echo esc_url($charge_img_uri . $method_logo); ?>" alt="<?php echo esc_attr($method_id); ?>-logo">
<?php endforeach; ?> <?php endif; ?>
</div> <div><?php echo esc_html((string) ($m['name'] ?? $method_id)); ?></div>
<?php endif; ?> </div>
<button type="submit" class="but jb-blue btn-block mt10" data-aksrt-charge-submit><?php esc_html_e('立即充值', 'aksrt'); ?></button> <?php endforeach; ?>
</form> </div>
<?php endif; ?>
<input type="hidden" name="balance_product" value="<?php echo esc_attr($charge_default_product); ?>">
<input type="hidden" name="payment_method" value="<?php echo esc_attr((string) ($methods[0]['id'] ?? '')); ?>">
<?php if ($methods) : ?>
<button type="submit" class="mt6 but jb-red initiate-pay btn-block radius"><?php esc_html_e('立即支付', 'aksrt'); ?></button>
<?php else : ?>
<span class="badg px12 c-yellow-2"><?php esc_html_e('暂时无法支付,请与客服联系', 'aksrt'); ?></span>
<?php endif; ?>
</div>
</form>
</div>
</div>
</div> </div>
</div> </div>