From 365b2eafe5ae46a94aa67d12f280777dd4c43882 Mon Sep 17 00:00:00 2001 From: kate522 Date: Tue, 6 Oct 2026 18:08:02 +0000 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E5=94=AE=E5=90=8E=E8=AF=A6?= =?UTF-8?q?=E7=BB=86=E5=BC=B9=E7=AA=97=E7=94=B3=E8=AF=B7=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E5=88=86=E8=A1=8C=E5=B1=95=E7=A4=BA=EF=BC=88=E5=8E=9F=E5=9B=A0?= =?UTF-8?q?/=E7=94=B3=E8=AF=B7=E8=B5=84=E6=96=99/=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/js/order-after-sale.js | 1072 +++++++++++++++++---------------- 1 file changed, 551 insertions(+), 521 deletions(-) diff --git a/assets/js/order-after-sale.js b/assets/js/order-after-sale.js index 95572af3..3c5b24e3 100644 --- a/assets/js/order-after-sale.js +++ b/assets/js/order-after-sale.js @@ -1,521 +1,551 @@ -/** - * AKSRT After-Sale — 前台售后弹窗(申请 / 超时效提示 / 售后详细) - * - * 与「禁封/举报」链路同构(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(复用评论图片上传校验) - * - * 按钮形态由订单视图模型的 after_sale 状态决定(UserOrders::after_sale_state): - * 已申请 → 售后详细;未申请且在时效内 → 申请售后;超时效 → 申请售后(点击弹超时效提示)。 - * 弹窗统一经 AksrtModalRuntime.open 直出 HTML:桌面为主题 modal-mini 几何 - * (400px)+ 蓝色炫彩标题区(modal-colorful-header colorful-bg jb-blue), - * 移动端由 mobileBottom 底部抽屉接管。 - * - * @package AKSRT - */ - -(function () { - 'use strict'; - - var API_ROOT = (function () { - var d = window.aksrtData || {}; - var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1'; - return String(raw).replace(/\/$/, ''); - })(); - var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; - var MAX_IMAGES = 3; - // 不可再申请售后(工单未结束 / 已完结 / 超过时效)时的固定提示。 - var REAPPLY_BLOCKED_TEXT = '该无法再次申请售后,该订单已售后,暂不支持再次申请售后。'; - - var STATUS_LABELS = { - pending: '待审核', - processing: '处理中', - approved: '已通过', - rejected: '已拒绝', - cancelled: '已取消', - completed: '已完成', - }; - - function settings() { - var d = window.aksrtData && window.aksrtData.afterSale; - return d && typeof d === 'object' ? d : {}; - } - - function reasonOptions() { - var list = settings().reasons; - if (!Array.isArray(list)) return []; - return list - .map(function (item) { - return { - slug: String((item && (item.slug || item.value)) || ''), - name: String((item && (item.name || item.label || item.t)) || ''), - }; - }) - .filter(function (item) { return item.name !== ''; }); - } - - function materialOptions() { - var list = settings().materials; - if (!Array.isArray(list)) return []; - return list.map(function (item) { return String(item == null ? '' : item); }) - .filter(function (text) { return text !== ''; }); - } - - function escapeHtml(value) { - return String(value == null ? '' : value) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - function colorfulHeader(title, icon) { - return ''; - } - - function orderAmountText(order) { - var raw = String(order && order.amount != null ? order.amount : '0'); - if (order && order.is_points) { - return escapeHtml(String(order.currency_symbol || '积分')) + ' ' + (/^\d+$/.test(raw) ? raw : '0'); - } - return '¥' + raw; - } - - function aftersaleAmountText(value, order) { - var amount = String(value == null ? '' : value); - return order && order.is_points ? amount + ' ' + String((order && order.currency_symbol) || '积分') : '¥' + amount; - } - - function orderSummaryHtml(order) { - if (!order) return ''; - return '
' + - '
商品' + - '' + escapeHtml(order.product_name || '订单') + '
' + - '
订单号' + - '' + escapeHtml(order.order_number || '') + '
' + - '
实付' + - '' + orderAmountText(order) + '
' + - '
'; - } - - function buildApplyHtml(order) { - var cfg = settings(); - var reasons = reasonOptions(); - var materials = materialOptions(); - - var html = '
'; - html += colorfulHeader('申请售后', 'fa-undo-alt'); - html += '
'; - // 滚动容器与禁封申诉弹窗一致:no-scrollbar 完全隐藏滚动条(含细滚动条)+ 50vh 内层滚动。 - html += '
'; - html += orderSummaryHtml(order); - - if (reasons.length) { - html += '
'; - html += '
售后原因
'; - html += '
'; - reasons.forEach(function (reason) { - html += '' + escapeHtml(reason.name) + ''; - }); - html += '
'; - } - - materials.forEach(function (label) { - html += '
' + escapeHtml(label) + '
' + - '
'; - }); - - var placeholder = String(cfg.reasonPlaceholder || '必填,请您详细填写申请原因'); - html += '
申请说明
' + - '
'; - - if (cfg.imgEnabled) { - html += '
' + - '
上传图片
' + - '
' + - '' + - '
最多上传 ' + MAX_IMAGES + ' 张图片
' + - '
'; - } - - // 申请售后说明(后台 after_sale_desc,服务端已 wp_kses_post)。 - var desc = String(cfg.desc || ''); - if (desc) { - html += '
' + desc + '
'; - } - - html += '
'; - html += ''; - html += '
'; - return html; - } - - function detailRow(label, value) { - if (value === '' || value == null) return ''; - return '
' + escapeHtml(label) + '' + - '' + escapeHtml(value) + '
'; - } - - function buildDetailHtml(order) { - var afterSale = (order && order.after_sale) || {}; - var latest = afterSale.latest || {}; - var status = String(latest.status || ''); - - var html = '
'; - html += colorfulHeader('售后详细', 'fa-clipboard-list'); - html += '
'; - html += orderSummaryHtml(order); - - html += '
' + - '售后状态' + - '' + escapeHtml(STATUS_LABELS[status] || status) + '' + - '
'; - - html += '
'; - html += detailRow('申请时间', String(latest.created_at || '')); - html += detailRow('退款金额', aftersaleAmountText(latest.refund_amount, order)); - html += detailRow('处理备注', String(latest.admin_note || '')); - html += detailRow('完成时间', String(latest.completed_at || '')); - html += '
'; - - if (latest.reason) { - html += '
申请原因
' + - '
' + escapeHtml(latest.reason).replace(/\r\n|\r|\n/g, '
') + '
'; - } - - var images = Array.isArray(latest.images) ? latest.images : []; - if (images.length) { - html += '
举证图片
'; - images.forEach(function (url) { - html += ''; - }); - html += '
'; - } - - html += '
'; - - if (afterSale.can_reapply) { - html += ''; - } else { - // 不可再申请售后(工单未结束 / 已完结 / 超过时效):仅提示 + 我知道了。 - html += '
' + - '
' + escapeHtml(REAPPLY_BLOCKED_TEXT) + '
' + - '' + - '
'; - } - - html += '
'; - return html; - } - - function buildExpiredHtml() { - var text = String(settings().expiredText || '抱歉,您的订单已超过售后申请时效'); - var html = '
'; - html += colorfulHeader('售后提示', 'fa-clock'); - // 超过时效提示(后台 after_sale_expired_text,服务端已 wp_kses_post,支持 HTML)。 - html += '
' + text + '
'; - html += ''; - html += '
'; - return html; - } - - function openModal(options) { - if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') { - notyf('售后组件加载失败,请刷新页面后重试', 'danger'); - return null; - } - var shell = window.AksrtModalRuntime.open({ - html: options.html, - className: options.className, - kind: options.kind, - mobileBottom: true, - }); - bindShell(shell); - return shell; - } - - function openApply(order) { - return openModal({ - html: buildApplyHtml(order), - className: 'aksrt-after-sale-apply-modal', - kind: 'after-sale-apply', - }); - } - - function openDetail(order) { - return openModal({ - html: buildDetailHtml(order), - className: 'aksrt-after-sale-detail-modal', - kind: 'after-sale-detail', - }); - } - - function openExpired() { - return openModal({ - html: buildExpiredHtml(), - className: 'aksrt-after-sale-expired-modal', - kind: 'after-sale-expired', - }); - } - - /** - * 解析按钮 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; - } - } - - // 申请售后入口:超时效 → 超时效提示;已申请 → 售后详细;否则 → 申请表单。 - 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(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) { - return Array.prototype.map.call( - shell.querySelectorAll('.preview .preview-item img'), - function (img) { return img.getAttribute('src') || ''; } - ).filter(function (url) { return url !== ''; }); - } - - function uploadImage(shell, file) { - var form = shell.querySelector('[data-aksrt-after-sale-order]'); - var preview = shell.querySelector('.form-upload .preview'); - var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : ''; - if (!preview || !orderNumber) return Promise.resolve(); - if (collectImages(shell).length >= MAX_IMAGES) { - notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger'); - return Promise.resolve(); - } - - var body = new FormData(); - body.append('image', file); - - return window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale/upload-image', { - method: 'POST', - headers: { 'X-WP-Nonce': NONCE }, - body: body, - }) - .then(function (res) { return res.json(); }) - .then(function (result) { - var url = result && result.success && result.data && result.data.url; - if (!url) { - notyf((result && result.message) || '图片上传失败', 'danger'); - return; - } - if (collectImages(shell).length >= MAX_IMAGES) { - notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger'); - return; - } - var item = document.createElement('div'); - item.className = 'preview-item'; - var img = document.createElement('img'); - img.src = url; - img.alt = ''; - var remove = document.createElement('button'); - remove.type = 'button'; - remove.className = 'remove'; - remove.setAttribute('data-aksrt-after-sale-image-remove', ''); - remove.setAttribute('aria-label', '删除图片'); - remove.innerHTML = '×'; - remove.addEventListener('click', function () { item.remove(); }); - item.appendChild(img); - item.appendChild(remove); - preview.insertBefore(item, preview.querySelector('.add')); - }) - .catch(function () { - notyf('图片上传失败,请重试', 'danger'); - }); - } - - function composeReason(shell) { - var parts = []; - var active = shell.querySelector('[data-aksrt-after-sale-reason].active'); - if (active) { - var label = active.getAttribute('data-aksrt-after-sale-reason') || ''; - if (label) parts.push(label); - } - var materials = []; - Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-material]'), function (input) { - var value = String(input.value || '').trim(); - if (value === '') return; - materials.push(String(input.getAttribute('data-aksrt-after-sale-material') || '') + ':' + value); - }); - if (materials.length) parts.push(materials.join('\n')); - var reasonInput = shell.querySelector('[name="reason"]'); - var reasonText = reasonInput ? String(reasonInput.value || '').trim() : ''; - if (reasonText) parts.push(reasonText); - return parts.join('\n'); - } - - function submitApply(shell) { - var form = shell.querySelector('[data-aksrt-after-sale-order]'); - var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : ''; - var reasonInput = shell.querySelector('[name="reason"]'); - var reasonText = reasonInput ? String(reasonInput.value || '').trim() : ''; - if (!reasonText) { - notyf('请填写申请说明', 'danger'); - if (reasonInput) reasonInput.focus(); - return; - } - // 预置原因被后台清空时不强制选择;有配置则必须选中一项(与举报弹窗一致)。 - if (shell.querySelector('[data-aksrt-after-sale-reason]') && !shell.querySelector('[data-aksrt-after-sale-reason].active')) { - notyf('请选择售后原因', 'danger'); - return; - } - - var submitBtn = shell.querySelector('[data-aksrt-after-sale-submit]'); - 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), - 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); - } - } else { - if (submitBtn) submitBtn.disabled = false; - notyf((result && result.message) || '售后申请提交失败', 'danger'); - } - }) - .catch(function () { - if (submitBtn) submitBtn.disabled = false; - notyf('网络错误,请重试', 'danger'); - }) - .then(function () { - var toast = document.getElementById('aksrt-after-sale-submit'); - if (toast) toast.remove(); - }); - } - - function bindShell(shell) { - if (!shell) return; - - shell.addEventListener('click', function (event) { - // 原因徽章:单选 active(与举报弹窗同语义,重复点击已选徽章保持选中)。 - var chip = event.target.closest('[data-aksrt-after-sale-reason]'); - if (chip) { - event.preventDefault(); - Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-reason].active'), function (el) { - el.classList.remove('active'); - }); - chip.classList.add('active'); - return; - } - - var addBtn = event.target.closest('[data-aksrt-after-sale-add]'); - if (addBtn) { - event.preventDefault(); - var fileInput = shell.querySelector('[data-aksrt-after-sale-file]'); - if (fileInput) fileInput.click(); - return; - } - - var removeBtn = event.target.closest('[data-aksrt-after-sale-image-remove]'); - if (removeBtn) { - event.preventDefault(); - var item = removeBtn.closest('.preview-item'); - if (item) item.remove(); - return; - } - - var submitBtn = event.target.closest('[data-aksrt-after-sale-submit]'); - if (submitBtn) { - event.preventDefault(); - submitApply(shell); - return; - } - - // 再次申请售后:载荷已随按钮渲染,直接开申请表单(时效/工单状态随列表刷新更新)。 - var reapplyBtn = event.target.closest('[data-aksrt-after-sale-reapply]'); - if (reapplyBtn) { - event.preventDefault(); - var payload = parsePayload(reapplyBtn.getAttribute('data-aksrt-after-sale-payload')); - window.AksrtModalRuntime.close('replace'); - openApplyFlow(payload); - } - }); - - shell.addEventListener('change', function (event) { - var fileInput = event.target.closest('[data-aksrt-after-sale-file]'); - if (!fileInput || !fileInput.files || !fileInput.files.length) return; - Array.prototype.forEach.call(fileInput.files, function (file) { - uploadImage(shell, file); - }); - fileInput.value = ''; - }); - } - - function initAfterSaleButtons() { - if (document.documentElement.dataset.aksrtAfterSaleInitialized === '1') return; - document.documentElement.dataset.aksrtAfterSaleInitialized = '1'; - document.addEventListener('click', function (event) { - var apply = event.target.closest('[data-aksrt-after-sale-apply]'); - if (apply) { - event.preventDefault(); - 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(parsePayload(detail.getAttribute('data-aksrt-after-sale-detail'))); - } - }); - } - - jQuery(document).off('auto_fun.aksrt-after-sale').on('auto_fun.aksrt-after-sale', initAfterSaleButtons); - // 委托监听不依赖 DOM 扫描,脚本加载时立即挂上;auto_fun 钩子保留作幂等重扫。 - initAfterSaleButtons(); -})(); +/** + * AKSRT After-Sale — 前台售后弹窗(申请 / 超时效提示 / 售后详细) + * + * 与「禁封/举报」链路同构(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(复用评论图片上传校验) + * + * 按钮形态由订单视图模型的 after_sale 状态决定(UserOrders::after_sale_state): + * 已申请 → 售后详细;未申请且在时效内 → 申请售后;超时效 → 申请售后(点击弹超时效提示)。 + * 弹窗统一经 AksrtModalRuntime.open 直出 HTML:桌面为主题 modal-mini 几何 + * (400px)+ 蓝色炫彩标题区(modal-colorful-header colorful-bg jb-blue), + * 移动端由 mobileBottom 底部抽屉接管。 + * + * @package AKSRT + */ + +(function () { + 'use strict'; + + var API_ROOT = (function () { + var d = window.aksrtData || {}; + var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1'; + return String(raw).replace(/\/$/, ''); + })(); + var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; + var MAX_IMAGES = 3; + // 不可再申请售后(工单流转中 / 已完结 / 超过时效)时的固定提示。 + var REAPPLY_BLOCKED_TEXT = '该订单当前状态暂不支持再次申请售后。'; + + var STATUS_LABELS = { + pending: '待审核', + processing: '处理中', + approved: '已通过', + rejected: '已拒绝', + cancelled: '已取消', + completed: '已完成', + }; + + function settings() { + var d = window.aksrtData && window.aksrtData.afterSale; + return d && typeof d === 'object' ? d : {}; + } + + function reasonOptions() { + var list = settings().reasons; + if (!Array.isArray(list)) return []; + return list + .map(function (item) { + return { + slug: String((item && (item.slug || item.value)) || ''), + name: String((item && (item.name || item.label || item.t)) || ''), + }; + }) + .filter(function (item) { return item.name !== ''; }); + } + + function materialOptions() { + var list = settings().materials; + if (!Array.isArray(list)) return []; + return list.map(function (item) { return String(item == null ? '' : item); }) + .filter(function (text) { return text !== ''; }); + } + + function escapeHtml(value) { + return String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function colorfulHeader(title, icon) { + return ''; + } + + function orderAmountText(order) { + var raw = String(order && order.amount != null ? order.amount : '0'); + if (order && order.is_points) { + return escapeHtml(String(order.currency_symbol || '积分')) + ' ' + (/^\d+$/.test(raw) ? raw : '0'); + } + return '¥' + raw; + } + + function aftersaleAmountText(value, order) { + var amount = String(value == null ? '' : value); + return order && order.is_points ? amount + ' ' + String((order && order.currency_symbol) || '积分') : '¥' + amount; + } + + function orderSummaryHtml(order) { + if (!order) return ''; + return '
' + + '
商品' + + '' + escapeHtml(order.product_name || '订单') + '
' + + '
订单号' + + '' + escapeHtml(order.order_number || '') + '
' + + '
实付' + + '' + orderAmountText(order) + '
' + + '
'; + } + + function buildApplyHtml(order) { + var cfg = settings(); + var reasons = reasonOptions(); + var materials = materialOptions(); + + var html = '
'; + html += colorfulHeader('申请售后', 'fa-undo-alt'); + html += '
'; + // 滚动容器与禁封申诉弹窗一致:no-scrollbar 完全隐藏滚动条(含细滚动条)+ 50vh 内层滚动。 + html += '
'; + html += orderSummaryHtml(order); + + if (reasons.length) { + html += '
'; + html += '
售后原因
'; + html += '
'; + reasons.forEach(function (reason) { + html += '' + escapeHtml(reason.name) + ''; + }); + html += '
'; + } + + materials.forEach(function (label) { + html += '
' + escapeHtml(label) + '
' + + '
'; + }); + + var placeholder = String(cfg.reasonPlaceholder || '必填,请您详细填写申请原因'); + html += '
申请说明
' + + '
'; + + if (cfg.imgEnabled) { + html += '
' + + '
上传图片
' + + '
' + + '' + + '
最多上传 ' + MAX_IMAGES + ' 张图片
' + + '
'; + } + + // 申请售后说明(后台 after_sale_desc,服务端已 wp_kses_post)。 + var desc = String(cfg.desc || ''); + if (desc) { + html += '
' + desc + '
'; + } + + html += '
'; + html += ''; + html += '
'; + return html; + } + + function detailRow(label, value) { + if (value === '' || value == null) return ''; + return '
' + escapeHtml(label) + '' + + '' + escapeHtml(value) + '
'; + } + + function buildDetailHtml(order) { + var afterSale = (order && order.after_sale) || {}; + var latest = afterSale.latest || {}; + var status = String(latest.status || ''); + + var html = '
'; + html += colorfulHeader('售后详细', 'fa-clipboard-list'); + html += '
'; + html += orderSummaryHtml(order); + + html += '
' + + '售后状态' + + '' + escapeHtml(STATUS_LABELS[status] || status) + '' + + '
'; + + html += '
'; + html += detailRow('申请时间', String(latest.created_at || '')); + html += detailRow('退款金额', aftersaleAmountText(latest.refund_amount, order)); + html += detailRow('处理备注', String(latest.admin_note || '')); + html += detailRow('完成时间', String(latest.completed_at || '')); + html += '
'; + + // 申请表单分行展示:预置原因 / 申请资料 / 申请说明各自独立成行, + // 与上方「申请时间」等明细行同格式;老数据无 apply_form 时兜底展示融合串。 + var applyForm = latest.apply_form && typeof latest.apply_form === 'object' ? latest.apply_form : null; + if (applyForm) { + html += detailRow('原因', String(applyForm.reason_label || '')); + (Array.isArray(applyForm.materials) ? applyForm.materials : []).forEach(function (material) { + html += detailRow(String((material && material.label) || ''), String((material && material.value) || '')); + }); + html += detailRow('申请说明', String(applyForm.description || '')); + } else if (latest.reason) { + html += '
申请原因
' + + '
' + escapeHtml(latest.reason).replace(/\r\n|\r|\n/g, '
') + '
'; + } + + var images = Array.isArray(latest.images) ? latest.images : []; + if (images.length) { + html += '
举证图片
'; + images.forEach(function (url) { + html += ''; + }); + html += '
'; + } + + html += '
'; + + if (afterSale.can_reapply) { + html += ''; + } else { + // 不可再申请售后(工单未结束 / 已完结 / 超过时效):仅提示 + 我知道了。 + html += '
' + + '
' + escapeHtml(REAPPLY_BLOCKED_TEXT) + '
' + + '' + + '
'; + } + + html += '
'; + return html; + } + + function buildExpiredHtml() { + var text = String(settings().expiredText || '抱歉,您的订单已超过售后申请时效'); + var html = '
'; + html += colorfulHeader('售后提示', 'fa-clock'); + // 超过时效提示(后台 after_sale_expired_text,服务端已 wp_kses_post,支持 HTML)。 + html += '
' + text + '
'; + html += ''; + html += '
'; + return html; + } + + function openModal(options) { + if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') { + notyf('售后组件加载失败,请刷新页面后重试', 'danger'); + return null; + } + var shell = window.AksrtModalRuntime.open({ + html: options.html, + className: options.className, + kind: options.kind, + mobileBottom: true, + }); + bindShell(shell); + return shell; + } + + function openApply(order) { + return openModal({ + html: buildApplyHtml(order), + className: 'aksrt-after-sale-apply-modal', + kind: 'after-sale-apply', + }); + } + + function openDetail(order) { + return openModal({ + html: buildDetailHtml(order), + className: 'aksrt-after-sale-detail-modal', + kind: 'after-sale-detail', + }); + } + + function openExpired() { + return openModal({ + html: buildExpiredHtml(), + className: 'aksrt-after-sale-expired-modal', + kind: 'after-sale-expired', + }); + } + + /** + * 解析按钮 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; + } + } + + // 申请售后入口:超时效 → 超时效提示;已申请 → 售后详细;否则 → 申请表单。 + 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(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) { + return Array.prototype.map.call( + shell.querySelectorAll('.preview .preview-item img'), + function (img) { return img.getAttribute('src') || ''; } + ).filter(function (url) { return url !== ''; }); + } + + function uploadImage(shell, file) { + var form = shell.querySelector('[data-aksrt-after-sale-order]'); + var preview = shell.querySelector('.form-upload .preview'); + var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : ''; + if (!preview || !orderNumber) return Promise.resolve(); + if (collectImages(shell).length >= MAX_IMAGES) { + notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger'); + return Promise.resolve(); + } + + var body = new FormData(); + body.append('image', file); + + return window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale/upload-image', { + method: 'POST', + headers: { 'X-WP-Nonce': NONCE }, + body: body, + }) + .then(function (res) { return res.json(); }) + .then(function (result) { + var url = result && result.success && result.data && result.data.url; + if (!url) { + notyf((result && result.message) || '图片上传失败', 'danger'); + return; + } + if (collectImages(shell).length >= MAX_IMAGES) { + notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger'); + return; + } + var item = document.createElement('div'); + item.className = 'preview-item'; + var img = document.createElement('img'); + img.src = url; + img.alt = ''; + var remove = document.createElement('button'); + remove.type = 'button'; + remove.className = 'remove'; + remove.setAttribute('data-aksrt-after-sale-image-remove', ''); + remove.setAttribute('aria-label', '删除图片'); + remove.innerHTML = '×'; + remove.addEventListener('click', function () { item.remove(); }); + item.appendChild(img); + item.appendChild(remove); + preview.insertBefore(item, preview.querySelector('.add')); + }) + .catch(function () { + notyf('图片上传失败,请重试', 'danger'); + }); + } + + function selectedReasonLabel(shell) { + var active = shell.querySelector('[data-aksrt-after-sale-reason].active'); + return active ? String(active.getAttribute('data-aksrt-after-sale-reason') || '') : ''; + } + + function collectMaterials(shell) { + var materials = []; + Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-material]'), function (input) { + var value = String(input.value || '').trim(); + if (value === '') return; + materials.push({ + label: String(input.getAttribute('data-aksrt-after-sale-material') || ''), + value: value, + }); + }); + return materials; + } + + function composeReason(shell) { + var parts = []; + var active = shell.querySelector('[data-aksrt-after-sale-reason].active'); + if (active) { + var label = active.getAttribute('data-aksrt-after-sale-reason') || ''; + if (label) parts.push(label); + } + var materials = []; + Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-material]'), function (input) { + var value = String(input.value || '').trim(); + if (value === '') return; + materials.push(String(input.getAttribute('data-aksrt-after-sale-material') || '') + ':' + value); + }); + if (materials.length) parts.push(materials.join('\n')); + var reasonInput = shell.querySelector('[name="reason"]'); + var reasonText = reasonInput ? String(reasonInput.value || '').trim() : ''; + if (reasonText) parts.push(reasonText); + return parts.join('\n'); + } + + function submitApply(shell) { + var form = shell.querySelector('[data-aksrt-after-sale-order]'); + var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : ''; + var reasonInput = shell.querySelector('[name="reason"]'); + var reasonText = reasonInput ? String(reasonInput.value || '').trim() : ''; + if (!reasonText) { + notyf('请填写申请说明', 'danger'); + if (reasonInput) reasonInput.focus(); + return; + } + // 预置原因被后台清空时不强制选择;有配置则必须选中一项(与举报弹窗一致)。 + if (shell.querySelector('[data-aksrt-after-sale-reason]') && !shell.querySelector('[data-aksrt-after-sale-reason].active')) { + notyf('请选择售后原因', 'danger'); + return; + } + + var submitBtn = shell.querySelector('[data-aksrt-after-sale-submit]'); + 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); + } + } else { + if (submitBtn) submitBtn.disabled = false; + notyf((result && result.message) || '售后申请提交失败', 'danger'); + } + }) + .catch(function () { + if (submitBtn) submitBtn.disabled = false; + notyf('网络错误,请重试', 'danger'); + }) + .then(function () { + var toast = document.getElementById('aksrt-after-sale-submit'); + if (toast) toast.remove(); + }); + } + + function bindShell(shell) { + if (!shell) return; + + shell.addEventListener('click', function (event) { + // 原因徽章:单选 active(与举报弹窗同语义,重复点击已选徽章保持选中)。 + var chip = event.target.closest('[data-aksrt-after-sale-reason]'); + if (chip) { + event.preventDefault(); + Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-reason].active'), function (el) { + el.classList.remove('active'); + }); + chip.classList.add('active'); + return; + } + + var addBtn = event.target.closest('[data-aksrt-after-sale-add]'); + if (addBtn) { + event.preventDefault(); + var fileInput = shell.querySelector('[data-aksrt-after-sale-file]'); + if (fileInput) fileInput.click(); + return; + } + + var removeBtn = event.target.closest('[data-aksrt-after-sale-image-remove]'); + if (removeBtn) { + event.preventDefault(); + var item = removeBtn.closest('.preview-item'); + if (item) item.remove(); + return; + } + + var submitBtn = event.target.closest('[data-aksrt-after-sale-submit]'); + if (submitBtn) { + event.preventDefault(); + submitApply(shell); + return; + } + + // 再次申请售后:载荷已随按钮渲染,直接开申请表单(时效/工单状态随列表刷新更新)。 + var reapplyBtn = event.target.closest('[data-aksrt-after-sale-reapply]'); + if (reapplyBtn) { + event.preventDefault(); + var payload = parsePayload(reapplyBtn.getAttribute('data-aksrt-after-sale-payload')); + window.AksrtModalRuntime.close('replace'); + openApplyFlow(payload); + } + }); + + shell.addEventListener('change', function (event) { + var fileInput = event.target.closest('[data-aksrt-after-sale-file]'); + if (!fileInput || !fileInput.files || !fileInput.files.length) return; + Array.prototype.forEach.call(fileInput.files, function (file) { + uploadImage(shell, file); + }); + fileInput.value = ''; + }); + } + + function initAfterSaleButtons() { + if (document.documentElement.dataset.aksrtAfterSaleInitialized === '1') return; + document.documentElement.dataset.aksrtAfterSaleInitialized = '1'; + document.addEventListener('click', function (event) { + var apply = event.target.closest('[data-aksrt-after-sale-apply]'); + if (apply) { + event.preventDefault(); + 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(parsePayload(detail.getAttribute('data-aksrt-after-sale-detail'))); + } + }); + } + + jQuery(document).off('auto_fun.aksrt-after-sale').on('auto_fun.aksrt-after-sale', initAfterSaleButtons); + // 委托监听不依赖 DOM 扫描,脚本加载时立即挂上;auto_fun 钩子保留作幂等重扫。 + initAfterSaleButtons(); +})();