feat(after-sale): 支持撤销售后工单并接入人机验证
This commit is contained in:
+151
-34
@@ -4,9 +4,12 @@
|
||||
* 与「禁封/举报」链路同构(report.js / user-ban.js):
|
||||
* - [data-aksrt-after-sale-apply] → 申请售后弹窗(超时效则改为超时效提示弹窗)
|
||||
* - [data-aksrt-after-sale-detail] → 售后详细弹窗(可再次申请 / 不可再申请提示)
|
||||
* - [data-aksrt-after-sale-withdraw] → 撤销售后二次确认弹窗(仅待审核工单),确认后 DELETE 工单
|
||||
* 两个入口的 data-* 属性携带订单与售后状态载荷(同 report.js 的 data-aksrt-report),
|
||||
* 点击即同步弹窗,不做前置请求(避免首次点击无反馈)。
|
||||
* - 提交走 REST /aksrt/v1/orders/{order_number}/after-sale
|
||||
* - 撤销走 DELETE 同端点(仅待审核工单,后端置为已取消)
|
||||
* - 提交与撤销前先过人机验证(after_sale_apply / after_sale_cancel,场景未启用自动放行)
|
||||
* - 举证图片走 /orders/{order_number}/after-sale/upload-image(复用评论图片上传校验)
|
||||
*
|
||||
* 按钮形态由订单视图模型的 after_sale 状态决定(UserOrders::after_sale_state):
|
||||
@@ -178,8 +181,13 @@
|
||||
|
||||
html += '<div class="aksrt-after-sale-detail__statusRow">' +
|
||||
'<span class="muted-2-color em09">售后状态</span>' +
|
||||
'<span class="aksrt-after-sale-detail__statusCell">' +
|
||||
'<span class="badg p2-10 aksrt-after-sale-status is-' + escapeHtml(status) + '">' + escapeHtml(STATUS_LABELS[status] || status) + '</span>' +
|
||||
'</div>';
|
||||
// 仅「待审核」可撤销(后端 cancel 仅放行 pending):入口随状态徽章渲染。
|
||||
(status === 'pending'
|
||||
? '<button type="button" class="aksrt-after-sale-withdraw" data-aksrt-after-sale-withdraw="' + escapeHtml((order && order.order_number) || '') + '">撤销售后<i class="fa fas fa-angle-right" aria-hidden="true"></i></button>'
|
||||
: '') +
|
||||
'</span></div>';
|
||||
|
||||
html += '<div class="aksrt-after-sale-detail__rows">';
|
||||
html += detailRow('申请时间', String(latest.created_at || ''));
|
||||
@@ -238,6 +246,19 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
// 撤销售后二次确认弹窗:待审核工单入口点入,确认后 DELETE 售后工单。
|
||||
function buildWithdrawHtml(orderNumber) {
|
||||
var html = '<div class="aksrt-after-sale-withdraw-modal">';
|
||||
html += colorfulHeader('撤销售后', 'fa-undo-alt');
|
||||
html += '<div class="aksrt-after-sale-withdraw__body em09">撤销后本次售后申请将关闭,订单恢复为可再次申请售后的状态。确定撤销吗?</div>';
|
||||
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-withdraw-confirm="' + escapeHtml(orderNumber) + '">确认撤销</button>' +
|
||||
'</div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function openModal(options) {
|
||||
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') {
|
||||
notyf('售后组件加载失败,请刷新页面后重试', 'danger');
|
||||
@@ -277,6 +298,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
function openWithdraw(orderNumber) {
|
||||
if (!orderNumber) return;
|
||||
return openModal({
|
||||
html: buildWithdrawHtml(orderNumber),
|
||||
className: 'aksrt-after-sale-withdraw-modal',
|
||||
kind: 'after-sale-withdraw',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析按钮 data-* 上的售后载荷(与举报链路 data-aksrt-report 同模式):
|
||||
* 入口数据随列表一起渲染,点击即可同步弹窗,不做前置请求。
|
||||
@@ -407,6 +437,22 @@
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function clearToast(id) {
|
||||
var toast = document.getElementById(id);
|
||||
if (toast) toast.remove();
|
||||
}
|
||||
|
||||
// 提交前的人机验证:场景未启用时无挂载点,run 直接 resolve {};
|
||||
// 用户中途取消验证时 run resolve null,此时还原按钮并中止提交。
|
||||
function runCaptcha(scene) {
|
||||
if (!window.AKSRTCaptcha || typeof window.AKSRTCaptcha.run !== 'function') {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return window.AKSRTCaptcha.run(scene).then(function (payload) {
|
||||
return payload === null ? Promise.reject({ cancelled: true }) : (payload || {});
|
||||
});
|
||||
}
|
||||
|
||||
function submitApply(shell) {
|
||||
var form = shell.querySelector('[data-aksrt-after-sale-order]');
|
||||
var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : '';
|
||||
@@ -427,41 +473,97 @@
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
notyf('正在提交售后申请…', 'load', 3000, 'aksrt-after-sale-submit');
|
||||
|
||||
window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reason: composeReason(shell),
|
||||
reason_label: selectedReasonLabel(shell),
|
||||
materials: collectMaterials(shell),
|
||||
description: reasonText,
|
||||
images: collectImages(shell),
|
||||
}),
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
window.AksrtModalRuntime.close('submit');
|
||||
notyf('售后申请已提交', 'success');
|
||||
if (window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.refresh === 'function') {
|
||||
window.AKSRTOrderCenter.refresh(orderNumber);
|
||||
var restoreButton = function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
};
|
||||
var sendApply = function (captchaPayload) {
|
||||
window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
},
|
||||
body: JSON.stringify(Object.assign({
|
||||
reason: composeReason(shell),
|
||||
reason_label: selectedReasonLabel(shell),
|
||||
materials: collectMaterials(shell),
|
||||
description: reasonText,
|
||||
images: collectImages(shell),
|
||||
}, captchaPayload)),
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
window.AksrtModalRuntime.close('submit');
|
||||
notyf('售后申请已提交', 'success');
|
||||
if (window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.refresh === 'function') {
|
||||
window.AKSRTOrderCenter.refresh(orderNumber);
|
||||
}
|
||||
} else {
|
||||
restoreButton();
|
||||
notyf((result && result.message) || '售后申请提交失败', 'danger');
|
||||
}
|
||||
} else {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
notyf((result && result.message) || '售后申请提交失败', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
restoreButton();
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () { clearToast('aksrt-after-sale-submit'); });
|
||||
};
|
||||
|
||||
runCaptcha('after_sale_apply').then(sendApply).catch(function (error) {
|
||||
restoreButton();
|
||||
clearToast('aksrt-after-sale-submit');
|
||||
if (error && error.cancelled) return;
|
||||
notyf('人机验证失败,请重试', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 撤销售后:待审核工单置为已取消(DELETE 售后工单),成功后刷新订单列表与统计。
|
||||
function submitWithdraw(shell, button) {
|
||||
var orderNumber = button.getAttribute('data-aksrt-after-sale-withdraw-confirm') || '';
|
||||
if (!orderNumber || button.disabled) return;
|
||||
button.disabled = true;
|
||||
notyf('正在撤销售后申请…', 'load', 3000, 'aksrt-after-sale-withdraw');
|
||||
|
||||
var restoreButton = function () {
|
||||
button.disabled = false;
|
||||
};
|
||||
var sendWithdraw = function (captchaPayload) {
|
||||
window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
},
|
||||
body: JSON.stringify(captchaPayload || {}),
|
||||
})
|
||||
.catch(function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () {
|
||||
var toast = document.getElementById('aksrt-after-sale-submit');
|
||||
if (toast) toast.remove();
|
||||
});
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
window.AksrtModalRuntime.close('withdraw');
|
||||
notyf('售后申请已撤销', 'success');
|
||||
if (window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.refresh === 'function') {
|
||||
window.AKSRTOrderCenter.refresh(orderNumber);
|
||||
}
|
||||
} else {
|
||||
restoreButton();
|
||||
notyf((result && result.message) || '撤销失败,请重试', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
restoreButton();
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () { clearToast('aksrt-after-sale-withdraw'); });
|
||||
};
|
||||
|
||||
runCaptcha('after_sale_cancel').then(sendWithdraw).catch(function (error) {
|
||||
restoreButton();
|
||||
clearToast('aksrt-after-sale-withdraw');
|
||||
if (error && error.cancelled) return;
|
||||
notyf('人机验证失败,请重试', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function bindShell(shell) {
|
||||
@@ -509,6 +611,21 @@
|
||||
var payload = parsePayload(reapplyBtn.getAttribute('data-aksrt-after-sale-payload'));
|
||||
window.AksrtModalRuntime.close('replace');
|
||||
openApplyFlow(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// 撤销售后:入口在售后详细弹窗状态行(仅待审核),点击先弹二次确认。
|
||||
var withdrawBtn = event.target.closest('[data-aksrt-after-sale-withdraw]');
|
||||
if (withdrawBtn) {
|
||||
event.preventDefault();
|
||||
openWithdraw(withdrawBtn.getAttribute('data-aksrt-after-sale-withdraw') || '');
|
||||
return;
|
||||
}
|
||||
|
||||
var withdrawConfirm = event.target.closest('[data-aksrt-after-sale-withdraw-confirm]');
|
||||
if (withdrawConfirm) {
|
||||
event.preventDefault();
|
||||
submitWithdraw(shell, withdrawConfirm);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -237,7 +237,6 @@
|
||||
}));
|
||||
html += '<div class="aksrt-order-detail__after-sale-foot">';
|
||||
html += '<button class="btn-ghost" type="button" data-aksrt-after-sale-detail="' + recordPayload + '">查看详细</button>';
|
||||
if (asStatus === "pending") html += '<button class="btn-ghost" type="button" data-aksrt-after-sale-cancel="' + escapeHtml(order.order_number) + '">取消申请</button>';
|
||||
html += "</div>";
|
||||
html += "</li>";
|
||||
});
|
||||
@@ -662,25 +661,7 @@
|
||||
state.detailReplacementSession = null;
|
||||
};
|
||||
state.onDocumentClick = function (event) {
|
||||
var afterSaleCancel = closest(event.target, "[data-aksrt-after-sale-cancel]");
|
||||
if (afterSaleCancel && !event.__aksrtAfterSaleCancelHandled) {
|
||||
var afterSaleNumber = afterSaleCancel.getAttribute("data-aksrt-after-sale-cancel") || "";
|
||||
if (activeDetail(state, afterSaleNumber)) {
|
||||
event.__aksrtAfterSaleCancelHandled = true;
|
||||
event.preventDefault();
|
||||
afterSaleCancel.disabled = true;
|
||||
request(orderPath(afterSaleNumber, "/after-sale"), undefined, "DELETE", window.AksrtRequest.createIdempotencyKey()).then(function () {
|
||||
notyf("售后申请已取消", "success");
|
||||
invalidateOrder(afterSaleNumber);
|
||||
openDetail(state, afterSaleNumber);
|
||||
refreshView(state);
|
||||
}).catch(function (error) {
|
||||
afterSaleCancel.disabled = false;
|
||||
notyf(errorMessage(error, "取消申请失败"), "danger");
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 取消/撤销售后已移入售后详细弹窗(order-after-sale.js 的「撤销售后」入口)。
|
||||
if (!state.detailSession || window.AksrtModalRuntime.getActive() !== state.detailSession.shell) return;
|
||||
var copy = closest(event.target, "[data-aksrt-order-copy]");
|
||||
if (copy) {
|
||||
|
||||
Reference in New Issue
Block a user