Merge pull request 'feat(shop): wire storefront buy path to server cart' (#67) from worktree-shop-buy-path-1to1 into main
Reviewed-on: CYL/wordpress-theme-aksrt#67
This commit is contained in:
commit
5a8f08db8a
6 files changed
+621
-304
No files matched your search
+89
-62
@@ -6,7 +6,28 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var CART_KEY = "aksrt_cart";
|
function restBase() {
|
||||||
|
return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1";
|
||||||
|
}
|
||||||
|
function rest(path, method, body) {
|
||||||
|
var opts = {
|
||||||
|
method: method || "GET",
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: { "X-WP-Nonce": aksrtData.restNonce || "" }
|
||||||
|
};
|
||||||
|
if (body !== undefined) {
|
||||||
|
opts.headers["Content-Type"] = "application/json";
|
||||||
|
opts.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
return fetch(restBase() + path, opts).then(function (r) { return r.json(); });
|
||||||
|
}
|
||||||
|
function ordersUrl() {
|
||||||
|
return aksrtData.ordersUrl || (aksrtData.routes && aksrtData.routes.orders) || "/";
|
||||||
|
}
|
||||||
|
function shopHomeUrl() {
|
||||||
|
return (aksrtData.routes && aksrtData.routes.home) || aksrtData.siteUrl || "/";
|
||||||
|
}
|
||||||
|
var cartItems = [];
|
||||||
var currencySymbol = page.getAttribute("data-currency-symbol") || "¥";
|
var currencySymbol = page.getAttribute("data-currency-symbol") || "¥";
|
||||||
var itemsEl = page.querySelector("[data-aksrt-cart-items]");
|
var itemsEl = page.querySelector("[data-aksrt-cart-items]");
|
||||||
var subtotalEl = page.querySelector("[data-aksrt-summary-subtotal]");
|
var subtotalEl = page.querySelector("[data-aksrt-summary-subtotal]");
|
||||||
@@ -20,18 +41,8 @@
|
|||||||
var previewState = null;
|
var previewState = null;
|
||||||
var searchParams = new URLSearchParams(window.location.search);
|
var searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
function getCart() {
|
function getCart() { return cartItems; }
|
||||||
try {
|
function setCart(items) { cartItems = Array.isArray(items) ? items : []; }
|
||||||
var parsed = JSON.parse(localStorage.getItem(CART_KEY) || "[]");
|
|
||||||
return Array.isArray(parsed) ? parsed : [];
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCart(items) {
|
|
||||||
localStorage.setItem(CART_KEY, JSON.stringify(items));
|
|
||||||
}
|
|
||||||
|
|
||||||
function money(value) {
|
function money(value) {
|
||||||
return currencySymbol + Number(value || 0).toFixed(2);
|
return currencySymbol + Number(value || 0).toFixed(2);
|
||||||
@@ -46,7 +57,8 @@
|
|||||||
var items = getCart().map(function (item) {
|
var items = getCart().map(function (item) {
|
||||||
return {
|
return {
|
||||||
product_id: Number(item.product_id || item.id || 0),
|
product_id: Number(item.product_id || item.id || 0),
|
||||||
quantity: Number(item.qty || item.quantity || 1)
|
quantity: Number(item.qty || item.quantity || 1),
|
||||||
|
options_string: item.options_string || ""
|
||||||
};
|
};
|
||||||
}).filter(function (item) {
|
}).filter(function (item) {
|
||||||
return item.product_id > 0;
|
return item.product_id > 0;
|
||||||
@@ -69,7 +81,7 @@
|
|||||||
'<div class="aksrt-cart-page__empty">' +
|
'<div class="aksrt-cart-page__empty">' +
|
||||||
'<i class="fas fa-shopping-cart" aria-hidden="true"></i>' +
|
'<i class="fas fa-shopping-cart" aria-hidden="true"></i>' +
|
||||||
"<p>购物车为空,去商品页添加想购买的内容。</p>" +
|
"<p>购物车为空,去商品页添加想购买的内容。</p>" +
|
||||||
'<a href="/" class="btn-outline btn-sm">继续浏览</a>' +
|
'<a href="' + shopHomeUrl() + '" class="btn-outline btn-sm">继续浏览</a>' +
|
||||||
"</div>";
|
"</div>";
|
||||||
subtotalEl.textContent = money(0);
|
subtotalEl.textContent = money(0);
|
||||||
shippingEl.textContent = money(0);
|
shippingEl.textContent = money(0);
|
||||||
@@ -84,15 +96,19 @@
|
|||||||
var html = "";
|
var html = "";
|
||||||
items.forEach(function (item, index) {
|
items.forEach(function (item, index) {
|
||||||
var qty = Number(item.qty || item.quantity || 1);
|
var qty = Number(item.qty || item.quantity || 1);
|
||||||
var price = Number(item.price || 0);
|
var price = Number(item.unit_price != null ? item.unit_price : (item.price != null ? item.price : (item.product && item.product.price) || 0));
|
||||||
var lineTotal = qty * price;
|
var lineTotal = Number(item.line_total != null ? item.line_total : qty * price);
|
||||||
total += lineTotal;
|
total += lineTotal;
|
||||||
|
var title = item.title || (item.product && item.product.title) || "商品";
|
||||||
|
var image = item.image || (item.product && item.product.image) || "";
|
||||||
|
var opts = item.options_label || item.options_string || "";
|
||||||
|
|
||||||
html += '' +
|
html += '' +
|
||||||
'<article class="aksrt-cart-page__item" data-cart-index="' + index + '">' +
|
'<article class="aksrt-cart-page__item" data-cart-index="' + index + '" data-product-id="' + Number(item.product_id || item.id || 0) + '" data-options-string="' + String(item.options_string || "").replace(/"/g, """) + '">' +
|
||||||
(item.image ? '<img class="aksrt-cart-page__item-image" src="' + item.image + '" alt="">' : '<div class="aksrt-cart-page__item-image aksrt-cart-page__item-image--placeholder"><i class="fas fa-box"></i></div>') +
|
(image ? '<img class="aksrt-cart-page__item-image" src="' + image + '" alt="">' : '<div class="aksrt-cart-page__item-image aksrt-cart-page__item-image--placeholder"><i class="fas fa-box"></i></div>') +
|
||||||
'<div class="aksrt-cart-page__item-main">' +
|
'<div class="aksrt-cart-page__item-main">' +
|
||||||
'<h3>' + (item.title || "商品") + "</h3>" +
|
'<h3>' + title + "</h3>" +
|
||||||
|
(opts ? "<small>" + opts + "</small>" : "") +
|
||||||
'<p>' + money(price) + "</p>" +
|
'<p>' + money(price) + "</p>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
'<div class="aksrt-cart-page__item-qty">' +
|
'<div class="aksrt-cart-page__item-qty">' +
|
||||||
@@ -124,18 +140,7 @@
|
|||||||
createBtn.disabled = !data;
|
createBtn.disabled = !data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(path, body) {
|
function request(path, body) { return rest(path, "POST", body || {}); }
|
||||||
return fetch("/wp-json/aksrt/v1" + path, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-WP-Nonce": aksrtData.restNonce
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body || {})
|
|
||||||
}).then(function (response) {
|
|
||||||
return response.json();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function previewCheckout() {
|
function previewCheckout() {
|
||||||
var payload = getPayload();
|
var payload = getPayload();
|
||||||
@@ -183,12 +188,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.removeItem(CART_KEY);
|
|
||||||
var orderNumber = (result.data && result.data.order_number) || "";
|
var orderNumber = (result.data && result.data.order_number) || "";
|
||||||
var ordersUrl = window.location.pathname.replace("cart", "orders") + "?order_status=pending";
|
var nextOrdersUrl = ordersUrl() + (String(ordersUrl()).indexOf("?") >= 0 ? "&" : "?") + "order_status=pending";
|
||||||
|
|
||||||
if (!orderNumber) {
|
if (!orderNumber) {
|
||||||
window.location.href = ordersUrl;
|
window.location.href = nextOrdersUrl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,10 +205,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 二维码 / 余额 / 失败:统一回订单中心处理(含 ?payment 提示)。
|
// 二维码 / 余额 / 失败:统一回订单中心处理(含 ?payment 提示)。
|
||||||
window.location.href = ordersUrl;
|
window.location.href = nextOrdersUrl;
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
window.location.href = ordersUrl;
|
window.location.href = nextOrdersUrl;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
@@ -217,31 +221,36 @@
|
|||||||
var actionButton = event.target.closest("[data-qty-action]");
|
var actionButton = event.target.closest("[data-qty-action]");
|
||||||
if (actionButton) {
|
if (actionButton) {
|
||||||
var itemEl = actionButton.closest("[data-cart-index]");
|
var itemEl = actionButton.closest("[data-cart-index]");
|
||||||
var index = Number(itemEl.getAttribute("data-cart-index"));
|
if (!itemEl) return;
|
||||||
var items = getCart();
|
var productId = Number(itemEl.getAttribute("data-product-id") || 0);
|
||||||
if (!items[index]) {
|
var optionsString = itemEl.getAttribute("data-options-string") || "";
|
||||||
|
var action = actionButton.getAttribute("data-qty-action");
|
||||||
|
var path = "/shop/frontend/cart/add";
|
||||||
|
var body = { product_id: productId, options_string: optionsString, count: 1 };
|
||||||
|
if (action === "dec") {
|
||||||
|
path = "/shop/frontend/cart/reduce";
|
||||||
|
} else if (action === "remove") {
|
||||||
|
path = "/shop/frontend/cart/remove";
|
||||||
|
body = { product_id: productId, options_string: optionsString };
|
||||||
|
} else if (action !== "inc") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
rest(path, "POST", body).then(function (result) {
|
||||||
var action = actionButton.getAttribute("data-qty-action");
|
if (!result || !result.success) {
|
||||||
if (action === "inc") {
|
window.alert((result && result.message) || "操作失败");
|
||||||
items[index].qty = Number(items[index].qty || 1) + 1;
|
return;
|
||||||
} else if (action === "dec") {
|
}
|
||||||
items[index].qty = Math.max(1, Number(items[index].qty || 1) - 1);
|
setSummary(null);
|
||||||
} else if (action === "remove") {
|
loadServerCart();
|
||||||
items.splice(index, 1);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
setCart(items);
|
|
||||||
setSummary(null);
|
|
||||||
renderCart();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.closest("[data-aksrt-clear-cart]")) {
|
if (event.target.closest("[data-aksrt-clear-cart]")) {
|
||||||
localStorage.removeItem(CART_KEY);
|
rest("/shop/frontend/cart/empty", "POST", {}).then(function () {
|
||||||
setSummary(null);
|
setSummary(null);
|
||||||
renderCart();
|
loadServerCart();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,10 +266,28 @@
|
|||||||
|
|
||||||
var resumeButton = event.target.closest("[data-aksrt-resume-order]");
|
var resumeButton = event.target.closest("[data-aksrt-resume-order]");
|
||||||
if (resumeButton) {
|
if (resumeButton) {
|
||||||
window.location.href = window.location.pathname.replace("cart", "orders") + "?order_status=pending";
|
window.location.href = ordersUrl() + (String(ordersUrl()).indexOf("?") >= 0 ? "&" : "?") + "order_status=pending";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadServerCart() {
|
||||||
|
return rest("/shop/frontend/cart", "GET").then(function (result) {
|
||||||
|
var items = (result && result.success && result.data && result.data.items) ? result.data.items : [];
|
||||||
|
items = items.map(function (it) {
|
||||||
|
it.qty = it.quantity || it.qty || 1;
|
||||||
|
it.price = it.unit_price != null ? it.unit_price : (it.product && it.product.price) || 0;
|
||||||
|
it.title = it.title || (it.product && it.product.title) || "商品";
|
||||||
|
it.image = it.image || (it.product && it.product.image) || "";
|
||||||
|
return it;
|
||||||
|
});
|
||||||
|
setCart(items);
|
||||||
|
renderCart();
|
||||||
|
}).catch(function () {
|
||||||
|
setCart([]);
|
||||||
|
renderCart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var couponInput = page.querySelector("[data-aksrt-coupon-code]");
|
var couponInput = page.querySelector("[data-aksrt-coupon-code]");
|
||||||
if (couponInput && searchParams.has("coupon")) {
|
if (couponInput && searchParams.has("coupon")) {
|
||||||
couponInput.value = searchParams.get("coupon") || "";
|
couponInput.value = searchParams.get("coupon") || "";
|
||||||
@@ -300,7 +327,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshAddressList() {
|
function refreshAddressList() {
|
||||||
fetch("/wp-json/aksrt/v1/shop/frontend/address", {
|
fetch(restBase() + "/shop/frontend/address", {
|
||||||
headers: { "X-WP-Nonce": aksrtData.restNonce }
|
headers: { "X-WP-Nonce": aksrtData.restNonce }
|
||||||
})
|
})
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
@@ -389,9 +416,9 @@
|
|||||||
if (deleteBtn) {
|
if (deleteBtn) {
|
||||||
var deleteId = deleteBtn.getAttribute("data-aksrt-address-delete");
|
var deleteId = deleteBtn.getAttribute("data-aksrt-address-delete");
|
||||||
if (!confirm("确定删除该地址吗?")) return;
|
if (!confirm("确定删除该地址吗?")) return;
|
||||||
request("/shop/frontend/address/" + deleteId, {}).then(function (result) {
|
rest("/shop/frontend/address/" + deleteId, "DELETE").then(function (result) {
|
||||||
if (!result.success) {
|
if (!result || !result.success) {
|
||||||
alert(result.message || "删除失败。");
|
alert((result && result.message) || "删除失败。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshAddressList();
|
refreshAddressList();
|
||||||
@@ -452,5 +479,5 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCart();
|
loadServerCart();
|
||||||
})();
|
})();
|
||||||
@@ -46,19 +46,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── REST helper ── */
|
/* ── REST helper ── */
|
||||||
function request(path, body) {
|
function restRoot() {
|
||||||
return fetch("/wp-json/aksrt/v1" + path, {
|
return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1";
|
||||||
method: "POST",
|
}
|
||||||
|
|
||||||
|
function request(path, body, method) {
|
||||||
|
return fetch(restRoot() + path, {
|
||||||
|
method: method || "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-WP-Nonce": aksrtData.restNonce
|
"X-WP-Nonce": aksrtData.restNonce
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body || {})
|
body: body === undefined ? undefined : JSON.stringify(body || {})
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cartUrl() {
|
||||||
|
return aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart) || "/";
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 扫码支付弹层(微信 Native code_url 等)── */
|
/* ── 扫码支付弹层(微信 Native code_url 等)── */
|
||||||
function showPaymentQr(content, orderNumber) {
|
function showPaymentQr(content, orderNumber) {
|
||||||
var existing = document.getElementById("aksrt-pay-qr-overlay");
|
var existing = document.getElementById("aksrt-pay-qr-overlay");
|
||||||
@@ -118,12 +126,12 @@
|
|||||||
|
|
||||||
function updateCardStatus(card, newStatus, statusLabel) {
|
function updateCardStatus(card, newStatus, statusLabel) {
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
var badge = card.querySelector(".aksrt-orders-page__item-badge");
|
var badge = card.querySelector(".aksrt-orders-page__card-status, .aksrt-orders-page__item-badge");
|
||||||
if (badge) {
|
if (badge) {
|
||||||
badge.textContent = statusLabel || newStatus;
|
badge.textContent = statusLabel || newStatus;
|
||||||
}
|
}
|
||||||
// Hide action buttons that are no longer relevant
|
// 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]");
|
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++) {
|
for (var i = 0; i < actions.length; i++) {
|
||||||
actions[i].style.display = "none";
|
actions[i].style.display = "none";
|
||||||
}
|
}
|
||||||
@@ -216,6 +224,75 @@
|
|||||||
|
|
||||||
/* ── Event delegation ── */
|
/* ── Event delegation ── */
|
||||||
page.addEventListener("click", function (event) {
|
page.addEventListener("click", function (event) {
|
||||||
|
// 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("/shop/frontend/orders/" + receiptOrder + "/confirm-receipt", {})
|
||||||
|
.then(function (result) {
|
||||||
|
receiptBtn.disabled = false;
|
||||||
|
if (!result || !result.success) {
|
||||||
|
showToast((result && result.message) || "确认收货失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showToast("已确认收货");
|
||||||
|
updateCardStatus(findOrderCard(receiptOrder), "completed", "已完成");
|
||||||
|
})
|
||||||
|
.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("/shop/frontend/orders/" + 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 — jump to product if possible
|
||||||
|
var reviewBtn = event.target.closest("[data-aksrt-order-review]");
|
||||||
|
if (reviewBtn) {
|
||||||
|
var card = reviewBtn.closest("[data-aksrt-order-card]");
|
||||||
|
var productLink = card ? card.querySelector("a[href*='product']") : null;
|
||||||
|
if (productLink && productLink.href) {
|
||||||
|
window.location.href = productLink.href + (productLink.href.indexOf("#") >= 0 ? "" : "#reviews");
|
||||||
|
} else {
|
||||||
|
showToast("请到商品详情页评价");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Pay
|
// Pay
|
||||||
var payButton = event.target.closest("[data-aksrt-order-pay]");
|
var payButton = event.target.closest("[data-aksrt-order-pay]");
|
||||||
if (payButton) {
|
if (payButton) {
|
||||||
|
|||||||
+373
-231
@@ -1,241 +1,383 @@
|
|||||||
|
/**
|
||||||
|
* Product purchase page — options selector + server cart.
|
||||||
|
*
|
||||||
|
* @package AKSRT
|
||||||
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function updateCart(product, quantity, replace) {
|
function restBase() {
|
||||||
var key = "aksrt_cart";
|
var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
|
||||||
var current;
|
return root + "/wp-json/aksrt/v1";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
function restNonce() {
|
||||||
current = JSON.parse(localStorage.getItem(key) || "[]");
|
return (window.aksrtData && aksrtData.restNonce) || "";
|
||||||
if (!Array.isArray(current)) {
|
}
|
||||||
current = [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
current = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = current.findIndex(function (item) {
|
function toast(msg, type) {
|
||||||
return Number(item.product_id || item.id || 0) === Number(product.product_id);
|
if (typeof window.aksrtToast === "function") {
|
||||||
});
|
window.aksrtToast(msg, type || "info");
|
||||||
|
return;
|
||||||
if (index >= 0) {
|
|
||||||
current[index].qty = replace ? quantity : Number(current[index].qty || 1) + quantity;
|
|
||||||
} else {
|
|
||||||
current.push({
|
|
||||||
product_id: product.product_id,
|
|
||||||
id: product.product_id,
|
|
||||||
title: product.title,
|
|
||||||
image: product.image,
|
|
||||||
price: product.price,
|
|
||||||
qty: quantity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(key, JSON.stringify(current));
|
|
||||||
}
|
}
|
||||||
|
if (window.aksrt && typeof aksrt.notyf === "function") {
|
||||||
var productPage = document.querySelector("[data-aksrt-product-page]");
|
aksrt.notyf(msg, { type: type || "info" });
|
||||||
if (productPage) {
|
return;
|
||||||
var quantityInput = productPage.querySelector("[data-aksrt-quantity]");
|
|
||||||
var productId = Number(productPage.getAttribute("data-product-id") || 0);
|
|
||||||
var titleEl = productPage.querySelector(".aksrt-product-page__title");
|
|
||||||
var imageEl = productPage.querySelector(".aksrt-product-page__primary-image img");
|
|
||||||
var priceEl = productPage.querySelector(".aksrt-product-page__price");
|
|
||||||
var couponEl = productPage.querySelector("[data-aksrt-product-coupon]");
|
|
||||||
|
|
||||||
productPage.addEventListener("click", function (event) {
|
|
||||||
var qtyBtn = event.target.closest("[data-aksrt-qty-action]");
|
|
||||||
if (qtyBtn && quantityInput) {
|
|
||||||
var action = qtyBtn.getAttribute("data-aksrt-qty-action");
|
|
||||||
var current = Number(quantityInput.value || 1);
|
|
||||||
var max = Number(quantityInput.getAttribute("max") || 999);
|
|
||||||
quantityInput.value = action === "inc" ? Math.min(max, current + 1) : Math.max(1, current - 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.target.closest("[data-aksrt-add-cart]")) {
|
|
||||||
updateCart({
|
|
||||||
product_id: productId,
|
|
||||||
title: titleEl ? titleEl.textContent.trim() : "商品",
|
|
||||||
image: imageEl ? imageEl.getAttribute("src") : "",
|
|
||||||
price: Number((priceEl ? priceEl.textContent : "0").replace(/[^\d.]/g, ""))
|
|
||||||
}, Number(quantityInput ? quantityInput.value : 1), false);
|
|
||||||
if (typeof window.aksrtToast === 'function') { window.aksrtToast("已加入购物车。", 'success'); }
|
|
||||||
else { window.alert("已加入购物车。"); }
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.target.closest("[data-aksrt-buy-now]")) {
|
|
||||||
updateCart({
|
|
||||||
product_id: productId,
|
|
||||||
title: titleEl ? titleEl.textContent.trim() : "商品",
|
|
||||||
image: imageEl ? imageEl.getAttribute("src") : "",
|
|
||||||
price: Number((priceEl ? priceEl.textContent : "0").replace(/[^\d.]/g, ""))
|
|
||||||
}, Number(quantityInput ? quantityInput.value : 1), true);
|
|
||||||
|
|
||||||
var cartUrl = new URL(aksrtData.cartUrl || window.location.href, window.location.origin);
|
|
||||||
if (couponEl && couponEl.value) {
|
|
||||||
cartUrl.searchParams.set("coupon", couponEl.value);
|
|
||||||
}
|
|
||||||
window.location.href = cartUrl.toString();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
productPage.addEventListener("click", function (event) {
|
|
||||||
var thumb = event.target.closest("[data-aksrt-product-thumb]");
|
|
||||||
var primaryImage = productPage.querySelector(".aksrt-product-page__primary-image img");
|
|
||||||
if (thumb && primaryImage) {
|
|
||||||
primaryImage.setAttribute("src", thumb.getAttribute("data-aksrt-product-thumb"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// SKU 规格选择器
|
|
||||||
var skuSelector = productPage.querySelector("[data-aksrt-sku-selector]");
|
|
||||||
if (skuSelector) {
|
|
||||||
var selectedSpecs = {};
|
|
||||||
var skuInfo = productPage.querySelector("[data-aksrt-sku-info]");
|
|
||||||
var skuPrice = skuInfo ? skuInfo.querySelector(".aksrt-product-page__sku-price") : null;
|
|
||||||
var skuStock = skuInfo ? skuInfo.querySelector(".aksrt-product-page__sku-stock") : null;
|
|
||||||
var skus = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
var skuData = skuSelector.getAttribute("data-aksrt-skus");
|
|
||||||
if (skuData) skus = JSON.parse(skuData);
|
|
||||||
} catch (e) { /* ignore */ }
|
|
||||||
|
|
||||||
skuSelector.addEventListener("click", function (event) {
|
|
||||||
var btn = event.target.closest("[data-aksrt-spec]");
|
|
||||||
if (!btn) return;
|
|
||||||
|
|
||||||
var specIndex = btn.getAttribute("data-aksrt-spec");
|
|
||||||
var specVal = btn.getAttribute("data-aksrt-spec-val");
|
|
||||||
|
|
||||||
// 切换选中状态
|
|
||||||
var siblings = skuSelector.querySelectorAll('[data-aksrt-spec="' + specIndex + '"]');
|
|
||||||
siblings.forEach(function (s) { s.classList.remove("is-active"); });
|
|
||||||
btn.classList.add("is-active");
|
|
||||||
|
|
||||||
selectedSpecs[specIndex] = specVal;
|
|
||||||
|
|
||||||
// 查找匹配的 SKU
|
|
||||||
var matchedSku = findMatchingSku(skus, selectedSpecs);
|
|
||||||
if (matchedSku && skuInfo) {
|
|
||||||
skuInfo.hidden = false;
|
|
||||||
if (skuPrice) skuPrice.textContent = "¥" + Number(matchedSku.price || 0).toFixed(2);
|
|
||||||
if (skuStock) skuStock.textContent = "库存: " + (matchedSku.stock || 0);
|
|
||||||
|
|
||||||
// 更新主价格显示
|
|
||||||
if (priceEl) priceEl.textContent = "¥" + Number(matchedSku.price || 0).toFixed(2);
|
|
||||||
|
|
||||||
// 更新库存限制
|
|
||||||
if (quantityInput) {
|
|
||||||
quantityInput.setAttribute("max", String(matchedSku.stock || 999));
|
|
||||||
if (Number(quantityInput.value) > (matchedSku.stock || 999)) {
|
|
||||||
quantityInput.value = matchedSku.stock || 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新主图
|
|
||||||
if (matchedSku.image && primaryImage) {
|
|
||||||
primaryImage.setAttribute("src", matchedSku.image);
|
|
||||||
}
|
|
||||||
} else if (skuInfo) {
|
|
||||||
skuInfo.hidden = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function findMatchingSku(skus, specs) {
|
|
||||||
if (!skus.length) return null;
|
|
||||||
var specKeys = Object.keys(specs);
|
|
||||||
if (specKeys.length === 0) return null;
|
|
||||||
|
|
||||||
return skus.find(function (sku) {
|
|
||||||
if (!sku.specs) return false;
|
|
||||||
return specKeys.every(function (key) {
|
|
||||||
return sku.specs[key] === specs[key];
|
|
||||||
});
|
|
||||||
}) || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
window.alert(msg);
|
||||||
|
}
|
||||||
|
|
||||||
var editor = document.querySelector("[data-aksrt-product-editor]");
|
function rest(path, method, body) {
|
||||||
if (!editor || !window.aksrtData) {
|
var opts = {
|
||||||
return;
|
method: method || "GET",
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
"X-WP-Nonce": restNonce(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (body !== undefined) {
|
||||||
|
opts.headers["Content-Type"] = "application/json";
|
||||||
|
opts.body = JSON.stringify(body);
|
||||||
}
|
}
|
||||||
|
return fetch(restBase() + path, opts).then(function (r) {
|
||||||
var form = editor.querySelector("[data-aksrt-product-form]");
|
return r.json();
|
||||||
var messageEl = editor.querySelector("[data-aksrt-product-editor-message]");
|
|
||||||
|
|
||||||
function selectedMultiValues(select) {
|
|
||||||
if (!select) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.prototype.slice.call(select.options)
|
|
||||||
.filter(function (option) {
|
|
||||||
return option.selected;
|
|
||||||
})
|
|
||||||
.map(function (option) {
|
|
||||||
return Number(option.value || 0);
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
form.addEventListener("submit", function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
var formData = new FormData(form);
|
|
||||||
var productId = Number(editor.getAttribute("data-product-id") || 0);
|
|
||||||
var gallery = String(formData.get("gallery") || "")
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.map(function (line) { return line.trim(); })
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
var payload = {
|
|
||||||
title: String(formData.get("title") || "").trim(),
|
|
||||||
price: Number(formData.get("price") || 0),
|
|
||||||
stock: Number(formData.get("stock") || 0),
|
|
||||||
excerpt: String(formData.get("excerpt") || ""),
|
|
||||||
content: String(formData.get("content") || ""),
|
|
||||||
status: String(formData.get("status") || "draft"),
|
|
||||||
categories: selectedMultiValues(form.querySelector('select[name="categories[]"]')),
|
|
||||||
tags: String(formData.get("tags") || "")
|
|
||||||
.split(",")
|
|
||||||
.map(function (tag) { return tag.trim(); })
|
|
||||||
.filter(Boolean),
|
|
||||||
image: String(formData.get("image") || "").trim(),
|
|
||||||
gallery: gallery,
|
|
||||||
shipping_mode: String(formData.get("shipping_mode") || "express"),
|
|
||||||
shipping_fee: Number(formData.get("shipping_fee") || 0),
|
|
||||||
after_sale_policy: String(formData.get("after_sale_policy") || ""),
|
|
||||||
support_refund: formData.get("support_refund") === "1"
|
|
||||||
};
|
|
||||||
|
|
||||||
var path = productId > 0 ? "/shop/frontend/products/" + productId : "/shop/frontend/products";
|
|
||||||
messageEl.textContent = "正在保存商品...";
|
|
||||||
|
|
||||||
fetch("/wp-json/aksrt/v1" + path, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-WP-Nonce": aksrtData.restNonce
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
.then(function (response) { return response.json(); })
|
|
||||||
.then(function (result) {
|
|
||||||
if (!result.success) {
|
|
||||||
messageEl.textContent = result.message || "保存失败。";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var centerUrl = aksrtData.userCenterUrl || "/";
|
|
||||||
messageEl.textContent = "商品已保存,正在返回用户中心...";
|
|
||||||
window.location.href = centerUrl;
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
messageEl.textContent = "网络错误,请稍后重试。";
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function money(value, payModo) {
|
||||||
|
var n = Number(value || 0).toFixed(2);
|
||||||
|
return payModo === "points" ? n : "¥" + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product page ── */
|
||||||
|
var productPage = document.querySelector("[data-aksrt-product-page]");
|
||||||
|
if (productPage) {
|
||||||
|
var quantityInput = productPage.querySelector("[data-aksrt-quantity]");
|
||||||
|
var productId = Number(productPage.getAttribute("data-product-id") || 0);
|
||||||
|
var titleEl = productPage.querySelector(".aksrt-product-page__title");
|
||||||
|
var imageEl = productPage.querySelector(".aksrt-product-page__primary-image img");
|
||||||
|
var priceEl = productPage.querySelector("[data-aksrt-dynamic-price], .aksrt-product-page__price");
|
||||||
|
var stockEl = productPage.querySelector("[data-aksrt-stock-display]");
|
||||||
|
var couponEl = productPage.querySelector("[data-aksrt-product-coupon]");
|
||||||
|
var optionsRoot = productPage.querySelector("[data-aksrt-options-selector]");
|
||||||
|
var optionResult = productPage.querySelector("[data-aksrt-option-result]");
|
||||||
|
var selectedLabelsEl = productPage.querySelector("[data-aksrt-selected-labels]");
|
||||||
|
var resolvedPriceEl = productPage.querySelector("[data-aksrt-resolved-price]");
|
||||||
|
var resolvedStockEl = productPage.querySelector("[data-aksrt-resolved-stock]");
|
||||||
|
var payModo = productPage.getAttribute("data-pay-modo") || "0";
|
||||||
|
var basePrice = Number(productPage.getAttribute("data-base-price") || 0);
|
||||||
|
var baseStock = Number(productPage.getAttribute("data-base-stock") || 0);
|
||||||
|
var selectedByGroup = {};
|
||||||
|
var groupCount = optionsRoot
|
||||||
|
? optionsRoot.querySelectorAll(".aksrt-product-page__option-group").length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
function currentOptionsKey() {
|
||||||
|
if (!groupCount) return "";
|
||||||
|
var labels = [];
|
||||||
|
for (var i = 0; i < groupCount; i++) {
|
||||||
|
if (!selectedByGroup[i]) return null;
|
||||||
|
labels.push(selectedByGroup[i].label);
|
||||||
|
}
|
||||||
|
return labels.join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSelection() {
|
||||||
|
if (!groupCount) {
|
||||||
|
return {
|
||||||
|
key: "",
|
||||||
|
labels: "",
|
||||||
|
price: basePrice,
|
||||||
|
stock: baseStock,
|
||||||
|
image: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var key = currentOptionsKey();
|
||||||
|
if (key === null) return null;
|
||||||
|
|
||||||
|
var price = basePrice;
|
||||||
|
var image = "";
|
||||||
|
var labels = [];
|
||||||
|
for (var i = 0; i < groupCount; i++) {
|
||||||
|
var sel = selectedByGroup[i];
|
||||||
|
labels.push(sel.label);
|
||||||
|
price += Number(sel.priceChange || 0);
|
||||||
|
if (sel.image) image = sel.image;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
labels: labels.join(" / "),
|
||||||
|
price: Math.max(0, price),
|
||||||
|
stock: baseStock,
|
||||||
|
image: image,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintSelection() {
|
||||||
|
var resolved = resolveSelection();
|
||||||
|
if (!resolved) {
|
||||||
|
if (optionResult) optionResult.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (priceEl) {
|
||||||
|
if (groupCount && productPage.getAttribute("data-price-range") === "1" && currentOptionsKey() === null) {
|
||||||
|
/* keep range until complete selection */
|
||||||
|
} else {
|
||||||
|
priceEl.textContent = money(resolved.price, payModo) + (payModo === "points" ? "" : "");
|
||||||
|
if (payModo === "points" && priceEl.querySelector("small") === null) {
|
||||||
|
/* leave as-is */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stockEl && resolved.stock > -1) {
|
||||||
|
stockEl.textContent = String(resolved.stock);
|
||||||
|
}
|
||||||
|
if (quantityInput && resolved.stock > 0) {
|
||||||
|
quantityInput.setAttribute("max", String(resolved.stock));
|
||||||
|
if (Number(quantityInput.value) > resolved.stock) {
|
||||||
|
quantityInput.value = String(resolved.stock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (imageEl && resolved.image) {
|
||||||
|
imageEl.setAttribute("src", resolved.image);
|
||||||
|
}
|
||||||
|
if (optionResult) {
|
||||||
|
optionResult.hidden = false;
|
||||||
|
if (selectedLabelsEl) selectedLabelsEl.textContent = resolved.labels;
|
||||||
|
if (resolvedPriceEl) resolvedPriceEl.textContent = money(resolved.price, payModo);
|
||||||
|
if (resolvedStockEl) {
|
||||||
|
resolvedStockEl.textContent =
|
||||||
|
resolved.stock <= -1 ? "库存充足" : "库存 " + resolved.stock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionsRoot) {
|
||||||
|
optionsRoot.addEventListener("click", function (event) {
|
||||||
|
var btn = event.target.closest("[data-option-group]");
|
||||||
|
if (!btn || !optionsRoot.contains(btn)) return;
|
||||||
|
var group = btn.getAttribute("data-option-group");
|
||||||
|
var label = btn.getAttribute("data-option-label") || "";
|
||||||
|
var priceChange = Number(btn.getAttribute("data-price-change") || 0);
|
||||||
|
var image = btn.getAttribute("data-option-image") || "";
|
||||||
|
|
||||||
|
optionsRoot
|
||||||
|
.querySelectorAll('[data-option-group="' + group + '"]')
|
||||||
|
.forEach(function (el) {
|
||||||
|
el.classList.remove("is-active");
|
||||||
|
});
|
||||||
|
btn.classList.add("is-active");
|
||||||
|
selectedByGroup[group] = { label: label, priceChange: priceChange, image: image };
|
||||||
|
paintSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureOptionsSelected() {
|
||||||
|
if (!groupCount) return true;
|
||||||
|
if (currentOptionsKey() === null) {
|
||||||
|
toast("请先选择商品规格", "warning");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToServerCart(replaceQty) {
|
||||||
|
if (!window.aksrtData || !aksrtData.isLoggedIn) {
|
||||||
|
window.location.href = (aksrtData && aksrtData.routes && aksrtData.routes.login) || "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ensureOptionsSelected()) return;
|
||||||
|
|
||||||
|
var qty = Number(quantityInput ? quantityInput.value : 1) || 1;
|
||||||
|
var optionsString = currentOptionsKey() || "";
|
||||||
|
var body = {
|
||||||
|
product_id: productId,
|
||||||
|
options_string: optionsString,
|
||||||
|
count: qty,
|
||||||
|
};
|
||||||
|
|
||||||
|
rest("/shop/frontend/cart/add", "POST", body)
|
||||||
|
.then(function (result) {
|
||||||
|
if (!result || !result.success) {
|
||||||
|
toast((result && (result.message || result.error)) || "加入购物车失败", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast("已加入购物车", "success");
|
||||||
|
if (replaceQty) {
|
||||||
|
var cartUrl = new URL(
|
||||||
|
(window.aksrtData && (aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart))) || "/",
|
||||||
|
window.location.origin
|
||||||
|
);
|
||||||
|
if (couponEl && couponEl.value) {
|
||||||
|
cartUrl.searchParams.set("coupon", couponEl.value);
|
||||||
|
}
|
||||||
|
window.location.href = cartUrl.toString();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
toast("网络错误,请稍后重试", "error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
productPage.addEventListener("click", function (event) {
|
||||||
|
var qtyBtn = event.target.closest("[data-aksrt-qty-action]");
|
||||||
|
if (qtyBtn && quantityInput) {
|
||||||
|
var action = qtyBtn.getAttribute("data-aksrt-qty-action");
|
||||||
|
var current = Number(quantityInput.value || 1);
|
||||||
|
var max = Number(quantityInput.getAttribute("max") || 999);
|
||||||
|
quantityInput.value = action === "inc" ? Math.min(max, current + 1) : Math.max(1, current - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest("[data-aksrt-add-cart]")) {
|
||||||
|
addToServerCart(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest("[data-aksrt-buy-now]")) {
|
||||||
|
addToServerCart(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var thumb = event.target.closest("[data-aksrt-product-thumb]");
|
||||||
|
if (thumb && imageEl) {
|
||||||
|
imageEl.setAttribute("src", thumb.getAttribute("data-aksrt-product-thumb"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoBtn = event.target.closest("[data-aksrt-product-video]");
|
||||||
|
if (videoBtn) {
|
||||||
|
var url = videoBtn.getAttribute("data-aksrt-product-video");
|
||||||
|
if (url) window.open(url, "_blank", "noopener");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var favBtn = event.target.closest("[data-aksrt-toggle-fav]");
|
||||||
|
if (favBtn) {
|
||||||
|
if (!window.aksrtData || !aksrtData.isLoggedIn) {
|
||||||
|
window.location.href = (aksrtData && aksrtData.routes && aksrtData.routes.login) || "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pid = Number(favBtn.getAttribute("data-aksrt-toggle-fav") || productId);
|
||||||
|
rest("/shop/frontend/products/" + pid + "/favorite", "POST", {})
|
||||||
|
.then(function (result) {
|
||||||
|
if (!result || !result.success) {
|
||||||
|
toast((result && result.message) || "操作失败", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var active = !!(result.data && result.data.is_favorite);
|
||||||
|
productPage.querySelectorAll("[data-aksrt-toggle-fav]").forEach(function (btn) {
|
||||||
|
btn.classList.toggle("is-active", active);
|
||||||
|
});
|
||||||
|
toast((result.message) || (active ? "已收藏" : "已取消收藏"), "success");
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
toast("网络错误", "error");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabBtn = event.target.closest("[data-aksrt-product-tab]");
|
||||||
|
if (tabBtn) {
|
||||||
|
var tab = tabBtn.getAttribute("data-aksrt-product-tab");
|
||||||
|
productPage.querySelectorAll("[data-aksrt-product-tab]").forEach(function (b) {
|
||||||
|
b.classList.toggle("is-active", b === tabBtn);
|
||||||
|
});
|
||||||
|
productPage.querySelectorAll("[data-aksrt-product-panel]").forEach(function (panel) {
|
||||||
|
panel.hidden = panel.getAttribute("data-aksrt-product-panel") !== tab;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest("[data-aksrt-show-params]")) {
|
||||||
|
var paramsModal = productPage.querySelector("[data-aksrt-params-modal]");
|
||||||
|
if (paramsModal) paramsModal.hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.target.closest("[data-aksrt-show-services]")) {
|
||||||
|
var servicesModal = productPage.querySelector("[data-aksrt-services-modal]");
|
||||||
|
if (servicesModal) servicesModal.hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.target.closest("[data-aksrt-close-modal]")) {
|
||||||
|
var modal = event.target.closest("[data-aksrt-params-modal], [data-aksrt-services-modal], .aksrt-modal");
|
||||||
|
if (modal) modal.hidden = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product editor (seller) ── */
|
||||||
|
var editor = document.querySelector("[data-aksrt-product-editor]");
|
||||||
|
if (!editor || !window.aksrtData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var form = editor.querySelector("[data-aksrt-product-form]");
|
||||||
|
var messageEl = editor.querySelector("[data-aksrt-product-editor-message]");
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
function selectedMultiValues(select) {
|
||||||
|
if (!select) return [];
|
||||||
|
return Array.prototype.slice
|
||||||
|
.call(select.options)
|
||||||
|
.filter(function (option) {
|
||||||
|
return option.selected;
|
||||||
|
})
|
||||||
|
.map(function (option) {
|
||||||
|
return Number(option.value || 0);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
var productId = Number(editor.getAttribute("data-product-id") || 0);
|
||||||
|
var gallery = String(formData.get("gallery") || "")
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map(function (line) {
|
||||||
|
return line.trim();
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
title: String(formData.get("title") || "").trim(),
|
||||||
|
price: Number(formData.get("price") || 0),
|
||||||
|
stock: Number(formData.get("stock") || 0),
|
||||||
|
excerpt: String(formData.get("excerpt") || ""),
|
||||||
|
content: String(formData.get("content") || ""),
|
||||||
|
status: String(formData.get("status") || "draft"),
|
||||||
|
categories: selectedMultiValues(form.querySelector('select[name="categories[]"]')),
|
||||||
|
tags: String(formData.get("tags") || "")
|
||||||
|
.split(",")
|
||||||
|
.map(function (tag) {
|
||||||
|
return tag.trim();
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
|
image: String(formData.get("image") || "").trim(),
|
||||||
|
gallery: gallery,
|
||||||
|
shipping_mode: String(formData.get("shipping_mode") || "express"),
|
||||||
|
shipping_fee: Number(formData.get("shipping_fee") || 0),
|
||||||
|
after_sale_policy: String(formData.get("after_sale_policy") || ""),
|
||||||
|
support_refund: formData.get("support_refund") === "1",
|
||||||
|
};
|
||||||
|
|
||||||
|
var path = productId > 0 ? "/shop/frontend/products/" + productId : "/shop/frontend/products";
|
||||||
|
if (messageEl) messageEl.textContent = "正在保存商品...";
|
||||||
|
|
||||||
|
rest(path, "POST", payload)
|
||||||
|
.then(function (result) {
|
||||||
|
if (!result.success) {
|
||||||
|
if (messageEl) messageEl.textContent = result.message || "保存失败。";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var centerUrl = aksrtData.userCenterUrl || "/";
|
||||||
|
if (messageEl) messageEl.textContent = "商品已保存,正在返回用户中心...";
|
||||||
|
window.location.href = centerUrl;
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
if (messageEl) messageEl.textContent = "网络错误,请稍后重试。";
|
||||||
|
});
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
@@ -130,16 +130,37 @@ trait AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations {
|
|||||||
|
|
||||||
$items = [];
|
$items = [];
|
||||||
foreach ($cart as $pid => $opts_map) {
|
foreach ($cart as $pid => $opts_map) {
|
||||||
$product = $shop->build_product_card_view((int) $pid);
|
$product_id = (int) $pid;
|
||||||
|
$product = $shop->build_product_card_view($product_id);
|
||||||
if (empty($product) || !is_array($opts_map)) {
|
if (empty($product) || !is_array($opts_map)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($opts_map as $opt_str => $qty) {
|
foreach ($opts_map as $opt_str => $qty) {
|
||||||
|
$options_string = $opt_str === '_default' ? '' : (string) $opt_str;
|
||||||
|
$unit_price = method_exists($shop, 'resolve_option_price')
|
||||||
|
? (float) $shop->resolve_option_price($product_id, $options_string !== '' ? $options_string : '_default')
|
||||||
|
: (float) ($product['price'] ?? 0);
|
||||||
|
$options_label = method_exists($shop, 'resolve_option_labels')
|
||||||
|
? (string) $shop->resolve_option_labels($product_id, $options_string !== '' ? $options_string : '_default')
|
||||||
|
: '';
|
||||||
|
$option_image = method_exists($shop, 'get_option_image')
|
||||||
|
? (string) $shop->get_option_image($product_id, $options_string !== '' ? $options_string : '_default')
|
||||||
|
: '';
|
||||||
|
$stock = method_exists($shop, 'resolve_option_stock')
|
||||||
|
? (int) $shop->resolve_option_stock($product_id, $options_string !== '' ? $options_string : '_default')
|
||||||
|
: (int) ($product['stock'] ?? 0);
|
||||||
|
|
||||||
$items[] = [
|
$items[] = [
|
||||||
'product_id' => (int) $pid,
|
'product_id' => $product_id,
|
||||||
'options_string' => $opt_str === '_default' ? '' : $opt_str,
|
'options_string' => $options_string,
|
||||||
'quantity' => (int) $qty,
|
'options_label' => $options_label,
|
||||||
'product' => $product,
|
'quantity' => (int) $qty,
|
||||||
|
'unit_price' => $unit_price,
|
||||||
|
'line_total' => round($unit_price * (int) $qty, 2),
|
||||||
|
'stock' => $stock,
|
||||||
|
'image' => $option_image !== '' ? $option_image : (string) ($product['image'] ?? ''),
|
||||||
|
'title' => (string) ($product['title'] ?? ''),
|
||||||
|
'product' => $product,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,6 +240,40 @@ trait AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations {
|
|||||||
return $this->success(['data' => $result]);
|
return $this->success(['data' => $result]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggle_frontend_product_favorite(WP_REST_Request $request): WP_REST_Response {
|
||||||
|
if (!class_exists('AKSRT_Shop')) {
|
||||||
|
return $this->error('商城服务不可用', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_id = (int) $request->get_param('id');
|
||||||
|
$result = AKSRT_Shop::get_instance()->toggle_favorite(get_current_user_id(), $product_id);
|
||||||
|
if (empty($result['success'])) {
|
||||||
|
return $this->error(__('收藏操作失败', 'aksrt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'message' => !empty($result['is_favorite']) ? __('已收藏', 'aksrt') : __('已取消收藏', 'aksrt'),
|
||||||
|
'data' => $result,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rebuy_frontend_order(WP_REST_Request $request): WP_REST_Response {
|
||||||
|
if (!class_exists('AKSRT_Shop')) {
|
||||||
|
return $this->error('商城服务不可用', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||||
|
$result = AKSRT_Shop::get_instance()->re_purchase(get_current_user_id(), $order_number);
|
||||||
|
if (empty($result['success'])) {
|
||||||
|
return $this->error($result['message'] ?? __('再次购买失败', 'aksrt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'message' => __('商品已加入购物车', 'aksrt'),
|
||||||
|
'data' => $result,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function get_frontend_addresses(): WP_REST_Response {
|
public function get_frontend_addresses(): WP_REST_Response {
|
||||||
if (!class_exists('AKSRT_Shop')) {
|
if (!class_exists('AKSRT_Shop')) {
|
||||||
return $this->success(['data' => ['addresses' => []]]);
|
return $this->success(['data' => ['addresses' => []]]);
|
||||||
|
|||||||
@@ -346,6 +346,18 @@ class AKSRT_Admin_Shop_Controller extends AKSRT_Admin_Controller_Base {
|
|||||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/rebuy', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'rebuy_frontend_order'],
|
||||||
|
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/(?P<id>\d+)/favorite', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'toggle_frontend_product_favorite'],
|
||||||
|
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||||
|
]);
|
||||||
|
|
||||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale/reasons', [
|
register_rest_route(self::API_NAMESPACE, '/shop/after-sale/reasons', [
|
||||||
'methods' => 'GET',
|
'methods' => 'GET',
|
||||||
'callback' => [$this, 'get_after_sale_reasons'],
|
'callback' => [$this, 'get_after_sale_reasons'],
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
|
|||||||
data-aksrt-product-page
|
data-aksrt-product-page
|
||||||
data-product-id="<?php echo esc_attr((string) $product_id); ?>"
|
data-product-id="<?php echo esc_attr((string) $product_id); ?>"
|
||||||
data-options='<?php echo esc_attr(wp_json_encode($options, JSON_UNESCAPED_UNICODE)); ?>'
|
data-options='<?php echo esc_attr(wp_json_encode($options, JSON_UNESCAPED_UNICODE)); ?>'
|
||||||
|
data-base-price="<?php echo esc_attr((string) (($product['start_price'] ?? 0) > 0 ? $product['start_price'] : ($product['price'] ?? 0))); ?>"
|
||||||
|
data-base-stock="<?php echo esc_attr((string) ($product['stock'] ?? 0)); ?>"
|
||||||
|
data-pay-modo="<?php echo esc_attr((string) $pay_modo); ?>"
|
||||||
|
data-price-range="<?php echo !empty($price_range) && (($price_range['min'] ?? 0) !== ($price_range['max'] ?? 0)) ? '1' : '0'; ?>"
|
||||||
>
|
>
|
||||||
<div class="aksrt-product-page__layout">
|
<div class="aksrt-product-page__layout">
|
||||||
<!-- Gallery -->
|
<!-- Gallery -->
|
||||||
|
|||||||
Reference in new issue
Block a user