From 93579cbc9f3de946830ac3964beabb605e1b2b35 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 6 Oct 2026 23:29:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(payment):=20=E5=94=AE=E5=90=8E=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E6=94=B9=E4=B8=BA=E6=8C=89=E9=92=AE=E8=BD=BD=E8=8D=B7?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=BC=B9=E5=87=BA=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E7=82=B9=E5=87=BB=E6=97=A0=E5=8F=8D=E9=A6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原实现是「点击 → 先请求 /orders/{n} → 再弹窗」,与项目里所有可用弹窗 (含举报链路 report.js)不同构:举报弹窗把数据放在按钮 data-* 属性里同步 弹出。前置请求会让首次点击在请求返回前毫无反应,体感就是"得二次点击"。 - 按钮 data-aksrt-after-sale-apply / -detail 携带订单与售后状态载荷 (PHP 模板与 order-center.js 的 actionHtml 同构生成),点击当拍即开 对应弹窗:申请表单 / 超时效提示 / 售后详细,零前置请求 - 「再次申请售后」同样使用已渲染载荷,不再重新拉取订单 - 提交与图片上传仍走 REST,服务端校验不变;列表刷新后载荷随之更新 - jsdom 多脚本环境实测:三种入口均在点击同一拍弹出且 fetch 计数为 0 --- assets/js/order-after-sale.js | 73 +++++++++---------- assets/js/order-center.js | 23 ++++-- .../user-center-renderers/profile-orders.php | 14 +++- .../after-sale-policy-contract.test.cjs | 18 +++-- 4 files changed, 77 insertions(+), 51 deletions(-) diff --git a/assets/js/order-after-sale.js b/assets/js/order-after-sale.js index 1115b6ac..db2d1574 100644 --- a/assets/js/order-after-sale.js +++ b/assets/js/order-after-sale.js @@ -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 += ''; } 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'))); } }); } diff --git a/assets/js/order-center.js b/assets/js/order-center.js index 0bf9f847..1684d5cc 100644 --- a/assets/js/order-center.js +++ b/assets/js/order-center.js @@ -201,7 +201,7 @@ if (allowedActions.can_cancel) html += ''; if (allowedActions.can_pay) html += ''; // 售后申请为独立弹窗,不再占用订单详情(order-after-sale.js)。 - html += afterSaleButtons(order.order_number, order.after_sale || {}); + html += afterSaleButtons(order); html += ""; session.content.innerHTML = html; @@ -282,19 +282,30 @@ if (allowedActions.can_cancel) html += ''; if (allowedActions.can_pay) html += ''; // 售后入口:申请售后 / 售后详细走 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 += ''; + html += ''; return html; } - html += ''; + html += ''; return html; } diff --git a/inc/helpers/user-center-renderers/profile-orders.php b/inc/helpers/user-center-renderers/profile-orders.php index 002cafad..ce745bca 100644 --- a/inc/helpers/user-center-renderers/profile-orders.php +++ b/inc/helpers/user-center-renderers/profile-orders.php @@ -123,12 +123,22 @@ $status_options = [ $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, + ]); ?> - + - + diff --git a/tests/widget-system/after-sale-policy-contract.test.cjs b/tests/widget-system/after-sale-policy-contract.test.cjs index a23aee82..bbb81954 100644 --- a/tests/widget-system/after-sale-policy-contract.test.cjs +++ b/tests/widget-system/after-sale-policy-contract.test.cjs @@ -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>我知道了 { 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')