Files
wordpress-theme-aksrt/assets/js/product-archive.js
T
kate522 433b7cb3a6 feat(shop): category filter bar + sort/load-more 1:1 UX
Port zibll hierarchical cat tabs, multi-metric sort with price toggle,
skeleton/empty states, and REST archive payload (header/html/paginate)
while reusing product-card template for AJAX HTML.
2026-07-18 16:42:52 +08:00

296 lines
11 KiB
JavaScript

/**
* Product archive — cat filter + sort + AJAX load more (zibll ajax-next UX).
*
* Uses REST /shop/frontend/products/archive and optional AksrtAjaxList events.
*
* @package AKSRT
*/
(function () {
"use strict";
var root = document.querySelector("[data-aksrt-product-archive]");
if (!root) return;
function restBase() {
var site = window.aksrtData && aksrtData.siteUrl ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
return site + "/wp-json/aksrt/v1";
}
function toast(msg) {
if (typeof window.aksrtToast === "function") {
window.aksrtToast(msg);
return;
}
if (window.console) console.log(msg);
}
var grid = root.querySelector("[data-aksrt-product-grid], #aksrt-product-grid");
var pager = root.querySelector("[data-aksrt-ajaxpager], .ajaxpager");
var countEl = root.querySelector("[data-aksrt-archive-count]");
var headerHost = root.querySelector("[data-aksrt-ajax-header]");
var loadMoreBtn = root.querySelector("[data-aksrt-shop-load-more]");
var loader = root.querySelector("[data-aksrt-ajax-loader], .post_ajax_loader");
var state = {
taxonomy: root.getAttribute("data-taxonomy") || "",
termId: Number(root.getAttribute("data-term-id") || 0),
orderby: root.getAttribute("data-orderby") || "date",
order: (root.getAttribute("data-order") || "DESC").toUpperCase(),
page: Number(root.getAttribute("data-page") || 1),
maxPages: Number(root.getAttribute("data-max-pages") || 1),
paging: root.getAttribute("data-paging") || "number",
ias: root.getAttribute("data-ias") === "1",
iasMax: Number(root.getAttribute("data-ias-max") || 5),
loading: false,
autoLoads: 0,
};
function showLoader(on) {
if (loader) {
loader.hidden = !on;
loader.style.display = on ? "" : "none";
loader.classList.toggle("is-loading", !!on);
}
if (pager) pager.classList.toggle("is-ajax-loading", !!on);
}
function updateLoadMore(hasMore) {
var pag = root.querySelector(".ajax-pag");
if (loadMoreBtn) {
loadMoreBtn.style.display = hasMore ? "" : "none";
loadMoreBtn.disabled = false;
loadMoreBtn.textContent = hasMore ? "加载更多" : "没有更多了";
if (!hasMore) loadMoreBtn.classList.add("ajax-nomore");
else loadMoreBtn.classList.remove("ajax-nomore");
}
if (pag) pag.style.display = hasMore ? "" : "none";
}
function syncOrderbyActive() {
root.querySelectorAll("[data-aksrt-shop-orderby]").forEach(function (a) {
var key = a.getAttribute("data-aksrt-shop-orderby") || "";
var active = key === state.orderby;
a.classList.toggle("is-active", active);
a.classList.toggle("focus-color", active && key === "price");
});
}
function fetchArchive(page, orderby, order, replace, termId) {
if (state.loading) return Promise.resolve();
state.loading = true;
showLoader(true);
if (loadMoreBtn) {
loadMoreBtn.disabled = true;
loadMoreBtn.textContent = "加载中…";
}
var qs = new URLSearchParams();
qs.set("taxonomy", state.taxonomy);
qs.set("term_id", String(typeof termId === "number" ? termId : state.termId));
qs.set("page", String(page));
qs.set("orderby", orderby || state.orderby);
if ((orderby || state.orderby) === "price") {
qs.set("order", order || state.order || "ASC");
}
var container = pager || grid || root;
try {
container.dispatchEvent(
new CustomEvent("post_ajax.before", {
bubbles: true,
detail: { page: page, orderby: orderby || state.orderby, replace: !!replace },
})
);
} catch (e0) {}
return fetch(restBase() + "/shop/frontend/products/archive?" + qs.toString(), {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"X-WP-Nonce": (window.aksrtData && aksrtData.restNonce) || "",
"X-AKSRT-Ajax": "list",
},
})
.then(function (r) {
return r.json();
})
.then(function (result) {
state.loading = false;
showLoader(false);
if (!result || !result.success || !result.data) {
toast((result && result.message) || "加载失败");
updateLoadMore(state.page < state.maxPages);
return;
}
var data = result.data;
state.page = Number(data.page || page);
state.maxPages = Number(data.max_pages || 1);
state.orderby = data.orderby || state.orderby;
state.order = String(data.order || state.order || "DESC").toUpperCase();
if (typeof termId === "number" && termId > 0) {
state.termId = termId;
}
if (data.term && data.term.id) {
state.termId = Number(data.term.id);
}
root.setAttribute("data-page", String(state.page));
root.setAttribute("data-max-pages", String(state.maxPages));
root.setAttribute("data-orderby", state.orderby);
root.setAttribute("data-order", state.order);
root.setAttribute("data-term-id", String(state.termId));
if (countEl && typeof data.total !== "undefined") {
countEl.textContent = "共 " + data.total + " 件商品";
}
var titleEl = root.querySelector("[data-aksrt-archive-title]");
if (titleEl && data.term && data.term.name) {
titleEl.textContent = data.term.name;
}
var descEl = root.querySelector("[data-aksrt-archive-desc]");
if (descEl && data.term) {
descEl.textContent = data.term.description || "";
descEl.style.display = data.term.description ? "" : "none";
}
if (headerHost && data.header_html) {
// Keep title/count; replace filter widget if present.
var existingFilter = headerHost.querySelector("[data-aksrt-shop-cat-filter], .shop-cat-filter");
var tmp = document.createElement("div");
tmp.innerHTML = data.header_html;
var nextFilter = tmp.querySelector("[data-aksrt-shop-cat-filter], .shop-cat-filter") || tmp.firstElementChild;
if (existingFilter && nextFilter) {
existingFilter.replaceWith(nextFilter);
} else if (!existingFilter && nextFilter) {
headerHost.appendChild(nextFilter);
}
}
var html = data.html || data.empty_html || "";
if (grid) {
if (replace) {
grid.innerHTML = html;
} else if (html) {
grid.insertAdjacentHTML("beforeend", html);
}
}
if (data.paginate_html) {
var oldPag = root.querySelector(".ajax-pag");
var wrap = document.createElement("div");
wrap.innerHTML = data.paginate_html;
var nextPag = wrap.firstElementChild;
if (oldPag && nextPag) oldPag.replaceWith(nextPag);
else if (!oldPag && nextPag && pager) pager.appendChild(nextPag);
loadMoreBtn = root.querySelector("[data-aksrt-shop-load-more]");
}
syncOrderbyActive();
updateLoadMore(!!data.has_more || state.page < state.maxPages);
try {
container.dispatchEvent(
new CustomEvent("post_ajax.ed", {
bubbles: true,
detail: { page: state.page, replace: !!replace, data: data },
})
);
document.dispatchEvent(
new CustomEvent("aksrt:posts:rendered", {
bubbles: true,
detail: { container: grid, replace: !!replace },
})
);
} catch (e1) {}
})
.catch(function () {
state.loading = false;
showLoader(false);
updateLoadMore(state.page < state.maxPages);
toast("网络错误");
});
}
function pushUrl(orderby, order, termUrl) {
try {
var u = termUrl ? new URL(termUrl, window.location.origin) : new URL(window.location.href);
if (orderby) u.searchParams.set("orderby", orderby);
else u.searchParams.delete("orderby");
u.searchParams.delete("shop_orderby");
if (orderby === "price" && order) u.searchParams.set("order", order);
else u.searchParams.delete("order");
u.searchParams.delete("paged");
window.history.replaceState({}, "", u.toString());
} catch (err) {}
}
root.addEventListener("click", function (e) {
// Category / sort links with ajax-next — intercept for REST (richer payload).
var termLink = e.target.closest("[data-aksrt-shop-term].ajax-next, a.ajax-next[data-aksrt-shop-term]");
if (termLink && root.contains(termLink)) {
e.preventDefault();
e.stopPropagation();
var tid = Number(termLink.getAttribute("data-aksrt-shop-term") || 0);
var href = termLink.getAttribute("href") || "";
state.autoLoads = 0;
if (tid > 0) state.termId = tid;
pushUrl(state.orderby, state.order, href);
var rt = termLink.getAttribute("route-title");
if (rt) {
try {
document.title = rt;
} catch (e2) {}
}
fetchArchive(1, state.orderby, state.order, true, tid);
return;
}
var sort = e.target.closest("[data-aksrt-shop-orderby]");
if (sort && root.contains(sort) && sort.tagName === "A") {
e.preventDefault();
e.stopPropagation();
var orderby = sort.getAttribute("data-aksrt-shop-orderby") || "date";
var nextOrder = sort.getAttribute("data-aksrt-shop-order") || "";
if (orderby === "price") {
if (state.orderby === "price" && nextOrder) {
state.order = nextOrder.toUpperCase();
} else if (state.orderby !== "price") {
state.order = "ASC";
} else {
state.order = state.order === "ASC" ? "DESC" : "ASC";
}
}
if (orderby === state.orderby && orderby !== "price" && state.paging === "ajax") return;
state.orderby = orderby;
state.autoLoads = 0;
pushUrl(orderby, state.order);
fetchArchive(1, orderby, state.order, true);
return;
}
var more = e.target.closest("[data-aksrt-shop-load-more]");
if (more && root.contains(more)) {
e.preventDefault();
e.stopPropagation();
if (state.page >= state.maxPages) return;
fetchArchive(state.page + 1, state.orderby, state.order, false);
}
});
if (state.paging === "ajax" && state.ias && "IntersectionObserver" in window && loadMoreBtn) {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
if (state.loading || state.page >= state.maxPages) return;
if (state.iasMax > 0 && state.autoLoads >= state.iasMax) return;
state.autoLoads += 1;
fetchArchive(state.page + 1, state.orderby, state.order, false);
});
},
{ rootMargin: "240px" }
);
io.observe(loadMoreBtn);
}
})();