Order progress was stuck on "订单创建" because timeline only read sparse timestamp columns and status transitions didn't always stamp them. Drive timeline from current status + related records, backfill lifecycle timestamps on update_status, and replace window.confirm cancel with a 关闭订单 → 确认取消 button step in the detail modal.
1152 lines
53 KiB
JavaScript
1152 lines
53 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|
||
|
||
function cartUrl() {
|
||
return aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart) || "/";
|
||
}
|
||
|
||
/* ── 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="删除">×</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, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
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], [data-aksrt-order-after-sale], [data-aksrt-order-receipt], [data-aksrt-order-rebuy]");
|
||
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_receipt) {
|
||
footer += '<button type="button" class="btn-primary" data-aksrt-order-receipt="' + escapeHtml(order.order_number) + '">确认收货</button>';
|
||
}
|
||
if (actions.can_review) {
|
||
footer += '<button type="button" class="btn-primary c-green" data-aksrt-order-review="' + escapeHtml(order.order_number) + '">评价</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_after_sale) {
|
||
footer += '<button type="button" class="btn-outline" data-aksrt-order-after-sale="' + escapeHtml(order.order_number) + '">申请售后</button>';
|
||
}
|
||
if (actions.can_rebuy) {
|
||
footer += '<button type="button" class="btn-outline" data-aksrt-order-rebuy="' + escapeHtml(order.order_number) + '">再次购买</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);
|
||
}
|
||
}
|
||
|
||
/* ── After-Sale Modal ── */
|
||
var afterSaleModal = page.querySelector("[data-aksrt-aftersale-modal]");
|
||
var afterSaleForm = page.querySelector("[data-aksrt-aftersale-form]");
|
||
var afterSaleOrderInput = afterSaleForm ? afterSaleForm.querySelector('[name="order_number"]') : null;
|
||
|
||
function openAfterSaleModal(orderNumber) {
|
||
if (!afterSaleModal || !afterSaleForm) {
|
||
showToast("售后表单加载失败,请刷新页面后重试。");
|
||
return;
|
||
}
|
||
afterSaleForm.reset();
|
||
if (afterSaleOrderInput) afterSaleOrderInput.value = orderNumber;
|
||
afterSaleModal.hidden = false;
|
||
}
|
||
|
||
function closeAfterSaleModal() {
|
||
if (afterSaleModal) afterSaleModal.hidden = true;
|
||
}
|
||
|
||
if (afterSaleForm) {
|
||
afterSaleForm.addEventListener("submit", function (event) {
|
||
event.preventDefault();
|
||
var formData = new FormData(afterSaleForm);
|
||
var orderNumber = formData.get("order_number");
|
||
var reason = (formData.get("reason") || "").trim();
|
||
if (!reason) {
|
||
showToast("请填写售后原因。");
|
||
return;
|
||
}
|
||
|
||
var submitBtn = afterSaleForm.querySelector('button[type="submit"]');
|
||
if (submitBtn) {
|
||
submitBtn.disabled = true;
|
||
submitBtn.textContent = "提交中...";
|
||
}
|
||
|
||
request(orderPath(orderNumber, "/after-sale"), {
|
||
type: formData.get("type") || "refund",
|
||
reason: reason,
|
||
note: (formData.get("note") || "").trim()
|
||
}).then(function (result) {
|
||
if (submitBtn) {
|
||
submitBtn.disabled = false;
|
||
submitBtn.textContent = "提交售后";
|
||
}
|
||
if (!result.success) {
|
||
showToast(result.message || "提交售后失败。");
|
||
return;
|
||
}
|
||
closeAfterSaleModal();
|
||
showToast("售后申请已提交。");
|
||
// Local refresh: update card status
|
||
var card = findOrderCard(orderNumber);
|
||
updateCardStatus(card, "refunded", "已退款");
|
||
}).catch(function () {
|
||
if (submitBtn) {
|
||
submitBtn.disabled = false;
|
||
submitBtn.textContent = "提交售后";
|
||
}
|
||
showToast("网络错误,请稍后重试。");
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ── 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;
|
||
}
|
||
|
||
// Confirm receipt
|
||
var receiptBtn = event.target.closest("[data-aksrt-order-receipt]");
|
||
if (receiptBtn) {
|
||
var receiptOrder = receiptBtn.getAttribute("data-aksrt-order-receipt");
|
||
if (!receiptOrder) return;
|
||
if (!window.confirm("确认已收到货物?")) return;
|
||
receiptBtn.disabled = true;
|
||
request(orderPath(receiptOrder, "/confirm-receipt"), {})
|
||
.then(function (result) {
|
||
receiptBtn.disabled = false;
|
||
if (!result || !result.success) {
|
||
showToast((result && result.message) || "确认收货失败");
|
||
return;
|
||
}
|
||
showToast("已确认收货");
|
||
updateCardStatus(findOrderCard(receiptOrder), "completed", "已完成");
|
||
if (detailOrderNumber === receiptOrder) {
|
||
closeOrderDetailModal();
|
||
}
|
||
})
|
||
.catch(function () {
|
||
receiptBtn.disabled = false;
|
||
showToast("网络错误");
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Rebuy
|
||
var rebuyBtn = event.target.closest("[data-aksrt-order-rebuy]");
|
||
if (rebuyBtn) {
|
||
var rebuyOrder = rebuyBtn.getAttribute("data-aksrt-order-rebuy");
|
||
if (!rebuyOrder) return;
|
||
rebuyBtn.disabled = true;
|
||
request(orderPath(rebuyOrder, "/rebuy"), {})
|
||
.then(function (result) {
|
||
rebuyBtn.disabled = false;
|
||
if (!result || !result.success) {
|
||
showToast((result && result.message) || "再次购买失败");
|
||
return;
|
||
}
|
||
showToast("已加入购物车");
|
||
window.location.href = cartUrl();
|
||
})
|
||
.catch(function () {
|
||
rebuyBtn.disabled = false;
|
||
showToast("网络错误");
|
||
});
|
||
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;
|
||
}
|
||
|
||
// After-Sale
|
||
var afterSaleButton = event.target.closest("[data-aksrt-order-after-sale]");
|
||
if (afterSaleButton) {
|
||
var afterSaleOrderNumber = afterSaleButton.getAttribute("data-aksrt-order-after-sale");
|
||
closeOrderDetailModal();
|
||
openAfterSaleModal(afterSaleOrderNumber);
|
||
return;
|
||
}
|
||
|
||
// Return shipping
|
||
var returnShipBtn = event.target.closest("[data-aksrt-return-ship]");
|
||
if (returnShipBtn) {
|
||
openReturnShipModal(returnShipBtn.getAttribute("data-aksrt-return-ship"));
|
||
return;
|
||
}
|
||
|
||
// Cancel after-sale ticket
|
||
var asCancelBtn = event.target.closest("[data-aksrt-as-cancel]");
|
||
if (asCancelBtn) {
|
||
var tid = asCancelBtn.getAttribute("data-aksrt-as-cancel");
|
||
if (!tid || !window.confirm("确认取消该售后申请?")) return;
|
||
asCancelBtn.disabled = true;
|
||
request("/shop/frontend/after-sale/" + tid + "/cancel", { reason: "" })
|
||
.then(function (result) {
|
||
asCancelBtn.disabled = false;
|
||
if (!result || !result.success) {
|
||
showToast((result && result.message) || "取消失败");
|
||
return;
|
||
}
|
||
showToast(result.message || "已取消售后");
|
||
window.location.reload();
|
||
})
|
||
.catch(function () {
|
||
asCancelBtn.disabled = false;
|
||
showToast("网络错误");
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Close after-sale modal
|
||
if (event.target.closest("[data-aksrt-aftersale-modal-close]") || (event.target.classList && event.target.classList.contains("aksrt-modal__overlay") && event.target.closest("[data-aksrt-aftersale-modal]"))) {
|
||
closeAfterSaleModal();
|
||
return;
|
||
}
|
||
|
||
// Close return-ship modal
|
||
if (event.target.closest("[data-aksrt-return-ship-close]") || (event.target.classList && event.target.classList.contains("aksrt-modal__overlay") && event.target.closest("[data-aksrt-return-ship-modal]"))) {
|
||
closeReturnShipModal();
|
||
}
|
||
});
|
||
|
||
/* ── Return shipping modal ── */
|
||
var returnShipModal = page.querySelector("[data-aksrt-return-ship-modal]");
|
||
var returnShipForm = page.querySelector("[data-aksrt-return-ship-form]");
|
||
function openReturnShipModal(ticketId) {
|
||
if (!returnShipModal || !returnShipForm) return;
|
||
returnShipForm.reset();
|
||
var idInput = returnShipForm.querySelector('[name="ticket_id"]');
|
||
if (idInput) idInput.value = ticketId || "";
|
||
returnShipModal.hidden = false;
|
||
}
|
||
function closeReturnShipModal() {
|
||
if (returnShipModal) returnShipModal.hidden = true;
|
||
}
|
||
if (returnShipForm) {
|
||
returnShipForm.addEventListener("submit", function (event) {
|
||
event.preventDefault();
|
||
var fd = new FormData(returnShipForm);
|
||
var tid = fd.get("ticket_id");
|
||
var carrier = (fd.get("carrier") || "").trim();
|
||
var tracking = (fd.get("tracking_no") || "").trim();
|
||
if (!carrier || !tracking) {
|
||
showToast("请填写快递公司与运单号");
|
||
return;
|
||
}
|
||
var btn = returnShipForm.querySelector('button[type="submit"]');
|
||
if (btn) { btn.disabled = true; }
|
||
request("/shop/frontend/after-sale/" + tid + "/return-shipping", {
|
||
carrier: carrier,
|
||
tracking_no: tracking
|
||
}).then(function (result) {
|
||
if (btn) btn.disabled = false;
|
||
if (!result || !result.success) {
|
||
showToast((result && result.message) || "提交失败");
|
||
return;
|
||
}
|
||
closeReturnShipModal();
|
||
showToast(result.message || "退货物流已提交");
|
||
window.location.reload();
|
||
}).catch(function () {
|
||
if (btn) btn.disabled = false;
|
||
showToast("网络错误");
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ── 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);
|
||
}
|
||
});
|
||
}
|
||
})();
|