feat(shop): complete storefront archive, seller edit, revenue

Wire product cat/tag archives to settings-driven sort and AJAX paging,
seller product options/video/params editing, revenue withdraw form and
order review submit — finishing remaining shop frontend gaps after buy-path.
This commit is contained in:
kate522 committed 2026-07-12 10:38:02 +08:00
1 parent 5a8f08db8a
commit f3db6c89cc
17 files changed
+1024 -248

No files matched your search

+37 -6
View File
@@ -280,16 +280,47 @@
return;
}
// Review — jump to product if possible
// Review submit modal
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 productLink = card ? card.querySelector("a[href*='product']") : null;
if (productLink && productLink.href) {
window.location.href = productLink.href + (productLink.href.indexOf("#") >= 0 ? "" : "#reviews");
} else {
showToast("请到商品详情页评价");
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("网络错误");
});
return;
}
+140 -32
View File
@@ -1,44 +1,152 @@
/**
* AKSRT Product Archive — 商品归档页交互
*
* 处理商品分类/标签归档页的筛选、排序和加载更多。
* Product archive — sort + AJAX load more.
*
* @package AKSRT
*/
(function () {
'use strict';
"use strict";
var grid = document.getElementById('aksrt-product-grid');
if (!grid) return;
var root = document.querySelector("[data-aksrt-product-archive]");
if (!root) return;
// 商品卡片懒加载图片观察器
if ('IntersectionObserver' in window) {
var imgObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
var img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
imgObserver.unobserve(img);
}
});
}, { rootMargin: '200px' });
function restBase() {
var site = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
return site + "/wp-json/aksrt/v1";
}
grid.querySelectorAll('img[data-src]').forEach(function (img) {
imgObserver.observe(img);
});
function toast(msg) {
if (typeof window.aksrtToast === "function") {
window.aksrtToast(msg);
return;
}
if (window.console) console.log(msg);
}
var grid = root.querySelector("[data-aksrt-product-grid], #aksrt-product-grid");
var countEl = root.querySelector("[data-aksrt-archive-count]");
var loadMoreBtn = root.querySelector("[data-aksrt-shop-load-more]");
var state = {
taxonomy: root.getAttribute("data-taxonomy") || "",
termId: Number(root.getAttribute("data-term-id") || 0),
orderby: root.getAttribute("data-orderby") || "date",
page: Number(root.getAttribute("data-page") || 1),
maxPages: Number(root.getAttribute("data-max-pages") || 1),
paging: root.getAttribute("data-paging") || "number",
ias: root.getAttribute("data-ias") === "1",
iasMax: Number(root.getAttribute("data-ias-max") || 5),
loading: false,
autoLoads: 0,
};
function fetchArchive(page, orderby, replace) {
if (state.loading) return Promise.resolve();
state.loading = true;
if (loadMoreBtn) {
loadMoreBtn.disabled = true;
loadMoreBtn.textContent = "加载中...";
}
// 商品卡片 hover 效果增强
grid.querySelectorAll('.aksrt-product-card').forEach(function (card) {
card.addEventListener('mouseenter', function () {
this.classList.add('aksrt-product-card--hover');
var qs = new URLSearchParams();
qs.set("taxonomy", state.taxonomy);
qs.set("term_id", String(state.termId));
qs.set("page", String(page));
qs.set("orderby", orderby || state.orderby);
return fetch(restBase() + "/shop/frontend/products/archive?" + qs.toString(), {
method: "GET",
credentials: "same-origin",
headers: {
"X-WP-Nonce": (window.aksrtData && aksrtData.restNonce) || "",
},
})
.then(function (r) { return r.json(); })
.then(function (result) {
state.loading = false;
if (!result || !result.success || !result.data) {
toast((result && result.message) || "加载失败");
if (loadMoreBtn) {
loadMoreBtn.disabled = false;
loadMoreBtn.textContent = "加载更多";
}
return;
}
var data = result.data;
state.page = Number(data.page || page);
state.maxPages = Number(data.max_pages || 1);
state.orderby = data.orderby || state.orderby;
root.setAttribute("data-page", String(state.page));
root.setAttribute("data-max-pages", String(state.maxPages));
root.setAttribute("data-orderby", state.orderby);
if (countEl && typeof data.total !== "undefined") {
countEl.textContent = "共 " + data.total + " 件商品";
}
if (grid) {
if (replace) {
grid.innerHTML = data.html || "";
} else if (data.html) {
grid.insertAdjacentHTML("beforeend", data.html);
}
}
root.querySelectorAll("[data-aksrt-shop-orderby]").forEach(function (a) {
a.classList.toggle("is-active", a.getAttribute("data-aksrt-shop-orderby") === state.orderby);
});
card.addEventListener('mouseleave', function () {
this.classList.remove('aksrt-product-card--hover');
});
});
if (loadMoreBtn) {
var hasMore = state.page < state.maxPages;
loadMoreBtn.style.display = hasMore ? "" : "none";
loadMoreBtn.disabled = false;
loadMoreBtn.textContent = "加载更多";
}
})
.catch(function () {
state.loading = false;
if (loadMoreBtn) {
loadMoreBtn.disabled = false;
loadMoreBtn.textContent = "加载更多";
}
toast("网络错误");
});
}
root.addEventListener("click", function (e) {
var sort = e.target.closest("[data-aksrt-shop-orderby]");
if (sort && root.contains(sort)) {
e.preventDefault();
var orderby = sort.getAttribute("data-aksrt-shop-orderby") || "date";
if (orderby === state.orderby && state.paging === "ajax") return;
state.orderby = orderby;
state.autoLoads = 0;
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);
return;
}
var more = e.target.closest("[data-aksrt-shop-load-more]");
if (more && root.contains(more)) {
e.preventDefault();
if (state.page >= state.maxPages) return;
fetchArchive(state.page + 1, state.orderby, 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" });
io.observe(loadMoreBtn);
}
})();
+37 -13
View File
@@ -320,25 +320,43 @@
if (!select) return [];
return Array.prototype.slice
.call(select.options)
.filter(function (option) {
return option.selected;
})
.map(function (option) {
return Number(option.value || 0);
})
.filter(function (option) { return option.selected; })
.map(function (option) { return Number(option.value || 0); })
.filter(Boolean);
}
function parseOptionsText(text) {
var groups = [];
String(text || "").split(String.fromCharCode(10)).forEach(function (line) {
line = line.replace(/\r/g, "").trim();
if (!line) return;
var parts = line.split("|");
if (parts.length < 2) return;
var name = parts[0].trim();
var values = [];
parts.slice(1).join("|").split(",").forEach(function (chunk) {
chunk = chunk.trim();
if (!chunk) return;
var segs = chunk.split(":");
var label = (segs[0] || "").trim();
if (!label) return;
values.push({ label: label, price_change: Number(segs[1] || 0), image: "" });
});
if (name && values.length) {
groups.push({ name: name, view_mode: "list", values: values });
}
});
return groups;
}
form.addEventListener("submit", function (event) {
event.preventDefault();
var formData = new FormData(form);
var productId = Number(editor.getAttribute("data-product-id") || 0);
var gallery = String(formData.get("gallery") || "")
.split(/\r?\n/)
.map(function (line) {
return line.trim();
})
.split(String.fromCharCode(10))
.map(function (line) { return line.replace(/\r/g, "").trim(); })
.filter(Boolean);
var payload = {
@@ -351,9 +369,7 @@
categories: selectedMultiValues(form.querySelector('select[name="categories[]"]')),
tags: String(formData.get("tags") || "")
.split(",")
.map(function (tag) {
return tag.trim();
})
.map(function (tag) { return tag.trim(); })
.filter(Boolean),
image: String(formData.get("image") || "").trim(),
gallery: gallery,
@@ -361,6 +377,14 @@
shipping_fee: Number(formData.get("shipping_fee") || 0),
after_sale_policy: String(formData.get("after_sale_policy") || ""),
support_refund: formData.get("support_refund") === "1",
options: parseOptionsText(formData.get("options_text") || ""),
start_price: Number(formData.get("start_price") || 0),
pay_modo: String(formData.get("pay_modo") || "0"),
product_params: String(formData.get("product_params") || ""),
product_video: String(formData.get("product_video") || "")
.split(String.fromCharCode(10))
.map(function (line) { return line.replace(/\r/g, "").trim(); })
.filter(Boolean)
};
var path = productId > 0 ? "/shop/frontend/products/" + productId : "/shop/frontend/products";
+114
View File
@@ -0,0 +1,114 @@
/**
* Creator revenue / withdraw form.
*
* @package AKSRT
*/
(function () {
"use strict";
var page = document.querySelector("[data-aksrt-revenue-page]");
if (!page || !window.aksrtData) return;
function restBase() {
return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1";
}
function rest(path, method, body) {
var opts = {
method: method || "GET",
credentials: "same-origin",
headers: { "X-WP-Nonce": aksrtData.restNonce || "" },
};
if (body !== undefined) {
opts.headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
return fetch(restBase() + path, opts).then(function (r) {
return r.json();
});
}
var form = page.querySelector("[data-aksrt-withdraw-form]");
var msg = page.querySelector("[data-aksrt-withdraw-msg]");
var balanceEl = page.querySelector("[data-aksrt-revenue-balance]");
var list = page.querySelector("[data-aksrt-withdraw-list]");
if (!form) return;
form.addEventListener("submit", function (e) {
e.preventDefault();
var fd = new FormData(form);
var amount = Number(fd.get("amount") || 0);
var method = String(fd.get("method") || "alipay");
var account = String(fd.get("account_info") || "").trim();
var min = Number(page.getAttribute("data-min-amount") || 50);
if (amount < min) {
if (msg) {
msg.hidden = false;
msg.textContent = "最低提现金额为 " + min;
}
return;
}
if (!account) {
if (msg) {
msg.hidden = false;
msg.textContent = "请填写收款账户信息";
}
return;
}
var btn = form.querySelector('button[type="submit"]');
if (btn) {
btn.disabled = true;
btn.textContent = "提交中...";
}
rest("/shop/frontend/revenue/withdraw", "POST", {
amount: amount,
method: method,
account_info: account,
})
.then(function (result) {
if (btn) {
btn.disabled = false;
btn.textContent = "提交提现申请";
}
if (!result || !result.success) {
if (msg) {
msg.hidden = false;
msg.textContent = (result && result.message) || "提现失败";
}
return;
}
if (msg) {
msg.hidden = false;
msg.textContent = result.message || "提现申请已提交";
}
if (balanceEl && result.data && typeof result.data.balance !== "undefined") {
balanceEl.textContent = "¥" + Number(result.data.balance).toFixed(2);
}
if (list && result.data) {
var item = document.createElement("div");
item.className = "aksrt-revenue-page__item";
item.innerHTML =
"<div><strong>¥" +
Number(result.data.amount || amount).toFixed(2) +
"</strong><span>" +
method +
"</span></div><div><span>pending</span><time>just now</time></div>";
list.insertBefore(item, list.firstChild);
}
form.reset();
})
.catch(function () {
if (btn) {
btn.disabled = false;
btn.textContent = "提交提现申请";
}
if (msg) {
msg.hidden = false;
msg.textContent = "网络错误";
}
});
});
})();