Wire product cat/tag archives to settings-driven sort and AJAX paging, seller product options/video/params editing, revenue withdraw form and order review submit — finishing remaining shop frontend gaps after buy-path.
408 lines
15 KiB
JavaScript
408 lines
15 KiB
JavaScript
/**
|
||
* 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 = "网络错误,请稍后重试。";
|
||
});
|
||
});
|
||
})();
|