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.
This commit is contained in:
@@ -733,3 +733,23 @@
|
||||
.aksrt-revenue-page__item .is-in{color:#27ae60;font-weight:700}
|
||||
.aksrt-revenue-page__item .is-out{color:#e74c3c;font-weight:700}
|
||||
.aksrt-product-editor__hint{font-size:13px;color:var(--color-ink-4,#8a909c);margin:0 0 10px}
|
||||
|
||||
/* ===== zibll-parity cat filter / skeleton ===== */
|
||||
.aksrt-shop-cat-filter.shop-cat-filter{margin-top:14px}
|
||||
.shop-cat-filter-tab{display:flex;gap:10px;overflow-x:auto;list-style:none;margin:0 0 10px;padding:0 0 4px;-webkit-overflow-scrolling:touch}
|
||||
.shop-cat-filter-tab li{flex:0 0 auto}
|
||||
.shop-cat-filter-tab a,
|
||||
.shop-cat-filter-tab span,
|
||||
.shop-cat-filter-child a,
|
||||
.shop-cat-filter-child span{display:inline-flex;align-items:center;padding:6px 12px;border-radius:999px;font-size:13px;font-weight:600;text-decoration:none;color:var(--color-ink-2,#5c6370);background:var(--surface-4,#f3f4f6)}
|
||||
.shop-cat-filter-tab .active span,
|
||||
.shop-cat-filter-child .focus-color,
|
||||
.shop-cat-filter-orderby .focus-color{color:var(--color-primary,#f04494);background:color-mix(in srgb,var(--color-primary,#f04494) 12%, #fff)}
|
||||
.shop-cat-filter-child{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:0 0 10px}
|
||||
.shop-cat-filter-orderby{gap:8px}
|
||||
.shop-cat-filter-orderby .opacity5{opacity:.55;margin-right:4px;font-size:13px}
|
||||
.aksrt-product-archive__skeleton{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;padding:8px 0 16px}
|
||||
.aksrt-product-card--placeholder{min-height:220px;border-radius:12px;background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb 37%,#f3f4f6 63%);background-size:400% 100%;animation:aksrtShopSk 1.2s ease infinite}
|
||||
@keyframes aksrtShopSk{0%{background-position:100% 0}100%{background-position:0 0}}
|
||||
@media (max-width:992px){.aksrt-product-archive__skeleton{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
@media (max-width:560px){.aksrt-product-archive__skeleton{grid-template-columns:1fr}}
|
||||
|
||||
+184
-41
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* Product archive — sort + AJAX load more.
|
||||
* Product archive — cat filter + sort + AJAX load more (zibll ajax-next UX).
|
||||
*
|
||||
* Uses REST /shop/frontend/products/archive and optional AksrtAjaxList events.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -10,7 +12,7 @@
|
||||
if (!root) return;
|
||||
|
||||
function restBase() {
|
||||
var site = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
|
||||
var site = window.aksrtData && aksrtData.siteUrl ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
|
||||
return site + "/wp-json/aksrt/v1";
|
||||
}
|
||||
|
||||
@@ -23,12 +25,17 @@
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -38,115 +45,251 @@
|
||||
autoLoads: 0,
|
||||
};
|
||||
|
||||
function fetchArchive(page, orderby, replace) {
|
||||
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 = "加载中...";
|
||||
loadMoreBtn.textContent = "加载中…";
|
||||
}
|
||||
|
||||
var qs = new URLSearchParams();
|
||||
qs.set("taxonomy", state.taxonomy);
|
||||
qs.set("term_id", String(state.termId));
|
||||
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 (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (result) {
|
||||
state.loading = false;
|
||||
showLoader(false);
|
||||
if (!result || !result.success || !result.data) {
|
||||
toast((result && result.message) || "加载失败");
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.disabled = false;
|
||||
loadMoreBtn.textContent = "加载更多";
|
||||
}
|
||||
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 = data.html || "";
|
||||
} else if (data.html) {
|
||||
grid.insertAdjacentHTML("beforeend", data.html);
|
||||
grid.innerHTML = html;
|
||||
} else if (html) {
|
||||
grid.insertAdjacentHTML("beforeend", html);
|
||||
}
|
||||
}
|
||||
|
||||
root.querySelectorAll("[data-aksrt-shop-orderby]").forEach(function (a) {
|
||||
a.classList.toggle("is-active", a.getAttribute("data-aksrt-shop-orderby") === state.orderby);
|
||||
});
|
||||
|
||||
if (loadMoreBtn) {
|
||||
var hasMore = state.page < state.maxPages;
|
||||
loadMoreBtn.style.display = hasMore ? "" : "none";
|
||||
loadMoreBtn.disabled = false;
|
||||
loadMoreBtn.textContent = "加载更多";
|
||||
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;
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.disabled = false;
|
||||
loadMoreBtn.textContent = "加载更多";
|
||||
}
|
||||
showLoader(false);
|
||||
updateLoadMore(state.page < state.maxPages);
|
||||
toast("网络错误");
|
||||
});
|
||||
}
|
||||
|
||||
root.addEventListener("click", function (e) {
|
||||
var sort = e.target.closest("[data-aksrt-shop-orderby]");
|
||||
if (sort && root.contains(sort)) {
|
||||
e.preventDefault();
|
||||
var orderby = sort.getAttribute("data-aksrt-shop-orderby") || "date";
|
||||
if (orderby === state.orderby && state.paging === "ajax") return;
|
||||
state.orderby = orderby;
|
||||
state.autoLoads = 0;
|
||||
function pushUrl(orderby, order, termUrl) {
|
||||
try {
|
||||
var u = new URL(window.location.href);
|
||||
u.searchParams.set("shop_orderby", orderby);
|
||||
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) {}
|
||||
fetchArchive(1, orderby, true);
|
||||
}
|
||||
|
||||
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, false);
|
||||
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) {
|
||||
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, false);
|
||||
fetchArchive(state.page + 1, state.orderby, state.order, false);
|
||||
});
|
||||
}, { rootMargin: "240px" });
|
||||
},
|
||||
{ rootMargin: "240px" }
|
||||
);
|
||||
io.observe(loadMoreBtn);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -363,75 +363,92 @@ trait ShopControllerFrontendFlowOperations {
|
||||
$term_id = (int) ($request->get_param('term_id') ?? 0);
|
||||
$page = max(1, (int) ($request->get_param('page') ?? 1));
|
||||
$orderby = sanitize_key((string) ($request->get_param('orderby') ?? ''));
|
||||
$order = sanitize_key((string) ($request->get_param('order') ?? ''));
|
||||
|
||||
$view = \AKSRT\Shop\Shop::get_instance()->get_product_archive_view([
|
||||
'taxonomy' => $taxonomy,
|
||||
'term_id' => $term_id,
|
||||
'paged' => $page,
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
]);
|
||||
|
||||
$sales_cfg = is_array($view['sales'] ?? null) ? $view['sales'] : [];
|
||||
$html = '';
|
||||
if (!empty($view['products']) && is_array($view['products'])) {
|
||||
ob_start();
|
||||
foreach ($view['products'] as $product) {
|
||||
$this->render_product_card_html($product, $view);
|
||||
if (function_exists('get_template_part')) {
|
||||
get_template_part('templates/parts/shop/product', 'card', [
|
||||
'product' => $product,
|
||||
'sales_cfg' => $sales_cfg,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$html = (string) ob_get_clean();
|
||||
}
|
||||
|
||||
$empty_html = '';
|
||||
if ($html === '' && function_exists('aksrt_get_ajax_null')) {
|
||||
$empty_html = aksrt_get_ajax_null(__('内容空空如也', 'aksrt'));
|
||||
} elseif ($html === '') {
|
||||
$empty_html = '<div class="aksrt-product-archive__empty ajax-item"><p>' . esc_html__('内容空空如也', 'aksrt') . '</p></div>';
|
||||
}
|
||||
|
||||
// Header HTML for cat filter replace (when filter present).
|
||||
$header_html = '';
|
||||
if (!empty($view['filter']) && is_array($view['filter'])) {
|
||||
ob_start();
|
||||
$this->render_product_cat_filter_html($view);
|
||||
$header_html = (string) ob_get_clean();
|
||||
}
|
||||
|
||||
$paginate_html = '';
|
||||
$page_n = (int) ($view['page'] ?? 1);
|
||||
$max_pages = (int) ($view['max_pages'] ?? 1);
|
||||
$has_more = $page_n < $max_pages;
|
||||
if ($has_more && (string) ($view['paging'] ?? '') === 'ajax') {
|
||||
$next_url = add_query_arg([
|
||||
'taxonomy' => $taxonomy,
|
||||
'term_id' => $term_id,
|
||||
'page' => $page_n + 1,
|
||||
'orderby' => (string) ($view['orderby'] ?? 'date'),
|
||||
'order' => (string) ($view['order'] ?? ''),
|
||||
], rest_url('aksrt/v1/shop/frontend/products/archive'));
|
||||
if (function_exists('aksrt_get_ajax_next_paginate')) {
|
||||
$paginate_html = aksrt_get_ajax_next_paginate(
|
||||
(int) ($view['total'] ?? 0),
|
||||
$page_n,
|
||||
max(1, (int) ($view['per_page'] ?? 12)),
|
||||
$next_url
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => array_merge($view, [
|
||||
'html' => $html,
|
||||
'has_more' => (int) ($view['page'] ?? 1) < (int) ($view['max_pages'] ?? 1),
|
||||
'html' => $html !== '' ? $html : $empty_html,
|
||||
'empty_html' => $empty_html,
|
||||
'header_html' => $header_html,
|
||||
'paginate_html' => $paginate_html,
|
||||
'has_more' => $has_more,
|
||||
'title' => !empty($view['term']['name'])
|
||||
? (string) $view['term']['name'] . ' - ' . wp_strip_all_tags(get_bloginfo('name'))
|
||||
: '',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
private function render_product_card_html(array $product, array $view = []): void {
|
||||
$sales_cfg = is_array($view['sales'] ?? null) ? $view['sales'] : [];
|
||||
$mode = (string) ($sales_cfg['show'] ?? 'show');
|
||||
$min_sales = max(0, (int) ($sales_cfg['show_min'] ?? 0));
|
||||
$sales = (int) ($product['sales'] ?? 0);
|
||||
$can_show_sales = false;
|
||||
if ($mode === 'show') {
|
||||
$can_show_sales = $sales > 0;
|
||||
} elseif ($mode === 'threshold') {
|
||||
$can_show_sales = $sales >= $min_sales;
|
||||
}
|
||||
|
||||
$permalink = (string) ($product['permalink'] ?? '#');
|
||||
$title = (string) ($product['title'] ?? '');
|
||||
$image = (string) ($product['image'] ?? '');
|
||||
$price = (float) ($product['price'] ?? 0);
|
||||
$excerpt = (string) ($product['excerpt'] ?? '');
|
||||
?>
|
||||
<article class="aksrt-product-card">
|
||||
<a href="<?php echo esc_url($permalink); ?>" class="aksrt-product-card__link">
|
||||
<?php if ($image !== '') : ?>
|
||||
<div class="aksrt-product-card__image">
|
||||
<img src="<?php echo esc_url($image); ?>" alt="<?php echo esc_attr($title); ?>" loading="lazy">
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-product-card__image aksrt-product-card__image--placeholder">
|
||||
<i class="fas fa-image"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-product-card__body">
|
||||
<h3 class="aksrt-product-card__title"><?php echo esc_html($title); ?></h3>
|
||||
<?php if ($excerpt !== '') : ?>
|
||||
<p class="aksrt-product-card__excerpt"><?php echo esc_html(wp_trim_words($excerpt, 15)); ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-product-card__footer">
|
||||
<span class="aksrt-product-card__price">¥<?php echo esc_html(number_format($price, 2)); ?></span>
|
||||
<?php if ($can_show_sales) : ?>
|
||||
<span class="aksrt-product-card__sales"><?php printf(esc_html__('已售 %d', 'aksrt'), $sales); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
<?php
|
||||
/**
|
||||
* Render cat filter bar HTML for AJAX header replace.
|
||||
*/
|
||||
private function render_product_cat_filter_html(array $view): void {
|
||||
$filter = is_array($view['filter'] ?? null) ? $view['filter'] : [];
|
||||
$orderby = (string) ($view['orderby'] ?? ($filter['orderby'] ?? 'date'));
|
||||
$order = strtoupper((string) ($view['order'] ?? ($filter['order'] ?? 'DESC')));
|
||||
$term = is_array($view['term'] ?? null) ? $view['term'] : [];
|
||||
$current_id = (int) ($filter['current_id'] ?? ($term['id'] ?? 0));
|
||||
include get_template_directory() . '/templates/parts/shop/product-cat-filter.php';
|
||||
}
|
||||
|
||||
public function get_frontend_revenue(\WP_REST_Request $request): \WP_REST_Response {
|
||||
|
||||
+163
-10
@@ -57,10 +57,39 @@ trait Part1 {
|
||||
$shop_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_shop_settings() : [];
|
||||
$list = is_array($shop_settings['list'] ?? null) ? $shop_settings['list'] : [];
|
||||
$per_page = max(4, min(100, (int) ($args['per_page'] ?? ($list['count'] ?? 12))));
|
||||
$orderby = sanitize_key((string) ($args['orderby'] ?? ($list['orderby'] ?? 'date')));
|
||||
if (!in_array($orderby, ['date', 'sales', 'price_asc', 'price_desc', 'comments', 'modified'], true)) {
|
||||
$orderby = 'date';
|
||||
|
||||
// Normalize orderby aliases (zibll + legacy AKSRT keys).
|
||||
$orderby_raw = sanitize_key((string) ($args['orderby'] ?? ($list['orderby'] ?? 'date')));
|
||||
$order = strtoupper(sanitize_key((string) ($args['order'] ?? '')));
|
||||
if (!in_array($order, ['ASC', 'DESC'], true)) {
|
||||
$order = '';
|
||||
}
|
||||
// Legacy split price keys → price + order.
|
||||
if ($orderby_raw === 'price_asc') {
|
||||
$orderby_raw = 'price';
|
||||
$order = 'ASC';
|
||||
} elseif ($orderby_raw === 'price_desc') {
|
||||
$orderby_raw = 'price';
|
||||
$order = 'DESC';
|
||||
} elseif ($orderby_raw === 'sales_volume') {
|
||||
$orderby_raw = 'sales';
|
||||
} elseif ($orderby_raw === 'zibpay_price') {
|
||||
$orderby_raw = 'price';
|
||||
} elseif ($orderby_raw === 'comments') {
|
||||
$orderby_raw = 'score';
|
||||
}
|
||||
$allowed_orderby = ['date', 'views', 'favorite_count', 'score', 'sales', 'price', 'modified'];
|
||||
if (!in_array($orderby_raw, $allowed_orderby, true)) {
|
||||
$orderby_raw = 'date';
|
||||
}
|
||||
if ($orderby_raw === 'price' && $order === '') {
|
||||
$order = 'ASC';
|
||||
}
|
||||
if ($order === '' && $orderby_raw !== 'price') {
|
||||
$order = 'DESC';
|
||||
}
|
||||
$orderby = $orderby_raw;
|
||||
|
||||
$paging = sanitize_key((string) ($args['paging'] ?? ($list['paging'] ?? 'number')));
|
||||
if (!in_array($paging, ['number', 'ajax'], true)) {
|
||||
$paging = 'number';
|
||||
@@ -76,12 +105,14 @@ trait Part1 {
|
||||
'max_pages' => 0,
|
||||
'page' => $paged,
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
'paging' => $paging,
|
||||
'ias_s' => $ias_s,
|
||||
'ias_max' => $ias_max,
|
||||
'per_page' => $per_page,
|
||||
'style' => $style,
|
||||
'sales' => is_array($shop_settings['sales'] ?? null) ? $shop_settings['sales'] : [],
|
||||
'filter' => null,
|
||||
];
|
||||
|
||||
if (!in_array($taxonomy, ['aksrt_product_cat', 'aksrt_product_tag'], true) && empty($args['allow_all'])) {
|
||||
@@ -93,7 +124,7 @@ trait Part1 {
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $paged,
|
||||
'post_status' => 'publish',
|
||||
'order' => 'DESC',
|
||||
'order' => $order ?: 'DESC',
|
||||
'orderby' => 'date',
|
||||
];
|
||||
|
||||
@@ -114,16 +145,21 @@ trait Part1 {
|
||||
$query_args['meta_key'] = '_aksrt_sales';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ($orderby === 'price_asc') {
|
||||
} elseif ($orderby === 'price') {
|
||||
$query_args['meta_key'] = '_aksrt_price';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'ASC';
|
||||
} elseif ($orderby === 'price_desc') {
|
||||
$query_args['meta_key'] = '_aksrt_price';
|
||||
$query_args['order'] = $order === 'ASC' ? 'ASC' : 'DESC';
|
||||
} elseif ($orderby === 'favorite_count') {
|
||||
$query_args['meta_key'] = '_aksrt_favorite_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ($orderby === 'comments') {
|
||||
$query_args['orderby'] = 'comment_count';
|
||||
} elseif ($orderby === 'views') {
|
||||
$query_args['meta_key'] = '_aksrt_views';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ($orderby === 'score') {
|
||||
$query_args['meta_key'] = '_aksrt_score_average';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ($orderby === 'modified') {
|
||||
$query_args['orderby'] = 'modified';
|
||||
@@ -138,6 +174,11 @@ trait Part1 {
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
$filter = null;
|
||||
if ($taxonomy === 'aksrt_product_cat' && $term) {
|
||||
$filter = $this->build_product_cat_filter((int) $term->term_id, $orderby, $order);
|
||||
}
|
||||
|
||||
return [
|
||||
'products' => $products,
|
||||
'term' => $term ? [
|
||||
@@ -146,17 +187,129 @@ trait Part1 {
|
||||
'description' => (string) $term->description,
|
||||
'count' => (int) $term->count,
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => (int) $term->parent,
|
||||
'url' => get_term_link($term),
|
||||
] : null,
|
||||
'total' => (int) $query->found_posts,
|
||||
'max_pages' => (int) $query->max_num_pages,
|
||||
'page' => $paged,
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
'paging' => $paging,
|
||||
'ias_s' => $ias_s,
|
||||
'ias_max' => $ias_max,
|
||||
'per_page' => $per_page,
|
||||
'style' => $style,
|
||||
'sales' => is_array($shop_settings['sales'] ?? null) ? $shop_settings['sales'] : [],
|
||||
'filter' => $filter,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical cat filter data (zibll zib_shop_get_cat_filter).
|
||||
*
|
||||
* @return array{siblings:array<int,array>,children:array<int,array>,parent_id:int,mode:string}
|
||||
*/
|
||||
public function build_product_cat_filter(int $cat_id, string $orderby = 'date', string $order = 'DESC'): array {
|
||||
$taxonomy = 'aksrt_product_cat';
|
||||
$cat = get_term($cat_id, $taxonomy);
|
||||
if (!$cat || is_wp_error($cat)) {
|
||||
return ['siblings' => [], 'children' => [], 'parent_id' => 0, 'mode' => 'flat', 'orderby' => $orderby, 'order' => $order];
|
||||
}
|
||||
|
||||
$map_term = static function ($t) use ($orderby, $order): array {
|
||||
$url = get_term_link($t);
|
||||
if (!is_wp_error($url) && $orderby) {
|
||||
$url = add_query_arg(array_filter([
|
||||
'orderby' => $orderby,
|
||||
'order' => $orderby === 'price' ? $order : null,
|
||||
]), $url);
|
||||
}
|
||||
return [
|
||||
'id' => (int) $t->term_id,
|
||||
'name' => (string) $t->name,
|
||||
'url' => is_wp_error($url) ? '' : (string) $url,
|
||||
'count' => (int) $t->count,
|
||||
];
|
||||
};
|
||||
|
||||
$parent_id = (int) $cat->parent;
|
||||
$children = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => $cat_id,
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
if (is_wp_error($children)) {
|
||||
$children = [];
|
||||
}
|
||||
|
||||
if ($parent_id > 0) {
|
||||
$siblings = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => $parent_id,
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
} else {
|
||||
$siblings = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => 0,
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
}
|
||||
if (is_wp_error($siblings)) {
|
||||
$siblings = [];
|
||||
}
|
||||
|
||||
$mode = 'siblings';
|
||||
$parent_terms = [];
|
||||
if (empty($children) && $parent_id > 0) {
|
||||
$parent_term = get_term($parent_id, $taxonomy);
|
||||
if ($parent_term && !is_wp_error($parent_term) && (int) $parent_term->parent > 0) {
|
||||
$parent_terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => (int) $parent_term->parent,
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
if (is_wp_error($parent_terms)) {
|
||||
$parent_terms = [];
|
||||
}
|
||||
$mode = 'parent_siblings';
|
||||
} elseif ($parent_id > 0) {
|
||||
$parent_terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'parent' => 0,
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
if (is_wp_error($parent_terms)) {
|
||||
$parent_terms = [];
|
||||
}
|
||||
$mode = 'parent_siblings';
|
||||
}
|
||||
} elseif (!empty($children)) {
|
||||
$mode = 'with_children';
|
||||
}
|
||||
|
||||
$parent_url = '';
|
||||
if ($parent_id > 0) {
|
||||
$plink = get_term_link($parent_id, $taxonomy);
|
||||
if (!is_wp_error($plink)) {
|
||||
$parent_url = add_query_arg(array_filter([
|
||||
'orderby' => $orderby,
|
||||
'order' => $orderby === 'price' ? $order : null,
|
||||
]), $plink);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'mode' => $mode,
|
||||
'current_id' => $cat_id,
|
||||
'parent_id' => $parent_id,
|
||||
'parent_url' => $parent_url,
|
||||
'siblings' => array_map($map_term, empty($parent_terms) ? $siblings : $parent_terms),
|
||||
'children' => array_map($map_term, $mode === 'with_children' ? $children : ($mode === 'parent_siblings' ? $siblings : [])),
|
||||
'child_tabs' => array_map($map_term, $children),
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@ class ProductReview {
|
||||
update_comment_meta($comment_id, '_aksrt_review_images', $images);
|
||||
}
|
||||
|
||||
$this->refresh_product_scores($product_id);
|
||||
$scores = $this->refresh_product_scores($product_id);
|
||||
update_post_meta($product_id, '_aksrt_score_average', (float) ($scores['average'] ?? $average));
|
||||
|
||||
if (class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
$order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number);
|
||||
|
||||
@@ -34,7 +34,7 @@ $image = (string) ($product['image'] ?? '');
|
||||
$excerpt = (string) ($product['excerpt'] ?? '');
|
||||
$price = (float) ($product['price'] ?? 0);
|
||||
?>
|
||||
<article class="aksrt-product-card">
|
||||
<article class="aksrt-product-card product-item posts-item card ajax-item">
|
||||
<a href="<?php echo esc_url($permalink !== '' ? $permalink : '#'); ?>" class="aksrt-product-card__link">
|
||||
<?php if ($image !== '') : ?>
|
||||
<div class="aksrt-product-card__image">
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Product category hierarchical filter bar (zibll shop-cat-filter parity).
|
||||
*
|
||||
* Expects in scope:
|
||||
* - $filter array from Shop::build_product_cat_filter()
|
||||
* - $orderby string
|
||||
* - $order string ASC|DESC
|
||||
* - $term array|null
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$filter = is_array($filter ?? null) ? $filter : [];
|
||||
$orderby = sanitize_key((string) ($orderby ?? ($filter['orderby'] ?? 'date')));
|
||||
$order = strtoupper(sanitize_key((string) ($order ?? ($filter['order'] ?? 'DESC'))));
|
||||
if (!in_array($order, ['ASC', 'DESC'], true)) {
|
||||
$order = 'DESC';
|
||||
}
|
||||
$current_id = (int) ($filter['current_id'] ?? 0);
|
||||
$mode = (string) ($filter['mode'] ?? 'siblings');
|
||||
$siblings = is_array($filter['siblings'] ?? null) ? $filter['siblings'] : [];
|
||||
$children = is_array($filter['children'] ?? null) ? $filter['children'] : [];
|
||||
$child_tabs = is_array($filter['child_tabs'] ?? null) ? $filter['child_tabs'] : [];
|
||||
$parent_url = (string) ($filter['parent_url'] ?? '');
|
||||
$parent_id = (int) ($filter['parent_id'] ?? 0);
|
||||
$term_name = is_array($term ?? null) ? (string) ($term['name'] ?? '') : '';
|
||||
|
||||
$order_labels = [
|
||||
'date' => __('最新', 'aksrt'),
|
||||
'views' => __('最热', 'aksrt'),
|
||||
'favorite_count' => __('收藏', 'aksrt'),
|
||||
'score' => __('评分', 'aksrt'),
|
||||
'sales' => __('销量', 'aksrt'),
|
||||
];
|
||||
|
||||
$base_term_url = !empty($term['url']) ? (string) $term['url'] : '';
|
||||
if ($base_term_url === '' && $current_id > 0) {
|
||||
$link = get_term_link($current_id, 'aksrt_product_cat');
|
||||
$base_term_url = is_wp_error($link) ? '' : (string) $link;
|
||||
}
|
||||
|
||||
$blog = wp_strip_all_tags(get_bloginfo('name'));
|
||||
?>
|
||||
<div class="shop-cat-filter aksrt-shop-cat-filter zib-widget relative" data-aksrt-shop-cat-filter win-ajax-replace="shop-cat-filter">
|
||||
<?php if (!empty($siblings)) : ?>
|
||||
<div class="shop-cat-filter-tab list-inline scroll-x mini-scrollbar tab-nav-theme" data-aksrt-cat-tabs>
|
||||
<?php foreach ($siblings as $sib) :
|
||||
$sid = (int) ($sib['id'] ?? 0);
|
||||
$is_active = ($mode === 'with_children' && $sid === $current_id)
|
||||
|| ($mode === 'parent_siblings' && $sid === $parent_id)
|
||||
|| ($mode === 'siblings' && $sid === $current_id);
|
||||
?>
|
||||
<?php if ($is_active) : ?>
|
||||
<li class="active"><span class="focus-color"><?php echo esc_html((string) ($sib['name'] ?? '')); ?></span></li>
|
||||
<?php else : ?>
|
||||
<li>
|
||||
<a class="ajax-next"
|
||||
ajax-replace="true"
|
||||
route="1"
|
||||
route-title="<?php echo esc_attr((string) ($sib['name'] ?? '') . ' - ' . __('商城', 'aksrt') . ($blog ? ' · ' . $blog : '')); ?>"
|
||||
href="<?php echo esc_url((string) ($sib['url'] ?? '#')); ?>"
|
||||
data-aksrt-shop-term="<?php echo esc_attr((string) $sid); ?>">
|
||||
<?php echo esc_html((string) ($sib['name'] ?? '')); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($mode === 'with_children' && !empty($child_tabs)) : ?>
|
||||
<div class="shop-cat-filter-child" data-aksrt-cat-children>
|
||||
<span class="focus-color"><?php esc_html_e('全部', 'aksrt'); ?></span>
|
||||
<?php foreach ($child_tabs as $child) : ?>
|
||||
<a class="ajax-next"
|
||||
ajax-replace="true"
|
||||
route="1"
|
||||
route-title="<?php echo esc_attr((string) ($child['name'] ?? '') . ' - ' . __('商城', 'aksrt')); ?>"
|
||||
href="<?php echo esc_url((string) ($child['url'] ?? '#')); ?>"
|
||||
data-aksrt-shop-term="<?php echo esc_attr((string) ((int) ($child['id'] ?? 0))); ?>">
|
||||
<?php echo esc_html((string) ($child['name'] ?? '')); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php elseif ($mode === 'parent_siblings' && !empty($children)) : ?>
|
||||
<div class="shop-cat-filter-child" data-aksrt-cat-children>
|
||||
<?php if ($parent_url !== '') : ?>
|
||||
<a class="ajax-next" ajax-replace="true" route="1" href="<?php echo esc_url($parent_url); ?>" data-aksrt-shop-term="<?php echo esc_attr((string) $parent_id); ?>">
|
||||
<?php esc_html_e('全部', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($children as $child) :
|
||||
$cid = (int) ($child['id'] ?? 0);
|
||||
?>
|
||||
<?php if ($cid === $current_id) : ?>
|
||||
<span class="focus-color"><?php echo esc_html((string) ($child['name'] ?? '')); ?></span>
|
||||
<?php else : ?>
|
||||
<a class="ajax-next"
|
||||
ajax-replace="true"
|
||||
route="1"
|
||||
route-title="<?php echo esc_attr((string) ($child['name'] ?? '') . ' - ' . __('商城', 'aksrt')); ?>"
|
||||
href="<?php echo esc_url((string) ($child['url'] ?? '#')); ?>"
|
||||
data-aksrt-shop-term="<?php echo esc_attr((string) $cid); ?>">
|
||||
<?php echo esc_html((string) ($child['name'] ?? '')); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="shop-cat-filter-orderby shop-cat-filter-child" win-ajax-replace="orderby" data-aksrt-shop-orderby-bar>
|
||||
<span class="opacity5"><?php esc_html_e('排序', 'aksrt'); ?></span>
|
||||
<?php foreach ($order_labels as $key => $label) :
|
||||
if ($orderby === $key) : ?>
|
||||
<span class="focus-color"><?php echo esc_html($label); ?></span>
|
||||
<?php else :
|
||||
$href = $base_term_url !== '' ? add_query_arg(['orderby' => $key], $base_term_url) : add_query_arg(['orderby' => $key]);
|
||||
?>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="<?php echo esc_attr($key); ?>">
|
||||
<?php echo esc_html($label); ?>
|
||||
</a>
|
||||
<?php endif;
|
||||
endforeach;
|
||||
|
||||
// Price toggle (single 售价 control).
|
||||
if ($orderby === 'price') {
|
||||
$next_order = $order === 'DESC' ? 'ASC' : 'DESC';
|
||||
$icon = $next_order === 'DESC' ? 'up' : 'down';
|
||||
$href = $base_term_url !== ''
|
||||
? add_query_arg(['orderby' => 'price', 'order' => $next_order], $base_term_url)
|
||||
: add_query_arg(['orderby' => 'price', 'order' => $next_order]);
|
||||
?>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next focus-color" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="price" data-aksrt-shop-order="<?php echo esc_attr($next_order); ?>">
|
||||
<?php esc_html_e('售价', 'aksrt'); ?><i class="opacity5 ml3 fa fa-long-arrow-<?php echo esc_attr($icon); ?>"></i>
|
||||
</a>
|
||||
<?php } else {
|
||||
$href = $base_term_url !== ''
|
||||
? add_query_arg(['orderby' => 'price'], $base_term_url)
|
||||
: add_query_arg(['orderby' => 'price']);
|
||||
?>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="price">
|
||||
<?php esc_html_e('售价', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* 商品分类归档模板
|
||||
* 商品分类归档模板 — zibll shop cat filter + ajax list 1:1 UX
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -11,14 +11,25 @@ get_header();
|
||||
|
||||
$term = get_queried_object();
|
||||
$paged = max(1, (int) get_query_var('paged'));
|
||||
$orderby = isset($_GET['shop_orderby']) ? sanitize_key(wp_unslash((string) $_GET['shop_orderby'])) : '';
|
||||
$orderby = '';
|
||||
$order = '';
|
||||
if (isset($_GET['orderby'])) {
|
||||
$orderby = sanitize_key(wp_unslash((string) $_GET['orderby']));
|
||||
} elseif (isset($_GET['shop_orderby'])) {
|
||||
$orderby = sanitize_key(wp_unslash((string) $_GET['shop_orderby']));
|
||||
}
|
||||
if (isset($_GET['order'])) {
|
||||
$order = sanitize_key(wp_unslash((string) $_GET['order']));
|
||||
}
|
||||
|
||||
$shop = class_exists('\AKSRT\Shop\Shop') ? \AKSRT\Shop\Shop::get_instance() : null;
|
||||
$view = $shop ? $shop->get_product_archive_view([
|
||||
'taxonomy' => 'aksrt_product_cat',
|
||||
'term_id' => $term ? (int) $term->term_id : 0,
|
||||
'paged' => $paged,
|
||||
'orderby' => $orderby,
|
||||
]) : ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0, 'orderby' => 'date', 'paging' => 'number'];
|
||||
'order' => $order,
|
||||
]) : ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0, 'orderby' => 'date', 'order' => 'DESC', 'paging' => 'number'];
|
||||
|
||||
$products = $view['products'] ?? [];
|
||||
$total = (int) ($view['total'] ?? 0);
|
||||
@@ -26,19 +37,13 @@ $max_pages = (int) ($view['max_pages'] ?? 0);
|
||||
$term_name = $term->name ?? '';
|
||||
$term_desc = $term->description ?? '';
|
||||
$active_orderby = (string) ($view['orderby'] ?? 'date');
|
||||
$active_order = strtoupper((string) ($view['order'] ?? 'DESC'));
|
||||
$paging = (string) ($view['paging'] ?? 'number');
|
||||
$ias_s = !empty($view['ias_s']);
|
||||
$ias_max = (int) ($view['ias_max'] ?? 5);
|
||||
$style = (string) ($view['style'] ?? 'default');
|
||||
$sales_cfg = is_array($view['sales'] ?? null) ? $view['sales'] : [];
|
||||
|
||||
$order_options = [
|
||||
'date' => __('最新', 'aksrt'),
|
||||
'sales' => __('销量', 'aksrt'),
|
||||
'price_asc' => __('价格升序', 'aksrt'),
|
||||
'price_desc' => __('价格降序', 'aksrt'),
|
||||
'comments' => __('评价', 'aksrt'),
|
||||
];
|
||||
$filter = is_array($view['filter'] ?? null) ? $view['filter'] : null;
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main content-shell" tabindex="-1">
|
||||
@@ -49,6 +54,7 @@ $order_options = [
|
||||
data-taxonomy="aksrt_product_cat"
|
||||
data-term-id="<?php echo esc_attr((string) ($term->term_id ?? 0)); ?>"
|
||||
data-orderby="<?php echo esc_attr($active_orderby); ?>"
|
||||
data-order="<?php echo esc_attr($active_order); ?>"
|
||||
data-page="<?php echo esc_attr((string) $paged); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
|
||||
data-paging="<?php echo esc_attr($paging); ?>"
|
||||
@@ -56,43 +62,58 @@ $order_options = [
|
||||
data-ias-max="<?php echo esc_attr((string) $ias_max); ?>"
|
||||
data-style="<?php echo esc_attr($style); ?>">
|
||||
|
||||
<header class="aksrt-product-archive__header">
|
||||
<header class="aksrt-product-archive__header shop-term-header ajax-item-header" data-aksrt-ajax-header>
|
||||
<div class="aksrt-product-archive__eyebrow"><?php esc_html_e('商城商品', 'aksrt'); ?></div>
|
||||
<h1 class="aksrt-product-archive__title"><?php echo esc_html($term_name); ?></h1>
|
||||
<h1 class="aksrt-product-archive__title" data-aksrt-archive-title><?php echo esc_html($term_name); ?></h1>
|
||||
<?php if ($term_desc) : ?>
|
||||
<p class="aksrt-product-archive__desc"><?php echo esc_html($term_desc); ?></p>
|
||||
<p class="aksrt-product-archive__desc" data-aksrt-archive-desc><?php echo esc_html($term_desc); ?></p>
|
||||
<?php endif; ?>
|
||||
<p class="aksrt-product-archive__count" data-aksrt-archive-count><?php printf(esc_html__('共 %d 件商品', 'aksrt'), $total); ?></p>
|
||||
|
||||
<?php
|
||||
if ($filter) {
|
||||
$orderby = $active_orderby;
|
||||
$order = $active_order;
|
||||
$term = $view['term'] ?? null;
|
||||
include get_template_directory() . '/templates/parts/shop/product-cat-filter.php';
|
||||
}
|
||||
?>
|
||||
</header>
|
||||
|
||||
<nav class="aksrt-product-archive__toolbar" aria-label="<?php esc_attr_e('商品排序', 'aksrt'); ?>">
|
||||
<?php foreach ($order_options as $key => $label) :
|
||||
$url = add_query_arg(['shop_orderby' => $key]);
|
||||
?>
|
||||
<a class="aksrt-product-archive__sort<?php echo $active_orderby === $key ? ' is-active' : ''; ?>"
|
||||
href="<?php echo esc_url($url); ?>"
|
||||
data-aksrt-shop-orderby="<?php echo esc_attr($key); ?>">
|
||||
<?php echo esc_html($label); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="ajaxpager product-lists-row aksrt-product-archive__ajaxpager"
|
||||
data-aksrt-ajaxpager
|
||||
data-aksrt-ajax-clear="1">
|
||||
<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden>
|
||||
<div class="aksrt-product-archive__skeleton">
|
||||
<?php for ($i = 0; $i < 4; $i++) : ?>
|
||||
<div class="aksrt-product-card aksrt-product-card--placeholder placeholder"></div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($products)) : ?>
|
||||
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--<?php echo esc_attr($style); ?>" id="aksrt-product-grid" data-aksrt-product-grid>
|
||||
<?php if (!empty($products)) : ?>
|
||||
<?php foreach ($products as $product) :
|
||||
get_template_part('templates/parts/shop/product', 'card', [
|
||||
'product' => $product,
|
||||
'sales_cfg' => $sales_cfg,
|
||||
]);
|
||||
endforeach; ?>
|
||||
<?php else : ?>
|
||||
<?php echo function_exists('aksrt_get_ajax_null') ? aksrt_get_ajax_null(__('内容空空如也', 'aksrt')) : '<div class="aksrt-product-archive__empty ajax-item"><p>' . esc_html__('内容空空如也', 'aksrt') . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($max_pages > 1) : ?>
|
||||
<?php if ($paging === 'ajax') : ?>
|
||||
<div class="aksrt-product-archive__load-more">
|
||||
<button type="button" class="aksrt-load-more-btn" data-aksrt-shop-load-more><?php esc_html_e('加载更多', 'aksrt'); ?></button>
|
||||
<?php if ($max_pages > 1 && $paging === 'ajax') : ?>
|
||||
<div class="ajax-pag text-center theme-pagination">
|
||||
<div class="next-page ajax-next">
|
||||
<a href="#"
|
||||
class="ajax-next aksrt-load-more-btn"
|
||||
data-aksrt-shop-load-more
|
||||
data-aksrt-ajax-next><?php esc_html_e('加载更多', 'aksrt'); ?></a>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
</div>
|
||||
<?php elseif ($max_pages > 1) : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
@@ -100,21 +121,24 @@ $order_options = [
|
||||
'current' => $paged,
|
||||
'prev_text' => '<i class="fas fa-chevron-left"></i>',
|
||||
'next_text' => '<i class="fas fa-chevron-right"></i>',
|
||||
'add_args' => $active_orderby ? ['shop_orderby' => $active_orderby] : [],
|
||||
'add_args' => array_filter([
|
||||
'orderby' => $active_orderby,
|
||||
'order' => $active_orderby === 'price' ? $active_order : null,
|
||||
]),
|
||||
]);
|
||||
?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-product-archive__empty">
|
||||
<i class="fas fa-box-open"></i>
|
||||
<p><?php esc_html_e('内容空空如也', 'aksrt'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
wp_enqueue_script('aksrt-product-archive', get_template_directory_uri() . '/assets/js/product-archive.js', ['aksrt-script'], AKSRT_VERSION, true);
|
||||
wp_enqueue_script(
|
||||
'aksrt-product-archive',
|
||||
get_template_directory_uri() . '/assets/js/product-archive.js',
|
||||
['aksrt-script', 'aksrt-core-ajax-list'],
|
||||
AKSRT_VERSION,
|
||||
true
|
||||
);
|
||||
get_footer();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* 商品标签归档模板
|
||||
* 商品标签归档模板 — sort + ajax load-more (zibll tag parity)
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -11,14 +11,25 @@ get_header();
|
||||
|
||||
$term = get_queried_object();
|
||||
$paged = max(1, (int) get_query_var('paged'));
|
||||
$orderby = isset($_GET['shop_orderby']) ? sanitize_key(wp_unslash((string) $_GET['shop_orderby'])) : '';
|
||||
$orderby = '';
|
||||
$order = '';
|
||||
if (isset($_GET['orderby'])) {
|
||||
$orderby = sanitize_key(wp_unslash((string) $_GET['orderby']));
|
||||
} elseif (isset($_GET['shop_orderby'])) {
|
||||
$orderby = sanitize_key(wp_unslash((string) $_GET['shop_orderby']));
|
||||
}
|
||||
if (isset($_GET['order'])) {
|
||||
$order = sanitize_key(wp_unslash((string) $_GET['order']));
|
||||
}
|
||||
|
||||
$shop = class_exists('\AKSRT\Shop\Shop') ? \AKSRT\Shop\Shop::get_instance() : null;
|
||||
$view = $shop ? $shop->get_product_archive_view([
|
||||
'taxonomy' => 'aksrt_product_tag',
|
||||
'term_id' => $term ? (int) $term->term_id : 0,
|
||||
'paged' => $paged,
|
||||
'orderby' => $orderby,
|
||||
]) : ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0, 'orderby' => 'date', 'paging' => 'number'];
|
||||
'order' => $order,
|
||||
]) : ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0, 'orderby' => 'date', 'order' => 'DESC', 'paging' => 'number'];
|
||||
|
||||
$products = $view['products'] ?? [];
|
||||
$total = (int) ($view['total'] ?? 0);
|
||||
@@ -26,19 +37,21 @@ $max_pages = (int) ($view['max_pages'] ?? 0);
|
||||
$term_name = $term->name ?? '';
|
||||
$term_desc = $term->description ?? '';
|
||||
$active_orderby = (string) ($view['orderby'] ?? 'date');
|
||||
$active_order = strtoupper((string) ($view['order'] ?? 'DESC'));
|
||||
$paging = (string) ($view['paging'] ?? 'number');
|
||||
$ias_s = !empty($view['ias_s']);
|
||||
$ias_max = (int) ($view['ias_max'] ?? 5);
|
||||
$style = (string) ($view['style'] ?? 'default');
|
||||
$sales_cfg = is_array($view['sales'] ?? null) ? $view['sales'] : [];
|
||||
|
||||
$order_options = [
|
||||
$order_labels = [
|
||||
'date' => __('最新', 'aksrt'),
|
||||
'views' => __('最热', 'aksrt'),
|
||||
'favorite_count' => __('收藏', 'aksrt'),
|
||||
'score' => __('评分', 'aksrt'),
|
||||
'sales' => __('销量', 'aksrt'),
|
||||
'price_asc' => __('价格升序', 'aksrt'),
|
||||
'price_desc' => __('价格降序', 'aksrt'),
|
||||
'comments' => __('评价', 'aksrt'),
|
||||
];
|
||||
$base_url = $term && !is_wp_error(get_term_link($term)) ? get_term_link($term) : '';
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main content-shell" tabindex="-1">
|
||||
@@ -49,6 +62,7 @@ $order_options = [
|
||||
data-taxonomy="aksrt_product_tag"
|
||||
data-term-id="<?php echo esc_attr((string) ($term->term_id ?? 0)); ?>"
|
||||
data-orderby="<?php echo esc_attr($active_orderby); ?>"
|
||||
data-order="<?php echo esc_attr($active_order); ?>"
|
||||
data-page="<?php echo esc_attr((string) $paged); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
|
||||
data-paging="<?php echo esc_attr($paging); ?>"
|
||||
@@ -56,43 +70,74 @@ $order_options = [
|
||||
data-ias-max="<?php echo esc_attr((string) $ias_max); ?>"
|
||||
data-style="<?php echo esc_attr($style); ?>">
|
||||
|
||||
<header class="aksrt-product-archive__header">
|
||||
<div class="aksrt-product-archive__eyebrow"><?php esc_html_e('商品标签', 'aksrt'); ?></div>
|
||||
<header class="aksrt-product-archive__header shop-term-header" data-aksrt-ajax-header>
|
||||
<div class="aksrt-product-archive__eyebrow"><?php esc_html_e('商城标签', 'aksrt'); ?></div>
|
||||
<h1 class="aksrt-product-archive__title"><?php echo esc_html($term_name); ?></h1>
|
||||
<?php if ($term_desc) : ?>
|
||||
<p class="aksrt-product-archive__desc"><?php echo esc_html($term_desc); ?></p>
|
||||
<?php endif; ?>
|
||||
<p class="aksrt-product-archive__count" data-aksrt-archive-count><?php printf(esc_html__('共 %d 件商品', 'aksrt'), $total); ?></p>
|
||||
</header>
|
||||
|
||||
<nav class="aksrt-product-archive__toolbar" aria-label="<?php esc_attr_e('商品排序', 'aksrt'); ?>">
|
||||
<?php foreach ($order_options as $key => $label) :
|
||||
$url = add_query_arg(['shop_orderby' => $key]);
|
||||
<div class="shop-cat-filter-orderby shop-cat-filter-child" win-ajax-replace="orderby" data-aksrt-shop-orderby-bar>
|
||||
<span class="opacity5"><?php esc_html_e('排序', 'aksrt'); ?></span>
|
||||
<?php foreach ($order_labels as $key => $label) :
|
||||
if ($active_orderby === $key) : ?>
|
||||
<span class="focus-color"><?php echo esc_html($label); ?></span>
|
||||
<?php else :
|
||||
$href = $base_url ? add_query_arg(['orderby' => $key], $base_url) : add_query_arg(['orderby' => $key]);
|
||||
?>
|
||||
<a class="aksrt-product-archive__sort<?php echo $active_orderby === $key ? ' is-active' : ''; ?>"
|
||||
href="<?php echo esc_url($url); ?>"
|
||||
data-aksrt-shop-orderby="<?php echo esc_attr($key); ?>">
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="<?php echo esc_attr($key); ?>">
|
||||
<?php echo esc_html($label); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<?php endif;
|
||||
endforeach;
|
||||
if ($active_orderby === 'price') {
|
||||
$next_order = $active_order === 'DESC' ? 'ASC' : 'DESC';
|
||||
$icon = $next_order === 'DESC' ? 'up' : 'down';
|
||||
$href = $base_url ? add_query_arg(['orderby' => 'price', 'order' => $next_order], $base_url) : add_query_arg(['orderby' => 'price', 'order' => $next_order]);
|
||||
?>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next focus-color" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="price" data-aksrt-shop-order="<?php echo esc_attr($next_order); ?>">
|
||||
<?php esc_html_e('售价', 'aksrt'); ?><i class="opacity5 ml3 fa fa-long-arrow-<?php echo esc_attr($icon); ?>"></i>
|
||||
</a>
|
||||
<?php } else {
|
||||
$href = $base_url ? add_query_arg(['orderby' => 'price'], $base_url) : add_query_arg(['orderby' => 'price']);
|
||||
?>
|
||||
<a rel="nofollow" ajax-replace="true" class="ajax-next" href="<?php echo esc_url($href); ?>" data-aksrt-shop-orderby="price">
|
||||
<?php esc_html_e('售价', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="ajaxpager product-lists-row aksrt-product-archive__ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-clear="1">
|
||||
<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden>
|
||||
<div class="aksrt-product-archive__skeleton">
|
||||
<?php for ($i = 0; $i < 4; $i++) : ?>
|
||||
<div class="aksrt-product-card aksrt-product-card--placeholder placeholder"></div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($products)) : ?>
|
||||
<div class="aksrt-product-archive__grid aksrt-product-archive__grid--<?php echo esc_attr($style); ?>" id="aksrt-product-grid" data-aksrt-product-grid>
|
||||
<?php if (!empty($products)) : ?>
|
||||
<?php foreach ($products as $product) :
|
||||
get_template_part('templates/parts/shop/product', 'card', [
|
||||
'product' => $product,
|
||||
'sales_cfg' => $sales_cfg,
|
||||
]);
|
||||
endforeach; ?>
|
||||
<?php else : ?>
|
||||
<?php echo function_exists('aksrt_get_ajax_null') ? aksrt_get_ajax_null(__('内容空空如也', 'aksrt')) : '<div class="aksrt-product-archive__empty ajax-item"><p>' . esc_html__('内容空空如也', 'aksrt') . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($max_pages > 1) : ?>
|
||||
<?php if ($paging === 'ajax') : ?>
|
||||
<div class="aksrt-product-archive__load-more">
|
||||
<button type="button" class="aksrt-load-more-btn" data-aksrt-shop-load-more><?php esc_html_e('加载更多', 'aksrt'); ?></button>
|
||||
<?php if ($max_pages > 1 && $paging === 'ajax') : ?>
|
||||
<div class="ajax-pag text-center theme-pagination">
|
||||
<div class="next-page ajax-next">
|
||||
<a href="#" class="ajax-next aksrt-load-more-btn" data-aksrt-shop-load-more data-aksrt-ajax-next><?php esc_html_e('加载更多', 'aksrt'); ?></a>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
</div>
|
||||
<?php elseif ($max_pages > 1) : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
@@ -100,21 +145,24 @@ $order_options = [
|
||||
'current' => $paged,
|
||||
'prev_text' => '<i class="fas fa-chevron-left"></i>',
|
||||
'next_text' => '<i class="fas fa-chevron-right"></i>',
|
||||
'add_args' => $active_orderby ? ['shop_orderby' => $active_orderby] : [],
|
||||
'add_args' => array_filter([
|
||||
'orderby' => $active_orderby,
|
||||
'order' => $active_orderby === 'price' ? $active_order : null,
|
||||
]),
|
||||
]);
|
||||
?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-product-archive__empty">
|
||||
<i class="fas fa-box-open"></i>
|
||||
<p><?php esc_html_e('内容空空如也', 'aksrt'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
wp_enqueue_script('aksrt-product-archive', get_template_directory_uri() . '/assets/js/product-archive.js', ['aksrt-script'], AKSRT_VERSION, true);
|
||||
wp_enqueue_script(
|
||||
'aksrt-product-archive',
|
||||
get_template_directory_uri() . '/assets/js/product-archive.js',
|
||||
['aksrt-script', 'aksrt-core-ajax-list'],
|
||||
AKSRT_VERSION,
|
||||
true
|
||||
);
|
||||
get_footer();
|
||||
|
||||
Reference in New Issue
Block a user