所有商品 - 分页网格
:root {
--gap: 20px;
--cols: 4;
--text: #222;
--muted: #666;
--border: #eee;
--accent: #e11;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background: #fff;
}
.container {
max-width: 1200px;
padding: 24px;
margin: 0 auto;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.toolbar .count {
color: var(--muted);
font-size: 14px;
}
.grid {
display: grid;
gap: var(--gap);
grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
}
@media (max-width: 1024px) { :root { --cols: 3; } }
@media (max-width: 768px) { :root { --cols: 2; } }
@media (max-width: 480px) { :root { --cols: 1; } }
.card {
text-decoration: none;
color: inherit;
display: block;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: #fff;
transition: box-shadow .2s ease, transform .02s ease;
}
.card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.06); transform: translateY(-1px); }
.thumb {
width: 100%;
aspect-ratio: 1/1;
object-fit: cover;
display: block;
background: #f6f6f6;
}
.content {
padding: 12px;
display: grid;
gap: 8px;
}
.title {
font-size: 14px;
line-height: 1.4;
min-height: 38px;
}
.price {
font-weight: 700;
color: var(--accent);
min-height: 20px;
}
.sku {
font-size: 12px;
color: var(--muted);
}
.pagination {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-top: 22px;
}
.pagination button, .pagination a {
border: 1px solid var(--border);
background: #fff;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
color: var(--text);
text-decoration: none;
min-width: 40px;
text-align: center;
}
.pagination .active {
background: #111;
color: #fff;
border-color: #111;
}
.pagination button[disabled] { opacity: .5; cursor: not-allowed; }
.empty {
text-align: center;
color: var(--muted);
padding: 48px 0;
}
.controls {
display: flex;
gap: 10px;
align-items: center;
}
select {
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: #fff;
}
// 基础配置:如与 WordPress 同域,留空即可
const baseUrl = ''; // 例如不同域时可写 'https://example.com'
const storeApi = (baseUrl || '') + '/wp-json/wc/store';
const productsEndpoint = storeApi + '/products';
// 状态
const state = {
page: 1,
perPage: 12,
orderby: 'date',
total: 0,
totalPages: 0
};
// DOM
const gridEl = document.getElementById('js-grid');
const emptyEl = document.getElementById('js-empty');
const pgEl = document.getElementById('js-pagination');
const countEl = document.getElementById('js-count');
const perPageEl = document.getElementById('js-per-page');
const orderbyEl = document.getElementById('js-orderby');
// 初始化
perPageEl.addEventListener('change', () => {
state.perPage = parseInt(perPageEl.value, 10) || 12;
state.page = 1;
fetchAndRender();
});
orderbyEl.addEventListener('change', () => {
state.orderby = orderbyEl.value;
state.page = 1;
fetchAndRender();
});
function buildQuery(params) {
const q = new URLSearchParams(params);
return '?' + q.toString();
}
function imgSrcFromImages(images) {
if (Array.isArray(images) && images.length) return images[0].src;
return '';
}
function priceHtml(p) {
// Woo Store API already returns formatted price_html
return p?.price_html || '';
}
function productLink(p) {
return p?.permalink || '#';
}
function renderProducts(items) {
gridEl.innerHTML = '';
if (!items || !items.length) {
gridEl.style.display = 'none';
emptyEl.style.display = 'block';
return;
}
gridEl.style.display = 'grid';
emptyEl.style.display = 'none';
const frag = document.createDocumentFragment();
items.forEach(p => {
const a = document.createElement('a');
a.className = 'card';
a.href = productLink(p);
const img = document.createElement('img');
img.className = 'thumb';
img.alt = p.name || '';
img.src = imgSrcFromImages(p.images);
const content = document.createElement('div');
content.className = 'content';
const title = document.createElement('div');
title.className = 'title';
title.textContent = p.name || '';
const price = document.createElement('div');
price.className = 'price';
price.innerHTML = priceHtml(p);
const sku = document.createElement('div');
sku.className = 'sku';
sku.textContent = p.sku ? ('SKU: ' + p.sku) : '';
content.appendChild(title);
content.appendChild(price);
if (p.sku) content.appendChild(sku);
a.appendChild(img);
a.appendChild(content);
frag.appendChild(a);
});
gridEl.appendChild(frag);
}
function renderCount() {
const start = (state.total === 0) ? 0 : (state.perPage * (state.page - 1) + 1);
const end = Math.min(state.total, state.perPage * state.page);
countEl.textContent = `共 ${state.total} 个商品,当前显示 ${start}-${end}`;
}
function renderPagination() {
pgEl.innerHTML = '';
if (state.totalPages {
const el = document.createElement('button');
el.textContent = text;
if (active) el.classList.add('active');
if (disabled) el.disabled = true;
el.addEventListener('click', () => {
if (page === state.page || disabled) return;
state.page = page;
fetchAndRender();
});
pgEl.appendChild(el);
};
// Prev
addBtn('« 上一页', Math.max(1, state.page - 1), state.page === 1);
// Pages window
const windowSize = 5;
let start = Math.max(1, state.page - Math.floor(windowSize / 2));
let end = Math.min(state.totalPages, start + windowSize - 1);
if (end - start + 1 1) addBtn('1', 1, false, state.page === 1);
if (start > 2) addBtn('…', state.page, true);
for (let i = start; i <= end; i++) {
addBtn(String(i), i, false, i === state.page);
}
if (end < state.totalPages - 1) addBtn('…', state.page, true);
if (end < state.totalPages) addBtn(String(state.totalPages), state.totalPages, false, state.page === state.totalPages);
// Next
addBtn('下一页 »', Math.min(state.totalPages, state.page + 1), state.page === state.totalPages);
}
async function fetchAndRender() {
// 映射 orderby 到 Store API 参数
// Store API 支持:date、title、price、popularity、rating
// 降序:加上 &order=desc;价格降序我们手动处理
let params = {
page: state.page,
per_page: state.perPage,
};
const ob = state.orderby;
if (ob === 'price-desc') {
params.orderby = 'price';
params.order = 'desc';
} else if (ob === 'price') {
params.orderby = 'price';
params.order = 'asc';
} else {
params.orderby = ob; // date/title/popularity/rating
if (ob === 'date') params.order = 'desc';
}
const url = productsEndpoint + buildQuery(params);
gridEl.innerHTML = '';
emptyEl.style.display = 'none';
countEl.textContent = '加载中…';
pgEl.innerHTML = '';
try {
const res = await fetch(url, { credentials: 'same-origin' });
if (!res.ok) throw new Error('请求失败: ' + res.status);
// Store API 在响应头提供分页信息
const total = parseInt(res.headers.get('x-wp-total') || '0', 10);
const totalPages = parseInt(res.headers.get('x-wp-totalpages') || '0', 10);
const data = await res.json();
state.total = total;
state.totalPages = totalPages;
renderProducts(data);
renderCount();
renderPagination();
} catch (err) {
console.error(err);
gridEl.innerHTML = '';
emptyEl.style.display = 'block';
emptyEl.textContent = '加载失败,请稍后重试';
countEl.textContent = '—';
}
}
// 初次加载
fetchAndRender();