feat(payment): 通用卡密兑换弹窗并修正核销订单实付与订单名称

- /user/balance/ 余额卡片入口改为通用「卡密兑换」弹窗:请求不带类型,
  后端按卡密自动识别余额/会员/积分;卡号输入恒展示且选填
- 后台「余额充值 → 卡密充值」语义更新为「卡密兑换」,开关更名
  「启用卡密兑换功能」(设置键不变),双源 schema 同步并重建 SPA
- 删除死代码:credits-vip.php 死文件、credits-balance.php 内联积分
  兑换块、/pay/card-pass/options 路由与 rest_options、labels 数组、
  asset_points_card_description/password_only 死字段及内联块专属 CSS
- 卡密核销订单实付恒 0:create_redeem_order 源头写 0,UserOrders
  对 card_pass 订单视图层归零覆盖历史行(不迁移数据)
- 订单名称按 note.meta 生成:卡密兑换-余额充值X / 卡密兑换-积分充值X /
  购入[会员档位名]X天(永久)
- 新增契约 card-pass-universal-redeem.test.cjs,收紧 user-assets-layout
  契约(禁 .uc-card-pass 根类回潮)
This commit is contained in:
kate522 committed 2026-09-17 19:04:47 +08:00
1 parent ba8d4b1b90
commit 3481620ed0
14 files changed
+213 -415

No files matched your search

@@ -1,13 +1,13 @@
/**
* 余额充值 — 参考主题结构:用户余额(含余额提现)、转账、充值商品选项、
* 卡密充值与余额兑换积分。卡密的生成/管理在后台「卡密管理」页完成,
* 卡密兑换与余额兑换积分。卡密的生成/管理在后台「卡密管理」页完成,
* 此处不再保留「充值卡」配置项。
*/
export const rechargeSchema = {
key: 'payment.recharge',
title: '余额充值',
icon: 'fa fas fa-bolt',
description: '配置余额功能、充值商品选项、卡密充值与余额兑换积分。',
description: '配置余额功能、充值商品选项、卡密兑换与余额兑换积分。',
defaults: {
pay_balance_s: true,
pay_balance_withdraw_s: false,
@@ -95,12 +95,12 @@ export const rechargeSchema = {
},
{
key: 'pass-charge',
title: '卡密充值',
title: '卡密兑换',
icon: 'fa fas fa-ticket-alt',
fields: [
{ key: 'pay_balance_pass_charge_s', label: '启用卡密充值余额功能', type: 'switch', default: false },
{ key: 'pay_balance_pass_charge_desc', label: '卡密充值说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'pay_balance_pass_charge_s', value: true }, help: '用户使用卡密充值时显示的内容,建议为提醒事项、充值协议等,支持HTML代码' },
{ key: 'pay_balance_pass_charge_only_password', label: '单密码模式', type: 'switch', default: false, dependsOn: { key: 'pay_balance_pass_charge_s', value: true }, help: '仅需输入密码,不需要卡号' },
{ key: 'pay_balance_pass_charge_s', label: '启用卡密兑换功能', type: 'switch', default: false, help: '开启后用户中心余额卡片展示通用「卡密兑换」入口,支持余额 / 会员 / 积分卡密,类型按卡密自动识别' },
{ key: 'pay_balance_pass_charge_desc', label: '卡密兑换说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'pay_balance_pass_charge_s', value: true }, help: '用户使用卡密兑换时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
{ key: 'pay_balance_pass_charge_only_password', label: '单密码模式', type: 'switch', default: false, dependsOn: { key: 'pay_balance_pass_charge_s', value: true }, help: '余额卡密仅需输入密码,不需要卡号' },
],
},
{
@@ -27,8 +27,6 @@ export const creditsSchema = {
points_deduction_rate: 100,
points_deduction_max: 50,
asset_points_card_enabled: false,
asset_points_card_description: '',
asset_points_card_password_only: true,
points_show_balance: true,
points_show_log: true,
points_expire_days: 0,
@@ -95,9 +93,7 @@ export const creditsSchema = {
title: '积分卡密兑换',
icon: 'fa fas fa-ticket-alt',
fields: [
{ key: 'asset_points_card_enabled', label: '启用积分卡密兑换', type: 'switch', default: false, help: '后台充值卡可生成积分卡,用户中心兑换入账。' },
{ key: 'asset_points_card_description', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'asset_points_card_enabled', value: true } },
{ key: 'asset_points_card_password_only', label: '仅密码兑换', type: 'switch', default: true, dependsOn: { key: 'asset_points_card_enabled', value: true }, help: '开启后只需输入卡密;关闭则需卡号+卡密。' },
{ key: 'asset_points_card_enabled', label: '启用积分卡密兑换', type: 'switch', default: false, help: '开启后积分卡密可在用户中心「卡密兑换」弹窗兑换入账。' },
],
},
{
+3 -40
View File
@@ -33,16 +33,12 @@
.uc-assets-row--dual > .uc-assets-col:first-child > .uc-asset-card {
grid-column: 1;
}
.uc-assets-row--dual > .uc-assets-col:last-child > .uc-asset-card,
.uc-assets-row--dual > .uc-assets-col:last-child > .uc-card-pass {
.uc-assets-row--dual > .uc-assets-col:last-child > .uc-asset-card {
grid-column: 2;
}
.uc-assets-row--dual > .uc-assets-col > .uc-asset-card {
grid-row: 1;
}
.uc-assets-row--dual > .uc-assets-col > .uc-card-pass {
grid-row: 2;
}
.uc-assets-col {
min-width: 0;
display: flex;
@@ -59,8 +55,7 @@
grid-column: auto;
grid-row: auto;
}
.uc-assets-row--dual > .uc-assets-col > .uc-asset-card,
.uc-assets-row--dual > .uc-assets-col > .uc-card-pass {
.uc-assets-row--dual > .uc-assets-col > .uc-asset-card {
grid-column: auto;
grid-row: auto;
}
@@ -87,15 +82,6 @@
flex-wrap: wrap;
row-gap: 10px;
}
/* Card-pass under a col must not look like a second stacked card */
.uc-assets-col > .uc-card-pass {
margin: 0;
padding: 12px 14px;
border-radius: 8px;
border: 1px dashed var(--color-line, #e5e7eb);
background: var(--muted-bg-color, #f8fafc);
box-shadow: none;
}
.uc-asset-card__head {
margin-bottom: 8px;
}
@@ -830,30 +816,7 @@ body.uc-modal-open {
border-color: var(--color-line, #e5e7eb);
}
/* Card-pass redeem (balance / VIP) */
.uc-card-pass {
margin: 16px 0 0;
padding: 14px 16px;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 12px;
background: #fff;
}
.uc-card-pass__title {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 700;
color: #111827;
margin: 0 0 8px;
}
.uc-card-pass__title i { color: #2563eb; }
.uc-card-pass__desc {
margin: 0 0 10px;
font-size: 12px;
color: #6b7280;
line-height: 1.5;
}
/* Card-pass redeem modal inputs (universal modal in credits-wallet.php) */
.uc-card-pass__input {
display: block;
width: 100%;
+5 -7
View File
@@ -266,7 +266,7 @@
});
}
// ===== 卡密兑换(余额 / 会员) =====
// ===== 卡密兑换(余额 / 会员 / 积分,通用弹窗由后端自动识别类型) =====
function initCardPass() {
var roots = document.querySelectorAll('[data-aksrt-card-pass]');
if (!roots || !roots.length) return;
@@ -306,16 +306,12 @@
var cardInput = root.querySelector('[data-aksrt-card-no]');
var password = pwInput ? String(pwInput.value || '').trim() : '';
var card = cardInput ? String(cardInput.value || '').trim() : '';
// 卡号为选填:是否需要卡号由后端按卡密类型判定并返回错误提示。
if (!password) {
setTip('请输入卡密', true);
if (pwInput) pwInput.focus();
return;
}
if (cardInput && !card) {
setTip('请输入卡号', true);
cardInput.focus();
return;
}
busy = true;
btn.disabled = true;
@@ -337,7 +333,9 @@
}
var data = (r.json && r.json.data) ? r.json.data : r.json;
resetOperationKey(root);
if (type === 'vip' && window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
// 通用兑换(type 为空)时以后端识别出的卡密类型为准。
var redeemedType = (data && data.type) || type;
if (redeemedType === 'vip' && window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
window.AksrtModalRuntime.invalidate(['vip', 'orders']);
}
setTip((data && data.message) || '兑换成功,正在刷新…');