diff --git a/assets/css/frontend/product-archive.css b/assets/css/frontend/product-archive.css
index 81230196..91ae9662 100644
--- a/assets/css/frontend/product-archive.css
+++ b/assets/css/frontend/product-archive.css
@@ -719,3 +719,16 @@
.aksrt-product-edit__actions .btn-ghost:hover {
border-color: var(--color-primary, var(--color-primary));
}
+
+.aksrt-product-archive__toolbar{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 16px}
+.aksrt-product-archive__sort{display:inline-flex;padding:8px 14px;border-radius:999px;background:var(--surface-1,#fff);color:var(--color-ink-3,#5c6370);text-decoration:none;font-weight:600;font-size:14px;box-shadow:var(--shadow-sm)}
+.aksrt-product-archive__sort.is-active,.aksrt-product-archive__sort:hover{background:var(--color-primary,#f04494);color:#fff}
+.aksrt-product-archive__load-more{display:flex;justify-content:center;margin:20px 0}
+.aksrt-revenue-page__layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin:20px 0}
+.aksrt-revenue-page__panel{padding:18px;border-radius:16px;background:var(--surface-1,#fff);box-shadow:var(--shadow-sm)}
+.aksrt-revenue-page__withdraw-form label{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}
+.aksrt-revenue-page__withdraw-form input,.aksrt-revenue-page__withdraw-form select,.aksrt-revenue-page__withdraw-form textarea{width:100%;padding:10px 12px;border:1px solid var(--color-line-soft,rgba(0,0,0,.08));border-radius:10px}
+.aksrt-revenue-page__item{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:1px solid var(--color-line-soft,rgba(0,0,0,.06))}
+.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}
diff --git a/assets/js/order-center.js b/assets/js/order-center.js
index e5f2a2bb..0116e4e9 100644
--- a/assets/js/order-center.js
+++ b/assets/js/order-center.js
@@ -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;
}
diff --git a/assets/js/product-archive.js b/assets/js/product-archive.js
index 17a3d994..74a54470 100644
--- a/assets/js/product-archive.js
+++ b/assets/js/product-archive.js
@@ -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);
+ }
})();
diff --git a/assets/js/product-purchase.js b/assets/js/product-purchase.js
index 778cdb7b..9dea3012 100644
--- a/assets/js/product-purchase.js
+++ b/assets/js/product-purchase.js
@@ -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";
diff --git a/assets/js/revenue.js b/assets/js/revenue.js
new file mode 100644
index 00000000..e687f0a9
--- /dev/null
+++ b/assets/js/revenue.js
@@ -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 =
+ "
¥" +
+ Number(result.data.amount || amount).toFixed(2) +
+ "" +
+ method +
+ "
pending
";
+ list.insertBefore(item, list.firstChild);
+ }
+ form.reset();
+ })
+ .catch(function () {
+ if (btn) {
+ btn.disabled = false;
+ btn.textContent = "提交提现申请";
+ }
+ if (msg) {
+ msg.hidden = false;
+ msg.textContent = "网络错误";
+ }
+ });
+ });
+})();
diff --git a/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php b/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
index dcc4d640..d84894f7 100644
--- a/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
+++ b/inc/classes/admin/class-aksrt-admin-shop-controller-frontend-flow-operations.php
@@ -354,6 +354,190 @@ trait AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations {
return $this->success(['message' => __('已设为默认地址', 'aksrt')]);
}
+ public function get_frontend_product_archive(WP_REST_Request $request): WP_REST_Response {
+ if (!class_exists('AKSRT_Shop')) {
+ return $this->error('商城服务不可用', 500);
+ }
+
+ $taxonomy = sanitize_key((string) ($request->get_param('taxonomy') ?? ''));
+ $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') ?? ''));
+
+ $view = AKSRT_Shop::get_instance()->get_product_archive_view([
+ 'taxonomy' => $taxonomy,
+ 'term_id' => $term_id,
+ 'paged' => $page,
+ 'orderby' => $orderby,
+ ]);
+
+ $html = '';
+ if (!empty($view['products']) && is_array($view['products'])) {
+ ob_start();
+ foreach ($view['products'] as $product) {
+ $this->render_product_card_html($product, $view);
+ }
+ $html = (string) ob_get_clean();
+ }
+
+ return $this->success([
+ 'data' => array_merge($view, [
+ 'html' => $html,
+ 'has_more' => (int) ($view['page'] ?? 1) < (int) ($view['max_pages'] ?? 1),
+ ]),
+ ]);
+ }
+
+ 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'] ?? '');
+ ?>
+
+
+
+
+
; ?>)
+
+
+
+
+
+
+
+
+
+ error('收益服务不可用', 500);
+ }
+ $view = aksrt_get_revenue_page_view(get_current_user_id());
+ $withdrawals = [];
+ if (class_exists('AKSRT_Withdrawal')) {
+ $rows = AKSRT_Withdrawal::get_instance()->get_user_withdrawals(get_current_user_id(), ['limit' => 20]);
+ foreach ($rows as $row) {
+ $withdrawals[] = [
+ 'id' => (int) ($row['id'] ?? 0),
+ 'amount' => (float) ($row['amount'] ?? 0),
+ 'status' => (string) ($row['status'] ?? ''),
+ 'method' => (string) ($row['method'] ?? ''),
+ 'created_at' => (string) ($row['created_at'] ?? ''),
+ 'note' => (string) ($row['note'] ?? ''),
+ ];
+ }
+ }
+ $settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_withdrawal_settings() : [];
+ return $this->success([
+ 'data' => array_merge($view, [
+ 'withdrawals' => $withdrawals,
+ 'settings' => [
+ 'min_amount' => (float) ($settings['min_amount'] ?? 50),
+ 'service_charge_rate' => (float) ($settings['service_charge_rate'] ?? 0),
+ 'text' => (string) ($settings['text'] ?? ''),
+ ],
+ ]),
+ ]);
+ }
+
+ public function create_frontend_withdraw(WP_REST_Request $request): WP_REST_Response {
+ if (!class_exists('AKSRT_Withdrawal') || !class_exists('AKSRT_Credits')) {
+ return $this->error('提现服务不可用', 500);
+ }
+
+ $params = $this->get_json_params($request);
+ $amount = (float) ($params['amount'] ?? 0);
+ $method = sanitize_key((string) ($params['method'] ?? 'alipay'));
+ $account = sanitize_textarea_field((string) ($params['account_info'] ?? $params['account'] ?? ''));
+
+ $settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_withdrawal_settings() : [];
+ $min = (float) ($settings['min_amount'] ?? 50);
+ if ($amount < $min) {
+ return $this->error(sprintf(__('最低提现金额为 %s', 'aksrt'), $min));
+ }
+ if ($account === '') {
+ return $this->error(__('请填写收款账户信息', 'aksrt'));
+ }
+
+ $user_id = get_current_user_id();
+ $balance = (float) AKSRT_Credits::balance($user_id);
+ if ($amount > $balance) {
+ return $this->error(__('可提现余额不足', 'aksrt'));
+ }
+
+ $cost = (int) round($amount);
+ if ($cost <= 0) {
+ return $this->error(__('提现金额无效', 'aksrt'));
+ }
+ $deducted = AKSRT_Credits::deduct($user_id, $cost, 'withdraw', ['type' => 'withdraw'], __('用户提现申请', 'aksrt'));
+ if (is_wp_error($deducted)) {
+ return $this->error($deducted->get_error_message());
+ }
+
+ $id = AKSRT_Withdrawal::get_instance()->create($user_id, $amount, $method, $account);
+ if ($id <= 0) {
+ AKSRT_Credits::add($user_id, $cost, 'withdraw_refund', ['type' => 'withdraw'], __('提现失败退回', 'aksrt'));
+ return $this->error(__('提现申请创建失败', 'aksrt'), 500);
+ }
+
+ return $this->success([
+ 'message' => __('提现申请已提交', 'aksrt'),
+ 'data' => [
+ 'id' => $id,
+ 'amount' => $amount,
+ 'balance' => (float) AKSRT_Credits::balance($user_id),
+ ],
+ ], 201);
+ }
+
+ public function submit_frontend_product_review(WP_REST_Request $request): WP_REST_Response {
+ if (!class_exists('AKSRT_Product_Review')) {
+ return $this->error('评价服务不可用', 500);
+ }
+ $params = $this->get_json_params($request);
+ $order_number = sanitize_text_field((string) $request->get_param('order_number'));
+ $product_id = (int) ($params['product_id'] ?? 0);
+ $result = AKSRT_Product_Review::get_instance()->submit_review(
+ get_current_user_id(),
+ $order_number,
+ $product_id,
+ $params
+ );
+ if (empty($result['success'])) {
+ return $this->error($result['message'] ?? __('评价失败', 'aksrt'));
+ }
+ return $this->success([
+ 'message' => __('评价已提交', 'aksrt'),
+ 'data' => $result,
+ ], 201);
+ }
+
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();
diff --git a/inc/classes/admin/class-aksrt-admin-shop-controller-product-operations.php b/inc/classes/admin/class-aksrt-admin-shop-controller-product-operations.php
index 3f149a9f..7abf8ae6 100644
--- a/inc/classes/admin/class-aksrt-admin-shop-controller-product-operations.php
+++ b/inc/classes/admin/class-aksrt-admin-shop-controller-product-operations.php
@@ -333,6 +333,10 @@ trait AKSRT_Admin_Shop_Controller_Product_Operations {
update_post_meta($post_id, '_aksrt_product_image', $image);
}
+ if (isset($params['options']) && is_array($params['options']) && class_exists('AKSRT_Shop')) {
+ AKSRT_Shop::get_instance()->save_product_options((int) $post_id, $params['options']);
+ }
+
return $this->success([
'message' => __('商品已创建', 'aksrt'),
'data' => ['id' => $post_id],
@@ -484,6 +488,10 @@ trait AKSRT_Admin_Shop_Controller_Product_Operations {
update_post_meta($id, '_aksrt_product_image', esc_url_raw((string) $params['image']));
}
+ if (isset($params['options']) && is_array($params['options']) && class_exists('AKSRT_Shop')) {
+ AKSRT_Shop::get_instance()->save_product_options($id, $params['options']);
+ }
+
return $this->success([
'message' => __('商品已更新', 'aksrt'),
]);
diff --git a/inc/classes/admin/class-aksrt-admin-shop-controller.php b/inc/classes/admin/class-aksrt-admin-shop-controller.php
index 1112cfd3..4ea2c1ce 100644
--- a/inc/classes/admin/class-aksrt-admin-shop-controller.php
+++ b/inc/classes/admin/class-aksrt-admin-shop-controller.php
@@ -352,7 +352,31 @@ class AKSRT_Admin_Shop_Controller extends AKSRT_Admin_Controller_Base {
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
- register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/(?P\d+)/favorite', [
+ register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/archive', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_frontend_product_archive'],
+ 'permission_callback' => '__return_true',
+ ]);
+
+ register_rest_route(self::API_NAMESPACE, '/shop/frontend/revenue', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_frontend_revenue'],
+ 'permission_callback' => [$this, 'check_logged_in_permission'],
+ ]);
+
+ register_rest_route(self::API_NAMESPACE, '/shop/frontend/revenue/withdraw', [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'create_frontend_withdraw'],
+ 'permission_callback' => [$this, 'check_logged_in_permission'],
+ ]);
+
+ register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P[A-Za-z0-9]+)/review', [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'submit_frontend_product_review'],
+ 'permission_callback' => [$this, 'check_logged_in_permission'],
+ ]);
+
+register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/(?P\d+)/favorite', [
'methods' => 'POST',
'callback' => [$this, 'toggle_frontend_product_favorite'],
'permission_callback' => [$this, 'check_logged_in_permission'],
diff --git a/inc/classes/class-aksrt-settings.php b/inc/classes/class-aksrt-settings.php
index 674de9d0..a93d9ead 100644
--- a/inc/classes/class-aksrt-settings.php
+++ b/inc/classes/class-aksrt-settings.php
@@ -1000,6 +1000,10 @@ class AKSRT_Settings {
return AKSRT_Settings_Post_Display::get_comment_display_settings();
}
+ public static function get_withdrawal_settings(): array {
+ return AKSRT_Settings_Payment_Runtime::get_withdrawal_settings();
+ }
+
/**
* pre_get_posts: apply post_list_per_page on archive/tag/category/search/home pages.
*
diff --git a/inc/classes/class-aksrt-shop.php b/inc/classes/class-aksrt-shop.php
index f5f2756a..71378645 100644
--- a/inc/classes/class-aksrt-shop.php
+++ b/inc/classes/class-aksrt-shop.php
@@ -152,6 +152,11 @@ class AKSRT_Shop {
'after_sale_policy' => '',
'support_refund' => true,
'status' => 'draft',
+ 'options' => [],
+ 'pay_modo' => '0',
+ 'start_price' => 0,
+ 'product_video' => [],
+ 'product_params' => '',
],
'taxonomy' => [
'categories' => $this->product_service()->get_term_options('aksrt_product_cat'),
@@ -181,6 +186,10 @@ class AKSRT_Shop {
}
$view['is_edit'] = true;
+ $videos = get_post_meta($product_id, '_aksrt_product_video', true);
+ if (!is_array($videos)) {
+ $videos = [];
+ }
$view['product'] = [
'id' => $product_id,
'title' => (string) $product->post_title,
@@ -197,6 +206,11 @@ class AKSRT_Shop {
'after_sale_policy' => (string) get_post_meta($product_id, '_aksrt_after_sale_policy', true),
'support_refund' => get_post_meta($product_id, '_aksrt_support_refund', true) !== '0',
'status' => (string) get_post_status($product_id),
+ 'options' => method_exists($this, 'get_product_options') ? $this->get_product_options($product_id) : [],
+ 'pay_modo' => sanitize_key((string) get_post_meta($product_id, '_aksrt_pay_modo', true)) ?: '0',
+ 'start_price' => (float) get_post_meta($product_id, '_aksrt_start_price', true),
+ 'product_video' => array_values(array_filter(array_map('esc_url_raw', $videos))),
+ 'product_params' => (string) get_post_meta($product_id, '_aksrt_product_params', true),
];
return $view;
diff --git a/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php b/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php
index af68b558..38fa26f3 100644
--- a/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php
+++ b/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php
@@ -46,48 +46,111 @@ trait AKSRT_Shop_Part_1 {
public function get_product_archive_view(array $args = []): array {
$taxonomy = sanitize_key((string) ($args['taxonomy'] ?? ''));
$term_id = (int) ($args['term_id'] ?? 0);
- $paged = max(1, (int) ($args['paged'] ?? 1));
- $per_page = 12;
+ $paged = max(1, (int) ($args['paged'] ?? $args['page'] ?? 1));
- if (!in_array($taxonomy, ['aksrt_product_cat', 'aksrt_product_tag'], true)) {
- return ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0];
+ $shop_settings = class_exists('AKSRT_Settings') ? AKSRT_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';
+ }
+ $paging = sanitize_key((string) ($args['paging'] ?? ($list['paging'] ?? 'number')));
+ if (!in_array($paging, ['number', 'ajax'], true)) {
+ $paging = 'number';
+ }
+ $ias_s = array_key_exists('ias_s', $args) ? !empty($args['ias_s']) : !empty($list['ias_s']);
+ $ias_max = max(1, min(50, (int) ($args['ias_max'] ?? ($list['ias_max'] ?? 5))));
+ $style = sanitize_key((string) ($list['style'] ?? 'default'));
+
+ $empty = [
+ 'products' => [],
+ 'term' => null,
+ 'total' => 0,
+ 'max_pages' => 0,
+ 'page' => $paged,
+ 'orderby' => $orderby,
+ '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'] : [],
+ ];
+
+ if (!in_array($taxonomy, ['aksrt_product_cat', 'aksrt_product_tag'], true) && empty($args['allow_all'])) {
+ return $empty;
}
- $term = get_term($term_id, $taxonomy);
- if (is_wp_error($term) || !$term) {
- return ['products' => [], 'term' => null, 'total' => 0, 'max_pages' => 0];
- }
-
- $query = new WP_Query([
+ $query_args = [
'post_type' => 'aksrt_product',
'posts_per_page' => $per_page,
'paged' => $paged,
'post_status' => 'publish',
- 'tax_query' => [[
+ 'order' => 'DESC',
+ 'orderby' => 'date',
+ ];
+
+ $term = null;
+ if ($taxonomy !== '' && $term_id > 0) {
+ $term = get_term($term_id, $taxonomy);
+ if (is_wp_error($term) || !$term) {
+ return $empty;
+ }
+ $query_args['tax_query'] = [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
- ]],
- ]);
+ ]];
+ }
+ if ($orderby === 'sales') {
+ $query_args['meta_key'] = '_aksrt_sales';
+ $query_args['orderby'] = 'meta_value_num';
+ $query_args['order'] = 'DESC';
+ } elseif ($orderby === 'price_asc') {
+ $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['orderby'] = 'meta_value_num';
+ $query_args['order'] = 'DESC';
+ } elseif ($orderby === 'comments') {
+ $query_args['orderby'] = 'comment_count';
+ $query_args['order'] = 'DESC';
+ } elseif ($orderby === 'modified') {
+ $query_args['orderby'] = 'modified';
+ $query_args['order'] = 'DESC';
+ }
+
+ $query = new WP_Query($query_args);
$products = [];
while ($query->have_posts()) {
$query->the_post();
$products[] = $this->build_product_card_view(get_the_ID());
}
-
wp_reset_postdata();
return [
'products' => $products,
- 'term' => [
- 'id' => $term->term_id,
- 'name' => $term->name,
- 'description' => $term->description,
- 'count' => $term->count,
- ],
+ 'term' => $term ? [
+ 'id' => (int) $term->term_id,
+ 'name' => (string) $term->name,
+ 'description' => (string) $term->description,
+ 'count' => (int) $term->count,
+ 'taxonomy' => $taxonomy,
+ ] : null,
'total' => (int) $query->found_posts,
'max_pages' => (int) $query->max_num_pages,
+ 'page' => $paged,
+ 'orderby' => $orderby,
+ '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'] : [],
];
}
diff --git a/inc/helpers/aksrt-transactions.php b/inc/helpers/aksrt-transactions.php
index 93b2cd46..98827170 100644
--- a/inc/helpers/aksrt-transactions.php
+++ b/inc/helpers/aksrt-transactions.php
@@ -82,12 +82,13 @@ function aksrt_get_revenue_page_view(int $user_id): array {
if (class_exists('AKSRT_Revenue')) {
$revenues = AKSRT_Revenue::get_instance()->get_user_revenues($user_id, ['limit' => 200]);
foreach ($revenues as $rev) {
- $total_earned += (float) ($rev->amount ?? 0);
+ $row = is_array($rev) ? $rev : (array) $rev;
+ $total_earned += (float) ($row['amount'] ?? 0);
$records[] = [
'type' => 'earning',
- 'title' => (string) ($rev->description ?? __('创作分成', 'aksrt')),
- 'amount' => (float) ($rev->amount ?? 0),
- 'created_at' => (string) ($rev->created_at ?? ''),
+ 'title' => (string) ($row['description'] ?? $row['type'] ?? __('创作分成', 'aksrt')),
+ 'amount' => (float) ($row['amount'] ?? 0),
+ 'created_at' => (string) ($row['created_at'] ?? ''),
];
}
}
@@ -95,12 +96,14 @@ function aksrt_get_revenue_page_view(int $user_id): array {
if (class_exists('AKSRT_Withdrawal')) {
$withdrawals = AKSRT_Withdrawal::get_instance()->get_user_withdrawals($user_id, ['limit' => 200]);
foreach ($withdrawals as $w) {
- $total_withdrawn += (float) ($w->amount ?? 0);
+ $wrow = is_array($w) ? $w : (array) $w;
+ $total_withdrawn += (float) ($wrow['amount'] ?? 0);
$records[] = [
'type' => 'withdrawal',
'title' => __('提现', 'aksrt'),
- 'amount' => (float) ($w->amount ?? 0),
- 'created_at' => (string) ($w->created_at ?? ''),
+ 'amount' => (float) ($wrow['amount'] ?? 0),
+ 'created_at' => (string) ($wrow['created_at'] ?? ''),
+ 'status' => (string) ($wrow['status'] ?? ''),
];
}
}
diff --git a/templates/pages/page-orders.php b/templates/pages/page-orders.php
index f6417be5..70c172eb 100644
--- a/templates/pages/page-orders.php
+++ b/templates/pages/page-orders.php
@@ -92,7 +92,7 @@ $tabs = [
-
+
 $item['image']); ?>)
@@ -109,7 +109,7 @@ $tabs = [
-
+
¥
diff --git a/templates/pages/page-product-edit.php b/templates/pages/page-product-edit.php
index 736ccb55..c4897d9a 100644
--- a/templates/pages/page-product-edit.php
+++ b/templates/pages/page-product-edit.php
@@ -91,7 +91,49 @@ $product = (array) ($edit_view['product'] ?? []);
+
+
+
+
+
+
+
+
+
+
+
+