' +
@@ -124,18 +140,7 @@
createBtn.disabled = !data;
}
- function request(path, 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 request(path, body) { return rest(path, "POST", body || {}); }
function previewCheckout() {
var payload = getPayload();
@@ -183,12 +188,11 @@
return;
}
- localStorage.removeItem(CART_KEY);
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) {
- window.location.href = ordersUrl;
+ window.location.href = nextOrdersUrl;
return;
}
@@ -201,10 +205,10 @@
return;
}
// 二维码 / 余额 / 失败:统一回订单中心处理(含 ?payment 提示)。
- window.location.href = ordersUrl;
+ window.location.href = nextOrdersUrl;
})
.catch(function () {
- window.location.href = ordersUrl;
+ window.location.href = nextOrdersUrl;
});
})
.catch(function () {
@@ -217,31 +221,36 @@
var actionButton = event.target.closest("[data-qty-action]");
if (actionButton) {
var itemEl = actionButton.closest("[data-cart-index]");
- var index = Number(itemEl.getAttribute("data-cart-index"));
- var items = getCart();
- if (!items[index]) {
+ if (!itemEl) return;
+ var productId = Number(itemEl.getAttribute("data-product-id") || 0);
+ 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;
}
-
- var action = actionButton.getAttribute("data-qty-action");
- if (action === "inc") {
- items[index].qty = Number(items[index].qty || 1) + 1;
- } else if (action === "dec") {
- items[index].qty = Math.max(1, Number(items[index].qty || 1) - 1);
- } else if (action === "remove") {
- items.splice(index, 1);
- }
-
- setCart(items);
- setSummary(null);
- renderCart();
+ rest(path, "POST", body).then(function (result) {
+ if (!result || !result.success) {
+ window.alert((result && result.message) || "操作失败");
+ return;
+ }
+ setSummary(null);
+ loadServerCart();
+ });
return;
}
if (event.target.closest("[data-aksrt-clear-cart]")) {
- localStorage.removeItem(CART_KEY);
- setSummary(null);
- renderCart();
+ rest("/shop/frontend/cart/empty", "POST", {}).then(function () {
+ setSummary(null);
+ loadServerCart();
+ });
return;
}
@@ -257,10 +266,28 @@
var resumeButton = event.target.closest("[data-aksrt-resume-order]");
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]");
if (couponInput && searchParams.has("coupon")) {
couponInput.value = searchParams.get("coupon") || "";
@@ -300,7 +327,7 @@
}
function refreshAddressList() {
- fetch("/wp-json/aksrt/v1/shop/frontend/address", {
+ fetch(restBase() + "/shop/frontend/address", {
headers: { "X-WP-Nonce": aksrtData.restNonce }
})
.then(function (r) { return r.json(); })
@@ -389,9 +416,9 @@
if (deleteBtn) {
var deleteId = deleteBtn.getAttribute("data-aksrt-address-delete");
if (!confirm("确定删除该地址吗?")) return;
- request("/shop/frontend/address/" + deleteId, {}).then(function (result) {
- if (!result.success) {
- alert(result.message || "删除失败。");
+ rest("/shop/frontend/address/" + deleteId, "DELETE").then(function (result) {
+ if (!result || !result.success) {
+ alert((result && result.message) || "删除失败。");
return;
}
refreshAddressList();
@@ -452,5 +479,5 @@
});
}
- renderCart();
+ loadServerCart();
})();
diff --git a/assets/js/order-center.js b/assets/js/order-center.js
index 38015b06..e5f2a2bb 100644
--- a/assets/js/order-center.js
+++ b/assets/js/order-center.js
@@ -46,19 +46,27 @@
}
/* ── REST helper ── */
- function request(path, body) {
- return fetch("/wp-json/aksrt/v1" + path, {
- method: "POST",
+ function restRoot() {
+ return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1";
+ }
+
+ function request(path, body, method) {
+ return fetch(restRoot() + path, {
+ method: method || "POST",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": aksrtData.restNonce
},
- body: JSON.stringify(body || {})
+ body: body === undefined ? undefined : JSON.stringify(body || {})
}).then(function (response) {
return response.json();
});
}
+ function cartUrl() {
+ return aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart) || "/";
+ }
+
/* ── 扫码支付弹层(微信 Native code_url 等)── */
function showPaymentQr(content, orderNumber) {
var existing = document.getElementById("aksrt-pay-qr-overlay");
@@ -118,12 +126,12 @@
function updateCardStatus(card, newStatus, statusLabel) {
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) {
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]");
+ 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";
}
@@ -216,6 +224,75 @@
/* ── Event delegation ── */
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
var payButton = event.target.closest("[data-aksrt-order-pay]");
if (payButton) {
diff --git a/assets/js/product-purchase.js b/assets/js/product-purchase.js
index 2d805b5a..778cdb7b 100644
--- a/assets/js/product-purchase.js
+++ b/assets/js/product-purchase.js
@@ -1,241 +1,383 @@
+/**
+ * Product purchase page — options selector + server cart.
+ *
+ * @package AKSRT
+ */
(function () {
- "use strict";
+ "use strict";
- function updateCart(product, quantity, replace) {
- var key = "aksrt_cart";
- var current;
+ function restBase() {
+ var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
+ return root + "/wp-json/aksrt/v1";
+ }
- try {
- current = JSON.parse(localStorage.getItem(key) || "[]");
- if (!Array.isArray(current)) {
- current = [];
- }
- } catch (error) {
- current = [];
- }
+ function restNonce() {
+ return (window.aksrtData && aksrtData.restNonce) || "";
+ }
- var index = current.findIndex(function (item) {
- return Number(item.product_id || item.id || 0) === Number(product.product_id);
- });
-
- 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));
+ function toast(msg, type) {
+ if (typeof window.aksrtToast === "function") {
+ window.aksrtToast(msg, type || "info");
+ return;
}
-
- 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(".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;
- }
- }
+ if (window.aksrt && typeof aksrt.notyf === "function") {
+ aksrt.notyf(msg, { type: type || "info" });
+ return;
}
+ window.alert(msg);
+ }
- var editor = document.querySelector("[data-aksrt-product-editor]");
- if (!editor || !window.aksrtData) {
- return;
+ function rest(path, method, body) {
+ var opts = {
+ 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);
}
-
- var form = editor.querySelector("[data-aksrt-product-form]");
- 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 = "网络错误,请稍后重试。";
- });
+ return fetch(restBase() + path, opts).then(function (r) {
+ return r.json();
});
+ }
+
+ 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 = "网络错误,请稍后重试。";
+ });
+ });
})();
diff --git a/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php b/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
index 63430a09..dcc4d640 100644
--- a/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
+++ b/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
@@ -130,16 +130,37 @@ trait AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations {
$items = [];
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)) {
continue;
}
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[] = [
- 'product_id' => (int) $pid,
- 'options_string' => $opt_str === '_default' ? '' : $opt_str,
- 'quantity' => (int) $qty,
- 'product' => $product,
+ 'product_id' => $product_id,
+ 'options_string' => $options_string,
+ 'options_label' => $options_label,
+ '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]);
}
+ 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 {
if (!class_exists('AKSRT_Shop')) {
return $this->success(['data' => ['addresses' => []]]);
diff --git a/inc/classes/admin/class-aksrt-admin-shop-controller.php b/inc/classes/admin/class-aksrt-admin-shop-controller.php
index 2e0d9ab8..1112cfd3 100644
--- a/inc/classes/admin/class-aksrt-admin-shop-controller.php
+++ b/inc/classes/admin/class-aksrt-admin-shop-controller.php
@@ -346,6 +346,18 @@ class AKSRT_Admin_Shop_Controller extends AKSRT_Admin_Controller_Base {
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
+ register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P
[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\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', [
'methods' => 'GET',
'callback' => [$this, 'get_after_sale_reasons'],
diff --git a/templates/pages/page-product.php b/templates/pages/page-product.php
index cf40925b..745b2676 100644
--- a/templates/pages/page-product.php
+++ b/templates/pages/page-product.php
@@ -46,6 +46,10 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
data-aksrt-product-page
data-product-id=""
data-options=''
+ data-base-price=" 0 ? $product['start_price'] : ($product['price'] ?? 0))); ?>"
+ data-base-stock=""
+ data-pay-modo=""
+ data-price-range=""
>