Files
wordpress-theme-aksrt/assets/js/order-center.js
T
kate522 1d2505356e feat: remove shop feature (Phase 2)
Delete all shop domain classes, controllers, schema builders, templates,
JS/CSS, admin SPA pages, and shop-only payment classes (Shipping,
AfterSale, Discount). Remove shop-only DB table definitions (product_variant,
after_sale, order_shipping, card_key) from migrator and add their DROP
to uninstall.php. Clean up shared files:

- Remove aksrt_product CPT + taxonomies from Setup.php
- Remove shop rewrite rules/query vars/template redirect from SetupPart2.php
- Remove shop widget areas from SetupPart1.php
- Remove shop enqueues from ScriptsPart1b.php
- Remove sepakerator-aksrt-shop from SetupPart2.php
- Remove get_shop_settings facade (delete ForumShop.php, clean DomainGroups.php)
- Remove ORDER_PRODUCT_SHOP constant from DomainModel.php
- Remove Shop::class fallback branches from UserOrders.php, OrderCenterRest.php
- Remove shipping/after-sale branches from OrderRepositoryAdmin.php, PayMenus.php, PayOrders.php
- Remove shop product widgets from widgets-register.php
- Remove cart page from template-registry.php, routing.php, ScriptsPart2.php
- Remove cart UI from sidebar-nav.php, profile-orders.php, sidebar-orders.php
- Clean up order-center.js (remove rebuy/cartUrl/receipt/after-sale handlers)
- Remove aksrt_get_cart_page_url/product_page/product_edit from transactions.php
- Clean up aksrt_product_cat references in Coupon.php, PayCoupons.php

Shared payment/order/credits/content-pay stack fully retained.
2026-08-02 21:25:52 +08:00

927 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AKSRT Order Center — v2.0
*
* 前台订单中心交互:
* - 支付 / 取消 / 售后:操作成功后局部刷新订单卡片,不再整页 reload
* - 倒计时
* - Toast 通知替代 window.alert
*
* @package AKSRT
*/
(function () {
"use strict";
var page = document.querySelector("[data-aksrt-order-center]");
if (!page || !window.aksrtData) {
return;
}
/* ── Toast helper ── */
function showToast(text, duration) {
duration = duration || 2500;
var container = document.getElementById("toast-container");
if (!container) {
container = document.createElement("div");
container.id = "toast-container";
container.style.cssText = "position:fixed;bottom:24px;right:24px;z-index:99999;display:flex;flex-direction:column-reverse;gap:10px;pointer-events:none;";
document.body.appendChild(container);
}
var toast = document.createElement("div");
toast.style.cssText = "pointer-events:auto;display:flex;align-items:center;gap:8px;padding:10px 20px;background:rgba(0,0,0,0.78);backdrop-filter:blur(12px);color:#fff;font-family:var(--font-ui);font-size:14px;font-weight:500;border-radius:100px;box-shadow:0 4px 20px rgba(0,0,0,0.2);animation:aksrtToastIn 0.35s cubic-bezier(0.22,1,0.36,1) forwards;white-space:nowrap;";
toast.textContent = text;
container.appendChild(toast);
setTimeout(function () {
toast.style.animation = "aksrtToastOut 0.4s ease forwards";
setTimeout(function () { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 400);
}, duration);
}
// Inject toast keyframes if not present
if (!document.getElementById("aksrt-toast-keyframes")) {
var style = document.createElement("style");
style.id = "aksrt-toast-keyframes";
style.textContent = "@keyframes aksrtToastIn{0%{opacity:0;transform:translateY(16px) scale(0.92)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes aksrtToastOut{0%{opacity:1;transform:translateY(0) scale(1)}100%{opacity:0;transform:translateY(8px) scale(0.95)}}";
document.head.appendChild(style);
}
/* ── REST helper ── */
function restRoot() {
return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1";
}
function request(path, body, method) {
var opts = {
method: method || "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": (aksrtData && aksrtData.restNonce) || ""
}
};
if (body !== undefined) {
opts.body = JSON.stringify(body || {});
}
return fetch(restRoot() + path, opts).then(function (response) {
return response.text().then(function (text) {
var data = {};
try {
data = text ? JSON.parse(text) : {};
} catch (e) {
data = {
success: false,
message: response.status === 404
? "支付接口未找到(404),请刷新页面或联系管理员。"
: ("请求失败(HTTP " + response.status + ")")
};
}
if (typeof data.success === "undefined" && response.ok === false) {
data.success = false;
data.message = data.message || data.code || ("HTTP " + response.status);
}
return data;
});
});
}
function orderPath(orderNumber, suffix) {
var base = "/shop/frontend/orders/" + encodeURIComponent(orderNumber || "");
return suffix ? base + suffix : base;
}
/* ── Order review modal (zibll 1:1) ── */
var STAR_SVG =
'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>';
var SCORE_LABELS = ["", "非常差", "较差", "一般", "较好", "非常好"];
function formatRemain(sec) {
sec = Math.max(0, sec | 0);
var d = Math.floor(sec / 86400);
var h = Math.floor((sec % 86400) / 3600);
var m = Math.floor((sec % 3600) / 60);
var s = sec % 60;
function z(n) { return n < 10 ? "0" + n : String(n); }
if (d > 0) return d + "天" + z(h) + ":" + z(m) + ":" + z(s);
return z(h) + ":" + z(m) + ":" + z(s);
}
function bindShopScore(root) {
if (!root) return;
root.querySelectorAll("[data-aksrt-score-box], .shop-score-box").forEach(function (box) {
if (box.dataset.scoreBound === "1") return;
box.dataset.scoreBound = "1";
var iconBox = box.querySelector(".score-icon");
var textBox = box.querySelector(".score-text");
var input = box.querySelector(".score-input");
if (!iconBox) return;
var value = parseInt(box.getAttribute("score-value") || (input && input.value) || "0", 10) || 0;
var html = "";
for (var i = 1; i <= 5; i++) {
html += '<i class="icon-star' + (i <= value ? " active" : "") + '" data-score="' + i + '">' + STAR_SVG + "</i>";
}
iconBox.innerHTML = html;
function paint(score, hover) {
iconBox.querySelectorAll(".icon-star").forEach(function (star) {
var s = parseInt(star.getAttribute("data-score"), 10) || 0;
star.classList.toggle("active", !hover && s <= score);
star.classList.toggle("hover", !!hover && s <= score);
});
if (textBox) textBox.textContent = SCORE_LABELS[score] || "";
}
paint(value, false);
iconBox.addEventListener("mouseover", function (e) {
var star = e.target.closest(".icon-star");
if (!star || !iconBox.contains(star)) return;
paint(parseInt(star.getAttribute("data-score"), 10) || 0, true);
});
iconBox.addEventListener("mouseleave", function () {
var cur = parseInt(box.getAttribute("score-value") || "0", 10) || 0;
paint(cur, false);
});
iconBox.addEventListener("click", function (e) {
var star = e.target.closest(".icon-star");
if (!star || !iconBox.contains(star)) return;
var score = parseInt(star.getAttribute("data-score"), 10) || 0;
box.setAttribute("score-value", String(score));
if (input) input.value = String(score);
paint(score, false);
});
});
}
function bindReviewImages(form) {
var preview = form.querySelector("[data-aksrt-review-preview]");
var fileInput = form.querySelector("[data-aksrt-review-file]");
var addBtn = form.querySelector("[data-aksrt-review-add]");
if (!preview || !fileInput) return;
var max = parseInt(preview.getAttribute("data-max") || "6", 10) || 6;
var files = [];
function render() {
preview.querySelectorAll(".preview-item").forEach(function (n) { n.remove(); });
files.forEach(function (item, idx) {
var node = document.createElement("div");
node.className = "preview-item";
node.innerHTML = '<img alt=""><button type="button" class="remove" aria-label="删除">&times;</button>';
node.querySelector("img").src = item.url;
node.querySelector(".remove").addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (item.url && item.url.indexOf("blob:") === 0) {
try { URL.revokeObjectURL(item.url); } catch (err) {}
}
files.splice(idx, 1);
render();
});
preview.insertBefore(node, addBtn);
});
if (addBtn) addBtn.style.display = files.length >= max ? "none" : "";
form._aksrtReviewFiles = files.slice();
}
function onPick() {
if (files.length >= max) return;
fileInput.click();
}
if (addBtn) {
addBtn.addEventListener("click", onPick);
addBtn.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onPick();
}
});
}
fileInput.addEventListener("change", function () {
var list = Array.prototype.slice.call(fileInput.files || []);
list.forEach(function (file) {
if (files.length >= max) return;
if (!file.type || file.type.indexOf("image/") !== 0) return;
files.push({ file: file, url: URL.createObjectURL(file) });
});
fileInput.value = "";
render();
});
form._aksrtReviewFiles = [];
render();
}
function bindReviewCountdown(root) {
var el = root.querySelector("[data-aksrt-review-countdown]");
if (!el) return;
var remain = parseInt(el.getAttribute("data-aksrt-review-countdown") || "0", 10) || 0;
if (remain <= 0) return;
el.textContent = formatRemain(remain);
var timer = window.setInterval(function () {
remain -= 1;
if (remain <= 0) {
window.clearInterval(timer);
el.textContent = "交易已关闭";
return;
}
el.textContent = formatRemain(remain);
}, 1000);
root._aksrtReviewTimer = timer;
}
function fileToDataUrl(file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function () { resolve(String(reader.result || "")); };
reader.onerror = function () { reject(new Error("read failed")); };
reader.readAsDataURL(file);
});
}
function openOrderReviewModal(orderNumber, triggerBtn) {
if (!window.AksrtRemoteModal || typeof window.AksrtRemoteModal.openRemote !== "function") {
showToast("评价组件未加载");
return;
}
if (triggerBtn) triggerBtn.disabled = true;
var url = restRoot() + orderPath(orderNumber, "/review-modal");
window.AksrtRemoteModal.openRemote({
url: url,
className: "modal-mini full-sm",
height: 380,
mobileBottom: true
}).then(function (shell) {
if (triggerBtn) triggerBtn.disabled = false;
if (!shell) return;
var form = shell.querySelector("[data-aksrt-order-review-form]");
if (!form) return;
bindShopScore(shell);
bindReviewImages(form);
bindReviewCountdown(shell);
form.addEventListener("submit", function (e) {
e.preventDefault();
var errBox = form.querySelector("[data-aksrt-review-error]");
function showErr(msg) {
if (errBox) {
errBox.hidden = false;
errBox.textContent = msg;
} else {
showToast(msg);
}
}
if (errBox) {
errBox.hidden = true;
errBox.textContent = "";
}
var productId = parseInt(form.getAttribute("data-product-id") || "0", 10) || 0;
var needShipping = form.getAttribute("data-need-shipping") === "1";
var scoreProduct = parseInt((form.querySelector('input[name="score[product]"]') || {}).value || "0", 10) || 0;
var scoreService = parseInt((form.querySelector('input[name="score[service]"]') || {}).value || "0", 10) || 0;
var scoreShipping = parseInt((form.querySelector('input[name="score[shipping]"]') || {}).value || "0", 10) || 0;
if (scoreProduct < 1 || scoreService < 1) {
showErr("请对商品进行评分");
return;
}
if (needShipping && scoreShipping < 1) {
showErr("请对物流服务进行评分");
return;
}
var comment = (form.querySelector('textarea[name="comment"]') || {}).value || "";
var submitBtn = form.querySelector("[data-aksrt-review-submit]");
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = "提交中…";
}
var pending = form._aksrtReviewFiles || [];
Promise.all(pending.map(function (item) {
return item.file ? fileToDataUrl(item.file) : Promise.resolve(item.url || "");
})).then(function (images) {
images = images.filter(Boolean);
return request(orderPath(orderNumber, "/review"), {
product_id: productId,
content: comment,
rating_product: scoreProduct,
rating_service: scoreService,
rating_shipping: needShipping ? scoreShipping : scoreService,
images: images
});
}).then(function (result) {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fa fa-check" aria-hidden="true"></i> 确认提交';
}
if (!result || !result.success) {
showErr((result && result.message) || "评价失败");
return;
}
showToast((result.message) || "评价成功");
if (window.AksrtRemoteModal) window.AksrtRemoteModal.close();
var card = findOrderCard(orderNumber);
updateCardStatus(card, "completed", "已完成");
if (card) {
var btn = card.querySelector("[data-aksrt-order-review]");
if (btn) btn.remove();
}
}).catch(function () {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fa fa-check" aria-hidden="true"></i> 确认提交';
}
showErr("网络错误");
});
});
}).catch(function (err) {
if (triggerBtn) triggerBtn.disabled = false;
showToast((err && err.message) || "无法打开评价");
});
}
/* ── 扫码支付弹层(微信 Native code_url 等)── */
function showPaymentQr(content, orderNumber) {
var existing = document.getElementById("aksrt-pay-qr-overlay");
if (existing) existing.remove();
var overlay = document.createElement("div");
overlay.id = "aksrt-pay-qr-overlay";
overlay.style.cssText = "position:fixed;inset:0;z-index:100000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.5);";
var qrImg = "";
if (window.AksrtShare && typeof window.AksrtShare.buildQrDataUrl === "function") {
var dataUrl = window.AksrtShare.buildQrDataUrl(content, 220);
if (dataUrl) {
qrImg = '<img src="' + dataUrl + '" alt="支付二维码" width="220" height="220" style="display:block;margin:0 auto;">';
}
}
if (!qrImg) {
qrImg = '<p style="word-break:break-all;font-size:12px;color:#666;">' + String(content).replace(/[<>&]/g, "") + "</p>";
}
var card = document.createElement("div");
card.style.cssText = "background:#fff;border-radius:12px;padding:24px;max-width:300px;text-align:center;box-shadow:0 10px 40px rgba(0,0,0,0.2);";
card.innerHTML =
'<h3 style="margin:0 0 12px;font-size:16px;">请扫码完成支付</h3>' +
qrImg +
'<p style="margin:12px 0 0;font-size:13px;color:#888;">支付完成后本页将自动更新</p>' +
'<button type="button" id="aksrt-pay-qr-close" style="margin-top:14px;padding:8px 20px;border:none;border-radius:8px;background:var(--color-primary,#f04494);color:#fff;cursor:pointer;">完成 / 关闭</button>';
overlay.appendChild(card);
document.body.appendChild(overlay);
function close() { overlay.remove(); }
overlay.addEventListener("click", function (e) { if (e.target === overlay) close(); });
card.querySelector("#aksrt-pay-qr-close").addEventListener("click", function () {
close();
// 关闭后查询订单是否已到账
if (orderNumber) {
request(orderPath(orderNumber), {}, "GET").then(function () {
window.location.reload();
}).catch(function () {});
}
});
}
/* ── 第三方支付返回提示(?payment=success|pending|cancelled)── */
(function paymentReturnNotice() {
var params = new URLSearchParams(window.location.search);
var pay = params.get("payment");
if (!pay) return;
var map = { success: "支付成功!", pending: "支付处理中,到账后将自动更新。", cancelled: "支付已取消。", fail: "支付失败,请重试。" };
showToast(map[pay] || "支付状态:" + pay, 3500);
})();
/* ── Local card refresh helpers ── */
function findOrderCard(orderNumber) {
return page.querySelector('[data-aksrt-order-card="' + orderNumber + '"]');
}
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function formatMoney(order) {
var amount = Number(order && order.amount != null ? order.amount : 0);
if (order && order.is_points) {
var symbol = order.currency_symbol || "积分";
return escapeHtml(symbol) + " " + Math.round(amount);
}
return "¥" + amount.toFixed(2);
}
/** Plain money for table cells (formatMoney already escapes). */
function formatMoneyText(order) {
var amount = Number(order && order.amount != null ? order.amount : 0);
if (order && order.is_points) {
var symbol = order.currency_symbol || "积分";
return String(symbol) + " " + Math.round(amount);
}
return "¥" + amount.toFixed(2);
}
function updateCardStatus(card, newStatus, statusLabel) {
if (!card) return;
if (newStatus) {
card.setAttribute("data-order-status", newStatus);
}
var badge = card.querySelector(".aksrt-orders-page__card-status, .aksrt-orders-page__item-badge");
if (badge) {
badge.textContent = statusLabel || newStatus;
}
// Hide action buttons that are no longer relevant
var actions = card.querySelectorAll("[data-aksrt-order-pay], [data-aksrt-order-cancel]");
for (var i = 0; i < actions.length; i++) {
actions[i].style.display = "none";
}
// Hide countdown if present
var countdown = card.querySelector("[data-aksrt-order-countdown]");
if (countdown) {
countdown.style.display = "none";
}
}
/* ── Order detail modal ── */
var detailModal = page.querySelector("[data-aksrt-order-detail-modal]");
var detailBody = page.querySelector("[data-aksrt-order-detail-body]");
var detailFooter = page.querySelector("[data-aksrt-order-detail-footer]");
var detailOrderNumber = "";
function closeOrderDetailModal() {
if (detailModal) detailModal.hidden = true;
detailOrderNumber = "";
document.documentElement.classList.remove("aksrt-modal-open");
}
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && detailModal && !detailModal.hidden) {
closeOrderDetailModal();
}
});
function openOrderDetailModal(orderNumber) {
if (!detailModal || !detailBody || !detailFooter) {
showToast("订单详情组件未加载");
return;
}
detailOrderNumber = orderNumber || "";
detailBody.innerHTML = '<p class="muted-2-color">加载中…</p>';
detailFooter.innerHTML = "";
detailModal.hidden = false;
document.documentElement.classList.add("aksrt-modal-open");
request(orderPath(orderNumber), undefined, "GET").then(function (result) {
var order = null;
if (result && result.data && result.data.order_number) {
order = result.data;
} else if (result && result.data && result.data.data && result.data.data.order_number) {
order = result.data.data;
} else if (result && result.order_number) {
order = result;
}
if (result && result.success === false) {
detailBody.innerHTML = '<p class="c-red">' + escapeHtml((result && result.message) || "加载失败") + "</p>";
return;
}
if (!order || !order.order_number) {
detailBody.innerHTML = '<p class="c-red">订单不存在</p>';
return;
}
renderOrderDetail(order);
}).catch(function () {
detailBody.innerHTML = '<p class="c-red">网络错误,请稍后重试</p>';
});
}
function renderOrderDetail(order) {
if (!detailBody || !detailFooter) return;
var actions = (order && order.actions) || {};
var payment = order.payment_method || {};
var paymentLabel = payment.label || payment.value || "—";
var qty = order.quantity || 1;
if ((!qty || qty < 1) && order.items && order.items.length) {
qty = 0;
order.items.forEach(function (item) {
qty += Math.max(1, parseInt(item.quantity || item.qty || 1, 10) || 1);
});
}
if (!qty || qty < 1) qty = 1;
var status = order.status || "";
var statusLabel = order.status_label || status || "—";
var statusClass = "is-" + String(status).replace(/[^a-z0-9_-]/gi, "-");
var title = order.product_name || (order.product && order.product.title) || "订单";
var image = order.image || (order.product && order.product.image) || "";
if (!image && order.items && order.items.length && order.items[0].image) {
image = order.items[0].image;
}
var orderNo = order.order_number || "—";
// Meta fields only — amount lives in hero; type is already on the list card.
var rows = [
{ key: "qty", label: "数量", value: "×" + String(qty) },
{ key: "no", label: "订单编号", value: orderNo, copyable: true },
{ key: "created", label: "创建时间", value: order.created_at || "—" },
{ key: "paid", label: "支付时间", value: order.paid_at || "—" },
{ key: "pay", label: "支付方式", value: paymentLabel }
];
if (Number(order.shipping_fee || 0) > 0) {
rows.push({ key: "ship", label: "运费", value: "¥" + Number(order.shipping_fee || 0).toFixed(2) });
}
if (Number(order.discount || 0) > 0) {
rows.push({ key: "disc", label: "优惠", value: "-¥" + Number(order.discount || 0).toFixed(2), accent: "discount" });
}
if (order.customer_note) {
rows.push({ key: "note", label: "备注", value: order.customer_note });
}
var html = "";
html += '<div class="aksrt-order-detail__hero ' + escapeHtml(statusClass) + '">';
html += ' <div class="aksrt-order-detail__hero-top">';
html += ' <span class="aksrt-order-detail__status-pill ' + escapeHtml(statusClass) + '">' + escapeHtml(statusLabel) + "</span>";
if (order.product_type_label) {
html += ' <span class="aksrt-order-detail__type-chip">' + escapeHtml(order.product_type_label) + "</span>";
}
html += " </div>";
html += ' <div class="aksrt-order-detail__hero-amount-wrap">';
html += ' <span class="aksrt-order-detail__hero-label">应付金额</span>';
html += ' <strong class="aksrt-order-detail__hero-amount">' + escapeHtml(formatMoneyText(order)) + "</strong>";
html += " </div>";
html += "</div>";
html += '<div class="aksrt-order-detail__product">';
if (image) {
html += '<img class="aksrt-order-detail__thumb" src="' + escapeHtml(image) + '" alt="" loading="lazy">';
} else {
html += '<div class="aksrt-order-detail__thumb aksrt-order-detail__thumb--empty" aria-hidden="true"><i class="fas fa-receipt"></i></div>';
}
html += ' <div class="aksrt-order-detail__product-info">';
html += ' <div class="aksrt-order-detail__product-title">' + escapeHtml(title) + "</div>";
html += ' <div class="aksrt-order-detail__product-meta">';
html += ' <span>数量 ' + escapeHtml(String(qty)) + "</span>";
if (paymentLabel && paymentLabel !== "—") {
html += ' <span class="aksrt-order-detail__dot" aria-hidden="true"></span>';
html += ' <span>' + escapeHtml(paymentLabel) + "</span>";
}
html += " </div>";
html += " </div>";
html += "</div>";
html += '<section class="aksrt-order-detail__section">';
html += ' <h4 class="aksrt-order-detail__section-title">订单信息</h4>';
html += ' <dl class="aksrt-order-detail__list">';
rows.forEach(function (row) {
html += '<div class="aksrt-order-detail__row' + (row.accent ? " is-" + row.accent : "") + '">';
html += '<dt>' + escapeHtml(row.label) + '</dt>';
html += '<dd>';
html += '<span class="aksrt-order-detail__value">' + escapeHtml(row.value) + "</span>";
if (row.copyable && orderNo && orderNo !== "—") {
html += '<button type="button" class="aksrt-order-detail__copy" data-aksrt-copy-order="' + escapeHtml(orderNo) + '" title="复制订单号" aria-label="复制订单号">';
html += '<i class="far fa-copy" aria-hidden="true"></i>';
html += "</button>";
}
html += "</dd>";
html += "</div>";
});
html += "</dl></section>";
if (order.items && order.items.length > 1) {
html += '<section class="aksrt-order-detail__section">';
html += ' <h4 class="aksrt-order-detail__section-title">商品明细</h4>';
html += ' <ul class="aksrt-order-detail__items">';
order.items.forEach(function (item) {
var itemTitle = item.product_title || item.title || order.product_name || "商品";
var itemQty = item.quantity || item.qty || 1;
var price = item.pay_price != null ? item.pay_price : (item.unit_price || 0);
var itemImg = item.image || "";
html += '<li class="aksrt-order-detail__item">';
if (itemImg) {
html += '<img class="aksrt-order-detail__item-img" src="' + escapeHtml(itemImg) + '" alt="" loading="lazy">';
}
html += ' <span class="aksrt-order-detail__item-title">' + escapeHtml(itemTitle) + "</span>";
html += ' <em class="aksrt-order-detail__item-qty">×' + escapeHtml(String(itemQty)) + '</em>';
if (order.is_points) {
html += '<strong class="aksrt-order-detail__item-price">' + escapeHtml(order.currency_symbol || "积分") + " " + Math.round(Number(price || 0)) + "</strong>";
} else {
html += '<strong class="aksrt-order-detail__item-price">¥' + Number(price || 0).toFixed(2) + "</strong>";
}
html += "</li>";
});
html += "</ul></section>";
}
if (order.timeline && order.timeline.length) {
// Newest first for a natural "latest activity on top" feel.
var timeline = order.timeline.slice().reverse();
html += '<section class="aksrt-order-detail__section">';
html += ' <h4 class="aksrt-order-detail__section-title">订单进度</h4>';
html += ' <ol class="aksrt-order-detail__timeline">';
timeline.forEach(function (tl, idx) {
html += '<li class="aksrt-order-detail__timeline-item' + (idx === 0 ? " is-latest" : "") + '">';
html += '<span class="aksrt-order-detail__timeline-dot"></span>';
html += '<div class="aksrt-order-detail__timeline-body">';
html += '<span class="aksrt-order-detail__timeline-label">' + escapeHtml(tl.label || "") + "</span>";
html += '<time class="aksrt-order-detail__timeline-time">' + escapeHtml(tl.time || "") + "</time>";
html += "</div></li>";
});
html += "</ol></section>";
}
detailBody.innerHTML = html;
var footer = "";
// Status-aware footer. Top-right ✕ is the only generic dismiss control.
if (actions.can_cancel) {
// Two-step cancel inside the modal — no window.confirm.
footer += '<button type="button" class="btn-ghost" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '" data-aksrt-cancel-step="1">关闭订单</button>';
}
if (actions.can_pay) {
footer += '<button type="button" class="btn-primary" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml((payment && payment.value) || "") + '">立即支付</button>';
}
if (actions.can_view_product && order.permalink) {
footer += '<a class="btn-ghost" href="' + escapeHtml(order.permalink) + '">查看商品</a>';
}
if (footer === "") {
footer = '<button type="button" class="btn-outline" data-aksrt-order-detail-close>关闭</button>';
}
detailFooter.innerHTML = footer;
}
function copyOrderNumber(code, trigger) {
if (!code) return;
function done() {
showToast("已复制订单号");
if (trigger) {
trigger.classList.add("is-copied");
window.setTimeout(function () { trigger.classList.remove("is-copied"); }, 1200);
}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code).then(done).catch(function () {
showToast(code);
});
return;
}
try {
var ta = document.createElement("textarea");
ta.value = code;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
done();
} catch (e) {
showToast(code);
}
}
/* ── Countdown timer ── */
function updateCountdowns() {
var nodes = page.querySelectorAll("[data-aksrt-order-countdown]");
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var value = Number(node.getAttribute("data-aksrt-order-countdown") || 0);
if (!value) continue;
value -= 1;
node.setAttribute("data-aksrt-order-countdown", String(value));
node.textContent = value > 0 ? "剩余支付时间 " + value + " 秒" : "待支付已超时,请重新发起。";
}
}
setInterval(updateCountdowns, 1000);
/* ── Event delegation ── */
page.addEventListener("click", function (event) {
// Order detail open
var detailBtn = event.target.closest("[data-aksrt-order-detail]");
if (detailBtn && !event.target.closest("[data-aksrt-order-detail-modal]")) {
var detailOrder = detailBtn.getAttribute("data-aksrt-order-detail");
if (detailOrder) openOrderDetailModal(detailOrder);
return;
}
// Copy order number inside detail modal
var copyBtn = event.target.closest("[data-aksrt-copy-order]");
if (copyBtn) {
event.preventDefault();
copyOrderNumber(copyBtn.getAttribute("data-aksrt-copy-order") || "", copyBtn);
return;
}
// Order detail close
if (event.target.closest("[data-aksrt-order-detail-close]")) {
closeOrderDetailModal();
return;
}
// Timeline toggle
var timelineToggle = event.target.closest("[data-aksrt-toggle-timeline]");
if (timelineToggle) {
var timeline = timelineToggle.closest("[data-aksrt-timeline]");
var body = timeline ? timeline.querySelector(".aksrt-orders-page__timeline-body") : null;
if (body) body.hidden = !body.hidden;
return;
}
// Review modal (zibll order_comment_modal 1:1 UX)
var reviewBtn = event.target.closest("[data-aksrt-order-review]");
if (reviewBtn) {
var reviewOrder = reviewBtn.getAttribute("data-aksrt-order-review");
if (!reviewOrder) return;
closeOrderDetailModal();
openOrderReviewModal(reviewOrder, reviewBtn);
return;
}
// Pay
var payButton = event.target.closest("[data-aksrt-order-pay]");
if (payButton) {
var payOrderNumber = payButton.getAttribute("data-aksrt-order-pay");
// 支付方式取订单创建时选定的方式(按钮 data 属性可覆盖);
// 留空则由服务端使用订单存储的 payment_method。
var payMethod = payButton.getAttribute("data-aksrt-order-method") || "";
var payLabel = payButton.textContent || "去支付";
payButton.disabled = true;
payButton.textContent = "处理中...";
request(orderPath(payOrderNumber, "/pay"), payMethod ? { payment_method: payMethod } : {})
.then(function (result) {
if (!result.success) {
payButton.disabled = false;
payButton.textContent = payLabel;
showToast(result.message || "支付失败。");
return;
}
var data = result.data || {};
// 跳转/扫码型网关:到第三方支付页或展示二维码,订单仍为待支付,
// 由回调确认到账。
if (data.redirect) {
if (data.payment_url && /^https?:\/\//i.test(data.payment_url)) {
window.location.href = data.payment_url;
return;
}
if (data.qr_content || data.payment_url) {
showPaymentQr(data.qr_content || data.payment_url, payOrderNumber);
payButton.disabled = false;
payButton.textContent = "已下单,待支付";
return;
}
}
// 同步型网关(站内余额):已到账。
showToast("支付成功!");
var card = findOrderCard(payOrderNumber);
updateCardStatus(card, "paid", "已支付");
if (detailOrderNumber === payOrderNumber) {
closeOrderDetailModal();
}
})
.catch(function () {
payButton.disabled = false;
payButton.textContent = payLabel;
showToast("网络错误,请稍后重试。");
});
return;
}
// Cancel — two-step in detail modal (关闭订单 → 确认取消), list card same.
var cancelButton = event.target.closest("[data-aksrt-order-cancel]");
if (cancelButton) {
var cancelOrderNumber = cancelButton.getAttribute("data-aksrt-order-cancel");
if (!cancelOrderNumber) return;
// First click: transform into "确认取消" (no browser confirm dialog).
var step = cancelButton.getAttribute("data-aksrt-cancel-step") || "1";
if (step !== "2") {
cancelButton.setAttribute("data-aksrt-cancel-step", "2");
cancelButton.classList.remove("btn-ghost");
cancelButton.classList.add("btn-primary", "is-cancel-confirm");
cancelButton.textContent = "确认取消";
// Auto-reset if user doesn't confirm within a few seconds.
if (cancelButton._aksrtCancelReset) {
window.clearTimeout(cancelButton._aksrtCancelReset);
}
cancelButton._aksrtCancelReset = window.setTimeout(function () {
if (!cancelButton.isConnected) return;
if (cancelButton.getAttribute("data-aksrt-cancel-step") !== "2") return;
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
cancelButton.classList.add("btn-ghost");
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
// Detail modal uses "关闭订单"; list cards use "取消".
var inDetail = !!(detailModal && detailModal.contains(cancelButton));
cancelButton.textContent = inDetail ? "关闭订单" : "取消";
}, 5000);
return;
}
if (cancelButton._aksrtCancelReset) {
window.clearTimeout(cancelButton._aksrtCancelReset);
cancelButton._aksrtCancelReset = null;
}
cancelButton.disabled = true;
cancelButton.textContent = "取消中...";
request(orderPath(cancelOrderNumber, "/cancel"))
.then(function (result) {
if (!result.success) {
cancelButton.disabled = false;
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
cancelButton.classList.add("btn-ghost");
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
var inDetailFail = !!(detailModal && detailModal.contains(cancelButton));
cancelButton.textContent = inDetailFail ? "关闭订单" : "取消";
showToast(result.message || "取消失败。");
return;
}
showToast("订单已取消。");
var card = findOrderCard(cancelOrderNumber);
updateCardStatus(card, "cancelled", "已取消");
if (detailOrderNumber === cancelOrderNumber) {
// Refresh modal body with cancelled timeline, then close shortly.
if (result.data && result.data.order) {
renderOrderDetail(result.data.order);
}
window.setTimeout(function () {
closeOrderDetailModal();
}, 600);
}
})
.catch(function () {
cancelButton.disabled = false;
cancelButton.setAttribute("data-aksrt-cancel-step", "1");
cancelButton.classList.add("btn-ghost");
cancelButton.classList.remove("btn-primary", "is-cancel-confirm");
var inDetailErr = !!(detailModal && detailModal.contains(cancelButton));
cancelButton.textContent = inDetailErr ? "关闭订单" : "取消";
showToast("网络错误,请稍后重试。");
});
return;
}
});
/* ── Coupon wallet ── */
var couponRoot = page.querySelector("[data-aksrt-coupon-wallet-list]");
if (couponRoot) {
request("/shop/frontend/coupons").then(function (result) {
var items = (result && result.data && result.data.items) || (result && result.items) || [];
// normalize success envelope
if (result && result.data && result.data.data && result.data.data.items) {
items = result.data.data.items;
}
if (result && result.success && result.data && result.data.items) {
items = result.data.items;
}
// handle this success() wrapper: {success,data:{items}}
if (Array.isArray(result && result.data)) items = result.data;
if (!items.length && result && result.data && Array.isArray(result.data.items)) {
items = result.data.items;
}
// fallback parse
try {
if (!items.length && result && result.data && result.data.data) {
items = result.data.data.items || [];
}
} catch (e) {}
if (!items || !items.length) {
couponRoot.innerHTML = '<p class="muted-2-color">暂无可用优惠券</p>';
return;
}
var html = '<div class="aksrt-coupon-grid">';
items.forEach(function (c) {
var val = c.type === "percent" ? (c.value + "%") : ("¥" + Number(c.value || 0).toFixed(2));
html += '<div class="aksrt-coupon-card">';
html += '<div class="aksrt-coupon-card__value">' + val + '</div>';
html += '<div class="aksrt-coupon-card__code">' + (c.code || "") + '</div>';
if (c.min_amount > 0) {
html += '<div class="aksrt-coupon-card__meta">满 ¥' + Number(c.min_amount).toFixed(2) + ' 可用</div>';
}
if (c.expires_at) {
html += '<div class="aksrt-coupon-card__meta">有效期至 ' + c.expires_at + '</div>';
}
html += '<button type="button" class="btn-outline btn-sm" data-aksrt-copy-coupon="' + (c.code || "") + '">复制券码</button>';
html += '</div>';
});
html += '</div>';
couponRoot.innerHTML = html;
}).catch(function () {
couponRoot.innerHTML = '<p class="muted-2-color">加载失败</p>';
});
page.addEventListener("click", function (e) {
var b = e.target.closest("[data-aksrt-copy-coupon]");
if (!b) return;
var code = b.getAttribute("data-aksrt-copy-coupon") || "";
if (!code) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code).then(function () { showToast("已复制 " + code); });
} else {
showToast(code);
}
});
}
})();