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:
2026-07-18 16:42:52 +08:00
parent e2587ca3a0
commit 433b7cb3a6
9 changed files with 828 additions and 273 deletions
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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,
];
}
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -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">
+149
View File
@@ -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>
+63 -39
View File
@@ -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();
+80 -32
View File
@@ -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();