/** * Knowledge-payment order center interactions. * * @package AKSRT */ (function () { "use strict"; var instances = new WeakMap(); var liveStates = []; function request(path, body, method, operationKey) { var options = { method: method || "POST" }; if (body !== undefined) options.body = JSON.stringify(body || {}); if (operationKey) options.headers = { "Idempotency-Key": operationKey }; return window.AksrtRequest.request(path, options); } function orderPath(orderNumber, suffix) { return "/orders/" + encodeURIComponent(orderNumber || "") + (suffix || ""); } function errorMessage(error, fallback) { if (error && error.message) return String(error.message); return fallback || "请求失败"; } function escapeHtml(value) { return String(value == null ? "" : value) .replace(/&/g, "&").replace(//g, ">").replace(/\"/g, """) .replace(/'/g, "'"); } function formatMoney(order) { var raw = String(order && order.amount != null ? order.amount : "0"); if (order && order.is_points) { return escapeHtml(order.currency_symbol || "积分") + " " + (/^\d+$/.test(raw) ? raw : "0"); } var match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(raw); var amount = match ? match[1] + "." + (match[2] || "").padEnd(2, "0") : "0.00"; return "¥" + amount; } function orderTag(orderNumber) { return "order:" + String(orderNumber || "").toLowerCase(); } function invalidateOrder(orderNumber, order) { var tags = order && Array.isArray(order.invalidation_tags) ? order.invalidation_tags : [ "orders", orderTag(orderNumber), ]; window.AksrtModalRuntime.invalidate(tags); } function activeDetail(state, number) { return state.detailOrderNumber === number && !!state.detailSession && !state.detailSession.closed && state.detailSession.options && state.detailSession.options.kind === "orders" && window.AksrtModalRuntime.getActive() === state.detailSession.shell; } function detailSkeleton() { return '
' + '' + '' + '' + '
'; } function statusMeta(status) { var map = { pending: { icon: "fa-clock", desc: "订单已生成,请在有效期内完成支付" }, paid: { icon: "fa-check-circle", desc: "支付已完成,相关权益已发放至账户" }, refunded: { icon: "fa-undo-alt", desc: "退款已完成,款项将原路退回" }, closed: { icon: "fa-times-circle", desc: "订单已关闭,如有需要可重新下单" }, }; return map[status] || map.closed; } function amountCaption(status) { if (status === "pending") return "应付款"; if (status === "closed") return "订单金额"; return "实付款"; } function paymentMethodText(order) { var method = order && order.payment_method ? order.payment_method : {}; // 卡密核销不是真实收银渠道,直接回退的原始 key 对用户没有意义。 if (method.value === "card_pass") return "卡密核销"; return String(method.label || ""); } function detailRow(label, value, copyable) { var text = value == null ? "" : String(value); var cell = '' + escapeHtml(text || "—") + ""; if (copyable && text !== "") { cell += ''; } return "
" + escapeHtml(label) + "
" + cell + "
"; } function gatewayResultText(value) { if (!value) return ""; var result = value; if (typeof value === "string") { try { result = JSON.parse(value); } catch (_) { return String(value); } } if (!result || typeof result !== "object") return String(result); var labels = { succeeded: "退款成功", failed: "渠道明确拒绝", pending: "渠道处理中", unknown: "结果待确认" }; var parts = []; if (result.status) parts.push(labels[String(result.status)] || String(result.status)); if (result.message) parts.push(String(result.message)); if (result.refund_id) parts.push("退款单号 " + String(result.refund_id)); return parts.join(" · ") || "渠道暂未提供可读结果"; } function afterSaleAmount(value, order) { var amount = String(value == null ? "" : value); return order && order.is_points ? amount + " " + String(order.currency_symbol || "积分") : "¥" + amount; } function renderDetail(state, order, session) { var allowedActions = order.allowed_actions || {}; var reconciliation = order.reconciliation || {}; var paymentMethod = order.payment_method || {}; var image = order.image || ""; var title = order.product_name || "订单"; var status = order.status_group || "closed"; var meta = statusMeta(status); var timeLeft = Number(order.time_left || 0); var html = ""; // 状态横幅:整块配色只由 --hero-accent 驱动,换状态只改一处变量。 html += '
'; html += ''; html += '
'; html += '' + escapeHtml(order.status_label || "") + ""; html += '

' + escapeHtml(meta.desc) + "

"; if (status === "pending" && timeLeft > 0) { html += '剩余支付时间 ' + timeLeft + " 秒"; } html += "
"; html += '
' + formatMoney(order) + ""; html += "" + escapeHtml(amountCaption(status)) + "
"; html += '
'; html += '

商品信息

'; html += '
'; html += image ? '' : ''; html += '
'; html += '' + escapeHtml(title) + ""; html += '' + escapeHtml(order.product_type_label || "") + ""; html += "
"; html += '
'; html += '

交易信息

'; html += '
'; [ ["订单号", order.order_number], ["支付方式", paymentMethodText(order)], ["创建时间", order.created_at], ["支付时间", order.paid_at], ].forEach(function (row) { html += detailRow(row[0], row[1], row[0] === "订单号"); }); html += "
"; var afterSales = Array.isArray(order.after_sales) ? order.after_sales : []; if (afterSales.length) { html += '

售后记录

"; } if (reconciliation.state === "refresh_available") { html += '
'; html += ''; html += "

支付状态可能仍在同步,请刷新后查看。

"; html += ''; html += "
"; } html += '"; session.content.innerHTML = html; } function detailOptions(state, orderNumber, refresh) { var actionKey = refresh ? "refresh:" + orderNumber : ""; if (actionKey && !state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey(); return { url: orderPath(orderNumber, refresh ? "/refresh" : ""), method: refresh ? "POST" : "GET", headers: actionKey ? { "Idempotency-Key": state.operationKeys[actionKey] } : {}, kind: "orders", title: "订单详情", className: "aksrt-order-detail-modal", responseType: "json", cache: !refresh, cacheKey: "orders:detail:" + orderNumber, cacheTags: ["orders", orderTag(orderNumber)], skeleton: detailSkeleton(), onState: function (_, session) { if (orderNumber === state.detailOrderNumber) state.detailSession = session; }, renderData: function (data, session) { if (orderNumber !== state.detailOrderNumber || !instances.has(state.root)) return false; state.detailSession = session; if (!data || typeof data !== "object") { session.content.innerHTML = '

订单不存在

'; return; } renderDetail(state, data, session); }, onData: function (data) { if (refresh) { delete state.operationKeys[actionKey]; invalidateOrder(orderNumber, data); } }, }; } function openDetail(state, orderNumber) { if (!orderNumber) return; state.detailSession = null; state.detailReplacement = false; state.detailReplacementSession = null; state.detailOrderNumber = orderNumber; window.AksrtModalRuntime.openRemote(detailOptions(state, orderNumber, false)); } function refreshDetail(state, orderNumber) { if (!orderNumber || !activeDetail(state, orderNumber)) return; state.detailReplacement = true; state.detailReplacementSession = state.detailSession; var refreshPromise = window.AksrtModalRuntime.openRemote(detailOptions(state, orderNumber, true)); refreshPromise.then(function () { state.detailReplacement = false; state.detailReplacementSession = null; }, function () { state.detailReplacement = false; state.detailReplacementSession = null; }); } function currentViewPath(state) { var query = new URLSearchParams(); if (state.query.status && state.query.status !== "all") query.set("order_status", state.query.status); if (state.query.type) query.set("order_type", state.query.type); if (state.query.orderNumber) query.set("order_number", state.query.orderNumber); if (Number(state.query.page) > 1) query.set("order_page", String(state.query.page)); return "/orders/view" + (query.toString() ? "?" + query.toString() : ""); } function actionHtml(order) { var allowedActions = order.allowed_actions || {}; var afterSale = order.after_sale || {}; var html = ''; if (allowedActions.can_cancel) html += ''; if (allowedActions.can_pay) html += ''; // 售后入口:申请售后 / 售后详细走 order-after-sale.js 的独立弹窗。 if (afterSale.enabled) html += afterSaleButtons(order.order_number, afterSale); return html; } // 售后按钮(列表卡片与订单详情弹窗共用):已申请 → 售后详细,否则 → 申请售后。 function afterSaleButtons(orderNumber, afterSale) { var html = ""; if (!afterSale || !afterSale.enabled) return html; if (afterSale.has_application) { html += ''; return html; } html += ''; return html; } function cardHtml(order) { var image = order.image ? '' : ''; var countdown = Number(order.time_left || 0) > 0 ? '' : ""; return '
' + '
' + escapeHtml(order.product_type_label || "") + '' + escapeHtml(order.status_label || "") + "
" + '
' + image + '

' + escapeHtml(order.product_name || "订单") + "

订单号 " + escapeHtml(order.order_number) + "

" + formatMoney(order) + "实付
" + '
"; } function renderStats(state, stats) { var root = state.root.querySelector("[data-aksrt-order-stats]"); if (!root) return; root.innerHTML = '
全部' + Number(stats.total || 0) + "
" + '
待支付' + Number(stats.pending || 0) + "
" + '
已支付' + Number(stats.paid || 0) + "
" + '
已退款' + Number(stats.refunded || 0) + "
" + '
已关闭' + Number(stats.closed || 0) + "
" + '
售后待处理' + Number(stats.after_sale_pending || 0) + "
"; var pendingCount = state.root.querySelector('[data-aksrt-order-filter="status"][data-aksrt-order-filter-value="pending"] .aksrt-orders-page__filter-count'); if (pendingCount) { pendingCount.textContent = String(Number(stats.pending || 0)); pendingCount.hidden = Number(stats.pending || 0) < 1; } } function renderPagination(state, view) { var existing = state.root.querySelector(".aksrt-orders-page__pagination"); if (existing) existing.remove(); if (Number(view.total_pages || 1) <= 1) return; var root = document.createElement("nav"); root.className = "aksrt-orders-page__pagination"; root.setAttribute("aria-label", "订单分页"); for (var i = 1; i <= Number(view.total_pages); i++) { var button = document.createElement("button"); button.type = "button"; button.className = "aksrt-orders-page__page-chip" + (i === Number(view.page) ? " is-active" : ""); button.setAttribute("data-aksrt-order-page", String(i)); if (i === Number(view.page)) button.setAttribute("aria-current", "page"); button.textContent = String(i); root.appendChild(button); } var list = state.root.querySelector("[data-aksrt-order-list]"); if (list) list.after(root); } function countdownScopes(state) { // 详情弹窗挂在 body 上,不在 state.root 内,需要单独纳入倒计时扫描。 var scopes = [state.root]; if (state.detailSession && state.detailSession.content) scopes.push(state.detailSession.content); return scopes; } function updateCountdowns(state) { countdownScopes(state).forEach(function (scope) { if (!scope || !scope.querySelectorAll) return; scope.querySelectorAll("[data-aksrt-order-countdown]").forEach(function (node) { var value = Math.max(0, Number(node.getAttribute("data-aksrt-order-countdown") || 0) - 1); node.setAttribute("data-aksrt-order-countdown", String(value)); node.textContent = value ? "剩余支付时间 " + value + " 秒" : "订单即将关闭"; }); }); } function renderView(state, view) { var viewQuery = view.query || {}; state.query.status = String(viewQuery.order_status || "all"); state.query.type = String(viewQuery.order_type || ""); state.query.orderNumber = String(viewQuery.order_number || ""); state.query.page = Math.max(1, Number(view.page || viewQuery.order_page || 1)); renderStats(state, view.stats || {}); var list = state.root.querySelector("[data-aksrt-order-list]"); if (!list) return; var items = Array.isArray(view.items) ? view.items : []; if (!items.length) { list.innerHTML = '

没有匹配的订单

换个条件试试,或清除当前筛选。

'; list.classList.remove("aksrt-orders-page__list"); list.classList.add("aksrt-orders-page__empty"); } else { list.classList.remove("aksrt-orders-page__empty"); list.classList.add("aksrt-orders-page__list"); list.innerHTML = items.map(cardHtml).join(""); } renderPagination(state, view); syncControls(state); updateCountdowns(state); } function syncControls(state) { state.root.querySelectorAll("[data-aksrt-order-filter]").forEach(function (button) { var field = button.getAttribute("data-aksrt-order-filter"); var expected = field === "status" ? state.query.status : state.query.type; var active = button.getAttribute("data-aksrt-order-filter-value") === expected; button.classList.toggle("is-active", active); button.setAttribute("aria-pressed", active ? "true" : "false"); }); var input = state.root.querySelector("#aksrt-order-number"); if (input && input.value !== state.query.orderNumber) input.value = state.query.orderNumber; } function setViewLoading(state, loading) { state.root.classList.toggle("is-loading", loading); state.root.setAttribute("aria-busy", loading ? "true" : "false"); } function loadView(state, patch) { Object.keys(patch || {}).forEach(function (key) { state.query[key] = patch[key]; }); var requestId = ++state.viewRequest; setViewLoading(state, true); return request(currentViewPath(state), undefined, "GET").then(function (result) { if (requestId !== state.viewRequest) return null; if (!result || !result.data) throw new Error("刷新失败"); renderView(state, result.data); return result.data; }).catch(function (error) { if (requestId === state.viewRequest) notyf(errorMessage(error, "订单加载失败,请稍后重试"), "danger"); return null; }).finally(function () { if (requestId === state.viewRequest) setViewLoading(state, false); }); } function refreshView(state) { return loadView(state); } function startPayment(state, button) { var number = button.getAttribute("data-aksrt-order-pay") || ""; var method = button.getAttribute("data-aksrt-order-method") || ""; if (!number || button.disabled) return; var actionKey = "pay:" + number; if (!state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey(); button.disabled = true; request(orderPath(number, "/pay"), method ? { payment_method: method } : {}, "POST", state.operationKeys[actionKey]).then(function (result) { delete state.operationKeys[actionKey]; var data = result.data || {}; invalidateOrder(number, data.order); if (data.redirect && data.payment_url) { window.location.assign(data.payment_url); return; } notyf(data.message || "支付成功", "success"); refreshView(state); if (activeDetail(state, number)) openDetail(state, number); }).catch(function (error) { button.disabled = false; notyf(errorMessage(error, "网络错误,请稍后重试"), "danger"); }); } function closeOrder(state, button) { var number = button.getAttribute("data-aksrt-order-cancel") || ""; if (!number || button.disabled) return; if (button.getAttribute("data-aksrt-confirm") !== "true") { button.setAttribute("data-aksrt-confirm", "true"); button.textContent = "确认关闭"; return; } button.disabled = true; var actionKey = "cancel:" + number; if (!state.operationKeys[actionKey]) state.operationKeys[actionKey] = window.AksrtRequest.createIdempotencyKey(); request(orderPath(number, "/cancel"), {}, "POST", state.operationKeys[actionKey]).then(function (result) { delete state.operationKeys[actionKey]; var data = result.data || {}; invalidateOrder(number, data.order); notyf("订单已关闭", "success"); refreshView(state); if (activeDetail(state, number)) openDetail(state, number); }).catch(function (error) { button.disabled = false; button.removeAttribute("data-aksrt-confirm"); button.textContent = "关闭订单"; notyf(errorMessage(error, "网络错误,请稍后重试"), "danger"); }); } function copyOrderValue(button) { var value = button.getAttribute("data-aksrt-order-copy") || ""; if (!value) return; var icon = button.querySelector("i"); var settle = function (ok) { notyf(ok ? "订单号已复制" : "复制失败,请手动选择复制", ok ? "success" : "danger"); if (!ok) return; button.classList.add("is-copied"); if (icon) icon.className = "fa fas fa-check"; window.setTimeout(function () { button.classList.remove("is-copied"); if (icon) icon.className = "fa fas fa-copy"; }, 1600); }; var fallback = function () { try { var field = document.createElement("textarea"); field.value = value; field.setAttribute("readonly", "readonly"); field.style.position = "fixed"; field.style.top = "-1000px"; field.style.opacity = "0"; document.body.appendChild(field); field.select(); document.execCommand("copy"); document.body.removeChild(field); settle(true); } catch (error) { settle(false); } }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(value).then(function () { settle(true); }, fallback); return; } fallback(); } function closest(target, selector) { return target && target.closest ? target.closest(selector) : null; } function initRoot(root) { if (instances.has(root)) return; var state = { root: root, detailOrderNumber: "", detailSession: null, detailReplacement: false, detailReplacementSession: null, countdownTimer: null, operationKeys: {}, query: { status: "all", type: "", orderNumber: "", page: 1 }, viewRequest: 0, }; state.onRootClick = function (event) { var filterToggle = closest(event.target, "[data-aksrt-orders-filter-toggle]"); if (filterToggle && root.contains(filterToggle)) { var panel = root.querySelector("[data-aksrt-orders-filter-panel]"); var expanded = filterToggle.getAttribute("aria-expanded") === "true"; filterToggle.setAttribute("aria-expanded", String(!expanded)); if (panel) panel.hidden = expanded; return; } var filter = closest(event.target, "[data-aksrt-order-filter]"); if (filter && root.contains(filter)) { event.preventDefault(); var field = filter.getAttribute("data-aksrt-order-filter"); var patch = { page: 1 }; patch[field] = filter.getAttribute("data-aksrt-order-filter-value") || ""; loadView(state, patch); return; } var page = closest(event.target, "[data-aksrt-order-page]"); if (page && root.contains(page)) { event.preventDefault(); loadView(state, { page: Math.max(1, Number(page.getAttribute("data-aksrt-order-page") || 1)) }); return; } var clear = closest(event.target, "[data-aksrt-orders-clear]"); if (clear && root.contains(clear)) { event.preventDefault(); loadView(state, { status: "all", type: "", orderNumber: "", page: 1 }); return; } var detail = closest(event.target, "[data-aksrt-order-detail]"); if (detail && root.contains(detail)) { openDetail(state, detail.getAttribute("data-aksrt-order-detail")); return; } var pay = closest(event.target, "[data-aksrt-order-pay]"); if (pay && root.contains(pay)) { event.preventDefault(); startPayment(state, pay); return; } var cancel = closest(event.target, "[data-aksrt-order-cancel]"); if (cancel && root.contains(cancel)) { event.preventDefault(); closeOrder(state, cancel); } }; state.onRootSubmit = function (event) { var form = closest(event.target, "[data-aksrt-orders-search]"); if (!form || !root.contains(form)) return; event.preventDefault(); var input = form.querySelector("#aksrt-order-number"); loadView(state, { orderNumber: input ? String(input.value || "").trim() : "", page: 1 }); }; state.onModalClose = function (event) { var detail = event && event.detail ? event.detail : {}; if (!state.detailSession || detail.session !== state.detailSession) return; if (detail.reason === "replace" && state.detailReplacement && detail.session === state.detailReplacementSession) return; state.detailSession = null; state.detailOrderNumber = ""; state.detailReplacement = false; 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; } } if (!state.detailSession || window.AksrtModalRuntime.getActive() !== state.detailSession.shell) return; var copy = closest(event.target, "[data-aksrt-order-copy]"); if (copy) { event.preventDefault(); copyOrderValue(copy); return; } var refresh = closest(event.target, "[data-aksrt-order-refresh]"); if (refresh) { event.preventDefault(); refreshDetail(state, refresh.getAttribute("data-aksrt-order-refresh") || ""); return; } var pay = closest(event.target, "[data-aksrt-order-pay]"); if (pay && !root.contains(pay)) { event.preventDefault(); startPayment(state, pay); return; } var cancel = closest(event.target, "[data-aksrt-order-cancel]"); if (cancel && !root.contains(cancel)) { event.preventDefault(); closeOrder(state, cancel); } }; root.addEventListener("click", state.onRootClick); root.addEventListener("submit", state.onRootSubmit); document.addEventListener("aksrt:modal:close", state.onModalClose); document.addEventListener("click", state.onDocumentClick); state.countdownTimer = window.setInterval(function () { updateCountdowns(state); }, 1000); instances.set(root, state); liveStates.push(state); } function rootsIn(root) { if (!root) return []; var roots = []; if (root.matches && root.matches("[data-aksrt-order-center]")) roots.push(root); if (root.querySelectorAll) root.querySelectorAll("[data-aksrt-order-center]").forEach(function (item) { roots.push(item); }); return roots; } function init(root) { rootsIn(root || document).forEach(initRoot); } function destroy(root) { rootsIn(root).forEach(function (item) { var state = instances.get(item); if (!state) return; if (activeDetail(state, state.detailOrderNumber)) window.AksrtModalRuntime.close("replace"); item.removeEventListener("click", state.onRootClick); item.removeEventListener("submit", state.onRootSubmit); document.removeEventListener("aksrt:modal:close", state.onModalClose); document.removeEventListener("click", state.onDocumentClick); window.clearInterval(state.countdownTimer); instances.delete(item); liveStates = liveStates.filter(function (entry) { return entry !== state; }); }); } // refresh 供售后弹窗(order-after-sale.js)提交/取消后刷新列表与统计。 function refresh(orderNumber) { invalidateOrder(orderNumber); liveStates.forEach(function (state) { refreshView(state); }); } window.AKSRTOrderCenter = { init: init, destroy: destroy, refresh: refresh }; jQuery(document).off("auto_fun.aksrt-order-center").on("auto_fun.aksrt-order-center", function () { init(document); }); }());