fix(payment): 售后弹窗改为按钮载荷同步弹出,修复首次点击无反馈
原实现是「点击 → 先请求 /orders/{n} → 再弹窗」,与项目里所有可用弹窗
(含举报链路 report.js)不同构:举报弹窗把数据放在按钮 data-* 属性里同步
弹出。前置请求会让首次点击在请求返回前毫无反应,体感就是"得二次点击"。
- 按钮 data-aksrt-after-sale-apply / -detail 携带订单与售后状态载荷
(PHP 模板与 order-center.js 的 actionHtml 同构生成),点击当拍即开
对应弹窗:申请表单 / 超时效提示 / 售后详细,零前置请求
- 「再次申请售后」同样使用已渲染载荷,不再重新拉取订单
- 提交与图片上传仍走 REST,服务端校验不变;列表刷新后载荷随之更新
- jsdom 多脚本环境实测:三种入口均在点击同一拍弹出且 fetch 计数为 0
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
* 与「禁封/举报」链路同构(report.js / user-ban.js):
|
||||
* - [data-aksrt-after-sale-apply] → 申请售后弹窗(超时效则改为超时效提示弹窗)
|
||||
* - [data-aksrt-after-sale-detail] → 售后详细弹窗(可再次申请 / 不可再申请提示)
|
||||
* 两个入口的 data-* 属性携带订单与售后状态载荷(同 report.js 的 data-aksrt-report),
|
||||
* 点击即同步弹窗,不做前置请求(避免首次点击无反馈)。
|
||||
* - 提交走 REST /aksrt/v1/orders/{order_number}/after-sale
|
||||
* - 举证图片走 /orders/{order_number}/after-sale/upload-image(复用评论图片上传校验)
|
||||
*
|
||||
@@ -206,7 +208,7 @@
|
||||
if (afterSale.can_reapply) {
|
||||
html += '<div class="but-average modal-buts">' +
|
||||
'<button type="button" class="but" data-aksrt-modal-close>关闭</button>' +
|
||||
'<button type="button" class="but c-blue" data-aksrt-after-sale-reapply="' + escapeHtml((order && order.order_number) || '') + '"><i class="fa fas fa-undo-alt" aria-hidden="true"></i>再次申请售后</button>' +
|
||||
'<button type="button" class="but c-blue" data-aksrt-after-sale-reapply data-aksrt-after-sale-payload="' + escapeHtml(JSON.stringify(order)) + '"><i class="fa fas fa-undo-alt" aria-hidden="true"></i>再次申请售后</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
// 不可再申请售后(工单未结束 / 已完结 / 超过时效):仅提示 + 我知道了。
|
||||
@@ -270,42 +272,39 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadOrder(number) {
|
||||
if (!window.AksrtRequest || typeof window.AksrtRequest.request !== 'function') {
|
||||
return Promise.reject(new Error('请求组件加载失败,请刷新页面后重试'));
|
||||
/**
|
||||
* 解析按钮 data-* 上的售后载荷(与举报链路 data-aksrt-report 同模式):
|
||||
* 入口数据随列表一起渲染,点击即可同步弹窗,不做前置请求。
|
||||
*/
|
||||
function parsePayload(raw) {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
var data = JSON.parse(raw);
|
||||
return data && typeof data === 'object' ? data : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return window.AksrtRequest.request('/orders/' + encodeURIComponent(number), { method: 'GET' })
|
||||
.then(function (result) { return (result && result.data) || null; });
|
||||
}
|
||||
|
||||
function openApplyFlow(number) {
|
||||
if (!number) return;
|
||||
loadOrder(number).then(function (order) {
|
||||
var afterSale = order && order.after_sale ? order.after_sale : null;
|
||||
if (!order || !afterSale || !afterSale.enabled) {
|
||||
notyf('售后申请暂不可用', 'danger');
|
||||
return;
|
||||
}
|
||||
if (afterSale.expired) { openExpired(); return; }
|
||||
if (afterSale.has_application) { openDetail(order); return; }
|
||||
openApply(order);
|
||||
}).catch(function (error) {
|
||||
notyf((error && error.message) || '订单加载失败,请刷新后重试', 'danger');
|
||||
});
|
||||
// 申请售后入口:超时效 → 超时效提示;已申请 → 售后详细;否则 → 申请表单。
|
||||
function openApplyFlow(payload) {
|
||||
var afterSale = payload && payload.after_sale ? payload.after_sale : null;
|
||||
if (!payload || !afterSale || !afterSale.enabled) {
|
||||
notyf('售后申请暂不可用', 'danger');
|
||||
return;
|
||||
}
|
||||
if (afterSale.expired) { openExpired(); return; }
|
||||
if (afterSale.has_application) { openDetail(payload); return; }
|
||||
openApply(payload);
|
||||
}
|
||||
|
||||
function openDetailFlow(number) {
|
||||
if (!number) return;
|
||||
loadOrder(number).then(function (order) {
|
||||
var afterSale = order && order.after_sale ? order.after_sale : null;
|
||||
if (!order || !afterSale || !afterSale.enabled || !afterSale.has_application) {
|
||||
notyf('暂无售后记录', 'danger');
|
||||
return;
|
||||
}
|
||||
openDetail(order);
|
||||
}).catch(function (error) {
|
||||
notyf((error && error.message) || '订单加载失败,请刷新后重试', 'danger');
|
||||
});
|
||||
function openDetailFlow(payload) {
|
||||
var afterSale = payload && payload.after_sale ? payload.after_sale : null;
|
||||
if (!payload || !afterSale || !afterSale.enabled || !afterSale.has_application) {
|
||||
notyf('暂无售后记录', 'danger');
|
||||
return;
|
||||
}
|
||||
openDetail(payload);
|
||||
}
|
||||
|
||||
function collectImages(shell) {
|
||||
@@ -477,13 +476,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 再次申请售后:重新拉取订单状态,时效/工单口径以最新为准。
|
||||
// 再次申请售后:载荷已随按钮渲染,直接开申请表单(时效/工单状态随列表刷新更新)。
|
||||
var reapplyBtn = event.target.closest('[data-aksrt-after-sale-reapply]');
|
||||
if (reapplyBtn) {
|
||||
event.preventDefault();
|
||||
var number = reapplyBtn.getAttribute('data-aksrt-after-sale-reapply') || '';
|
||||
var payload = parsePayload(reapplyBtn.getAttribute('data-aksrt-after-sale-payload'));
|
||||
window.AksrtModalRuntime.close('replace');
|
||||
openApplyFlow(number);
|
||||
openApplyFlow(payload);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -504,13 +503,13 @@
|
||||
var apply = event.target.closest('[data-aksrt-after-sale-apply]');
|
||||
if (apply) {
|
||||
event.preventDefault();
|
||||
openApplyFlow(apply.getAttribute('data-aksrt-after-sale-apply') || '');
|
||||
openApplyFlow(parsePayload(apply.getAttribute('data-aksrt-after-sale-apply')));
|
||||
return;
|
||||
}
|
||||
var detail = event.target.closest('[data-aksrt-after-sale-detail]');
|
||||
if (detail) {
|
||||
event.preventDefault();
|
||||
openDetailFlow(detail.getAttribute('data-aksrt-after-sale-detail') || '');
|
||||
openDetailFlow(parsePayload(detail.getAttribute('data-aksrt-after-sale-detail')));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
if (allowedActions.can_cancel) html += '<button class="btn-ghost" type="button" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
if (allowedActions.can_pay) html += '<button class="btn-primary" type="button" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml(paymentMethod.value || "") + '">继续支付</button>';
|
||||
// 售后申请为独立弹窗,不再占用订单详情(order-after-sale.js)。
|
||||
html += afterSaleButtons(order.order_number, order.after_sale || {});
|
||||
html += afterSaleButtons(order);
|
||||
html += "</footer>";
|
||||
|
||||
session.content.innerHTML = html;
|
||||
@@ -282,19 +282,30 @@
|
||||
if (allowedActions.can_cancel) html += '<button class="btn-ghost" type="button" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
if (allowedActions.can_pay) html += '<button class="btn-primary" type="button" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml((order.payment_method || {}).value || "") + '">继续支付</button>';
|
||||
// 售后入口:申请售后 / 售后详细走 order-after-sale.js 的独立弹窗。
|
||||
if (afterSale.enabled) html += afterSaleButtons(order.order_number, afterSale);
|
||||
if (afterSale.enabled) html += afterSaleButtons(order);
|
||||
return html;
|
||||
}
|
||||
|
||||
// 售后按钮(列表卡片与订单详情弹窗共用):已申请 → 售后详细,否则 → 申请售后。
|
||||
function afterSaleButtons(orderNumber, afterSale) {
|
||||
// 载荷与 PHP 模板同构(order-after-sale.js 同步消费),点击即弹窗、无前置请求。
|
||||
function afterSaleButtons(order) {
|
||||
var afterSale = order.after_sale || {};
|
||||
var html = "";
|
||||
if (!afterSale || !afterSale.enabled) return html;
|
||||
if (!afterSale.enabled) return html;
|
||||
var payload = escapeHtml(JSON.stringify({
|
||||
order_number: order.order_number || "",
|
||||
product_name: order.product_name || "",
|
||||
amount: order.amount || "0",
|
||||
is_points: !!order.is_points,
|
||||
currency_symbol: order.currency_symbol || "",
|
||||
status_label: order.status_label || "",
|
||||
after_sale: afterSale,
|
||||
}));
|
||||
if (afterSale.has_application) {
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-detail="' + escapeHtml(orderNumber) + '">售后详细</button>';
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-detail="' + payload + '">售后详细</button>';
|
||||
return html;
|
||||
}
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-apply="' + escapeHtml(orderNumber) + '">申请售后</button>';
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-apply="' + payload + '">申请售后</button>';
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,12 +123,22 @@ $status_options = [
|
||||
<?php if (!empty($actions['can_pay'])) : ?><button class="btn-primary" type="button" data-aksrt-order-pay="<?php echo esc_attr($number); ?>" data-aksrt-order-method="<?php echo esc_attr((string) ($order['payment_method']['value'] ?? '')); ?>"><?php esc_html_e('继续支付', 'aksrt'); ?></button><?php endif; ?>
|
||||
<?php
|
||||
$after_sale = is_array($order['after_sale'] ?? null) ? $order['after_sale'] : [];
|
||||
// 售后弹窗载荷随按钮渲染(同举报链路 data-aksrt-report):点击即同步弹窗。
|
||||
$after_sale_payload = wp_json_encode([
|
||||
'order_number' => $number,
|
||||
'product_name' => (string) ($order['product_name'] ?? ''),
|
||||
'amount' => (string) ($order['amount'] ?? '0'),
|
||||
'is_points' => !empty($order['is_points']),
|
||||
'currency_symbol' => (string) ($order['currency_symbol'] ?? ''),
|
||||
'status_label' => (string) ($order['status_label'] ?? ''),
|
||||
'after_sale' => $after_sale,
|
||||
]);
|
||||
?>
|
||||
<?php if (!empty($after_sale['enabled'])) : ?>
|
||||
<?php if (!empty($after_sale['has_application'])) : ?>
|
||||
<button class="btn-outline" type="button" data-aksrt-after-sale-detail="<?php echo esc_attr($number); ?>"><?php esc_html_e('售后详细', 'aksrt'); ?></button>
|
||||
<button class="btn-outline" type="button" data-aksrt-after-sale-detail="<?php echo esc_attr($after_sale_payload); ?>"><?php esc_html_e('售后详细', 'aksrt'); ?></button>
|
||||
<?php else : ?>
|
||||
<button class="btn-outline" type="button" data-aksrt-after-sale-apply="<?php echo esc_attr($number); ?>"><?php esc_html_e('申请售后', 'aksrt'); ?></button>
|
||||
<button class="btn-outline" type="button" data-aksrt-after-sale-apply="<?php echo esc_attr($after_sale_payload); ?>"><?php esc_html_e('申请售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
|
||||
@@ -279,7 +279,7 @@ test('frontend after-sale modals are standalone and config-driven', () => {
|
||||
assert.match(js, /REAPPLY_BLOCKED_TEXT = '该无法再次申请售后,该订单已售后,暂不支持再次申请售后。'/)
|
||||
assert.match(js, /escapeHtml\(REAPPLY_BLOCKED_TEXT\)/)
|
||||
assert.match(js, /data-aksrt-modal-close>我知道了</)
|
||||
assert.match(js, /data-aksrt-after-sale-reapply=/)
|
||||
assert.match(js, /data-aksrt-after-sale-reapply data-aksrt-after-sale-payload="/)
|
||||
|
||||
// 提交后关闭弹窗并刷新订单列表
|
||||
assert.match(js, /AKSRTOrderCenter\.refresh\(orderNumber\)/)
|
||||
@@ -300,9 +300,12 @@ test('order center drops the embedded after-sale form and exposes refresh()', ()
|
||||
assert.ok(!js.includes('onDocumentSubmit'), 'document submit handler for the after-sale form must be removed')
|
||||
|
||||
// 列表卡片与订单详情弹窗底部共用新的售后按钮
|
||||
assert.match(js, /function afterSaleButtons\(orderNumber, afterSale\)/)
|
||||
assert.match(js, /if \(afterSale\.enabled\) html \+= afterSaleButtons\(order\.order_number, afterSale\);/)
|
||||
assert.match(js, /html \+= afterSaleButtons\(order\.order_number, order\.after_sale \|\| \{\}\);/)
|
||||
assert.match(js, /function afterSaleButtons\(order\)/)
|
||||
assert.match(js, /var payload = escapeHtml\(JSON.stringify\(\{/)
|
||||
assert.match(js, /data-aksrt-after-sale-detail="' \+ payload \+ '"/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="' \+ payload \+ '"/)
|
||||
assert.match(js, /if \(afterSale\.enabled\) html \+= afterSaleButtons\(order\);/)
|
||||
assert.ok(js.includes('html += afterSaleButtons(order);'), 'detail modal footer renders the after-sale buttons')
|
||||
assert.match(js, /data-aksrt-after-sale-detail="/)
|
||||
assert.match(js, /data-aksrt-after-sale-apply="/)
|
||||
|
||||
@@ -328,8 +331,11 @@ test('after-sale assets, template buttons and runtime data are wired', () => {
|
||||
|
||||
const template = read('inc/helpers/user-center-renderers/profile-orders.php')
|
||||
assert.match(template, /\$after_sale = is_array\(\$order\['after_sale'\] \?\? null\) \? \$order\['after_sale'\] : \[\];/)
|
||||
assert.match(template, /data-aksrt-after-sale-detail="/)
|
||||
assert.match(template, /data-aksrt-after-sale-apply="/)
|
||||
// 载荷随按钮渲染(order-after_sale.js 同步消费),点击即弹窗、无前置请求
|
||||
assert.match(template, /\$after_sale_payload = wp_json_encode\(\[/)
|
||||
assert.match(template, /'after_sale'\s*=> \$after_sale,/)
|
||||
assert.match(template, /data-aksrt-after-sale-detail="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /data-aksrt-after-sale-apply="<\?php echo esc_attr\(\$after_sale_payload\);/)
|
||||
assert.match(template, /esc_html_e\('售后详细', 'aksrt'\)/)
|
||||
assert.ok(!template.includes('can_after_sale'), 'template must not use the removed can_after_sale flag')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user