fix(vip): 在 /user/vip/ 档位购买卡下方补充积分兑换会员入口
参考主题 zibpay_user_page_tab_content_vip:非会员 + 积分兑换会员开启 + 有可兑换套餐时,在两张档位购买卡下方输出全宽入口横条(points-color 图标 + 标题 + 立即兑换 badg),点击经 data-aksrt-vip-points-open 打开 wp_footer 静态兑换弹窗,与会员购买弹窗内入口共用同一弹窗。 - aksrt-vip.php 新增 aksrt_get_vip_points_exchange_entry_html(),门控与 弹窗壳同源(取服务端 catalog),已是会员不展示(对齐参考主题契约) - vip-tab.php 在卡片区之后输出入口;credits.css 补 .uc-vip-points-entry 表面样式(与档位卡同一变量体系,暗色自适应) - pay-vip.js 改文档级委托承接 data-aksrt-vip-points-open,一处绑定覆盖 静态页面横条与弹窗内按钮;移除 bindModalUi 内的重复分支 - 契约测试更新为双入口(11 项),node --test 420 项通过,php -l 通过 未写数据迁移;未做真实视觉效果验收。
This commit is contained in:
@@ -49,8 +49,8 @@
|
||||
opacity:0 形成隐形遮罩,拦截整页点击;
|
||||
- 打开态由 pay-vip.js 自包含开关的内联 display:flex 驱动(block 会破坏
|
||||
.flex.jc 双轴居中),与余额充值/购买积分弹窗同构;
|
||||
- 入口在本页 runtime 会员弹窗内,壳需高于 runtime 宿主(100050),以复现
|
||||
参考主题在会员弹窗之上再叠一层兑换弹窗的行为。 */
|
||||
- 入口在会员购买弹窗与 /user/vip/ 页面横条两处,壳需高于 runtime 宿主
|
||||
(100050),以复现参考主题在会员弹窗之上再叠一层兑换弹窗的行为。 */
|
||||
.modal[data-aksrt-vip-points-modal] {
|
||||
display: none;
|
||||
z-index: 100060;
|
||||
|
||||
@@ -1325,6 +1325,28 @@ body.uc-modal-open {
|
||||
margin: 0 !important;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 积分兑换会员入口(参考主题用户中心 VIP 页 1:1:两张档位购买卡下方的全宽横条,
|
||||
左侧 points-color 图标 + 「积分兑换会员」,右侧「立即兑换」badg + 右箭头;
|
||||
结构与配色由 zibll 工具类(flex/ac/jsb/flex1/em12/c-yellow/badg)承担,
|
||||
此处只提供与 .uc-vip-level-card 同一变量体系的表面与间距)。 */
|
||||
.uc-vip-points-entry {
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius, 10px);
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.uc-vip-points-entry:hover,
|
||||
.uc-vip-points-entry:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
.uc-vip-points-entry .icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.uc-vip-assets-entry {
|
||||
gap: 12px;
|
||||
}
|
||||
@@ -1361,6 +1383,9 @@ html:not(.dark):not(.theme-dark) .uc-vip-level-card.level-2 .v-name {
|
||||
.uc-vip-level-card {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.uc-vip-points-entry {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.uc-vip-tab .user-content-myvip {
|
||||
padding: 14px 16px;
|
||||
gap: 12px;
|
||||
|
||||
+13
-6
@@ -695,12 +695,6 @@
|
||||
});
|
||||
return;
|
||||
}
|
||||
var pointsLink = event.target.closest("[data-aksrt-vip-points-open]");
|
||||
if (pointsLink) {
|
||||
event.preventDefault();
|
||||
openPointsModal(pointsLink);
|
||||
return;
|
||||
}
|
||||
var submit = event.target.closest("[data-vip-submit]");
|
||||
if (submit) {
|
||||
event.preventDefault();
|
||||
@@ -763,11 +757,24 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 积分兑换入口(data-aksrt-vip-points-open)文档级委托:一处绑定同时覆盖
|
||||
// /user/vip/ 页面静态输出的入口横条与会员购买弹窗内运行时注入的同款按钮
|
||||
// (事件冒泡至 document,弹窗内不再单独挂监听)。
|
||||
function bindPointsEntry() {
|
||||
document.addEventListener("click", function (event) {
|
||||
var pointsLink = event.target.closest ? event.target.closest("[data-aksrt-vip-points-open]") : null;
|
||||
if (!pointsLink) return;
|
||||
event.preventDefault();
|
||||
openPointsModal(pointsLink);
|
||||
});
|
||||
}
|
||||
|
||||
function bindOnce() {
|
||||
if (window.__aksrtPayVipBound) return;
|
||||
window.__aksrtPayVipBound = true;
|
||||
document.addEventListener("click", onDocClick, true);
|
||||
bindPointsModal();
|
||||
bindPointsEntry();
|
||||
}
|
||||
|
||||
jQuery(document).off("auto_fun.aksrt-pay-vip").on("auto_fun.aksrt-pay-vip", bindOnce);
|
||||
|
||||
@@ -427,6 +427,55 @@ if (!function_exists('aksrt_get_vip_purchase_url')) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户中心 /user/vip/ 页面的积分兑换会员入口(参考主题 zibpay_user_page_tab_content_vip 1:1)。
|
||||
*
|
||||
* 参考主题在「我的会员」摘要与两张档位购买卡之后输出全宽入口横条:
|
||||
* .c-yellow.zib-widget.flex.ac.jsb(zib-widget → 本主题 aksrt-panel 表面)
|
||||
* ├─ .flex1.em12:points-color 图标 +「积分兑换会员」
|
||||
* └─ .badg.radius.c-yellow:「立即兑换」+ fa-chevron-circle-right
|
||||
* 门控对齐参考主题 zibpay_get_vip_points_exchange_link:已是会员不展示
|
||||
* ($vip_level ? '' : link),且要求积分兑换会员开启、至少一个会员等级
|
||||
* 开启并存在可兑换套餐(catalog.points_exchange 已内含 credits 门控)。
|
||||
*
|
||||
* 入口点击经 data-aksrt-vip-points-open 由 pay-vip.js 打开 wp_footer 输出的
|
||||
* 静态兑换弹窗(aksrt_render_vip_points_exchange_modal),与会员购买弹窗内
|
||||
* 的入口共用同一弹窗与开合开关。
|
||||
*
|
||||
* @param int $user_id 当前用户 ID(<=0 返回空串)。
|
||||
* @param int $vip_level 用户当前会员等级(>0 时返回空串;缺省 -1 表示自行解析)。
|
||||
* @return string 空串表示不满足展示条件。
|
||||
*/
|
||||
if (!function_exists('aksrt_get_vip_points_exchange_entry_html')) :
|
||||
function aksrt_get_vip_points_exchange_entry_html(int $user_id, int $vip_level = -1): string {
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\Content\VipService')) {
|
||||
return '';
|
||||
}
|
||||
if ($vip_level < 0) {
|
||||
$vip_level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
}
|
||||
// 参考主题契约:已经是会员就不再展示积分兑换入口。
|
||||
if ($vip_level > 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$catalog = \AKSRT\Content\VipService::get_instance()->get_catalog($user_id);
|
||||
$points_exchange = is_array($catalog['points_exchange'] ?? null) ? $catalog['points_exchange'] : [];
|
||||
$packages = is_array($points_exchange['packages'] ?? null) ? $points_exchange['packages'] : [];
|
||||
if (empty($points_exchange['enabled']) || $packages === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<a class="uc-vip-points-entry flex ac jsb" href="javascript:;" data-aksrt-vip-points-open>'
|
||||
. '<span class="flex1 em12 c-yellow">'
|
||||
. '<svg class="icon mr6 em12" aria-hidden="true"><use xlink:href="#icon-points-color"></use></svg>'
|
||||
. esc_html__('积分兑换会员', 'aksrt')
|
||||
. '</span>'
|
||||
. '<span class="badg radius c-yellow">' . esc_html__('立即兑换', 'aksrt')
|
||||
. '<i class="fa fas fa-chevron-circle-right ml6" aria-hidden="true"></i></span></a>';
|
||||
}
|
||||
endif;
|
||||
|
||||
/**
|
||||
* Format VIP equity HTML for front-end display.
|
||||
*
|
||||
@@ -507,10 +556,13 @@ if (!function_exists('aksrt_format_vip_equity_html')) {
|
||||
/**
|
||||
* 全局积分兑换会员弹窗(参考主题 refresh_modal 同构静态壳)。
|
||||
*
|
||||
* 入口位于会员购买弹窗(.pay-vip → assets/js/pay-vip.js)内,而 .pay-vip
|
||||
* 触发点全站可见(页头、浮动按钮、用户中心、付费卡),因此弹窗壳在满足
|
||||
* 条件时随 wp_footer 全局输出一次;显隐由 pay-vip.js 自包含开关驱动,
|
||||
* 静止态由 vip-modal.css 的 .modal[data-aksrt-vip-points-modal] 显式隐藏。
|
||||
* 入口有两处:会员购买弹窗(.pay-vip → assets/js/pay-vip.js)内的「积分兑换
|
||||
* 会员」按钮,以及 /user/vip/ 档位购买卡下方的入口横条
|
||||
* (aksrt_get_vip_points_exchange_entry_html);二者均带
|
||||
* data-aksrt-vip-points-open,由 pay-vip.js 文档级委托打开本弹窗。.pay-vip
|
||||
* 触发点全站可见,因此弹窗壳在满足条件时随 wp_footer 全局输出一次;显隐由
|
||||
* pay-vip.js 自包含开关驱动,静止态由 vip-modal.css 的
|
||||
* .modal[data-aksrt-vip-points-modal] 显式隐藏。
|
||||
*/
|
||||
if (!function_exists('aksrt_render_vip_points_exchange_modal')) :
|
||||
function aksrt_render_vip_points_exchange_modal(): void {
|
||||
|
||||
@@ -164,6 +164,12 @@ foreach ([1, 2] as $lv) {
|
||||
<?php echo $cards; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// 积分兑换会员入口(参考主题 zibpay_user_page_tab_content_vip 1:1:档位购买卡
|
||||
// 下方的全宽横条;仅非会员 + 积分兑换开启 + 有可兑换套餐时输出)。
|
||||
echo aksrt_get_vip_points_exchange_entry_html($user_id, $level); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 各字段已在生成时转义。
|
||||
?>
|
||||
|
||||
<?php if (!empty($vip_settings['more'])) : ?>
|
||||
<div class="muted-2-color em09 muted-box padding-10 mini-radius mt15">
|
||||
<?php echo $vip_settings['more']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
|
||||
@@ -164,9 +164,11 @@ trait VipServiceModal {
|
||||
* 与余额充值/购买积分弹窗同构(静止态由 vip-modal.css 显式隐藏,打开态由
|
||||
* pay-vip.js 的自包含开关以内联 display:flex 驱动)。
|
||||
*
|
||||
* 入口位于会员购买弹窗内(pay-vip.js 的「积分兑换会员」按钮),而 .pay-vip
|
||||
* 触发点全站可见,因此由 aksrt_render_vip_points_exchange_modal() 在
|
||||
* wp_footer 全局输出一次。
|
||||
* 入口有两处(均带 data-aksrt-vip-points-open,pay-vip.js 文档级委托开合):
|
||||
* 会员购买弹窗内的「积分兑换会员」按钮,以及 /user/vip/ 档位购买卡下方的
|
||||
* 入口横条(aksrt_get_vip_points_exchange_entry_html)。.pay-vip 触发点
|
||||
* 全站可见,因此由 aksrt_render_vip_points_exchange_modal() 在 wp_footer
|
||||
* 全局输出一次。
|
||||
*
|
||||
* @return string 空串表示积分兑换不可用(不输出弹窗壳)。
|
||||
*/
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
/**
|
||||
* 积分兑换会员弹窗契约(参考主题 zibpay_get_vip_points_exchange_modal 1:1 对齐)。
|
||||
* 积分兑换会员弹窗与双入口契约(参考主题 1:1 对齐)。
|
||||
*
|
||||
* 三条硬约束:
|
||||
* 硬约束:
|
||||
* 1) 服务端静态输出参考主题 refresh_modal 同构壳(`modal flex jc fade` >
|
||||
* `modal-dialog modal-mini full-sm` > `modal-content` > `modal-body`),
|
||||
* 入口只保留在会员购买弹窗内——/user/vip/ 页底部的独立入口已移除。
|
||||
* `modal-dialog modal-mini full-sm` > `modal-content` > `modal-body`)。
|
||||
* 入口有两处,分别对应参考主题的两个变体:
|
||||
* - 会员购买弹窗内(zibpay_get_pay_uservip_modal 的
|
||||
* `btn-block but mb10 padding-lg c-green mini-radius` 按钮);
|
||||
* - /user/vip/ 档位购买卡下方(zibpay_user_page_tab_content_vip 的
|
||||
* `c-yellow zib-widget flex ac jsb` 全宽横条,仅非会员展示)。
|
||||
* 两处入口均带 data-aksrt-vip-points-open,由 pay-vip.js 文档级委托打开
|
||||
* 同一静态弹窗。
|
||||
* 2) 静止态必须被 vip-modal.css 显式隐藏(.flex 覆盖回归护栏),打开态为
|
||||
* flex 而非 block,且层级高于 runtime 宿主(100050),以复现参考主题在
|
||||
* 会员弹窗之上再叠一层兑换弹窗的行为。
|
||||
* 3) 旧的客户端自实现弹窗(renderPointsModal / aksrt-vip-points-option /
|
||||
* catalog.vip_url)必须已彻底移除。
|
||||
* catalog.vip_url)与旧版 /user/vip/ 自定义入口(uc-vip-points-link)
|
||||
* 必须已彻底移除。
|
||||
*/
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
@@ -102,11 +109,54 @@ test('会员弹窗内的入口为参考主题同款按钮并开本地弹窗', ()
|
||||
assert.match(vipJs, /openPointsModal\(pointsLink\)/);
|
||||
});
|
||||
|
||||
test('旧的自实现兑换弹窗与独立入口已彻底移除', () => {
|
||||
test('/user/vip/ 档位购买卡下方的入口为参考主题用户中心同款横条', () => {
|
||||
// 参考主题 zibpay_user_page_tab_content_vip:my_box → vip_card → points link,
|
||||
// 横条为 `c-yellow zib-widget flex ac jsb`(zib-widget → 本主题aksrt-panel表面)。
|
||||
assert.match(footer, /function aksrt_get_vip_points_exchange_entry_html/);
|
||||
assert.match(vipTab, /aksrt_get_vip_points_exchange_entry_html\(\$user_id, \$level\)/);
|
||||
// 结构 1:1:左 points-color 图标 + 标题(flex1 em12 c-yellow),
|
||||
// 右「立即兑换」badg + 右箭头。
|
||||
assert.match(footer, /uc-vip-points-entry flex ac jsb/);
|
||||
assert.match(footer, /flex1 em12 c-yellow/);
|
||||
assert.match(footer, /#icon-points-color/);
|
||||
assert.match(footer, /badg radius c-yellow/);
|
||||
assert.match(footer, /fa fas fa-chevron-circle-right ml6/);
|
||||
assert.match(footer, /data-aksrt-vip-points-open/);
|
||||
});
|
||||
|
||||
test('/user/vip/ 入口门控与弹窗壳同源:非会员 + 兑换开启 + 有可兑换套餐', () => {
|
||||
// 参考主题 zibpay_get_vip_points_exchange_link 门控:已是会员不展示。
|
||||
assert.match(footer, /if \(\$vip_level > 0\) \{\s*return '';/);
|
||||
// 可用性直接取服务端 catalog(内含 credits 门控与等级启用过滤),
|
||||
// 与 wp_footer 弹窗壳的渲染条件一致,避免出现无弹窗可开的死入口。
|
||||
assert.match(footer, /get_catalog\(\$user_id\)/);
|
||||
assert.match(footer, /empty\(\$points_exchange\['enabled'\]\) \|\| \$packages === \[\]/);
|
||||
// 模板传入当前会员等级(>0 时不输出)。
|
||||
assert.match(footer, /int \$vip_level = -1/);
|
||||
});
|
||||
|
||||
test('页面入口样式落在 credits.css 且与档位卡同一表面体系', () => {
|
||||
assert.match(creditsCss, /\.uc-vip-points-entry \{/);
|
||||
assert.match(creditsCss, /\.uc-vip-points-entry\s*\{[^}]*var\(--main-bg-color, var\(--surface-1, #fff\)\)/s);
|
||||
assert.match(creditsCss, /\.uc-vip-points-entry\s*\{[^}]*box-shadow:\s*var\(--shadow-sm\)/s);
|
||||
});
|
||||
|
||||
test('入口开合由文档级委托驱动,弹窗内不再单独挂监听', () => {
|
||||
// /user/vip/ 静态横条与弹窗内按钮共用一处 document 委托(冒泡)。
|
||||
assert.match(vipJs, /function bindPointsEntry\(\)/);
|
||||
assert.match(vipJs, /bindPointsEntry\(\);/);
|
||||
assert.match(vipJs, /document\.addEventListener\("click", function \(event\) \{[\s\S]*?data-aksrt-vip-points-open/);
|
||||
// bindModalUi 内的重复分支已移除:openPointsModal(pointsLink) 只保留一处调用。
|
||||
const openCalls = vipJs.match(/openPointsModal\(pointsLink\)/g) || [];
|
||||
assert.strictEqual(openCalls.length, 1);
|
||||
});
|
||||
|
||||
test('旧的客户端自实现弹窗与旧版自定义入口已彻底移除', () => {
|
||||
assert.doesNotMatch(vipJs, /renderPointsModal|exchangeFromModal|vipPointsPay/);
|
||||
assert.doesNotMatch(vipJs, /aksrt-vip-points-option|aksrt-vip-points-modal__/);
|
||||
assert.doesNotMatch(vipCss, /\.aksrt-vip-points-option|\.aksrt-vip-points-submit/);
|
||||
// /user/vip/ 页底部不再有独立积分兑换入口。
|
||||
// 旧版 /user/vip/ 自定义入口(uc-vip-points-link)不得回归;现行入口为
|
||||
// 参考主题 1:1 的 uc-vip-points-entry 横条。
|
||||
assert.doesNotMatch(vipTab, /uc-vip-points-link/);
|
||||
assert.doesNotMatch(creditsCss, /\.uc-vip-points-link/);
|
||||
// 用户中心侧的旧兑换控制器([data-aksrt-vip-points],全站无对应 DOM)。
|
||||
|
||||
Reference in New Issue
Block a user