Connect product options/add-cart, server cart checkout payloads, favorite/rebuy/receipt REST, and order-center actions without localStorage dual-read or hardcoded cart/orders URL hacks.
484 lines
20 KiB
JavaScript
484 lines
20 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
var page = document.querySelector("[data-aksrt-cart-page]");
|
|
if (!page || !window.aksrtData) {
|
|
return;
|
|
}
|
|
|
|
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 itemsEl = page.querySelector("[data-aksrt-cart-items]");
|
|
var subtotalEl = page.querySelector("[data-aksrt-summary-subtotal]");
|
|
var shippingEl = page.querySelector("[data-aksrt-summary-shipping]");
|
|
var discountEl = page.querySelector("[data-aksrt-summary-discount]");
|
|
var totalEl = page.querySelector("[data-aksrt-summary-total]");
|
|
var previewMessageEl = page.querySelector("[data-aksrt-preview-message]");
|
|
var createBtn = page.querySelector("[data-aksrt-create-order]");
|
|
var previewBtn = page.querySelector("[data-aksrt-preview-checkout]");
|
|
|
|
var previewState = null;
|
|
var searchParams = new URLSearchParams(window.location.search);
|
|
|
|
function getCart() { return cartItems; }
|
|
function setCart(items) { cartItems = Array.isArray(items) ? items : []; }
|
|
|
|
function money(value) {
|
|
return currencySymbol + Number(value || 0).toFixed(2);
|
|
}
|
|
|
|
function selectedValue(name) {
|
|
var checked = page.querySelector('input[name="' + name + '"]:checked');
|
|
return checked ? checked.value : "";
|
|
}
|
|
|
|
function getPayload() {
|
|
var items = getCart().map(function (item) {
|
|
return {
|
|
product_id: Number(item.product_id || item.id || 0),
|
|
quantity: Number(item.qty || item.quantity || 1),
|
|
options_string: item.options_string || ""
|
|
};
|
|
}).filter(function (item) {
|
|
return item.product_id > 0;
|
|
});
|
|
|
|
return {
|
|
items: items,
|
|
coupon_code: (page.querySelector("[data-aksrt-coupon-code]") || {}).value || "",
|
|
shipping_method: selectedValue("aksrt_cart_shipping"),
|
|
address_id: Number(selectedValue("aksrt_cart_address") || 0),
|
|
payment_method: selectedValue("aksrt_cart_payment"),
|
|
customer_note: (page.querySelector("[data-aksrt-customer-note]") || {}).value || ""
|
|
};
|
|
}
|
|
|
|
function renderCart() {
|
|
var items = getCart();
|
|
if (!items.length) {
|
|
itemsEl.innerHTML = '' +
|
|
'<div class="aksrt-cart-page__empty">' +
|
|
'<i class="fas fa-shopping-cart" aria-hidden="true"></i>' +
|
|
"<p>购物车为空,去商品页添加想购买的内容。</p>" +
|
|
'<a href="' + shopHomeUrl() + '" class="btn-outline btn-sm">继续浏览</a>' +
|
|
"</div>";
|
|
subtotalEl.textContent = money(0);
|
|
shippingEl.textContent = money(0);
|
|
discountEl.textContent = "-" + money(0);
|
|
totalEl.textContent = money(0);
|
|
createBtn.disabled = true;
|
|
previewState = null;
|
|
return;
|
|
}
|
|
|
|
var total = 0;
|
|
var html = "";
|
|
items.forEach(function (item, index) {
|
|
var qty = Number(item.qty || item.quantity || 1);
|
|
var price = Number(item.unit_price != null ? item.unit_price : (item.price != null ? item.price : (item.product && item.product.price) || 0));
|
|
var lineTotal = Number(item.line_total != null ? item.line_total : qty * price);
|
|
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 += '' +
|
|
'<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, """) + '">' +
|
|
(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">' +
|
|
'<h3>' + title + "</h3>" +
|
|
(opts ? "<small>" + opts + "</small>" : "") +
|
|
'<p>' + money(price) + "</p>" +
|
|
"</div>" +
|
|
'<div class="aksrt-cart-page__item-qty">' +
|
|
'<button type="button" data-qty-action="dec">-</button>' +
|
|
"<span>" + qty + "</span>" +
|
|
'<button type="button" data-qty-action="inc">+</button>' +
|
|
"</div>" +
|
|
'<strong class="aksrt-cart-page__item-total">' + money(lineTotal) + "</strong>" +
|
|
'<button type="button" class="aksrt-cart-page__item-remove" data-qty-action="remove"><i class="fas fa-trash"></i></button>' +
|
|
"</article>";
|
|
});
|
|
|
|
itemsEl.innerHTML = html;
|
|
subtotalEl.textContent = money(total);
|
|
|
|
if (!previewState) {
|
|
shippingEl.textContent = money(0);
|
|
discountEl.textContent = "-" + money(0);
|
|
totalEl.textContent = money(total);
|
|
}
|
|
}
|
|
|
|
function setSummary(data) {
|
|
previewState = data || null;
|
|
subtotalEl.textContent = money((data && data.subtotal) || 0);
|
|
shippingEl.textContent = money((data && data.shipping_fee) || 0);
|
|
discountEl.textContent = "-" + money((data && data.discount) || 0);
|
|
totalEl.textContent = money((data && data.total) || 0);
|
|
createBtn.disabled = !data;
|
|
}
|
|
|
|
function request(path, body) { return rest(path, "POST", body || {}); }
|
|
|
|
function previewCheckout() {
|
|
var payload = getPayload();
|
|
if (!payload.items.length) {
|
|
previewMessageEl.textContent = "请先添加商品后再试算。";
|
|
return;
|
|
}
|
|
|
|
previewBtn.disabled = true;
|
|
previewMessageEl.textContent = "正在试算订单...";
|
|
|
|
request("/shop/frontend/checkout/preview", payload)
|
|
.then(function (result) {
|
|
if (!result.success) {
|
|
setSummary(null);
|
|
previewMessageEl.textContent = result.message || "试算失败。";
|
|
return;
|
|
}
|
|
|
|
setSummary(result.data);
|
|
previewMessageEl.textContent = "已完成试算,可直接创建订单。";
|
|
})
|
|
.catch(function () {
|
|
setSummary(null);
|
|
previewMessageEl.textContent = "网络错误,请稍后重试。";
|
|
})
|
|
.finally(function () {
|
|
previewBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
function createOrder() {
|
|
var payload = getPayload();
|
|
if (!previewState) {
|
|
previewCheckout();
|
|
return;
|
|
}
|
|
|
|
createBtn.disabled = true;
|
|
request("/shop/frontend/orders", payload)
|
|
.then(function (result) {
|
|
if (!result.success) {
|
|
previewMessageEl.textContent = result.message || "订单创建失败。";
|
|
createBtn.disabled = false;
|
|
return;
|
|
}
|
|
|
|
var orderNumber = (result.data && result.data.order_number) || "";
|
|
var nextOrdersUrl = ordersUrl() + (String(ordersUrl()).indexOf("?") >= 0 ? "&" : "?") + "order_status=pending";
|
|
|
|
if (!orderNumber) {
|
|
window.location.href = nextOrdersUrl;
|
|
return;
|
|
}
|
|
|
|
// 立即发起支付:跳转型网关直接前往第三方,余额型同步完成。
|
|
request("/shop/frontend/orders/" + orderNumber + "/pay", {})
|
|
.then(function (pay) {
|
|
var data = (pay && pay.data) || {};
|
|
if (pay && pay.success && data.redirect && data.payment_url && /^https?:\/\//i.test(data.payment_url)) {
|
|
window.location.href = data.payment_url;
|
|
return;
|
|
}
|
|
// 二维码 / 余额 / 失败:统一回订单中心处理(含 ?payment 提示)。
|
|
window.location.href = nextOrdersUrl;
|
|
})
|
|
.catch(function () {
|
|
window.location.href = nextOrdersUrl;
|
|
});
|
|
})
|
|
.catch(function () {
|
|
previewMessageEl.textContent = "网络错误,请稍后重试。";
|
|
createBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
page.addEventListener("click", function (event) {
|
|
var actionButton = event.target.closest("[data-qty-action]");
|
|
if (actionButton) {
|
|
var itemEl = actionButton.closest("[data-cart-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;
|
|
}
|
|
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]")) {
|
|
rest("/shop/frontend/cart/empty", "POST", {}).then(function () {
|
|
setSummary(null);
|
|
loadServerCart();
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (event.target.closest("[data-aksrt-preview-checkout]")) {
|
|
previewCheckout();
|
|
return;
|
|
}
|
|
|
|
if (event.target.closest("[data-aksrt-create-order]")) {
|
|
createOrder();
|
|
return;
|
|
}
|
|
|
|
var resumeButton = event.target.closest("[data-aksrt-resume-order]");
|
|
if (resumeButton) {
|
|
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") || "";
|
|
}
|
|
|
|
/* ── Address Book Management ── */
|
|
|
|
var addressModal = page.querySelector("[data-aksrt-address-modal]");
|
|
var addressForm = page.querySelector("[data-aksrt-address-form]");
|
|
var addressList = page.querySelector("[data-aksrt-address-list]");
|
|
var addressTitle = page.querySelector("[data-aksrt-address-modal-title]");
|
|
|
|
function openAddressModal(mode, address) {
|
|
if (!addressModal || !addressForm) return;
|
|
addressForm.reset();
|
|
addressForm.querySelector('[name="address_id"]').value = "";
|
|
|
|
if (mode === "edit" && address) {
|
|
addressTitle.textContent = "编辑地址";
|
|
addressForm.querySelector('[name="address_id"]').value = address.id || "";
|
|
addressForm.querySelector('[name="name"]').value = address.name || "";
|
|
addressForm.querySelector('[name="phone"]').value = address.phone || "";
|
|
addressForm.querySelector('[name="province"]').value = address.province || "";
|
|
addressForm.querySelector('[name="city"]').value = address.city || "";
|
|
addressForm.querySelector('[name="district"]').value = address.district || "";
|
|
addressForm.querySelector('[name="address"]').value = address.address || "";
|
|
addressForm.querySelector('[name="is_default"]').checked = !!address.is_default;
|
|
} else {
|
|
addressTitle.textContent = "新增地址";
|
|
}
|
|
|
|
addressModal.hidden = false;
|
|
}
|
|
|
|
function closeAddressModal() {
|
|
if (addressModal) addressModal.hidden = true;
|
|
}
|
|
|
|
function refreshAddressList() {
|
|
fetch(restBase() + "/shop/frontend/address", {
|
|
headers: { "X-WP-Nonce": aksrtData.restNonce }
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (result) {
|
|
if (!result.success || !addressList) return;
|
|
var addresses = (result.data && result.data.addresses) || [];
|
|
renderAddressList(addresses);
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
function renderAddressList(addresses) {
|
|
if (!addressList) return;
|
|
if (!addresses.length) {
|
|
addressList.innerHTML =
|
|
'<div class="aksrt-cart-page__address-empty" data-aksrt-address-empty>' +
|
|
'<i class="fas fa-map-marker-alt" aria-hidden="true"></i>' +
|
|
"<p>暂无收货地址,请先新增。</p>" +
|
|
"</div>";
|
|
return;
|
|
}
|
|
|
|
var html = "";
|
|
addresses.forEach(function (addr, index) {
|
|
html +=
|
|
'<div class="aksrt-cart-page__option-card aksrt-cart-page__address-card" data-aksrt-address-card="' + addr.id + '">' +
|
|
'<label class="aksrt-cart-page__address-label">' +
|
|
'<input type="radio" name="aksrt_cart_address" value="' + addr.id + '"' + (index === 0 || addr.is_default ? " checked" : "") + ">" +
|
|
'<span class="aksrt-cart-page__option-body">' +
|
|
"<strong>" + escHtml(addr.name) + (addr.is_default ? ' <span class="aksrt-cart-page__default-tag">默认</span>' : "") + "</strong>" +
|
|
"<span>" + escHtml(addr.phone) + "</span>" +
|
|
"<span>" + escHtml(addr.full_text) + "</span>" +
|
|
"</span></label>" +
|
|
'<div class="aksrt-cart-page__address-actions">' +
|
|
'<button type="button" class="btn-ghost btn-xs" data-aksrt-address-edit="' + addr.id + '">编辑</button>' +
|
|
'<button type="button" class="btn-ghost btn-xs" data-aksrt-address-delete="' + addr.id + '">删除</button>' +
|
|
(addr.is_default ? "" : '<button type="button" class="btn-ghost btn-xs" data-aksrt-address-set-default="' + addr.id + '">设为默认</button>') +
|
|
"</div></div>";
|
|
});
|
|
addressList.innerHTML = html;
|
|
}
|
|
|
|
function escHtml(str) {
|
|
var div = document.createElement("div");
|
|
div.appendChild(document.createTextNode(str || ""));
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function getAddressById(id) {
|
|
var cards = addressList ? addressList.querySelectorAll("[data-aksrt-address-card]") : [];
|
|
var found = null;
|
|
cards.forEach(function (card) {
|
|
if (String(card.getAttribute("data-aksrt-address-card")) === String(id)) {
|
|
var body = card.querySelector(".aksrt-cart-page__option-body");
|
|
if (body) {
|
|
var spans = body.querySelectorAll("span");
|
|
var strong = body.querySelector("strong");
|
|
found = {
|
|
id: id,
|
|
name: strong ? strong.textContent.replace("默认", "").trim() : "",
|
|
phone: spans[0] ? spans[0].textContent : "",
|
|
full_text: spans[1] ? spans[1].textContent : ""
|
|
};
|
|
}
|
|
}
|
|
});
|
|
return found;
|
|
}
|
|
|
|
// Address event delegation
|
|
page.addEventListener("click", function (event) {
|
|
if (event.target.closest("[data-aksrt-address-add]")) {
|
|
openAddressModal("add");
|
|
return;
|
|
}
|
|
|
|
var editBtn = event.target.closest("[data-aksrt-address-edit]");
|
|
if (editBtn) {
|
|
var editId = editBtn.getAttribute("data-aksrt-address-edit");
|
|
var addr = getAddressById(editId);
|
|
openAddressModal("edit", addr);
|
|
return;
|
|
}
|
|
|
|
var deleteBtn = event.target.closest("[data-aksrt-address-delete]");
|
|
if (deleteBtn) {
|
|
var deleteId = deleteBtn.getAttribute("data-aksrt-address-delete");
|
|
if (!confirm("确定删除该地址吗?")) return;
|
|
rest("/shop/frontend/address/" + deleteId, "DELETE").then(function (result) {
|
|
if (!result || !result.success) {
|
|
alert((result && result.message) || "删除失败。");
|
|
return;
|
|
}
|
|
refreshAddressList();
|
|
}).catch(function () { alert("网络错误。"); });
|
|
return;
|
|
}
|
|
|
|
var defaultBtn = event.target.closest("[data-aksrt-address-set-default]");
|
|
if (defaultBtn) {
|
|
var defaultId = defaultBtn.getAttribute("data-aksrt-address-set-default");
|
|
request("/shop/frontend/address/" + defaultId + "/default", {}).then(function (result) {
|
|
if (!result.success) {
|
|
alert(result.message || "设置失败。");
|
|
return;
|
|
}
|
|
refreshAddressList();
|
|
}).catch(function () { alert("网络错误。"); });
|
|
return;
|
|
}
|
|
|
|
if (event.target.closest("[data-aksrt-address-modal-close]") || event.target.classList.contains("aksrt-modal__overlay")) {
|
|
closeAddressModal();
|
|
}
|
|
});
|
|
|
|
if (addressForm) {
|
|
addressForm.addEventListener("submit", function (event) {
|
|
event.preventDefault();
|
|
var formData = new FormData(addressForm);
|
|
var addressId = formData.get("address_id");
|
|
var payload = {
|
|
name: (formData.get("name") || "").trim(),
|
|
phone: (formData.get("phone") || "").trim(),
|
|
province: (formData.get("province") || "").trim(),
|
|
city: (formData.get("city") || "").trim(),
|
|
district: (formData.get("district") || "").trim(),
|
|
address: (formData.get("address") || "").trim(),
|
|
is_default: formData.get("is_default") === "1"
|
|
};
|
|
|
|
if (!payload.name || !payload.phone || !payload.address) {
|
|
alert("请填写必填项。");
|
|
return;
|
|
}
|
|
|
|
var url = addressId
|
|
? "/shop/frontend/address/" + addressId
|
|
: "/shop/frontend/address";
|
|
|
|
request(url, payload).then(function (result) {
|
|
if (!result.success) {
|
|
alert(result.message || "保存失败。");
|
|
return;
|
|
}
|
|
closeAddressModal();
|
|
refreshAddressList();
|
|
}).catch(function () { alert("网络错误。"); });
|
|
});
|
|
}
|
|
|
|
loadServerCart();
|
|
})();
|