Merge pull request 'Worktree zibll ux 1to1' (#213) from worktree-zibll-ux-1to1 into main
Reviewed-on: CYL/wordpress-theme-aksrt#213
This commit is contained in:
@@ -586,6 +586,104 @@
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
/* detail list card (zibll forum-posts detail) */
|
||||
.aksrt-forum-topic-row.detail,
|
||||
.aksrt-forum-posts.detail {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 14px;
|
||||
background: var(--surface-1, #fff);
|
||||
box-shadow: var(--shadow-sm, 0 1px 4px rgba(0,0,0,.06));
|
||||
}
|
||||
.aksrt-forum-topic-row__author-card { gap: 10px; }
|
||||
.aksrt-forum-topic-row__author-name { font-weight: 700; font-size: 14px; }
|
||||
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover,
|
||||
.aksrt-forum-posts.detail .forum-lists-cover {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 280px;
|
||||
margin: 0 0 12px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 280px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-forum-topic-row .excerpt {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--color-ink-2, #4b5563);
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs.count-1 { grid-template-columns: 1fr; max-width: 280px; }
|
||||
.aksrt-forum-topic-row .lists-imgs.count-2 { grid-template-columns: 1fr 1fr; }
|
||||
.aksrt-forum-topic-row .lists-imgs.count-3,
|
||||
.aksrt-forum-topic-row .lists-imgs.count-4 { grid-template-columns: 1fr 1fr; }
|
||||
.aksrt-forum-topic-row .lists-imgs__item {
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 1;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs__item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote { display: grid; gap: 6px; }
|
||||
.aksrt-forum-topic-row__vote-opt {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(80px, 2fr) 42px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote-bar {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-4, #eef0f3);
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote-bar i {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: var(--color-primary, #f04494);
|
||||
}
|
||||
.aksrt-forum-topic-row__footer .but-plate,
|
||||
.aksrt-forum-topic-row__board.but {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 46%;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-4, #f3f4f6);
|
||||
color: var(--color-ink-2, #5c6370);
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-forum-topic-row__actions {
|
||||
display: inline-flex;
|
||||
gap: 12px;
|
||||
color: var(--color-ink-3, #8a909c);
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-forum-topic-row__actions a { color: inherit; text-decoration: none; }
|
||||
.aksrt-forum-topic-row.mini { display: flex; gap: 12px; align-items: flex-start; }
|
||||
|
||||
.aksrt-forum-board-card__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* Remote modal + ajax list primitives (shared).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
/* ---- scroll lock ---- */
|
||||
html.aksrt-remote-modal-open,
|
||||
body.aksrt-remote-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- remote modal shell ---- */
|
||||
.aksrt-remote-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100050;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 18, 28, 0.48);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.aksrt-remote-modal__dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(440px, calc(100vw - 32px));
|
||||
max-height: min(88vh, 720px);
|
||||
overflow: auto;
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 18px 48px rgba(15, 18, 28, 0.22);
|
||||
transform: translateY(12px) scale(0.98);
|
||||
transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
color: var(--color-ink, #1f2937);
|
||||
}
|
||||
|
||||
.aksrt-remote-modal.is-open .aksrt-remote-modal__dialog {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.aksrt-remote-modal--bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal--bottom .aksrt-remote-modal__dialog {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 90vh;
|
||||
transform: translateY(24px);
|
||||
}
|
||||
|
||||
.aksrt-remote-modal--bottom.is-open .aksrt-remote-modal__dialog {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.aksrt-remote-modal__content {
|
||||
padding: 18px 18px 16px;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal__loading,
|
||||
.aksrt-remote-modal__error-body {
|
||||
padding: 28px 8px;
|
||||
text-align: center;
|
||||
color: var(--color-ink-2, #6b7280);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal .touch.border-title,
|
||||
.aksrt-remote-modal .border-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 14px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal .abs-close,
|
||||
.aksrt-remote-modal .aksrt-remote-modal__close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: var(--color-ink-3, #9ca3af);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal .but-average.modal-buts,
|
||||
.aksrt-remote-modal .modal-buts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal .but-average.modal-buts .but,
|
||||
.aksrt-remote-modal .modal-buts .but,
|
||||
.aksrt-remote-modal .modal-buts button {
|
||||
min-height: 40px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
background: var(--surface-4, #f3f4f6);
|
||||
color: var(--color-ink, #111);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-remote-modal .modal-buts .but.c-blue,
|
||||
.aksrt-remote-modal .modal-buts button.c-blue,
|
||||
.aksrt-remote-modal .modal-buts .btn-primary {
|
||||
background: var(--color-primary, #f04494);
|
||||
border-color: color-mix(in srgb, var(--color-primary, #f04494) 70%, #000 0%);
|
||||
color: var(--color-on-primary, #fff);
|
||||
}
|
||||
|
||||
/* ---- ajax list ---- */
|
||||
.ajaxpager.is-ajax-loading {
|
||||
position: relative;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.ajaxpager .post_ajax_loader,
|
||||
.ajaxpager .post-ajax-loader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ajaxpager .post_ajax_loader.is-loading,
|
||||
.ajaxpager .post-ajax-loader.is-loading {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ajax-item.is-fading {
|
||||
opacity: 0.35;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
/* ---- order review modal content ---- */
|
||||
.order-comment-from .shop-score-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.order-comment-from .score-title {
|
||||
width: 4.5em;
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-2, #6b7280);
|
||||
}
|
||||
|
||||
.order-comment-from .score-icon {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.order-comment-from .icon-star {
|
||||
display: inline-flex;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #d1d5db;
|
||||
cursor: pointer;
|
||||
transition: color 0.12s ease, transform 0.12s ease;
|
||||
}
|
||||
|
||||
.order-comment-from .icon-star svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.order-comment-from .icon-star.active,
|
||||
.order-comment-from .icon-star.hover {
|
||||
color: #fc7049;
|
||||
}
|
||||
|
||||
.order-comment-from .icon-star.hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.order-comment-from .score-text {
|
||||
min-width: 3em;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-2, #6b7280);
|
||||
}
|
||||
|
||||
.order-comment-from textarea.form-control {
|
||||
width: 100%;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
min-height: 96px;
|
||||
background: var(--surface-1, #fff);
|
||||
color: var(--color-ink, #111);
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .add,
|
||||
.order-comment-from .form-upload .preview .preview-item {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed var(--color-line, #d1d5db);
|
||||
background: var(--surface-4, #f9fafb);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .add::before,
|
||||
.order-comment-from .form-upload .preview .add::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
background: var(--color-ink-3, #9ca3af);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .add::before {
|
||||
width: 22px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .add::after {
|
||||
width: 2px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .preview-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .preview .preview-item .remove {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.order-comment-from .form-upload .hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.order-comment-from .review-error {
|
||||
color: #ef4444;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-remote-modal:not(.aksrt-remote-modal--bottom) .aksrt-remote-modal__dialog {
|
||||
width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* AKSRT Ajax List Engine — zibll .ajax-next / ajax-replace / route parity.
|
||||
*
|
||||
* DOM contract:
|
||||
* .ajaxpager list container
|
||||
* .ajax-item replace/append unit
|
||||
* .ajax-pag .next-page.ajax-next paginate control
|
||||
* ajax-replace="true" replace mode
|
||||
* route / route-title history + document.title
|
||||
* [win-ajax-replace] partial header sync from response HTML
|
||||
*
|
||||
* Transport:
|
||||
* - Prefer data-aksrt-ajax-url JSON { html, header_html, paginate_html, title }
|
||||
* - Else fetch href as HTML fragment and parse .ajaxpager / .ajax-item
|
||||
*
|
||||
* Events: post_ajax.before / post_ajax.ed on container (bubbles).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
(function (window, document) {
|
||||
"use strict";
|
||||
|
||||
var loadingMap = new WeakMap();
|
||||
|
||||
function restHeaders() {
|
||||
var h = { Accept: "application/json, text/html", "X-AKSRT-Ajax": "list" };
|
||||
if (window.aksrtData && aksrtData.restNonce) {
|
||||
h["X-WP-Nonce"] = aksrtData.restNonce;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
function closestPager(el) {
|
||||
if (!el) return null;
|
||||
return (
|
||||
el.closest(".ajaxpager") ||
|
||||
el.closest("[data-aksrt-ajaxpager]") ||
|
||||
el.closest(".aksrt-ajaxpager")
|
||||
);
|
||||
}
|
||||
|
||||
function dispatch(container, name, detail) {
|
||||
if (!container) return;
|
||||
try {
|
||||
container.dispatchEvent(
|
||||
new CustomEvent(name, { bubbles: true, detail: detail || {} })
|
||||
);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
function showLoader(container) {
|
||||
if (!container) return;
|
||||
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
|
||||
if (loader) {
|
||||
loader.classList.add("is-loading");
|
||||
loader.hidden = false;
|
||||
loader.style.display = "";
|
||||
return;
|
||||
}
|
||||
container.classList.add("is-ajax-loading");
|
||||
}
|
||||
|
||||
function hideLoader(container) {
|
||||
if (!container) return;
|
||||
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
|
||||
if (loader) {
|
||||
loader.classList.remove("is-loading");
|
||||
if (loader.hasAttribute("data-aksrt-ajax-loader-hide")) {
|
||||
loader.hidden = true;
|
||||
loader.style.display = "none";
|
||||
}
|
||||
}
|
||||
container.classList.remove("is-ajax-loading");
|
||||
}
|
||||
|
||||
function parseHtml(html) {
|
||||
var wrap = document.createElement("div");
|
||||
wrap.innerHTML = html || "";
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function extractFromHtml(html, container) {
|
||||
var doc = parseHtml(html);
|
||||
var pager =
|
||||
doc.querySelector(".ajaxpager") ||
|
||||
doc.querySelector("[data-aksrt-ajaxpager]") ||
|
||||
doc.querySelector(".aksrt-ajaxpager");
|
||||
var items = [];
|
||||
var scope = pager || doc;
|
||||
scope.querySelectorAll(".ajax-item").forEach(function (n) {
|
||||
items.push(n.outerHTML);
|
||||
});
|
||||
if (!items.length && pager) {
|
||||
items.push(pager.innerHTML);
|
||||
}
|
||||
var pag = scope.querySelector(".ajax-pag");
|
||||
var headerBits = {};
|
||||
doc.querySelectorAll("[win-ajax-replace]").forEach(function (n) {
|
||||
var key = n.getAttribute("win-ajax-replace");
|
||||
if (key) headerBits[key] = n.outerHTML;
|
||||
});
|
||||
var titleEl = doc.querySelector("title");
|
||||
return {
|
||||
itemsHtml: items.join(""),
|
||||
paginateHtml: pag ? pag.outerHTML : "",
|
||||
headerBits: headerBits,
|
||||
title: titleEl ? titleEl.textContent : "",
|
||||
fullHtml: pager ? pager.innerHTML : html,
|
||||
};
|
||||
}
|
||||
|
||||
function syncWinReplace(bits) {
|
||||
if (!bits) return;
|
||||
Object.keys(bits).forEach(function (key) {
|
||||
var live = document.querySelector('[win-ajax-replace="' + key + '"]');
|
||||
if (!live) return;
|
||||
var tmp = document.createElement("div");
|
||||
tmp.innerHTML = bits[key];
|
||||
var next = tmp.firstElementChild;
|
||||
if (next) live.replaceWith(next);
|
||||
});
|
||||
}
|
||||
|
||||
function applyRoute(link, title) {
|
||||
if (!link) return;
|
||||
var route = link.getAttribute("route") || link.getAttribute("data-route");
|
||||
if (!(route === "1" || route === "true" || link.hasAttribute("route"))) return;
|
||||
var href = link.getAttribute("href") || link.getAttribute("ajax-href") || "";
|
||||
if (!href) return;
|
||||
try {
|
||||
window.history.replaceState({}, "", href);
|
||||
} catch (err) {}
|
||||
var rt =
|
||||
title ||
|
||||
link.getAttribute("route-title") ||
|
||||
link.getAttribute("data-route-title") ||
|
||||
"";
|
||||
if (rt) {
|
||||
try {
|
||||
document.title = rt;
|
||||
} catch (e2) {}
|
||||
}
|
||||
}
|
||||
|
||||
function replacePaginate(container, paginateHtml) {
|
||||
if (!container || !paginateHtml) return;
|
||||
var old = container.querySelector(".ajax-pag");
|
||||
if (!old) {
|
||||
container.insertAdjacentHTML("beforeend", paginateHtml);
|
||||
return;
|
||||
}
|
||||
var tmp = document.createElement("div");
|
||||
tmp.innerHTML = paginateHtml;
|
||||
var next = tmp.firstElementChild;
|
||||
if (next) old.replaceWith(next);
|
||||
}
|
||||
|
||||
function inject(container, payload, options) {
|
||||
options = options || {};
|
||||
var replace = options.replace !== false;
|
||||
if (!container) return;
|
||||
|
||||
var itemsHtml = payload.itemsHtml || payload.html || "";
|
||||
if (replace) {
|
||||
container.querySelectorAll(".ajax-item").forEach(function (n) {
|
||||
n.remove();
|
||||
});
|
||||
// If container itself holds only items, clear non-loader children.
|
||||
if (container.getAttribute("data-aksrt-ajax-clear") === "1") {
|
||||
Array.prototype.slice.call(container.children).forEach(function (child) {
|
||||
if (
|
||||
child.classList.contains("post_ajax_loader") ||
|
||||
child.classList.contains("post-ajax-loader") ||
|
||||
child.hasAttribute("data-aksrt-ajax-loader") ||
|
||||
child.classList.contains("ajax-pag")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
child.remove();
|
||||
});
|
||||
}
|
||||
if (itemsHtml) {
|
||||
var pag = container.querySelector(".ajax-pag");
|
||||
if (pag) {
|
||||
pag.insertAdjacentHTML("beforebegin", itemsHtml);
|
||||
} else {
|
||||
container.insertAdjacentHTML("beforeend", itemsHtml);
|
||||
}
|
||||
}
|
||||
} else if (itemsHtml) {
|
||||
var pag2 = container.querySelector(".ajax-pag");
|
||||
if (pag2) {
|
||||
pag2.insertAdjacentHTML("beforebegin", itemsHtml);
|
||||
} else {
|
||||
container.insertAdjacentHTML("beforeend", itemsHtml);
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.paginateHtml) {
|
||||
replacePaginate(container, payload.paginateHtml);
|
||||
}
|
||||
if (payload.headerBits) {
|
||||
syncWinReplace(payload.headerBits);
|
||||
}
|
||||
if (payload.headerHtml) {
|
||||
var headerHost = document.querySelector(
|
||||
options.headerSelector || "[data-aksrt-ajax-header]"
|
||||
);
|
||||
if (headerHost) headerHost.innerHTML = payload.headerHtml;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeJson(json) {
|
||||
var data = json && json.data ? json.data : json;
|
||||
if (!data) return null;
|
||||
var html = data.html || data.list_html || "";
|
||||
var itemsHtml = data.items_html || "";
|
||||
if (!itemsHtml && html) {
|
||||
// If html is only item nodes, use as-is; else extract.
|
||||
if (html.indexOf("ajax-item") !== -1) {
|
||||
var parsed = extractFromHtml("<div class='ajaxpager'>" + html + "</div>");
|
||||
itemsHtml = parsed.itemsHtml || html;
|
||||
} else {
|
||||
itemsHtml = html;
|
||||
}
|
||||
}
|
||||
return {
|
||||
itemsHtml: itemsHtml,
|
||||
html: html,
|
||||
paginateHtml: data.paginate_html || data.paginateHtml || "",
|
||||
headerHtml: data.header_html || data.headerHtml || "",
|
||||
headerBits: data.header_bits || null,
|
||||
title: data.title || "",
|
||||
emptyHtml: data.empty_html || "",
|
||||
hasMore: typeof data.has_more !== "undefined" ? !!data.has_more : null,
|
||||
};
|
||||
}
|
||||
|
||||
function loadFromLink(link, override) {
|
||||
override = override || {};
|
||||
if (!link) return Promise.resolve();
|
||||
|
||||
var container =
|
||||
override.container ||
|
||||
(link.getAttribute("ajaxpager-target")
|
||||
? document.querySelector(link.getAttribute("ajaxpager-target"))
|
||||
: null) ||
|
||||
closestPager(link);
|
||||
if (!container) {
|
||||
// Fall back: parent stream list.
|
||||
container = link.closest(".aksrt-product-grid, .aksrt-forum-topics__list, [data-aksrt-ajax-list]");
|
||||
}
|
||||
if (!container) return Promise.resolve();
|
||||
|
||||
if (loadingMap.get(container)) return Promise.resolve();
|
||||
loadingMap.set(container, true);
|
||||
|
||||
var replaceAttr = link.getAttribute("ajax-replace");
|
||||
var replace =
|
||||
typeof override.replace === "boolean"
|
||||
? override.replace
|
||||
: replaceAttr === "true" || replaceAttr === "1" || replaceAttr === "replace";
|
||||
|
||||
var href =
|
||||
override.url ||
|
||||
link.getAttribute("data-aksrt-ajax-url") ||
|
||||
link.getAttribute("ajax-href") ||
|
||||
link.getAttribute("href") ||
|
||||
"";
|
||||
|
||||
dispatch(container, "post_ajax.before", { link: link, replace: replace, url: href });
|
||||
showLoader(container);
|
||||
if (replace) {
|
||||
// Soft clear items while loading (keep skeleton if present).
|
||||
container.querySelectorAll(".ajax-item").forEach(function (n) {
|
||||
n.classList.add("is-fading");
|
||||
});
|
||||
}
|
||||
|
||||
return fetch(href, {
|
||||
method: override.method || "GET",
|
||||
credentials: "same-origin",
|
||||
headers: restHeaders(),
|
||||
})
|
||||
.then(function (res) {
|
||||
var type = (res.headers.get("content-type") || "").toLowerCase();
|
||||
if (type.indexOf("application/json") !== -1) {
|
||||
return res.json().then(function (json) {
|
||||
if (!res.ok || (json && json.success === false)) {
|
||||
throw new Error(
|
||||
(json && (json.message || (json.data && json.data.message))) || "加载失败"
|
||||
);
|
||||
}
|
||||
return normalizeJson(json);
|
||||
});
|
||||
}
|
||||
return res.text().then(function (text) {
|
||||
if (!res.ok) throw new Error("加载失败");
|
||||
return extractFromHtml(text, container);
|
||||
});
|
||||
})
|
||||
.then(function (payload) {
|
||||
if (!payload) throw new Error("空响应");
|
||||
if (replace && !payload.itemsHtml && payload.emptyHtml) {
|
||||
inject(container, { itemsHtml: payload.emptyHtml, paginateHtml: payload.paginateHtml }, { replace: true });
|
||||
} else {
|
||||
inject(container, payload, { replace: replace });
|
||||
}
|
||||
applyRoute(link, payload.title);
|
||||
hideLoader(container);
|
||||
loadingMap.set(container, false);
|
||||
dispatch(container, "post_ajax.ed", {
|
||||
link: link,
|
||||
replace: replace,
|
||||
url: href,
|
||||
payload: payload,
|
||||
});
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("aksrt:posts:rendered", {
|
||||
bubbles: true,
|
||||
detail: { container: container, replace: replace },
|
||||
})
|
||||
);
|
||||
return payload;
|
||||
})
|
||||
.catch(function (err) {
|
||||
hideLoader(container);
|
||||
loadingMap.set(container, false);
|
||||
container.querySelectorAll(".ajax-item.is-fading").forEach(function (n) {
|
||||
n.classList.remove("is-fading");
|
||||
});
|
||||
if (typeof window.aksrtToast === "function") {
|
||||
window.aksrtToast((err && err.message) || "加载失败");
|
||||
}
|
||||
dispatch(container, "post_ajax.error", { link: link, error: err });
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
var link = e.target.closest("a.ajax-next, .ajax-next a, button.ajax-next, [data-aksrt-ajax-next]");
|
||||
if (!link) return;
|
||||
// Ignore if inside a component that opts out.
|
||||
if (link.closest("[data-aksrt-ajax-list='off']")) return;
|
||||
// Native pagination core may own .aksrt-pagination-ajax buttons.
|
||||
if (link.closest(".aksrt-pagination-ajax") && !link.classList.contains("ajax-next")) return;
|
||||
|
||||
var href = link.getAttribute("href") || link.getAttribute("ajax-href") || link.getAttribute("data-aksrt-ajax-url");
|
||||
if (!href && !link.getAttribute("data-aksrt-ajax-url")) return;
|
||||
|
||||
e.preventDefault();
|
||||
loadFromLink(link.tagName === "A" || link.classList.contains("ajax-next") ? link : link.closest(".ajax-next") || link);
|
||||
});
|
||||
|
||||
window.AksrtAjaxList = {
|
||||
load: loadFromLink,
|
||||
inject: inject,
|
||||
extractFromHtml: extractFromHtml,
|
||||
closestPager: closestPager,
|
||||
};
|
||||
})(window, document);
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* AKSRT Remote Modal — zibll RefreshModal parity.
|
||||
*
|
||||
* Open a center/bottom modal with static HTML or remote fetch.
|
||||
* Contract:
|
||||
* - data-toggle="AksrtRemoteModal" | data-aksrt-remote-modal
|
||||
* - data-remote / href → fetch HTML
|
||||
* - data-class → dialog class (modal-mini full-sm …)
|
||||
* - data-height → min-height hint
|
||||
* - mobile-bottom="true" → bottom sheet on narrow viewports
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
(function (window, document) {
|
||||
"use strict";
|
||||
|
||||
var ROOT_ID = "aksrt-remote-modal-root";
|
||||
var active = null;
|
||||
|
||||
function isMobile() {
|
||||
return window.matchMedia && window.matchMedia("(max-width: 768px)").matches;
|
||||
}
|
||||
|
||||
function ensureRoot() {
|
||||
var root = document.getElementById(ROOT_ID);
|
||||
if (root) return root;
|
||||
root = document.createElement("div");
|
||||
root.id = ROOT_ID;
|
||||
root.className = "aksrt-remote-modal-host";
|
||||
document.body.appendChild(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
function restHeaders() {
|
||||
var h = { Accept: "text/html, application/json" };
|
||||
if (window.aksrtData && aksrtData.restNonce) {
|
||||
h["X-WP-Nonce"] = aksrtData.restNonce;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
function lockScroll(lock) {
|
||||
document.documentElement.classList.toggle("aksrt-remote-modal-open", !!lock);
|
||||
document.body.classList.toggle("aksrt-remote-modal-open", !!lock);
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!active) return;
|
||||
active.classList.remove("is-open");
|
||||
lockScroll(false);
|
||||
var node = active;
|
||||
active = null;
|
||||
window.setTimeout(function () {
|
||||
if (node && node.parentNode) node.parentNode.removeChild(node);
|
||||
}, 220);
|
||||
document.dispatchEvent(new CustomEvent("aksrt:remote-modal:close"));
|
||||
}
|
||||
|
||||
function bindChrome(shell) {
|
||||
shell.addEventListener("click", function (e) {
|
||||
if (e.target.closest("[data-aksrt-remote-modal-close], [data-dismiss='modal'], .aksrt-remote-modal__close")) {
|
||||
e.preventDefault();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if (e.target === shell || e.target.classList.contains("aksrt-remote-modal__overlay")) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
shell.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") close();
|
||||
});
|
||||
}
|
||||
|
||||
function open(options) {
|
||||
options = options || {};
|
||||
close();
|
||||
|
||||
var root = ensureRoot();
|
||||
var shell = document.createElement("div");
|
||||
var bottom = options.bottom === true || (options.mobileBottom !== false && isMobile());
|
||||
shell.className =
|
||||
"aksrt-remote-modal" +
|
||||
(bottom ? " aksrt-remote-modal--bottom" : "") +
|
||||
(options.className ? " " + options.className : "");
|
||||
shell.setAttribute("role", "dialog");
|
||||
shell.setAttribute("aria-modal", "true");
|
||||
|
||||
var heightStyle = options.height ? "min-height:" + parseInt(options.height, 10) + "px;" : "";
|
||||
shell.innerHTML =
|
||||
'<div class="aksrt-remote-modal__overlay" data-aksrt-remote-modal-close></div>' +
|
||||
'<div class="aksrt-remote-modal__dialog" style="' + heightStyle + '">' +
|
||||
'<div class="aksrt-remote-modal__content">' +
|
||||
(options.html || "") +
|
||||
"</div></div>";
|
||||
|
||||
root.appendChild(shell);
|
||||
bindChrome(shell);
|
||||
active = shell;
|
||||
lockScroll(true);
|
||||
|
||||
// Force reflow then open for CSS transition.
|
||||
void shell.offsetWidth;
|
||||
shell.classList.add("is-open");
|
||||
|
||||
var focusable = shell.querySelector(
|
||||
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
||||
);
|
||||
if (focusable) {
|
||||
try {
|
||||
focusable.focus();
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("aksrt:remote-modal:open", { detail: { shell: shell, options: options } })
|
||||
);
|
||||
return shell;
|
||||
}
|
||||
|
||||
function openRemote(options) {
|
||||
options = options || {};
|
||||
var url = options.url || "";
|
||||
if (!url) return Promise.reject(new Error("missing url"));
|
||||
|
||||
var loading = open({
|
||||
html:
|
||||
'<div class="aksrt-remote-modal__loading"><i class="fas fa-spinner fa-spin"></i> ' +
|
||||
((window.aksrtData && aksrtData.ajaxLoading) || "加载中…") +
|
||||
"</div>",
|
||||
className: options.className || "modal-mini full-sm",
|
||||
bottom: options.bottom,
|
||||
mobileBottom: options.mobileBottom,
|
||||
height: options.height,
|
||||
});
|
||||
|
||||
return fetch(url, {
|
||||
method: options.method || "GET",
|
||||
credentials: "same-origin",
|
||||
headers: Object.assign(restHeaders(), options.headers || {}),
|
||||
body: options.body,
|
||||
})
|
||||
.then(function (res) {
|
||||
var type = (res.headers.get("content-type") || "").toLowerCase();
|
||||
if (type.indexOf("application/json") !== -1) {
|
||||
return res.json().then(function (json) {
|
||||
if (!res.ok || (json && json.success === false)) {
|
||||
var msg =
|
||||
(json && (json.message || (json.data && json.data.message))) ||
|
||||
"加载失败";
|
||||
throw new Error(msg);
|
||||
}
|
||||
var html =
|
||||
(json && (json.html || (json.data && (json.data.html || json.data)))) || "";
|
||||
if (typeof html !== "string") html = "";
|
||||
return html;
|
||||
});
|
||||
}
|
||||
return res.text().then(function (text) {
|
||||
if (!res.ok) throw new Error("加载失败");
|
||||
return text;
|
||||
});
|
||||
})
|
||||
.then(function (html) {
|
||||
if (loading && loading.parentNode) loading.parentNode.removeChild(loading);
|
||||
if (active === loading) active = null;
|
||||
return open({
|
||||
html: html,
|
||||
className: options.className || "modal-mini full-sm",
|
||||
bottom: options.bottom,
|
||||
mobileBottom: options.mobileBottom,
|
||||
height: options.height,
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (loading && loading.parentNode) loading.parentNode.removeChild(loading);
|
||||
if (active === loading) active = null;
|
||||
open({
|
||||
html:
|
||||
'<div class="aksrt-remote-modal__error touch border-title"><b>提示</b></div>' +
|
||||
'<div class="aksrt-remote-modal__error-body">' +
|
||||
(err && err.message ? String(err.message) : "加载失败") +
|
||||
'</div><div class="but-average modal-buts"><button type="button" class="but" data-aksrt-remote-modal-close>关闭</button></div>',
|
||||
className: "modal-mini",
|
||||
mobileBottom: true,
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function fromTrigger(el) {
|
||||
if (!el) return null;
|
||||
var url =
|
||||
el.getAttribute("data-remote") ||
|
||||
el.getAttribute("data-aksrt-remote") ||
|
||||
el.getAttribute("href") ||
|
||||
"";
|
||||
if (!url || url === "#") return null;
|
||||
var height = el.getAttribute("data-height") || el.getAttribute("data-aksrt-height");
|
||||
return openRemote({
|
||||
url: url,
|
||||
className: el.getAttribute("data-class") || el.getAttribute("data-aksrt-class") || "modal-mini full-sm",
|
||||
height: height ? parseInt(height, 10) : 0,
|
||||
mobileBottom: el.getAttribute("mobile-bottom") !== "false",
|
||||
bottom: el.getAttribute("data-bottom") === "true",
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
var trigger = e.target.closest(
|
||||
'[data-toggle="AksrtRemoteModal"], [data-toggle="RefreshModal"], [data-aksrt-remote-modal], a.aksrt-remote-modal-link'
|
||||
);
|
||||
if (!trigger) return;
|
||||
if (trigger.getAttribute("data-aksrt-remote-modal") === "off") return;
|
||||
e.preventDefault();
|
||||
fromTrigger(trigger);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && active) close();
|
||||
});
|
||||
|
||||
window.AksrtRemoteModal = {
|
||||
open: open,
|
||||
openRemote: openRemote,
|
||||
close: close,
|
||||
fromTrigger: fromTrigger,
|
||||
getActive: function () {
|
||||
return active;
|
||||
},
|
||||
};
|
||||
})(window, document);
|
||||
+248
-37
@@ -67,6 +67,252 @@
|
||||
return aksrtData.cartUrl || (aksrtData.routes && aksrtData.routes.cart) || "/";
|
||||
}
|
||||
|
||||
/* ── Order review modal (zibll 1:1) ── */
|
||||
var STAR_SVG =
|
||||
'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>';
|
||||
var SCORE_LABELS = ["", "非常差", "较差", "一般", "较好", "非常好"];
|
||||
|
||||
function formatRemain(sec) {
|
||||
sec = Math.max(0, sec | 0);
|
||||
var d = Math.floor(sec / 86400);
|
||||
var h = Math.floor((sec % 86400) / 3600);
|
||||
var m = Math.floor((sec % 3600) / 60);
|
||||
var s = sec % 60;
|
||||
function z(n) { return n < 10 ? "0" + n : String(n); }
|
||||
if (d > 0) return d + "天" + z(h) + ":" + z(m) + ":" + z(s);
|
||||
return z(h) + ":" + z(m) + ":" + z(s);
|
||||
}
|
||||
|
||||
function bindShopScore(root) {
|
||||
if (!root) return;
|
||||
root.querySelectorAll("[data-aksrt-score-box], .shop-score-box").forEach(function (box) {
|
||||
if (box.dataset.scoreBound === "1") return;
|
||||
box.dataset.scoreBound = "1";
|
||||
var iconBox = box.querySelector(".score-icon");
|
||||
var textBox = box.querySelector(".score-text");
|
||||
var input = box.querySelector(".score-input");
|
||||
if (!iconBox) return;
|
||||
var value = parseInt(box.getAttribute("score-value") || (input && input.value) || "0", 10) || 0;
|
||||
var html = "";
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
html += '<i class="icon-star' + (i <= value ? " active" : "") + '" data-score="' + i + '">' + STAR_SVG + "</i>";
|
||||
}
|
||||
iconBox.innerHTML = html;
|
||||
function paint(score, hover) {
|
||||
iconBox.querySelectorAll(".icon-star").forEach(function (star) {
|
||||
var s = parseInt(star.getAttribute("data-score"), 10) || 0;
|
||||
star.classList.toggle("active", !hover && s <= score);
|
||||
star.classList.toggle("hover", !!hover && s <= score);
|
||||
});
|
||||
if (textBox) textBox.textContent = SCORE_LABELS[score] || "";
|
||||
}
|
||||
paint(value, false);
|
||||
iconBox.addEventListener("mouseover", function (e) {
|
||||
var star = e.target.closest(".icon-star");
|
||||
if (!star || !iconBox.contains(star)) return;
|
||||
paint(parseInt(star.getAttribute("data-score"), 10) || 0, true);
|
||||
});
|
||||
iconBox.addEventListener("mouseleave", function () {
|
||||
var cur = parseInt(box.getAttribute("score-value") || "0", 10) || 0;
|
||||
paint(cur, false);
|
||||
});
|
||||
iconBox.addEventListener("click", function (e) {
|
||||
var star = e.target.closest(".icon-star");
|
||||
if (!star || !iconBox.contains(star)) return;
|
||||
var score = parseInt(star.getAttribute("data-score"), 10) || 0;
|
||||
box.setAttribute("score-value", String(score));
|
||||
if (input) input.value = String(score);
|
||||
paint(score, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindReviewImages(form) {
|
||||
var preview = form.querySelector("[data-aksrt-review-preview]");
|
||||
var fileInput = form.querySelector("[data-aksrt-review-file]");
|
||||
var addBtn = form.querySelector("[data-aksrt-review-add]");
|
||||
if (!preview || !fileInput) return;
|
||||
var max = parseInt(preview.getAttribute("data-max") || "6", 10) || 6;
|
||||
var files = [];
|
||||
|
||||
function render() {
|
||||
preview.querySelectorAll(".preview-item").forEach(function (n) { n.remove(); });
|
||||
files.forEach(function (item, idx) {
|
||||
var node = document.createElement("div");
|
||||
node.className = "preview-item";
|
||||
node.innerHTML = '<img alt=""><button type="button" class="remove" aria-label="删除">×</button>';
|
||||
node.querySelector("img").src = item.url;
|
||||
node.querySelector(".remove").addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (item.url && item.url.indexOf("blob:") === 0) {
|
||||
try { URL.revokeObjectURL(item.url); } catch (err) {}
|
||||
}
|
||||
files.splice(idx, 1);
|
||||
render();
|
||||
});
|
||||
preview.insertBefore(node, addBtn);
|
||||
});
|
||||
if (addBtn) addBtn.style.display = files.length >= max ? "none" : "";
|
||||
form._aksrtReviewFiles = files.slice();
|
||||
}
|
||||
|
||||
function onPick() {
|
||||
if (files.length >= max) return;
|
||||
fileInput.click();
|
||||
}
|
||||
if (addBtn) {
|
||||
addBtn.addEventListener("click", onPick);
|
||||
addBtn.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onPick();
|
||||
}
|
||||
});
|
||||
}
|
||||
fileInput.addEventListener("change", function () {
|
||||
var list = Array.prototype.slice.call(fileInput.files || []);
|
||||
list.forEach(function (file) {
|
||||
if (files.length >= max) return;
|
||||
if (!file.type || file.type.indexOf("image/") !== 0) return;
|
||||
files.push({ file: file, url: URL.createObjectURL(file) });
|
||||
});
|
||||
fileInput.value = "";
|
||||
render();
|
||||
});
|
||||
form._aksrtReviewFiles = [];
|
||||
render();
|
||||
}
|
||||
|
||||
function bindReviewCountdown(root) {
|
||||
var el = root.querySelector("[data-aksrt-review-countdown]");
|
||||
if (!el) return;
|
||||
var remain = parseInt(el.getAttribute("data-aksrt-review-countdown") || "0", 10) || 0;
|
||||
if (remain <= 0) return;
|
||||
el.textContent = formatRemain(remain);
|
||||
var timer = window.setInterval(function () {
|
||||
remain -= 1;
|
||||
if (remain <= 0) {
|
||||
window.clearInterval(timer);
|
||||
el.textContent = "交易已关闭";
|
||||
return;
|
||||
}
|
||||
el.textContent = formatRemain(remain);
|
||||
}, 1000);
|
||||
root._aksrtReviewTimer = timer;
|
||||
}
|
||||
|
||||
function fileToDataUrl(file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () { resolve(String(reader.result || "")); };
|
||||
reader.onerror = function () { reject(new Error("read failed")); };
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
function openOrderReviewModal(orderNumber, triggerBtn) {
|
||||
if (!window.AksrtRemoteModal || typeof window.AksrtRemoteModal.openRemote !== "function") {
|
||||
showToast("评价组件未加载");
|
||||
return;
|
||||
}
|
||||
if (triggerBtn) triggerBtn.disabled = true;
|
||||
var url = restRoot() + "/shop/frontend/orders/" + encodeURIComponent(orderNumber) + "/review-modal";
|
||||
window.AksrtRemoteModal.openRemote({
|
||||
url: url,
|
||||
className: "modal-mini full-sm",
|
||||
height: 380,
|
||||
mobileBottom: true
|
||||
}).then(function (shell) {
|
||||
if (triggerBtn) triggerBtn.disabled = false;
|
||||
if (!shell) return;
|
||||
var form = shell.querySelector("[data-aksrt-order-review-form]");
|
||||
if (!form) return;
|
||||
bindShopScore(shell);
|
||||
bindReviewImages(form);
|
||||
bindReviewCountdown(shell);
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
var errBox = form.querySelector("[data-aksrt-review-error]");
|
||||
function showErr(msg) {
|
||||
if (errBox) {
|
||||
errBox.hidden = false;
|
||||
errBox.textContent = msg;
|
||||
} else {
|
||||
showToast(msg);
|
||||
}
|
||||
}
|
||||
if (errBox) {
|
||||
errBox.hidden = true;
|
||||
errBox.textContent = "";
|
||||
}
|
||||
|
||||
var productId = parseInt(form.getAttribute("data-product-id") || "0", 10) || 0;
|
||||
var needShipping = form.getAttribute("data-need-shipping") === "1";
|
||||
var scoreProduct = parseInt((form.querySelector('input[name="score[product]"]') || {}).value || "0", 10) || 0;
|
||||
var scoreService = parseInt((form.querySelector('input[name="score[service]"]') || {}).value || "0", 10) || 0;
|
||||
var scoreShipping = parseInt((form.querySelector('input[name="score[shipping]"]') || {}).value || "0", 10) || 0;
|
||||
if (scoreProduct < 1 || scoreService < 1) {
|
||||
showErr("请对商品进行评分");
|
||||
return;
|
||||
}
|
||||
if (needShipping && scoreShipping < 1) {
|
||||
showErr("请对物流服务进行评分");
|
||||
return;
|
||||
}
|
||||
|
||||
var comment = (form.querySelector('textarea[name="comment"]') || {}).value || "";
|
||||
var submitBtn = form.querySelector("[data-aksrt-review-submit]");
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = "提交中…";
|
||||
}
|
||||
|
||||
var pending = form._aksrtReviewFiles || [];
|
||||
Promise.all(pending.map(function (item) {
|
||||
return item.file ? fileToDataUrl(item.file) : Promise.resolve(item.url || "");
|
||||
})).then(function (images) {
|
||||
images = images.filter(Boolean);
|
||||
return request("/shop/frontend/orders/" + orderNumber + "/review", {
|
||||
product_id: productId,
|
||||
content: comment,
|
||||
rating_product: scoreProduct,
|
||||
rating_service: scoreService,
|
||||
rating_shipping: needShipping ? scoreShipping : scoreService,
|
||||
images: images
|
||||
});
|
||||
}).then(function (result) {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fa fa-check" aria-hidden="true"></i> 确认提交';
|
||||
}
|
||||
if (!result || !result.success) {
|
||||
showErr((result && result.message) || "评价失败");
|
||||
return;
|
||||
}
|
||||
showToast((result.message) || "评价成功");
|
||||
if (window.AksrtRemoteModal) window.AksrtRemoteModal.close();
|
||||
var card = findOrderCard(orderNumber);
|
||||
updateCardStatus(card, "completed", "已完成");
|
||||
if (card) {
|
||||
var btn = card.querySelector("[data-aksrt-order-review]");
|
||||
if (btn) btn.remove();
|
||||
}
|
||||
}).catch(function () {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fa fa-check" aria-hidden="true"></i> 确认提交';
|
||||
}
|
||||
showErr("网络错误");
|
||||
});
|
||||
});
|
||||
}).catch(function (err) {
|
||||
if (triggerBtn) triggerBtn.disabled = false;
|
||||
showToast((err && err.message) || "无法打开评价");
|
||||
});
|
||||
}
|
||||
|
||||
/* ── 扫码支付弹层(微信 Native code_url 等)── */
|
||||
function showPaymentQr(content, orderNumber) {
|
||||
var existing = document.getElementById("aksrt-pay-qr-overlay");
|
||||
@@ -280,47 +526,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Review submit modal
|
||||
// Review modal (zibll order_comment_modal 1:1 UX)
|
||||
var reviewBtn = event.target.closest("[data-aksrt-order-review]");
|
||||
if (reviewBtn) {
|
||||
var reviewOrder = reviewBtn.getAttribute("data-aksrt-order-review");
|
||||
var card = reviewBtn.closest("[data-aksrt-order-card]");
|
||||
var productId = 0;
|
||||
if (card) {
|
||||
var firstItem = card.querySelector("[data-product-id]");
|
||||
if (firstItem) productId = Number(firstItem.getAttribute("data-product-id") || 0);
|
||||
}
|
||||
if (!productId) {
|
||||
// try parse from item title block dataset if present
|
||||
var any = card ? card.querySelector(".aksrt-orders-page__card-item") : null;
|
||||
if (any && any.getAttribute("data-product-id")) {
|
||||
productId = Number(any.getAttribute("data-product-id") || 0);
|
||||
}
|
||||
}
|
||||
if (!reviewOrder) return;
|
||||
var content = window.prompt("请输入评价内容(可选)", "好评!");
|
||||
if (content === null) return;
|
||||
var rating = window.prompt("商品评分 1-5", "5");
|
||||
var r = Math.max(1, Math.min(5, parseInt(rating || "5", 10) || 5));
|
||||
reviewBtn.disabled = true;
|
||||
request("/shop/frontend/orders/" + reviewOrder + "/review", {
|
||||
product_id: productId,
|
||||
content: content || "好评!",
|
||||
rating_product: r,
|
||||
rating_service: r,
|
||||
rating_shipping: r
|
||||
}).then(function (result) {
|
||||
reviewBtn.disabled = false;
|
||||
if (!result || !result.success) {
|
||||
showToast((result && result.message) || "评价失败");
|
||||
return;
|
||||
}
|
||||
showToast(result.message || "评价已提交");
|
||||
updateCardStatus(findOrderCard(reviewOrder), "completed", "已完成");
|
||||
}).catch(function () {
|
||||
reviewBtn.disabled = false;
|
||||
showToast("网络错误");
|
||||
});
|
||||
openOrderReviewModal(reviewOrder, reviewBtn);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+191
-48
@@ -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 (grid) {
|
||||
if (replace) {
|
||||
grid.innerHTML = data.html || "";
|
||||
} else if (data.html) {
|
||||
grid.insertAdjacentHTML("beforeend", data.html);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 = "加载更多";
|
||||
var html = data.html || data.empty_html || "";
|
||||
if (grid) {
|
||||
if (replace) {
|
||||
grid.innerHTML = html;
|
||||
} else if (html) {
|
||||
grid.insertAdjacentHTML("beforeend", html);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.paginate_html) {
|
||||
var oldPag = root.querySelector(".ajax-pag");
|
||||
var wrap = document.createElement("div");
|
||||
wrap.innerHTML = data.paginate_html;
|
||||
var nextPag = wrap.firstElementChild;
|
||||
if (oldPag && nextPag) oldPag.replaceWith(nextPag);
|
||||
else if (!oldPag && nextPag && pager) pager.appendChild(nextPag);
|
||||
loadMoreBtn = root.querySelector("[data-aksrt-shop-load-more]");
|
||||
}
|
||||
|
||||
syncOrderbyActive();
|
||||
updateLoadMore(!!data.has_more || state.page < state.maxPages);
|
||||
|
||||
try {
|
||||
container.dispatchEvent(
|
||||
new CustomEvent("post_ajax.ed", {
|
||||
bubbles: true,
|
||||
detail: { page: state.page, replace: !!replace, data: data },
|
||||
})
|
||||
);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("aksrt:posts:rendered", {
|
||||
bubbles: true,
|
||||
detail: { container: grid, replace: !!replace },
|
||||
})
|
||||
);
|
||||
} catch (e1) {}
|
||||
})
|
||||
.catch(function () {
|
||||
state.loading = false;
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.disabled = false;
|
||||
loadMoreBtn.textContent = "加载更多";
|
||||
}
|
||||
showLoader(false);
|
||||
updateLoadMore(state.page < state.maxPages);
|
||||
toast("网络错误");
|
||||
});
|
||||
}
|
||||
|
||||
function pushUrl(orderby, order, termUrl) {
|
||||
try {
|
||||
var u = termUrl ? new URL(termUrl, window.location.origin) : new URL(window.location.href);
|
||||
if (orderby) u.searchParams.set("orderby", orderby);
|
||||
else u.searchParams.delete("orderby");
|
||||
u.searchParams.delete("shop_orderby");
|
||||
if (orderby === "price" && order) u.searchParams.set("order", order);
|
||||
else u.searchParams.delete("order");
|
||||
u.searchParams.delete("paged");
|
||||
window.history.replaceState({}, "", u.toString());
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
root.addEventListener("click", function (e) {
|
||||
var sort = e.target.closest("[data-aksrt-shop-orderby]");
|
||||
if (sort && root.contains(sort)) {
|
||||
// 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";
|
||||
if (orderby === state.orderby && state.paging === "ajax") return;
|
||||
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;
|
||||
try {
|
||||
var u = new URL(window.location.href);
|
||||
u.searchParams.set("shop_orderby", orderby);
|
||||
u.searchParams.delete("paged");
|
||||
window.history.replaceState({}, "", u.toString());
|
||||
} catch (err) {}
|
||||
fetchArchive(1, orderby, true);
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
}, { rootMargin: "240px" });
|
||||
var io = new IntersectionObserver(
|
||||
function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
if (state.loading || state.page >= state.maxPages) return;
|
||||
if (state.iasMax > 0 && state.autoLoads >= state.iasMax) return;
|
||||
state.autoLoads += 1;
|
||||
fetchArchive(state.page + 1, state.orderby, state.order, false);
|
||||
});
|
||||
},
|
||||
{ rootMargin: "240px" }
|
||||
);
|
||||
io.observe(loadMoreBtn);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -42,6 +42,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-pay.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php';
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Ajax list + remote modal helpers (zibll ajax-next / RefreshModal parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_ajax_null')) {
|
||||
/**
|
||||
* Empty state for AJAX list replace.
|
||||
*/
|
||||
function aksrt_get_ajax_null(?string $text = null, $margin = 60, string $img = 'null.svg'): string {
|
||||
if (null === $text) {
|
||||
$text = __('内容空空如也', 'aksrt');
|
||||
}
|
||||
if (function_exists('aksrt_get_null')) {
|
||||
return aksrt_get_null($text, $margin, $img, 'ajax-item aksrt-ajax-null');
|
||||
}
|
||||
return '<div class="text-center ajax-item aksrt-ajax-null" style="padding:' . esc_attr((string) $margin) . 'px 0;">'
|
||||
. '<p class="muted-3-color">' . esc_html((string) $text) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_ajax_next_paginate')) {
|
||||
/**
|
||||
* Load-more style paginate block (zibll zib_get_ajax_next_paginate subset).
|
||||
*
|
||||
* @param int $count_all Total items.
|
||||
* @param int $page Current page (1-based).
|
||||
* @param int $per_page Per page.
|
||||
* @param string $ajax_url Next page URL (full or REST).
|
||||
* @param string $pag_class Wrapper class.
|
||||
* @param string $next_class Link class.
|
||||
* @param string $label Button label.
|
||||
*/
|
||||
function aksrt_get_ajax_next_paginate(
|
||||
int $count_all,
|
||||
int $page = 1,
|
||||
int $per_page = 10,
|
||||
string $ajax_url = '',
|
||||
string $pag_class = 'text-center theme-pagination ajax-pag',
|
||||
string $next_class = 'next-page ajax-next',
|
||||
string $label = ''
|
||||
): string {
|
||||
$page = max(1, $page);
|
||||
$per_page = max(1, $per_page);
|
||||
$max = (int) ceil($count_all / $per_page);
|
||||
if ($page >= $max || $count_all <= 0) {
|
||||
return '<div class="' . esc_attr($pag_class) . ' hide"><div class="' . esc_attr($next_class) . '"><a href="#"></a></div></div>';
|
||||
}
|
||||
if ($label === '') {
|
||||
$label = __('加载更多', 'aksrt');
|
||||
}
|
||||
$next = $page + 1;
|
||||
$url = $ajax_url !== '' ? $ajax_url : '#';
|
||||
// Prefer caller-provided URL already containing next page.
|
||||
return '<div class="' . esc_attr($pag_class) . '">'
|
||||
. '<div class="' . esc_attr($next_class) . '">'
|
||||
. '<a href="' . esc_url($url) . '" class="ajax-next" data-page="' . esc_attr((string) $next) . '">'
|
||||
. esc_html($label)
|
||||
. '</a></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_remote_modal_link')) {
|
||||
/**
|
||||
* Build a remote-modal trigger (zibll zib_get_refresh_modal_link subset).
|
||||
*
|
||||
* @param array{
|
||||
* tag?:string,class?:string,text?:string,url?:string,
|
||||
* data_class?:string,height?:int,mobile_bottom?:bool,
|
||||
* query_arg?:array<string,scalar>
|
||||
* } $args
|
||||
*/
|
||||
function aksrt_get_remote_modal_link(array $args = []): string {
|
||||
$tag = sanitize_key((string) ($args['tag'] ?? 'a'));
|
||||
if (!in_array($tag, ['a', 'button', 'span'], true)) {
|
||||
$tag = 'a';
|
||||
}
|
||||
$class = trim((string) ($args['class'] ?? ''));
|
||||
$text = (string) ($args['text'] ?? __('打开', 'aksrt'));
|
||||
$url = (string) ($args['url'] ?? '');
|
||||
if ($url === '' && !empty($args['query_arg']) && is_array($args['query_arg'])) {
|
||||
// Prefer REST absolute URL if provided via action helper.
|
||||
$url = add_query_arg($args['query_arg'], admin_url('admin-ajax.php'));
|
||||
}
|
||||
$data_class = (string) ($args['data_class'] ?? 'modal-mini full-sm');
|
||||
$height = isset($args['height']) ? (int) $args['height'] : 0;
|
||||
$mobile = !isset($args['mobile_bottom']) || !empty($args['mobile_bottom']);
|
||||
|
||||
$attrs = ' class="' . esc_attr(trim($class . ' aksrt-remote-modal-link')) . '"';
|
||||
$attrs .= ' data-toggle="AksrtRemoteModal"';
|
||||
$attrs .= ' data-remote="' . esc_url($url) . '"';
|
||||
$attrs .= ' data-class="' . esc_attr($data_class) . '"';
|
||||
if ($height > 0) {
|
||||
$attrs .= ' data-height="' . esc_attr((string) $height) . '"';
|
||||
}
|
||||
if ($mobile) {
|
||||
$attrs .= ' mobile-bottom="true"';
|
||||
}
|
||||
if ($tag === 'a') {
|
||||
$attrs .= ' href="' . esc_url($url !== '' ? $url : '#') . '"';
|
||||
} else {
|
||||
$attrs .= ' type="button"';
|
||||
}
|
||||
|
||||
return '<' . $tag . $attrs . '>' . esc_html($text) . '</' . $tag . '>';
|
||||
}
|
||||
}
|
||||
@@ -373,6 +373,12 @@ class ShopController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/review-modal', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_product_review_modal'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/(?P<id>\d+)/favorite', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'toggle_frontend_product_favorite'],
|
||||
|
||||
@@ -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 {
|
||||
@@ -533,11 +550,35 @@ trait ShopControllerFrontendFlowOperations {
|
||||
return $this->error($result['message'] ?? __('评价失败', 'aksrt'));
|
||||
}
|
||||
return $this->success([
|
||||
'message' => __('评价已提交', 'aksrt'),
|
||||
'message' => __('评价成功', 'aksrt'),
|
||||
'data' => $result,
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET review modal HTML for an order (zibll order_comment_modal parity).
|
||||
*/
|
||||
public function get_frontend_product_review_modal(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\ProductReview')) {
|
||||
return $this->error(__('评价服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$result = \AKSRT\Shop\ProductReview::get_instance()->get_review_modal_html(
|
||||
get_current_user_id(),
|
||||
$order_number
|
||||
);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('无法打开评价', 'aksrt')));
|
||||
}
|
||||
return $this->success([
|
||||
'html' => (string) ($result['html'] ?? ''),
|
||||
'product_id' => (int) ($result['product_id'] ?? 0),
|
||||
'need_shipping' => !empty($result['need_shipping']),
|
||||
'img_max' => (int) ($result['img_max'] ?? 0),
|
||||
'remain_seconds' => (int) ($result['remain_seconds'] ?? 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create_frontend_product(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$params['author_id'] = get_current_user_id();
|
||||
|
||||
@@ -117,7 +117,7 @@ trait ScriptsPart1b {
|
||||
}
|
||||
|
||||
// 4a-b. 全局补充样式(骨架屏、空状态、分页、加载指示器)
|
||||
foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner'] as $slug) {
|
||||
foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner', 'remote-modal'] as $slug) {
|
||||
$css_path = get_template_directory() . '/assets/css/frontend/' . $slug . '.css';
|
||||
if (file_exists($css_path)) {
|
||||
wp_enqueue_style(
|
||||
@@ -419,7 +419,7 @@ trait ScriptsPart1b {
|
||||
wp_enqueue_script(
|
||||
'aksrt-order-center',
|
||||
$base_uri . '/assets/js/order-center.js',
|
||||
['aksrt-script', 'aksrt-weixin-share'],
|
||||
['aksrt-script', 'aksrt-weixin-share', 'aksrt-core-remote-modal'],
|
||||
file_exists(get_template_directory() . '/assets/js/order-center.js')
|
||||
? (string) filemtime(get_template_directory() . '/assets/js/order-center.js')
|
||||
: $version,
|
||||
|
||||
@@ -118,6 +118,9 @@ trait ScriptsPart1c {
|
||||
'aksrt-core-comments' => 'comments.js',
|
||||
'aksrt-core-comment-fixed' => 'comment-fixed-input.js',
|
||||
'aksrt-core-comment-move' => 'comment-move-form.js',
|
||||
// Shared zibll-parity list/modal engines (before pagination consumers).
|
||||
'aksrt-core-ajax-list' => 'ajax-list.js',
|
||||
'aksrt-core-remote-modal' => 'remote-modal.js',
|
||||
'aksrt-core-pagination' => 'pagination.js',
|
||||
'aksrt-core-archive' => 'archive.js',
|
||||
'aksrt-core-footer-tabbar' => 'footer-tabbar.js',
|
||||
|
||||
@@ -250,8 +250,8 @@ class Forum {
|
||||
$this->view_builder()->render_board_cards($boards);
|
||||
}
|
||||
|
||||
public function render_topic_row(array $topic): void {
|
||||
$this->view_builder()->render_topic_row($topic);
|
||||
public function render_topic_row(array $topic, string $style = 'detail'): void {
|
||||
$this->view_builder()->render_topic_row($topic, $style);
|
||||
}
|
||||
|
||||
public function get_board_view(int $board_id, array $args = []): array {
|
||||
|
||||
+181
-37
@@ -435,58 +435,97 @@ class ViewBuilder {
|
||||
|
||||
/**
|
||||
* Render a single topic row (reusable for AJAX and initial render).
|
||||
*
|
||||
* @param array $topic Topic payload.
|
||||
* @param string $style detail|mini (zibll forum-posts parity).
|
||||
*/
|
||||
public function render_topic_row(array $topic): void {
|
||||
public function render_topic_row(array $topic, string $style = 'detail'): void {
|
||||
$style = $style === 'mini' ? 'mini' : 'detail';
|
||||
$board = !empty($topic['boards'][0]) ? $topic['boards'][0] : null;
|
||||
$names = $this->forum->get_config()['names'] ?? [];
|
||||
$sticky_level = (int) ($topic['sticky_level'] ?? (!empty($topic['sticky']) ? 1 : 0));
|
||||
$row_class = 'aksrt-forum-topic-row';
|
||||
$permalink = (string) ($topic['permalink'] ?? '#');
|
||||
$cover = is_array($topic['cover'] ?? null) ? $topic['cover'] : null;
|
||||
$vote = is_array($topic['vote'] ?? null) ? $topic['vote'] : null;
|
||||
$excerpt = trim((string) ($topic['excerpt'] ?? ''));
|
||||
$topic_cat = is_array($topic['topic_cat'] ?? null) ? $topic['topic_cat'] : null;
|
||||
|
||||
$row_class = 'aksrt-forum-topic-row aksrt-forum-posts forum-posts ajax-item ' . $style;
|
||||
if ($sticky_level > 0) {
|
||||
$row_class .= ' is-sticky is-sticky-' . $sticky_level;
|
||||
}
|
||||
if (!empty($topic['featured'])) {
|
||||
$row_class .= ' is-featured';
|
||||
}
|
||||
$cover = $topic['cover'] ?? null;
|
||||
|
||||
$badges = '';
|
||||
if ($sticky_level > 0) {
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--sticky badge-topping">'
|
||||
. esc_html((string) ($topic['sticky_label'] ?? __('置顶', 'aksrt'))) . '</span>';
|
||||
}
|
||||
if (!empty($topic['featured'])) {
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--featured badge-essence">'
|
||||
. esc_html__('精华', 'aksrt') . '</span>';
|
||||
}
|
||||
if (($topic['bbs_type'] ?? '') === 'question') {
|
||||
$q_status = (int) ($topic['question_status'] ?? 0);
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--question'
|
||||
. ($q_status === 1 ? ' is-solved' : '') . '">'
|
||||
. esc_html($q_status === 1
|
||||
? (string) ($names['question_ok_badge'] ?? __('已解决', 'aksrt'))
|
||||
: (string) ($names['question_badge'] ?? __('提问', 'aksrt')))
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
$title_html = $badges
|
||||
. '<h3 class="aksrt-forum-topic-row__title forum-title">'
|
||||
. '<a href="' . esc_url($permalink) . '">' . esc_html((string) ($topic['title'] ?? '')) . '</a></h3>';
|
||||
|
||||
if ($style === 'mini') {
|
||||
?>
|
||||
<article class="<?php echo esc_attr($row_class); ?>" data-topic-id="<?php echo esc_attr((string) ($topic['id'] ?? 0)); ?>">
|
||||
<div class="aksrt-forum-topic-row__avatar forum-user">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="40" height="40" loading="lazy" class="forum-avatar">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__body entry-info">
|
||||
<div class="aksrt-forum-topic-row__title-wrap"><?php echo $title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<div class="aksrt-forum-topic-row__meta item-meta flex ac jsb">
|
||||
<div class="meta-left">
|
||||
<span class="aksrt-forum-topic-row__author"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__time icon-circle"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></span>
|
||||
</div>
|
||||
<div class="meta-right action-meta">
|
||||
<span title="<?php esc_attr_e('浏览', 'aksrt'); ?>"><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('回复', 'aksrt'); ?>"><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('评分', 'aksrt'); ?>"><i class="fas fa-thumbs-up"></i> <?php echo esc_html(number_format_i18n((int) ($topic['score'] ?? 0))); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
// detail
|
||||
$list_imgs = $this->extract_topic_list_images($topic, 4);
|
||||
?>
|
||||
<article class="<?php echo esc_attr($row_class); ?>">
|
||||
<div class="aksrt-forum-topic-row__avatar">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="40" height="40" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__body">
|
||||
<div class="aksrt-forum-topic-row__title-wrap">
|
||||
<?php if ($sticky_level > 0) : ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--sticky"><?php echo esc_html((string) ($topic['sticky_label'] ?? __('置顶', 'aksrt'))); ?></span>
|
||||
<article class="<?php echo esc_attr($row_class); ?>" data-topic-id="<?php echo esc_attr((string) ($topic['id'] ?? 0)); ?>">
|
||||
<div class="aksrt-forum-topic-row__author-card mb10 flex ac">
|
||||
<div class="aksrt-forum-topic-row__avatar">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="44" height="44" loading="lazy">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($topic['featured'])) : ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--featured"><?php esc_html_e('精华', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (($topic['bbs_type'] ?? '') === 'question') : ?>
|
||||
<?php $q_status = (int) ($topic['question_status'] ?? 0); ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--question<?php echo $q_status === 1 ? ' is-solved' : ''; ?>">
|
||||
<?php echo $q_status === 1
|
||||
? esc_html($names['question_ok_badge'] ?? __('已解决', 'aksrt'))
|
||||
: esc_html($names['question_badge'] ?? __('提问', 'aksrt')); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<h3 class="aksrt-forum-topic-row__title">
|
||||
<a href="<?php echo esc_url((string) ($topic['permalink'] ?? '#')); ?>"><?php echo esc_html((string) ($topic['title'] ?? '')); ?></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__meta">
|
||||
<?php if ($board) : ?>
|
||||
<a class="aksrt-forum-topic-row__board" href="<?php echo esc_url((string) ($board['url'] ?? '#')); ?>"><?php echo esc_html((string) ($board['name'] ?? '')); ?></a>
|
||||
<?php endif; ?>
|
||||
<span class="aksrt-forum-topic-row__author"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__time"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></span>
|
||||
<span><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></span>
|
||||
<div class="aksrt-forum-topic-row__author-meta">
|
||||
<div class="aksrt-forum-topic-row__author-name"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></div>
|
||||
<div class="aksrt-forum-topic-row__time muted-2-color em09"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (is_array($cover) && !empty($cover['type'])) : ?>
|
||||
<div class="aksrt-forum-topic-row__cover">
|
||||
<a class="aksrt-forum-topic-row__cover forum-lists-cover" href="<?php echo esc_url($permalink); ?>">
|
||||
<?php if (($cover['type'] ?? '') === 'video') : ?>
|
||||
<?php if (!empty($cover['poster'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $cover['poster']); ?>" alt="" loading="lazy">
|
||||
@@ -498,12 +537,117 @@ class ViewBuilder {
|
||||
<?php elseif (!empty($cover['url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $cover['url']); ?>" alt="" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-forum-topic-row__body">
|
||||
<div class="aksrt-forum-topic-row__title-wrap"><?php echo $title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
|
||||
<div class="content mt6">
|
||||
<?php if ($topic_cat && !empty($topic_cat['name'])) : ?>
|
||||
<a class="aksrt-forum-topic-row__topic-link focus-color mr3" href="<?php echo esc_url((string) ($topic_cat['url'] ?? '#')); ?>">
|
||||
<i class="fas fa-hashtag"></i><?php echo esc_html((string) $topic_cat['name']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($excerpt !== '') : ?>
|
||||
<div class="excerpt"><?php echo esc_html($excerpt); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($list_imgs) && empty($cover)) : ?>
|
||||
<div class="imgbox-container lists-imgs mt6 count-<?php echo esc_attr((string) count($list_imgs)); ?>">
|
||||
<?php foreach ($list_imgs as $img_url) : ?>
|
||||
<a class="lists-imgs__item cursor-zoom-in" href="<?php echo esc_url($permalink); ?>">
|
||||
<img src="<?php echo esc_url($img_url); ?>" alt="" loading="lazy">
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($vote['enabled']) && !empty($vote['options']) && is_array($vote['options'])) : ?>
|
||||
<div class="aksrt-forum-topic-row__vote mt10">
|
||||
<?php
|
||||
$total_votes = 0;
|
||||
foreach ($vote['options'] as $opt) {
|
||||
$total_votes += (int) ($opt['count'] ?? 0);
|
||||
}
|
||||
foreach (array_slice($vote['options'], 0, 4) as $opt) :
|
||||
$cnt = (int) ($opt['count'] ?? 0);
|
||||
$pct = $total_votes > 0 ? round($cnt * 100 / $total_votes) : 0;
|
||||
?>
|
||||
<div class="aksrt-forum-topic-row__vote-opt">
|
||||
<span class="aksrt-forum-topic-row__vote-label"><?php echo esc_html((string) ($opt['label'] ?? $opt['text'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__vote-bar"><i style="width:<?php echo esc_attr((string) $pct); ?>%"></i></span>
|
||||
<span class="aksrt-forum-topic-row__vote-pct"><?php echo esc_html((string) $pct); ?>%</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex ac jsb mt10 aksrt-forum-topic-row__footer">
|
||||
<?php if ($board) : ?>
|
||||
<a class="but but-plate aksrt-forum-topic-row__board" href="<?php echo esc_url((string) ($board['url'] ?? '#')); ?>">
|
||||
<?php echo esc_html((string) ($board['name'] ?? '')); ?>
|
||||
<i class="fa fa-angle-right ml6"></i>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="plate-null"></span>
|
||||
<?php endif; ?>
|
||||
<div class="action-meta flex0 flex jsb aksrt-forum-topic-row__actions">
|
||||
<span title="<?php esc_attr_e('评分', 'aksrt'); ?>"><i class="fas fa-thumbs-up"></i> <?php echo esc_html(number_format_i18n((int) ($topic['score'] ?? 0))); ?></span>
|
||||
<a href="<?php echo esc_url($permalink); ?>#comments" title="<?php esc_attr_e('回复', 'aksrt'); ?>"><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></a>
|
||||
<span title="<?php esc_attr_e('浏览', 'aksrt'); ?>"><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('收藏', 'aksrt'); ?>"><i class="fas fa-star"></i> <?php echo esc_html(number_format_i18n((int) ($topic['favorite_count'] ?? 0))); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull up to N image URLs from cover slides or content HTML for list grids.
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
private function extract_topic_list_images(array $topic, int $max = 4): array {
|
||||
$max = max(1, min(6, $max));
|
||||
$out = [];
|
||||
$cover = is_array($topic['cover'] ?? null) ? $topic['cover'] : null;
|
||||
if ($cover) {
|
||||
if (($cover['type'] ?? '') === 'slide' && !empty($cover['images']) && is_array($cover['images'])) {
|
||||
foreach ($cover['images'] as $u) {
|
||||
$u = esc_url_raw((string) $u);
|
||||
if ($u !== '') {
|
||||
$out[] = $u;
|
||||
}
|
||||
if (count($out) >= $max) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($cover['url'])) {
|
||||
$out[] = esc_url_raw((string) $cover['url']);
|
||||
}
|
||||
}
|
||||
if (count($out) >= $max) {
|
||||
return $out;
|
||||
}
|
||||
$content = (string) ($topic['content'] ?? '');
|
||||
if ($content !== '' && preg_match_all('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $m)) {
|
||||
foreach ($m[1] as $src) {
|
||||
$src = esc_url_raw((string) $src);
|
||||
if ($src === '' || in_array($src, $out, true)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = $src;
|
||||
if (count($out) >= $max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function get_user_forum_topics(int $user_id, array $args = []): array {
|
||||
$status = $args['status'] ?? 'publish';
|
||||
$page = max(1, (int) ($args['page'] ?? 1));
|
||||
|
||||
+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,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,12 @@ class ProductReview {
|
||||
$images = [];
|
||||
$max_images = $this->get_max_images();
|
||||
foreach ((array) ($data['images'] ?? []) as $img) {
|
||||
if (count($images) >= $max_images) break;
|
||||
$url = esc_url_raw((string) $img);
|
||||
if ($url !== '') {
|
||||
$images[] = $url;
|
||||
if (count($images) >= $max_images) {
|
||||
break;
|
||||
}
|
||||
$stored = $this->normalize_review_image((string) $img, $user_id);
|
||||
if ($stored !== '') {
|
||||
$images[] = $stored;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,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);
|
||||
@@ -301,9 +304,209 @@ class ProductReview {
|
||||
}
|
||||
}
|
||||
|
||||
private function get_max_images(): int {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) return 9;
|
||||
/**
|
||||
* Remaining seconds until auto-positive review, or 0 if unknown/closed.
|
||||
*/
|
||||
public function get_auto_review_remaining_seconds(string $order_number): int {
|
||||
if (!class_exists('\AKSRT\Payment\OrderRepository') || !class_exists('\AKSRT\Settings\Settings')) {
|
||||
return 0;
|
||||
}
|
||||
$order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number);
|
||||
if (!$order) {
|
||||
return 0;
|
||||
}
|
||||
$anchor = (string) ($order['delivered_at'] ?? $order['completed_at'] ?? $order['updated_at'] ?? $order['created_at'] ?? '');
|
||||
if ($anchor === '') {
|
||||
return 0;
|
||||
}
|
||||
$shop = \AKSRT\Settings\Settings::get_shop_settings();
|
||||
return max(1, min(20, (int) ($shop['timing']['comment_img_num'] ?? 9)));
|
||||
$max_day = max(1, (int) ($shop['timing']['comment_max_day'] ?? 15));
|
||||
$deadline = strtotime($anchor . ' +' . $max_day . ' days');
|
||||
if (!$deadline) {
|
||||
return 0;
|
||||
}
|
||||
$remain = $deadline - time();
|
||||
return $remain > 0 ? (int) $remain : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether shipping score row should be shown for this order.
|
||||
*/
|
||||
public function order_needs_shipping_score(array $order): bool {
|
||||
$method = is_array($order['shipping_method'] ?? null) ? $order['shipping_method'] : [];
|
||||
$value = strtolower((string) ($method['value'] ?? $method['type'] ?? $order['delivery_type'] ?? ''));
|
||||
if ($value === '') {
|
||||
// Prefer showing logistics when shipping records or fee exist.
|
||||
if (!empty($order['shipping_records']) || !empty($order['shipping']['count'])) {
|
||||
return true;
|
||||
}
|
||||
$fee = (float) ($order['shipping_fee'] ?? ($order['shipping_method']['fee'] ?? 0));
|
||||
return $fee > 0;
|
||||
}
|
||||
if (in_array($value, ['virtual', 'auto', 'none', 'offline', 'self', 'pickup', 'no_shipping'], true)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build order review modal HTML (zibll order_comment_modal parity).
|
||||
*
|
||||
* @return array{success:bool,html?:string,message?:string}
|
||||
*/
|
||||
public function get_review_modal_html(int $user_id, string $order_number): array {
|
||||
if ($user_id <= 0) {
|
||||
return ['success' => false, 'message' => __('请先登录', 'aksrt')];
|
||||
}
|
||||
if ($order_number === '' || !class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
return ['success' => false, 'message' => __('订单不存在', 'aksrt')];
|
||||
}
|
||||
|
||||
$order = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number);
|
||||
if (!$order || (int) ($order['user_id'] ?? 0) !== $user_id) {
|
||||
return ['success' => false, 'message' => __('订单数据异常', 'aksrt')];
|
||||
}
|
||||
|
||||
$product_id = 0;
|
||||
$items = class_exists('\AKSRT\Shop\Shop')
|
||||
? Shop::get_instance()->get_order_items($order_number)
|
||||
: [];
|
||||
if (!empty($items[0]['product_id'])) {
|
||||
$product_id = (int) $items[0]['product_id'];
|
||||
} elseif (!empty($order['product_id'])) {
|
||||
$product_id = (int) $order['product_id'];
|
||||
}
|
||||
|
||||
if ($product_id <= 0) {
|
||||
return ['success' => false, 'message' => __('订单商品无效', 'aksrt')];
|
||||
}
|
||||
|
||||
if ($this->has_reviewed($user_id, $order_number, $product_id)) {
|
||||
return ['success' => false, 'message' => __('订单已评价', 'aksrt')];
|
||||
}
|
||||
|
||||
if (!$this->can_review($user_id, $order_number, $product_id)) {
|
||||
return ['success' => false, 'message' => __('当前订单不可评价', 'aksrt')];
|
||||
}
|
||||
|
||||
$shop = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_shop_settings()
|
||||
: [];
|
||||
$placeholder = (string) ($shop['timing']['comment_placeholder'] ?? '');
|
||||
if ($placeholder === '') {
|
||||
$placeholder = __('展开说说对商品的看法吧~', 'aksrt');
|
||||
}
|
||||
$img_num = max(0, min(9, (int) ($shop['timing']['comment_img_num'] ?? 3)));
|
||||
$need_shipping = $this->order_needs_shipping_score($order);
|
||||
$remain = $this->get_auto_review_remaining_seconds($order_number);
|
||||
|
||||
$score_html = '';
|
||||
$score_html .= $this->render_score_row(__('商品评分', 'aksrt'), 'product');
|
||||
if ($need_shipping) {
|
||||
$score_html .= $this->render_score_row(__('物流服务', 'aksrt'), 'shipping');
|
||||
}
|
||||
$score_html .= $this->render_score_row(__('服务态度', 'aksrt'), 'service');
|
||||
|
||||
$img_html = '';
|
||||
if ($img_num > 0) {
|
||||
$img_html = '<div class="form-upload mb10">';
|
||||
$img_html .= '<div class="em09 muted-2-color mb6">' . esc_html__('图片', 'aksrt') . '</div>';
|
||||
$img_html .= '<div class="preview" data-aksrt-review-preview data-max="' . esc_attr((string) $img_num) . '">';
|
||||
$img_html .= '<div class="add" data-aksrt-review-add role="button" tabindex="0" aria-label="' . esc_attr__('添加图片', 'aksrt') . '"></div>';
|
||||
$img_html .= '</div>';
|
||||
$img_html .= '<input class="hide" type="file" data-aksrt-review-file multiple accept="image/gif,image/jpeg,image/jpg,image/png,image/webp" />';
|
||||
$img_html .= '</div>';
|
||||
}
|
||||
|
||||
$title = '<b>' . esc_html__('评价订单', 'aksrt') . '</b>';
|
||||
if ($remain > 0) {
|
||||
$title .= '<span class="px12 muted-2-color ml6">'
|
||||
. '<span data-aksrt-review-countdown="' . esc_attr((string) $remain) . '">' . esc_html($this->format_countdown($remain)) . '</span>'
|
||||
. esc_html__('后自动好评', 'aksrt')
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
$html = '<div class="touch border-title">' . $title . '</div>';
|
||||
$html .= '<button type="button" class="close abs-close aksrt-remote-modal__close" data-aksrt-remote-modal-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">×</button>';
|
||||
$html .= '<form class="order-comment-from form-upload" data-aksrt-order-review-form data-order-number="' . esc_attr($order_number) . '" data-product-id="' . esc_attr((string) $product_id) . '" data-need-shipping="' . ($need_shipping ? '1' : '0') . '">';
|
||||
$html .= '<div class="mb20">' . $score_html . '</div>';
|
||||
$html .= '<div class="mb20"><textarea class="form-control" name="comment" rows="4" placeholder="' . esc_attr($placeholder) . '"></textarea></div>';
|
||||
$html .= $img_html;
|
||||
$html .= '<div class="review-error" data-aksrt-review-error hidden></div>';
|
||||
$html .= '<div class="but-average modal-buts">';
|
||||
$html .= '<button type="button" class="but" data-aksrt-remote-modal-close>' . esc_html__('取消', 'aksrt') . '</button>';
|
||||
$html .= '<button type="submit" class="but c-blue btn-primary" data-aksrt-review-submit><i class="fa fa-check" aria-hidden="true"></i> ' . esc_html__('确认提交', 'aksrt') . '</button>';
|
||||
$html .= '</div></form>';
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
'product_id' => $product_id,
|
||||
'need_shipping' => $need_shipping,
|
||||
'img_max' => $img_num,
|
||||
'remain_seconds' => $remain,
|
||||
];
|
||||
}
|
||||
|
||||
private function render_score_row(string $label, string $key): string {
|
||||
return '<div class="mb10 shop-score-box flex ac" data-aksrt-score-box data-score-key="' . esc_attr($key) . '" score-value="0">'
|
||||
. '<div class="em09 muted-2-color score-title">' . esc_html($label) . '</div>'
|
||||
. '<div class="score-icon em16 mr10 ml20" style="height:30px;"></div>'
|
||||
. '<div class="score-text em09 muted-2-color"></div>'
|
||||
. '<input class="score-input" type="hidden" name="score[' . esc_attr($key) . ']" value="0">'
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
private function format_countdown(int $seconds): string {
|
||||
$seconds = max(0, $seconds);
|
||||
$d = intdiv($seconds, 86400);
|
||||
$h = intdiv($seconds % 86400, 3600);
|
||||
$m = intdiv($seconds % 3600, 60);
|
||||
$s = $seconds % 60;
|
||||
if ($d > 0) {
|
||||
return sprintf('%d天%02d:%02d:%02d', $d, $h, $m, $s);
|
||||
}
|
||||
return sprintf('%02d:%02d:%02d', $h, $m, $s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept https URL or data:image/*;base64,... and return a public URL.
|
||||
*/
|
||||
private function normalize_review_image(string $raw, int $user_id): string {
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('#^https?://#i', $raw)) {
|
||||
return esc_url_raw($raw);
|
||||
}
|
||||
if (!preg_match('#^data:image/(png|jpe?g|gif|webp);base64,#i', $raw, $m)) {
|
||||
return '';
|
||||
}
|
||||
$binary = base64_decode(substr($raw, strpos($raw, ',') + 1), true);
|
||||
if ($binary === false || $binary === '') {
|
||||
return '';
|
||||
}
|
||||
if (strlen($binary) > 5 * 1024 * 1024) {
|
||||
return '';
|
||||
}
|
||||
$ext = strtolower($m[1]);
|
||||
if ($ext === 'jpeg') {
|
||||
$ext = 'jpg';
|
||||
}
|
||||
$filename = 'review-' . $user_id . '-' . wp_generate_password(8, false) . '.' . $ext;
|
||||
$upload = wp_upload_bits($filename, null, $binary);
|
||||
if (!empty($upload['error']) || empty($upload['url'])) {
|
||||
return '';
|
||||
}
|
||||
return esc_url_raw((string) $upload['url']);
|
||||
}
|
||||
|
||||
private function get_max_images(): int {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return 9;
|
||||
}
|
||||
$shop = \AKSRT\Settings\Settings::get_shop_settings();
|
||||
return max(0, min(20, (int) ($shop['timing']['comment_img_num'] ?? 9)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ $tabs = [
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-receipt="<?php echo esc_attr($on); ?>"><?php esc_html_e('确认收货', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($os === \AKSRT\Payment\Order::STATUS_PENDING_REVIEW) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-review="<?php echo esc_attr($on); ?>"><?php esc_html_e('去评价', 'aksrt'); ?></button>
|
||||
<button type="button" class="btn-primary btn-sm c-green" data-aksrt-order-review="<?php echo esc_attr($on); ?>"><?php esc_html_e('评价', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['actions']['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-order-cancel="<?php echo esc_attr($on); ?>"><?php esc_html_e('取消', 'aksrt'); ?></button>
|
||||
|
||||
@@ -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>
|
||||
+108
-84
@@ -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'])) : '';
|
||||
$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'];
|
||||
$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,
|
||||
'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,95 +37,108 @@ $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">
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
|
||||
<div class="aksrt-product-archive container"
|
||||
data-aksrt-product-archive
|
||||
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-page="<?php echo esc_attr((string) $paged); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
|
||||
data-paging="<?php echo esc_attr($paging); ?>"
|
||||
data-ias="<?php echo $ias_s ? '1' : '0'; ?>"
|
||||
data-ias-max="<?php echo esc_attr((string) $ias_max); ?>"
|
||||
data-style="<?php echo esc_attr($style); ?>">
|
||||
<div class="aksrt-product-archive container"
|
||||
data-aksrt-product-archive
|
||||
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); ?>"
|
||||
data-ias="<?php echo $ias_s ? '1' : '0'; ?>"
|
||||
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>
|
||||
<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>
|
||||
<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" data-aksrt-archive-title><?php echo esc_html($term_name); ?></h1>
|
||||
<?php if ($term_desc) : ?>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<?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>
|
||||
|
||||
<?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 foreach ($products as $product) :
|
||||
get_template_part('templates/parts/shop/product', 'card', [
|
||||
'product' => $product,
|
||||
'sales_cfg' => $sales_cfg,
|
||||
]);
|
||||
endforeach; ?>
|
||||
</div>
|
||||
<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 ($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>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
'total' => $max_pages,
|
||||
'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] : [],
|
||||
]);
|
||||
?>
|
||||
</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>
|
||||
<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 && $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>
|
||||
</div>
|
||||
<?php elseif ($max_pages > 1) : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
'total' => $max_pages,
|
||||
'current' => $paged,
|
||||
'prev_text' => '<i class="fas fa-chevron-left"></i>',
|
||||
'next_text' => '<i class="fas fa-chevron-right"></i>',
|
||||
'add_args' => array_filter([
|
||||
'orderby' => $active_orderby,
|
||||
'order' => $active_orderby === 'price' ? $active_order : null,
|
||||
]),
|
||||
]);
|
||||
?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</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();
|
||||
|
||||
+130
-82
@@ -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'])) : '';
|
||||
$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'];
|
||||
$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,
|
||||
'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,95 +37,132 @@ $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'),
|
||||
$order_labels = [
|
||||
'date' => __('最新', 'aksrt'),
|
||||
'views' => __('最热', 'aksrt'),
|
||||
'favorite_count' => __('收藏', 'aksrt'),
|
||||
'score' => __('评分', 'aksrt'),
|
||||
'sales' => __('销量', '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">
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
|
||||
|
||||
<div class="aksrt-product-archive container"
|
||||
data-aksrt-product-archive
|
||||
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-page="<?php echo esc_attr((string) $paged); ?>"
|
||||
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
|
||||
data-paging="<?php echo esc_attr($paging); ?>"
|
||||
data-ias="<?php echo $ias_s ? '1' : '0'; ?>"
|
||||
data-ias-max="<?php echo esc_attr((string) $ias_max); ?>"
|
||||
data-style="<?php echo esc_attr($style); ?>">
|
||||
<div class="aksrt-product-archive container"
|
||||
data-aksrt-product-archive
|
||||
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); ?>"
|
||||
data-ias="<?php echo $ias_s ? '1' : '0'; ?>"
|
||||
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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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="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 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;
|
||||
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>
|
||||
|
||||
<?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 foreach ($products as $product) :
|
||||
get_template_part('templates/parts/shop/product', 'card', [
|
||||
'product' => $product,
|
||||
'sales_cfg' => $sales_cfg,
|
||||
]);
|
||||
endforeach; ?>
|
||||
</div>
|
||||
<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 ($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>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
'total' => $max_pages,
|
||||
'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] : [],
|
||||
]);
|
||||
?>
|
||||
</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>
|
||||
<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 && $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>
|
||||
</div>
|
||||
<?php elseif ($max_pages > 1) : ?>
|
||||
<nav class="aksrt-product-archive__pagination" aria-label="<?php esc_attr_e('商品分页', 'aksrt'); ?>">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
'total' => $max_pages,
|
||||
'current' => $paged,
|
||||
'prev_text' => '<i class="fas fa-chevron-left"></i>',
|
||||
'next_text' => '<i class="fas fa-chevron-right"></i>',
|
||||
'add_args' => array_filter([
|
||||
'orderby' => $active_orderby,
|
||||
'order' => $active_orderby === 'price' ? $active_order : null,
|
||||
]),
|
||||
]);
|
||||
?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</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