/** * Product purchase page — options selector + server cart. * * @package AKSRT */ (function () { "use strict"; function restBase() { var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : ""; return root + "/wp-json/aksrt/v1"; } function restNonce() { return (window.aksrtData && aksrtData.restNonce) || ""; } function toast(msg, type) { if (typeof window.aksrtToast === "function") { window.aksrtToast(msg, type || "info"); return; } if (window.aksrt && typeof aksrt.notyf === "function") { aksrt.notyf(msg, { type: type || "info" }); return; } window.alert(msg); } 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); } 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); } function parseOptionsText(text) { var groups = []; String(text || "").split(String.fromCharCode(10)).forEach(function (line) { line = line.replace(/\r/g, "").trim(); if (!line) return; var parts = line.split("|"); if (parts.length < 2) return; var name = parts[0].trim(); var values = []; parts.slice(1).join("|").split(",").forEach(function (chunk) { chunk = chunk.trim(); if (!chunk) return; var segs = chunk.split(":"); var label = (segs[0] || "").trim(); if (!label) return; values.push({ label: label, price_change: Number(segs[1] || 0), image: "" }); }); if (name && values.length) { groups.push({ name: name, view_mode: "list", values: values }); } }); return groups; } 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(String.fromCharCode(10)) .map(function (line) { return line.replace(/\r/g, "").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", options: parseOptionsText(formData.get("options_text") || ""), start_price: Number(formData.get("start_price") || 0), pay_modo: String(formData.get("pay_modo") || "0"), product_params: String(formData.get("product_params") || ""), product_video: String(formData.get("product_video") || "") .split(String.fromCharCode(10)) .map(function (line) { return line.replace(/\r/g, "").trim(); }) .filter(Boolean) }; 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 = "网络错误,请稍后重试。"; }); }); })();